Agent-almanac create-glyph
git clone https://github.com/pjt222/agent-almanac
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/caveman/skills/create-glyph" ~/.claude/skills/pjt222-agent-almanac-create-glyph-5e4609 && rm -rf "$T"
i18n/caveman/skills/create-glyph/SKILL.mdCreate Glyph
Create R-based pictogram glyphs for skill, agent, or team icons in the
viz/ visualization layer. Each glyph is a pure-ggplot2 function that draws a recognizable shape on a 100x100 canvas, rendered with a neon glow effect to transparent-background WebP.
When to Use
- A new skill, agent, or team has been added and needs a visual icon
- An existing glyph needs replacement or redesign
- Batch-creating glyphs for a new domain of skills
- Prototyping visual metaphors for entity concepts
Inputs
- Required: Entity type —
,skill
, oragentteam - Required: Entity ID (e.g.,
,create-glyph
,mystic
) and domain (for skills)r-package-review - Required: Visual concept — what the glyph should depict
- Optional: Reference glyph to study for complexity level
- Optional: Custom
value (default: 4)--glow-sigma
Procedure
Step 1: Concept — Design the Visual Metaphor
Identify the entity being iconified and choose a visual metaphor.
- Read the entity's source file to understand its core concept:
- Skills:
skills/<id>/SKILL.md - Agents:
agents/<id>.md - Teams:
teams/<id>.md
- Skills:
- Choose a metaphor type:
- Literal object: a flask for experiments, a shield for security
- Abstract symbol: arrows for merging, spirals for iteration
- Composite: combine 2-3 simple shapes (e.g., document + pen)
- Reference existing glyphs for complexity calibration:
Complexity Tiers: +----------+--------+-------------------------------------------+ | Tier | Layers | Examples | +----------+--------+-------------------------------------------+ | Simple | 2 | glyph_flame, glyph_heartbeat | | Moderate | 3-5 | glyph_document, glyph_experiment_flask | | Complex | 6+ | glyph_ship_wheel, glyph_bridge_cpp | +----------+--------+-------------------------------------------+
- Decide on a function name:
(snake_case, unique)glyph_<descriptive_name>
Expected: A clear mental sketch of the shape with 2-6 planned layers.
On failure: If the concept is too abstract, fall back to a related concrete object. Review existing glyphs in the same domain for inspiration.
Step 2: Compose — Write the Glyph Function
Write the R function that produces ggplot2 layers.
-
Function signature (immutable contract):
glyph_<name> <- function(cx, cy, s, col, bright) { # cx, cy = center coordinates (50, 50 on 100x100 canvas) # s = scale factor (1.0 = fill ~70% of canvas) # col = domain color hex (e.g., "#ff88dd" for design) # bright = brightened variant of col (auto-computed by renderer) # Returns: list() of ggplot2 layers } -
Apply scale factor
to ALL dimensions for consistent scaling:* sr <- 20 * s # radius hw <- 15 * s # half-width lw <- .lw(s) # line width (default base 2.5) lw_thin <- .lw(s, 1.2) # thinner line width -
Build geometry using available primitives:
Geometry Usage ggplot2::geom_polygon(data, .aes(x, y), ...)Filled shapes ggplot2::geom_path(data, .aes(x, y), ...)Open lines/curves ggplot2::geom_segment(data, .aes(x, xend, y, yend), ...)Line segments, arrows ggplot2::geom_rect(data, .aes(xmin, xmax, ymin, ymax), ...)Rectangles ggforce::geom_circle(data, .aes(x0, y0, r), ...)Circles -
Apply the color strategy:
Alpha Guide: +----------------------+------------+--------------------------+ | Purpose | Alpha | Example | +----------------------+------------+--------------------------+ | Large fill (body) | 0.08-0.15 | hex_with_alpha(col, 0.1) | | Medium fill (accent) | 0.15-0.25 | hex_with_alpha(col, 0.2) | | Small fill (detail) | 0.25-0.35 | hex_with_alpha(bright, 0.3) | | Outline stroke | 1.0 | color = bright | | Secondary stroke | 1.0 | color = col | | No fill | --- | fill = NA | +----------------------+------------+--------------------------+ -
Return a flat
of layers (the renderer iterates and wraps each with glow)list() -
Place the function in the appropriate primitives file based on entity type:
- Skills: domain-grouped across 19 primitives files:
— bushcraft, compliance, containerization, data-serialization, defensiveprimitives.R
— devops, general, git, mcp-integrationprimitives_2.R
— mlops, observability, PM, r-packages, reporting, review, web-dev, esoteric, designprimitives_3.R- Additional
throughprimitives_4.R
for newer domainsprimitives_19.R
- Agents:
viz/R/agent_primitives.R - Teams:
viz/R/team_primitives.R
- Skills: domain-grouped across 19 primitives files:
Expected: A working R function that returns a list of 2-6 ggplot2 layers.
On failure: If
ggforce::geom_circle causes errors, ensure ggforce is installed. If coordinates are off, remember the canvas is 100x100 with (0,0) at bottom-left. Test the function interactively:
source("viz/R/utils.R"); source("viz/R/primitives.R") # etc. layers <- glyph_<name>(50, 50, 1.0, "#ff88dd", "#ffa8f0") p <- ggplot2::ggplot() + ggplot2::coord_fixed(xlim=c(0,100), ylim=c(0,100)) + ggplot2::theme_void() for (l in layers) p <- p + l print(p)
Step 3: Register — Map Entity to Glyph
Add the entity-to-glyph mapping in the appropriate glyph mapping file.
For skills:
- Open
viz/R/glyphs.R - Find the comment section for the target domain (e.g.,
)# -- design (3) - Add the entry in alphabetical order within the domain block:
"skill-id" = "glyph_function_name", - Update the domain count in the comment if applicable
For agents:
- Open
viz/R/agent_glyphs.R - Find the alphabetical position in
AGENT_GLYPHS - Add the entry:
"agent-id" = "glyph_function_name",
For teams:
-
Open
viz/R/team_glyphs.R -
Find the alphabetical position in
TEAM_GLYPHS -
Add the entry:
"team-id" = "glyph_function_name", -
Verify no duplicate ID exists in the target list
Expected: The appropriate
*_GLYPHS list contains the new mapping.
On failure: If the build later reports "No glyph mapped", double-check that the entity ID exactly matches the one in the manifest and registry.
Step 4: Manifest — Add Icon Entry
Register the icon in the appropriate manifest file.
For skills:
viz/data/icon-manifest.json
{ "skillId": "skill-id", "domain": "domain-name", "prompt": "<domain basePrompt>, <descriptors>, dark background, vector art", "seed": <next_seed>, "path": "public/icons/cyberpunk/<domain>/<skill-id>.webp", "status": "pending" }
For agents:
viz/data/agent-icon-manifest.json
{ "agentId": "agent-id", "prompt": "<agent-specific descriptors>, dark background, vector art", "seed": <next_seed>, "path": "public/icons/cyberpunk/agents/<agent-id>.webp", "status": "pending" }
For teams:
viz/data/team-icon-manifest.json
{ "teamId": "team-id", "prompt": "<team-specific descriptors>, dark background, vector art", "seed": <next_seed>, "path": "public/icons/cyberpunk/teams/<team-id>.webp", "status": "pending" }
Expected: Valid JSON with the new entry placed among its type siblings.
On failure: Validate JSON syntax. Common mistakes: trailing comma after last array element, missing quotes.
Step 5: Render — Generate the Icon
Run the icon pipeline to render the new glyph. Always use
build.sh as the entry point — it handles platform detection and R binary selection. See render-icon-pipeline for the full flag reference and pipeline architecture.
# From project root — renders all palettes, standard + HD, skips existing icons bash viz/build.sh --only <domain> --skip-existing # skills bash viz/build.sh --type agent --only <id> --skip-existing # agents bash viz/build.sh --type team --only <id> --skip-existing # teams # Dry run first: bash viz/build.sh --only <domain> --dry-run
build.sh runs the full pipeline (palette → data → manifest → render → terminal glyphs). The non-render steps add ~10 seconds but ensure all data is current.
Output locations:
- Skills:
viz/public/icons/<palette>/<domain>/<skill-id>.webp - Agents:
viz/public/icons/<palette>/agents/<agent-id>.webp - Teams:
viz/public/icons/<palette>/teams/<team-id>.webp
Expected: The log shows
OK: <entity> (seed=XXXXX, XX.XKB) and the WebP file exists.
On failure:
— Step 3 mapping is missing or has a typo"No glyph mapped"
— Domain not in"Unknown domain"
inget_palette_colors()palettes.R- R package errors — Run
firstinstall.packages(c("ggplot2", "ggforce", "ggfx", "ragg", "magick")) - If rendering crashes, test the glyph function interactively (see Step 2 fallback)
Step 6: Verify — Visual Inspection
Check the rendered output meets quality standards.
-
Verify file exists and has reasonable size:
ls -la viz/public/icons/cyberpunk/<type-path>/<entity-id>.webp # Expected: 15-80 KB typical range -
Open the WebP in an image viewer to check:
- Shape reads clearly at full size (1024x1024)
- Neon glow is present but not overpowering
- Background is transparent (no black/white rectangle)
- No clipping at canvas edges
-
Check at small sizes (the icon renders at ~40-160px in the force graph):
- Shape remains recognizable
- Detail doesn't turn to noise
- Glow doesn't overwhelm the shape
Expected: A clear, recognizable pictogram with even neon glow on transparent background.
On failure:
- Glow too strong: re-render with
(default is 4)--glow-sigma 2 - Glow too weak: re-render with
--glow-sigma 8 - Shape unreadable at small sizes: simplify the glyph (fewer layers, bolder strokes, increase
base value).lw(s, base) - Clipping at edges: reduce shape dimensions or shift center
Step 7: Iterate — Refine if Needed
Make adjustments and re-render.
-
Common adjustments:
- Bolder strokes: increase
— try.lw(s, base)
orbase = 3.03.5 - More visible fill: increase alpha from 0.10 to 0.15-0.20
- Shape proportions: adjust multipliers on
(e.g.,s
->20 * s
)24 * s - Add/remove detail layers: keep total layers between 2-6 for best results
- Bolder strokes: increase
-
To re-render after changes:
# Delete the existing icon first, then re-render rm viz/public/icons/cyberpunk/<type-path>/<entity-id>.webp # Use the appropriate build command from Step 5 -
When satisfied, verify the manifest status shows
(the build script updates it automatically on success)"done"
Expected: The final icon passes all verification checks from Step 6.
On failure: If after 3+ iterations the glyph still doesn't read well, consider using a completely different visual metaphor (return to Step 1).
Reference
Domain and Entity Color Palettes
All 58 domain colors (for skills) are defined in
viz/R/palettes.R (the single source of truth). Agent and team colors are also managed in palettes.R. The cyberpunk palette (hand-tuned neon colors) is in get_cyberpunk_colors(). Viridis-family palettes are auto-generated via viridisLite.
To look up a color:
source("viz/R/palettes.R") get_palette_colors("cyberpunk")$domains[["design"]] # skill domain get_palette_colors("cyberpunk")$agents[["mystic"]] # agent get_palette_colors("cyberpunk")$teams[["tending"]] # team
When adding a new domain, add it to three places in
palettes.R:
(alphabetical)PALETTE_DOMAIN_ORDER
domains listget_cyberpunk_colors()- Run
to regenerate palettes, data, and manifestsbash viz/build.sh
Glyph Function Catalog
See the full catalog of available glyph functions in the primitives source files:
- Skills:
throughviz/R/primitives.R
(domain-grouped)viz/R/primitives_19.R - Agents:
viz/R/agent_primitives.R - Teams:
viz/R/team_primitives.R
Helper Functions
| Function | Signature | Purpose |
|---|---|---|
| | Scale-aware line width |
| alias for | Shorthand aesthetic mapping |
| | Add alpha to hex color |
| | Brighten a hex color |
| | Dim a hex color |
Validation Checklist
- Glyph function follows
signatureglyph_<name>(cx, cy, s, col, bright) -> list() - All dimensions use
scaling factor* s - Color strategy uses
for fills,col
for outlines,bright
for transparencyhex_with_alpha() - Function placed in correct primitives file for entity type and domain
- Glyph mapping entry added in the appropriate
file*_glyphs.R - Manifest entry added with correct entity ID, path, and
"status": "pending" - Build command runs without error (dry-run first)
- Rendered WebP exists at the expected path
- File size in expected range (15-80 KB)
- Icon reads clearly at both 1024px and ~40px display sizes
- Transparent background (no solid rectangle behind the glyph)
- Manifest status updated to
after successful render"done"
Common Pitfalls
- Forgetting
: Hard-coded pixel values break when scale changes. Always multiply by* s
.s - Canvas origin confusion: (0,0) is bottom-left, not top-left. Higher
values move UP.y - Double glow: The renderer already applies
to every layer. Do NOT add glow inside the glyph function.ggfx::with_outer_glow() - Too many layers: Each layer gets individual glow wrapping. More than 8 layers makes rendering slow and visually noisy.
- Mismatched IDs: The entity ID in the glyph mapping, manifest, and registry must all match exactly.
- JSON trailing commas: The manifest is strict JSON. No trailing comma after the last array element.
- Missing domain color: If the domain isn't in
inget_cyberpunk_colors()
, rendering will error. Add the color first, then regenerate.palettes.R - Wrong primitives file: Skills go in domain-grouped
, agents inprimitives*.R
, teams inagent_primitives.R
.team_primitives.R
Related Skills
- enhance-glyph — improve an existing glyph's visual quality, fix rendering issues, or add detail layers
- audit-icon-pipeline — detect missing glyphs and icons to know what needs creating
- render-icon-pipeline — run the full rendering pipeline end-to-end
- ornament-style-mono — complementary AI-based image generation (Z-Image vs R-coded glyphs)
- ornament-style-color — color theory applicable to glyph accent fill decisions
- create-skill — the parent workflow that triggers glyph creation when adding new skills