PhotoCraft is a Photoshop-style layered editor. All of its features are commands in one
engine registry: every menu item, tool and dialog is a command (948 in 0.18.0). The same
commands are reachable from the CLI, a headless JSON session and the desktop app. This
skill drives them headless, so no window or GPU is needed, and checks every result by
reading the document back. Verified against photocraft-cli 0.18.0 and
PhotoCraft.app 0.18.0 on macOS. The software is pre-1.0, so the command ids pinned in
references/ may change. When a pinned id fails, look it up.
command -v photocraft-cli || ls ~/.local/bin/photocraft-cli # the CLI
photocraft-cli --version
Pick the route
| Task | Route |
|---|---|
| One file: open, run commands, save | photocraft-cli run |
A folder of images through one action list, .atn action or droplet |
photocraft-cli batch / droplet |
| Build a layered composition (place images, text, fills, styles, anchors) | scripts/pc_compose.py spec.json |
| Many dependent steps, previews, or edits that need ids from earlier replies | scripts/pc_session.py (headless serve) |
| Exports that write folders (layer comps, layers, data sets) | photocraft-cli run (serve refuses raw paths) |
| The user wants to watch the edits in the app | scripts/pc_live.py (opt-in desktop control port) |
The headless routes are the default. Don't launch the GUI to read help: use
photocraft-cli <sub> --help, because running the app binary opens a window.
Scripts
Paths are relative to this skill's directory. They need Python 3.9+ and the standard
library only. Call them as python3 $S/<script> with S=<skill>/scripts.
-
pc_inspect.pylayers FILEprints the size, mode, depth, comps and the layer tree (id, kind, visibility, name,[x,y,w,h], text, effects). Run it before editing anything.ids FILE NAME...maps layer names to numeric ids.commands WORD...prints matching registry ids with their full params signature.
-
pc_compose.py SPEC.json: builds a composition from a canvas (or an opened template) and a bottom-to-top layer list:- images (
fitcover/contain,width,scale), text, solid or gradient fills, adjustment layers and rawcmdsteps; - placement by
anchor+marginorat; - per-layer
name,opacity,blend,visibleandstyle; - outputs in
saveand apreviewPNG.
It refuses to overwrite existing outputs or its inputs unless given
--overwrite. Spec example: recipes.md. - images (
-
pc_session.py STEPS|-: runs steps ({"cmd": id, "params"}or{"method": "doc.open", …}) through oneservesession. Pass--root DIRcovering every input and output (the default is the current directory). It sets the read/write roots, normalises paths, and stops at the first error with exit 1."@layer:Name"in params becomes that layer's id at that moment. -
pc_verify.pyprints JSON and exits 1 on any failed check:doc FILE --size WxH --text Name=… --hidden Name --effect "Name=Drop Shadow" --order Top,…,Bottom(--orderlists the layers top first)files DIR --count N --ext webp --long-edge 2048 --webp lossyhash DIR [--compare manifest.json]
-
pc_live.py:start --root DIR,call METHOD PARAMS,steps FILE,status,stopfor the running app over its authenticated loopback port.
Workflow
- Inspect first. Run
pc_inspect.py layerson any input document, andpc_inspect.py commands <words>for any command you haven't used in this session. Don't guess ids or param keys: unknown ids fail, and unknown keys are often ignored silently. - Never touch the originals. Write to a new path (
--out,save, a separate--outfolder for batches) unless the user explicitly asks for in-place. Before a batch, snapshot the inputs withpc_verify.py hash DIR > manifest.json. - Do the work with the route from the table. Use absolute paths in every JSON param, and create output folders first.
- Read back with
pc_verify.py: size, mode and depth; layer names, order and visibility; text; effects; output count, format and dimensions; unchanged input hashes. - Look at it. Render a preview of any visual result and view the image:
pc_compose.py'spreview,doc.render {"path": …, "maxSide": 1024}in a session, or the exported PNG itself. Overlaps, cropping and illegible text only show up there. - Report the output paths, what was verified, and anything PhotoCraft skipped
(unsupported
.atnsteps, warnings).
The traps that cost the most (details in gotchas.md)
- Pass layer ids, not names.
type.editsilently ignores alayername (even a correct one) and edits the active layer instead, with exit 0; check thelayerin its reply.image.variables.definerefuses names outright.layer.select {"name": …}selects by name, which makes a followingtype.editwithoutlayersafe. Get ids frompc_inspect.py ids(valid forrunon that same file) or write"@layer:Name"inpc_session.pysteps. Ids change after a save and re-open. - Layer comps restore visibility. Exporting comps re-applies each comp's recorded
visibility, so a layer you just hid reappears. Per comp:
layerComp.apply, then the change, thenlayerComp.update {"what": "visibility"}, and only then export. Text edits aren't part of a comp, so they carry through. - Bounds have two shapes: command replies give
[x0,y0,x1,y1];infogives[x,y,w,h]. Fill layers reportnull. Measure their shape withselect.loadSelection {"channel":"mask","layer":id}and thenselectionBounds. - Text:
type.createyis the baseline, and the size defaults to 12 pt. Always setsize, and place with an anchor or by translating after reading the bounds. servefile access fails closed: read/write roots are required,..is rejected, and raw-path commands (file.export.layerCompsToFiles,file.import.variableDataSets,layer.smartObjects.replaceContents) are disabled. Usedoc.*methods (doc.replaceSmartObject {path, layer}for a swap) orphotocraft-cli runfor those.- Overwrites:
run/convertrefuse an output that is the input unless--in-place; pass it only when the user asked for an in-place edit.runwithout--outsaves nothing, which makes it a free dry run. - Quality scales: CLI/
doc.savequalityis 1..100; enginequalityparams (droplets,file.export.*) are 0..12. WebP with a quality is lossy, without one lossless. replaceContents(anddoc.replaceSmartObject) keeps the old scale, not the frame. Refit it withedit.transformand aquad.
Common jobs
- Composition (social post, banner, thumbnail): write a spec, run
pc_compose.py, thenpc_verify.py doc+files, then view the preview. - Batch: write a JSON action list (
[["file.automate.fitImage",{"width":2048,"height":2048}],["filter.sharpen.unsharpMask",{"amount":80}]]), runphotocraft-cli batch --actions a.json --in IN --out OUT --format webp --quality 80, thenpc_verify.py files OUT …andhash IN --compare.batchdoesn't recurse; for subfolders usefile.automate.batchwithincludeSubfolders(recipes.md). - Template edit and comps, data sets, layer export, smart-object swap,
.atn, droplets: recipes.md. - Filters and adjustments:
run in.jpg --cmd image.autoTone --cmd filter.sharpen.smartSharpen --params '{"amount":80}' --out out.jpg. For non-destructive edits uselayer.newAdjustmentLayer.<kind>and save.psd.
References
- references/cli.md: every subcommand, its flags and verified behaviour, plus formats and quality.
- references/commands.md: the pinned command ids by task, with their key params.
- references/recipes.md: worked commands for each job above.
- references/gotchas.md: everything that fails silently.
- references/control-protocol.md:
servemethods, filesystem roots, the desktop control port, its security posture and limits.
Live app (opt-in)
pc_live.py start --root DIR opens PhotoCraft with --control 7878. It listens on
127.0.0.1 only and uses a 256-bit token from ~/.config/photocraft-skill/control.token
(0600, never on the command line). File access is limited to DIR. app.open/app.save
paths are relative to that root. pc_live.py stop sends app.quit, which discards
unsaved edits, so save first. Start the port only for this, and stop it when done.