Skip to content

Recipes — common "how do I" patterns

Cross-cutting tasks organized by user intent rather than by verb. For verb reference, see cli-reference.md. For vocabulary patterns (combining primitives), see vocabulary-patterns.md.

Each recipe is a small command sequence (CLI) plus a one-line MCP-tool equivalent where relevant. Both work; pick the surface that fits your context.


Workspace lifecycle

Reset an image to its baseline

You went down an exploration that didn't work; you want HEAD back at the original ingested state.

# CLI
chemigram reset <image_id>

# MCP
# tool: reset(image_id)

reset rewinds the current branch's HEAD to the baseline ref (created at ingest, immutable). All snapshots between baseline and HEAD remain in objects/ — they're recoverable via checkout <hash>. You haven't lost anything; you've just moved the working pointer.

See all snapshots for an image

chemigram log <image_id>
chemigram log <image_id> --json | jq -r '.[] | "\(.hash[0:8]) \(.label)"'

Check out an earlier snapshot to inspect

chemigram checkout <image_id> <hash-or-ref>
chemigram render-preview <image_id>
chemigram checkout <image_id> main    # back to your latest

checkout moves HEAD to a ref or hash. The working state on disk reflects that snapshot. Re-checkout to the head of main (or whichever branch you were on) when done inspecting.

Compare two snapshots side-by-side

chemigram compare <image_id> <hash-a> <hash-b> --size 1024
# Outputs a stitched JPEG; opens in Preview / xdg-open

The two snapshots can be branches (main, experimental) or hashes. The stitched output goes to <workspace>/previews/_compare_<a>_<b>.jpg.

Branch to explore a variant

chemigram branch <image_id> --name aggressive
chemigram apply-primitive <image_id> --entry contrast_high
chemigram apply-primitive <image_id> --entry blacks_crushed
chemigram render-preview <image_id>
# decide: yes or no
chemigram checkout <image_id> main    # back to original
# (the experimental branch is still in refs/heads/aggressive — abandon or tag)

Vocabulary discovery

List every entry across all loaded packs

chemigram vocab list
chemigram vocab list --json | jq -r '.[] | .name'

List by tag

chemigram vocab list --tag wb
chemigram vocab list --tag mask --tag gradient    # OR-filter; either tag matches

List by layer

chemigram vocab list --layer L3

Show full details for one entry

chemigram vocab show <entry-name>
chemigram vocab show gradient_top_dampen_highlights --json | jq

Shows the entry's manifest fields (subtype, touches, tags, description, modversions, optional mask_spec).

Apply any primitive through an ad-hoc drawn mask

You want to mask a global primitive (e.g., kill saturation in a region) for a specific photograph without authoring a new vocabulary entry. Use --mask-spec together with --value for parameterized entries:

chemigram apply-primitive <image_id> --entry saturation_global \
  --pack expressive-baseline \
  --value -1.0 \
  --mask-spec '{"dt_form":"ellipse","dt_params":{"center_x":0.5,"center_y":0.5,"radius_x":0.3,"radius_y":0.3,"border":0.1}}'

The JSON shape matches the manifest's mask_spec field — see mask-applicable-controls.md for the per-form parameter list and the per-module compatibility matrix (some module + mask combinations are documented as ineffective, e.g., temperature × any mask).

When the entry already has a manifest mask_spec, --mask-spec overrides it.

Apply a primitive at a specific magnitude

Parameterized vocabulary entries (RFC-021) take a value at apply time so you don't enumerate fixed strengths. The exposure entry covers any EV in [-3.0, +3.0]:

# Lift +0.7 EV — a value the old discrete vocabulary couldn't express
chemigram apply-primitive <image_id> --entry exposure --pack expressive-baseline --value 0.7

# Lower -1.5 EV
chemigram apply-primitive <image_id> --entry exposure --pack expressive-baseline --value -1.5

# Compose with a mask: brighten the center +0.5 EV, leave corners alone
chemigram apply-primitive <image_id> --entry exposure --pack expressive-baseline \
  --value 0.5 \
  --mask-spec '{"dt_form":"ellipse","dt_params":{"center_x":0.5,"center_y":0.5,"radius_x":0.3,"radius_y":0.3,"border":0.1}}'

For multi-parameter entries (when shipped), use --param NAME=VALUE (repeatable) instead of --value. Out-of-range values fail with INVALID_INPUT. See mask-applicable-controls.md for the full parameterization model.

Find entries that touch a specific darktable module

chemigram vocab list --json | jq -r '.[] | select(.touches[] == "exposure") | .name'

(There's no built-in --touches flag yet; this is a jq post-filter.)


Render and export

Quick preview at default size

chemigram render-preview <image_id>

Larger preview

chemigram render-preview <image_id> --size 2048

Export at full resolution as JPEG

chemigram export-final <image_id> --format jpeg
# output: <workspace>/<image_id>/exports/<image_id>_<hash[:8]>.jpeg

Export multiple sizes (for web + print)

chemigram export-final <image_id> --format jpeg --size 1920    # web
chemigram export-final <image_id> --format jpeg                 # full-res, print

The CLI doesn't yet support a single-call multi-size export; loop in shell.

Export every snapshot in a branch

chemigram log <image_id> --branch main --json | jq -r '.[].hash' | while read hash; do
  chemigram checkout <image_id> "$hash"
  chemigram export-final <image_id> --format jpeg --size 1920
done
chemigram checkout <image_id> main    # restore HEAD

For batch export, see examples/cli-batch-watch.sh for the watch-folder pattern.


Tagging and versioning

Tag the current snapshot

chemigram tag <image_id> --name v1-export
chemigram tag <image_id> --name v1-export --hash <specific-hash>   # tag a specific past snapshot

Tags are immutable — re-tagging an existing name is a VERSIONING_ERROR. To "rename" a tag, create a new one and the old one stays as historical record.

See all tags for an image

chemigram log <image_id> --json | jq -r '.[] | select(.refs | contains("v")) | "\(.hash[0:8]) \(.refs)"'

(Tags ride alongside branches in the refs field of log entries.)

Diff two snapshots (which primitives differ)

chemigram diff <image_id> baseline v1-export
# → list of added / removed / changed vocabulary primitives

Context and tastes

See your current taste files

ls ~/.chemigram/tastes/

Add a taste line directly (CLI-only)

chemigram apply-taste-update --content "Lift shadows on subjects almost always." --category appearance
chemigram apply-taste-update --content "Specifically reach for radial_subject_lift on portraits." --category process --file portrait.md

The CLI's apply-taste-update writes directly. The MCP equivalent is propose_taste_updateconfirm_taste_update, which is a two-step conversational dance.

Add a per-image note

chemigram apply-notes-update <image_id> --content "Manta belly was the focal point; mid-tone lift carried the shot."

Read what the agent will see at session start

chemigram --json read-context <image_id> | jq

This dumps the full first-turn context: tastes (default + genre), brief, notes, recent log, recent vocabulary gaps. Useful for understanding why the agent is suggesting what it suggests.


Vocabulary growth

Read your vocabulary gaps across images

cat ~/Pictures/Chemigram/*/vocabulary_gaps.jsonl | jq -r '.description'

Filter to recent gaps

find ~/Pictures/Chemigram -name vocabulary_gaps.jsonl -mtime -30 -exec cat {} \; | jq

Author a new vocabulary entry

See authoring-vocabulary-entries.md for the full GUI walkthrough.


Sessions and replay

Find your session transcripts for an image

ls ~/Pictures/Chemigram/<image_id>/sessions/

Each .jsonl file is one session. The first line is the header; subsequent lines are tool calls, proposals, and confirmations; the last line is the closing footer.

Read a session as prose

cat ~/Pictures/Chemigram/<image_id>/sessions/<session_id>.jsonl | \
  jq -r 'select(.event=="tool_call") | "\(.tool): \(.args)"'

Replay a session as a sequence of CLI calls

There's no built-in replay verb. The session transcript captures every tool call; manual replay = walk the transcript and re-issue equivalent CLI calls. For deterministic re-runs, prefer working from snapshots (the workspace objects/ store is the source of truth; transcripts are audit logs).


Failure / recovery

"My workspace is corrupted; how do I start over for this image?"

rm -rf ~/Pictures/Chemigram/<image_id>
chemigram ingest /path/to/raw.NEF

Workspaces are independent per image; deleting one doesn't affect others. The original raw is symlinked, not copied — deletion is safe.

"I applied something I didn't mean to; how do I undo?"

There's no undo verb. Either:

  • chemigram reset <image_id> rewinds to baseline (loses all snapshots-as-state but they remain in objects/)
  • chemigram checkout <image_id> <earlier-hash> rewinds HEAD to a specific earlier snapshot
  • chemigram log to find the hash you want to return to

"The MCP server isn't picking up my new vocabulary entry."

The agent loads vocabulary at session start. Restart your MCP session (Claude Code, Cursor, etc.) and the new entry will be in the action space. Or verify it loads correctly via CLI: chemigram vocab show <name>.

"Render is taking forever."

First render against a fresh CHEMIGRAM_DT_CONFIGDIR is always slow (darktable caches initialize). Subsequent renders should be 1–3 seconds at preview sizes. If renders consistently exceed 10s on Apple Silicon, profile darktable directly: darktable-cli --quiet <raw> <xmp> <out>. The slowness is darktable, not Chemigram.


See also