@smallpen/cli 0.1.0-alpha.3 → 0.1.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/help.mjs CHANGED
@@ -1,216 +1,267 @@
1
- const COMMANDS = [
2
- ["version", "Print the CLI package version without opening a workspace"],
3
- ["init", "Persist a guided Brief/Proposal and create Foundation + Product"],
4
- ["validate", "Validate a package and its same-workspace Foundation"],
5
- ["inspect", "Inspect package identity, capabilities, and domain summary"],
6
- ["list", "List stable domain objects by kind"],
7
- ["read", "Read the complete validated Canonical Snapshot"],
8
- ["read-view", "Read the deterministic default or selected Design View"],
9
- ["discover", "Discover exact selectors and executable next reads"],
10
- ["catalog", "Derive the Design System Catalog and requirement coverage"],
11
- ["search-components", "Find reusable Product and Foundation components"],
12
- ["component", "Inspect a Component Set and its legal variants"],
13
- ["compare", "Compare two to four explicitly selected Design Views"],
14
- ["tokens", "List Effective Tokens for a Context"],
15
- ["search-tokens", "Find reusable Tokens by name, type, or design value"],
16
- ["effective-token", "Resolve one Effective Token"],
17
- ["explain-token", "Explain one Effective Token resolution decision"],
18
- ["render", "Render the selected Design View to deterministic PNG"],
19
- ["inspect-view", "Return wireframe, semanticTree, optional image, and supporting context"],
20
- ["render-matrix", "Render multiple design contexts for AI comparison"],
21
- ["evidence", "Return PNG plus deterministic review evidence; --output exports files"],
22
- ["import-draft", "Import Figma clipboard, SVG, or PNG as a separate Draft file"],
23
- ["import-media", "Import binary Media (PNG/JPEG/GIF/WebP/SVG) into a Product Asset Library"],
24
- ["remove-media", "Remove an imported Media descriptor from the Asset Library"],
25
- ["import-font", "Import a TTF/OTF/WOFF font and convert SFNT to WOFF"],
26
- ["import-tokens", "Review and import a DTCG / Tokens Studio token file into the Token Library"],
27
- ["library-refresh", "Re-fetch a declared URL Library into its verified cache"],
28
- ["draft-diff", "Compare two independent Draft files without changing Canonical"],
29
- ["draft-compile", "Compile selected Draft fields into a current-revision batch"],
30
- ["flow", "Create flowchart nodes from a declarative intent"],
31
- ["page", "Create page nodes from a declarative intent"],
32
- ["token", "Apply declarative Token operations atomically"],
33
- ["impact", "Find every node and component using a Token"],
34
- ["apply", "Atomically apply one typed Operation Batch"],
35
- ["watch", "Emit NDJSON revision events for local external changes"],
36
- ["repair", "Inspect or execute one explicit Repair choice"],
1
+ import {
2
+ COMMAND_CONTRACTS,
3
+ commandContract,
4
+ commandHelp,
5
+ } from "./command-contract.mjs";
6
+ import { commandOverview, printBriefHelp } from "./help-topics.mjs";
7
+ import { printText } from "./result-files.mjs";
8
+ import { COMMAND_GROUPS, publicArgv } from "./command-tree.mjs";
9
+
10
+ const COMMANDS = Object.entries(COMMAND_CONTRACTS).map(
11
+ ([name, { purpose }]) => [name, purpose],
12
+ );
13
+ // What help lists: every grouped action and the shared commands.
14
+ const PUBLIC_PATHS = [
15
+ ...Object.entries(COMMAND_GROUPS).flatMap(([group, { actions }]) =>
16
+ Object.entries(actions).map(([action, { purpose }]) => [`${group} ${action}`, purpose]),
17
+ ),
18
+ ...["view", "changes", "export", "validate", "help", "schema", "version"].map((name) => [name, COMMAND_CONTRACTS[name].purpose]),
37
19
  ];
38
-
39
- const DESIGN_SELECTORS = `Design selectors:
40
- --screen ID Screen; default: manifest defaultScreenId
41
- --presentation ID Presentation; default: Screen Base Presentation
42
- --scenario ID Reproducible Scenario; default: canonical initial state
43
- --context-profile ID Named Context profile
44
- --context AXIS=VALUE Repeatable finite Context selection`;
45
-
46
- const VIEW_SELECTORS = `${DESIGN_SELECTORS}
47
- --format FORMAT structure|semantic|wireframe|screenshot`;
20
+ // Help texts are kept per engine; they print with the grouped path a caller
21
+ // types ("smallpen apply" reads "smallpen advanced apply").
22
+ const publicName = (name) => publicArgv([name]).join(" ");
23
+ const publicText = (text) =>
24
+ text.replace(/\b(smallpen|SmallPen) ([a-z][a-z-]*)/g, (match, word, name) =>
25
+ Object.hasOwn(COMMAND_CONTRACTS, name) ? `${word} ${publicName(name)}` : match);
26
+
27
+ const WRITE_REPLY = `Reply size:
28
+ Default: batchId, revision, changed/alreadyApplied/dryRun, counts, warnings,
29
+ and summary. changeReportPath records exact before/after values. reverseEdit
30
+ describes an explicit new write at the confirmed revision; inverseBatchPath
31
+ locates its JSON. This does not rewind history. --inverse-out FILE chooses a
32
+ durable caller-owned file (only when the batch commits). --compact is default.
33
+ --full includes inverseBatch, guidance, and exact before/after diff values.
34
+ After later edits, read current values and restore intended fields in a new
35
+ scoped edit. Never force an old snapshot by replacing its baseRevision.
36
+ For safe retries choose batchId before the first call and reuse it with the
37
+ same input. The local ledger retains at most 500 batch identities.
38
+ smallpen apply PACKAGE --batch FILE --inverse-out FILE.reverse.json`;
48
39
 
49
40
  const HELP = {
41
+ assets: `Usage:
42
+ smallpen advanced style list PACKAGE [--kind colors|typographies]
43
+ [--limit N] [--offset N] [--json]
44
+
45
+ Read the App's stored color and typography assets. Use token for design values
46
+ and token theme for their options and defaults. File assets are managed through
47
+ asset media and asset font. Write styles in their owning package.`,
48
+ help: `Usage:
49
+ smallpen help [OBJECT [GROUP] [ACTION]|rules [TOPIC]] [--json]
50
+
51
+ Use COMMAND --help for options, or schema for JSON input shapes. Use help rules for shared operation rules; schema command OBJECT ACTION
52
+ describes one command at a time.`,
53
+ themes: `Usage:
54
+ smallpen token theme list PACKAGE [--theme GROUP/NAME]... [--json]
55
+
56
+ Read project-defined groups, options (named Group/Option), defaults and this
57
+ call's selection. This is a lightweight settings query, with no Token inventory.
58
+ selection and option active flags mean this command's parameters and defaults,
59
+ never a previous CLI call. The stored App selection is unrelated to this query
60
+ and omitted by default; --full includes appSelection for explicit inspection.
61
+ It never supplies the CLI defaults.
62
+ No --theme uses project defaults; App current selection is independent.
63
+ --theme overrides only named groups. No data or App state is written.
64
+ Write: token theme add|rename|default|delete --theme Group/Option.`,
65
+ view: `Usage:
66
+ smallpen view PACKAGE [what] [--as text|wireframe|png|issues] [--theme G/O]...
67
+
68
+ Look at anything by name; no IDs. What:
69
+ (nothing) the whole package: themes, Tokens, components, pages
70
+ --tokens | --token color every Token, or those under a name
71
+ --components every component
72
+ --component Button one component: every variant
73
+ [--variant "Style=secondary"] [--element Label] one variant, one part of it
74
+ --pages [--platform mobile] every page
75
+ --page Board one page ("Board" or "Tasks / Board")
76
+ [--platform mobile] [--element "Top bar"] one version, one part of it
77
+ --as text (default) is a compact outline; wireframe is a text drawing of regions;
78
+ png writes an image (inspect it only if you can see images); issues lists layout,
79
+ text and contrast problems by element. Tokens have text, wireframe and png.
80
+ --theme Group/Option shows values under that option for this call only.
81
+ Large results go to a file. Unknown names fail with the names that exist;
82
+ "Name [2]" picks one of a name stored twice.
83
+ Next: changes PACKAGE --since REVISION after someone edits the design.`,
84
+ "token-export": `Usage:
85
+ smallpen token export PACKAGE [--type TYPE] [--token PREFIX] [--by GROUP]
86
+ [--format json|flat] [--theme G/O]... [--output DIR] [--json]
87
+
88
+ Export resolved Token values as JSON. --by GROUP writes one file
89
+ per option of that theme group, named after the option (--type string
90
+ --by Language with options English and Chinese -> English.json, Chinese.json);
91
+ without it one file, tokens.json, holds the values of the selected themes
92
+ (project defaults, or --theme). json nests by name
93
+ (text.task.new -> {"text":{"task":{"new":...}}}); flat keeps one key per name.
94
+ Other groups use the project defaults or --theme.`,
95
+ changes: `Usage:
96
+ smallpen changes PACKAGE [--since REVISION] [--limit N] [--full] [--json]
97
+
98
+ What changed since a revision you saw, by name: themes, Tokens (with values),
99
+ components (per variant and element) and pages (per platform and element),
100
+ including edits a person made in the App. Every read and write reports a
101
+ revision; keep the latest one and pass it as --since to align code with the
102
+ design. Without --since: the most recent edits, who made them (app or cli) and
103
+ the revision before each. --full adds the structured list. Nothing is stored
104
+ for the caller.`,
105
+ export: `Usage:
106
+ smallpen export PACKAGE [--page P [--platform X] [--element E]]
107
+ [--component C [--variant V] [--element E]] [--design-system]
108
+ [--format text|wireframe|png] [--output FILE] [--scale N] [--json]
109
+
110
+ Save a page, component or region as a text wireframe (default) or a PNG file,
111
+ named the way view names it. Nothing named is the main page. Request PNG with
112
+ --format png; read text and wireframes first, and inspect PNG only when the
113
+ caller can see images. Text files contain LF newlines and alignment spaces.
114
+ Missing --output uses a temporary file. Returns path, hash, bytes, target,
115
+ themes and revisions. --design-system exports the generated Token/component
116
+ page; do not draw a copy. --evidence adds review evidence to a page PNG.`,
50
117
  version: `Usage:
51
- smallpen --version
52
118
  smallpen version [--json]
53
119
 
54
120
  Output:
55
- The installed CLI package version, or {name,version} with --json.
121
+ JSON {name,version} for the installed CLI package.
56
122
  No package path is required; design files are unchanged and no services start.`,
57
- init: `Usage:
58
- smallpen init <workspace-directory> [--state FILE] [--answers FILE]
59
- [--answer QUESTION_ID=JSON]... [--confirm] [--locale LOCALE] [--json]
123
+ schema: `Usage:
124
+ smallpen schema [TOPIC] [--json]
125
+ smallpen schema operation TYPE [--json]
60
126
 
61
127
  Purpose:
62
- Persist a progressively completed Initialization Brief and validated Proposal.
63
- No hidden conversational state is used. --confirm atomically creates one local
64
- Foundation and one Product; an existing target is never overwritten.
128
+ Print the exact JSON that apply, token, page, flow, and init accept: fields,
129
+ types, allowed values, a valid example, and the inverse each operation
130
+ returns. No package is opened. Field lists come from the validators, and every
131
+ example is tested against a workspace made by smallpen init.
132
+
133
+ Topics:
134
+ command NAME one command: parameters, types, defaults and constraints
135
+ commands command names and purposes
136
+ operations every operation type, its purpose, Package, required fields
137
+ operation TYPE one operation in full
138
+ batch batch contract, Foundation vs Product, worked example
139
+ node node fields, textStyle, tokenBindings
140
+ node-types what each node type requires
141
+ token Tokens in token sets, DTCG files, bindings
142
+ theme token sets and themes, --theme, Foundation + Product themes
143
+ token-types the value each Token type accepts
144
+ component-set Component Set, Axis roles (configuration, state), variants
145
+ instance INSTANCE nodes and overrides
146
+ presentation Presentation fields, size, and resizing
147
+ screen Screen fields
148
+ scenario Scenario fields and viewport
149
+ context Context Axes (viewport, density, locale, ...)
150
+ init blank package paths, layouts and creation behavior
151
+
152
+ Examples:
153
+ smallpen schema operations
154
+ smallpen schema operation update-component-node
155
+ smallpen schema component-set`,
156
+ init: `Usage:
157
+ smallpen project init PATH [--name NAME] [--layout single|foundation-product] [--json]
65
158
 
66
- Defaults and values:
67
- --state defaults to <workspace-directory>.smallpen-init.json.
68
- Project kinds: application|motion|custom. Foundation choice: create-new.
69
- --answer values are JSON (for example projectKind=\"application\").
159
+ Create a blank package immediately. PATH ending in .smallpen creates that
160
+ package; a directory creates a new workspace containing a package. The default
161
+ is one self-contained package with one canvas and Theme/Default, no Tokens,
162
+ components, business flows or platform copies. Existing targets are rejected.
163
+ --layout foundation-product creates two blank packages with a local dependency.
164
+ No business questionnaire, saved selection or initialization state is required.
70
165
 
71
166
  Examples:
72
- smallpen init ./quincy --json
73
- smallpen init ./quincy --state ./quincy-init.json --answer projectKind=\"application\" --json
74
- smallpen init ./quincy --answers ./answers.json --confirm --json
167
+ smallpen project init ./acme.smallpen --name Acme --json
168
+ smallpen project init ./acme --layout foundation-product --json
169
+
170
+ Next: smallpen schema command project init --json`,
75
171
 
76
- Output:
77
- status needs_input returns a stable question id, localized label, schema,
78
- optional recommendation, and continuation args with an answer placeholder. Replace
79
- <JSON> with your schema-valid answer; if no recommendation is present, supply the
80
- project's own purpose, audience, or deliverable. status proposal returns the
81
- persisted Proposal and confirmation args. Only --confirm creates the packages;
82
- status initialized returns both package paths.
83
-
84
- Errors and next commands:
85
- Malformed --answer JSON identifies the question; schema-invalid values also return
86
- its schema. Correct the answer and retry with the same --state. After confirming:
87
- smallpen validate <product.smallpen> --json`,
88
172
  validate: `Usage:
89
- smallpen validate <product.smallpen> [--json]
173
+ smallpen validate PACKAGE [--page P [--platform X] [--element E]]
174
+ [--component C [--variant V] [--element E]] [--design-system]
175
+ [--theme G/O]... [--limit N] [--offset N] [--json]
90
176
 
91
177
  Purpose:
92
- Validate Canonical JSON, IDs, references, Product/Foundation dependency, all Context
93
- combinations used by projections, and strict Scenario/component selections.
178
+ Validate the stored data, references and the Product/Foundation dependency,
179
+ and check text fit, bounds, overlaps and contrast for the target. A page
180
+ checks every version unless --platform names one; a component every variant
181
+ unless --variant names one. Nothing named checks the main page and lists
182
+ names stored twice.
94
183
 
95
184
  Output:
96
- status valid with Product and Foundation revisions; dependency conflicts return
97
- repair_required with exact choices.
185
+ status/dataStatus valid means the data and references are valid. Visual
186
+ issues are separate: read visualStatus, issueCount, issues and
187
+ coverage.skipped. Valid data is not visual approval. Issue pagination does
188
+ not shrink coverage. Dependency conflicts return repair_required with exact
189
+ choices. view --as issues gives the same issues by element name.
98
190
 
99
191
  Next:
100
- smallpen inspect <package> --json
101
- smallpen repair <product> --json`,
192
+ smallpen view <package> --as issues --json
193
+ smallpen project repair <product> --json`,
102
194
  inspect: `Usage:
103
195
  smallpen inspect <package.smallpen> [--json]
104
196
 
105
197
  Purpose:
106
- Return package identity/revision, format capabilities, Contexts, Screens,
107
- Presentations, Tokens, Component Sets, Scenarios, requirements, Flows, and
108
- machine-readable Design System preflight operations.
198
+ Return Package identity, revision, and domain counts. --full includes all
199
+ domain summaries and format capabilities.
109
200
 
110
201
  Next:
111
- smallpen list <package> --kind screens --json
112
- smallpen read-view <package> --json`,
202
+ smallpen view <package> --json`,
113
203
  list: `Usage:
114
204
  smallpen list <package.smallpen> [--kind KIND] [--offset N] [--limit N] [--json]
115
205
 
116
206
  Values:
117
207
  KIND: all|screens|presentations|contexts|tokens|components|scenarios|requirements|flows
118
- Defaults: kind=all, offset=0, limit=100. Lists include total/range pagination.
119
-
120
- Next:
121
- Use returned stable IDs with read-view, effective-token, or compare.`,
122
- read: `Usage:
123
- smallpen read <package.smallpen> [--json]
124
-
125
- Purpose:
126
- Return the complete validated Canonical Snapshot used by adapters. Binary blobs are
127
- not embedded in JSON. Prefer inspect/list/read-view for bounded Agent reads.
128
-
129
- Next:
130
- smallpen inspect <package> --json`,
131
- "read-view": `Usage:
132
- smallpen read-view <product.smallpen> [selectors] [--offset N] [--limit N]
133
- [--locale LOCALE] [--json]
134
-
135
- ${VIEW_SELECTORS}
136
-
137
- Defaults:
138
- Base Presentation + default Context values + canonical initial state + structure.
139
- All formats resolve the same selection.
140
-
141
- Choose a format:
142
- wireframe A scaled 2D ASCII canvas for fast spatial reasoning, followed by a
143
- compact marker key with hierarchy, layer type, name, and stable ID.
144
- semantic Exact machine-readable hierarchy, bounds, text, alignment, resolved
145
- typography, component references, variants, and Token bindings.
146
- screenshot Rendered PNG metadata only; use render/evidence for inline pixels,
147
- or add --output to explicitly write PNG files.
148
- The PNG image itself is raster pixels only: it has no layer IDs,
149
- hierarchy, bounds, component references, or Token bindings.
150
-
151
- Agent guidance:
152
- Use wireframe or semantic to understand and edit the design. Use PNG to inspect
153
- high-fidelity color, typography, spacing, effects, and rendering. Use inspect-view
154
- --include-image when both structural reasoning and visual QA are required.
208
+ Defaults: kind=all, offset=0, limit=20. Return identities and counts, without
209
+ node trees or variants. page includes total/hasMore; --full includes definitions.
210
+ Default item fields follow the public allowlist in schema command list.
211
+ Descriptions and editor root IDs belong to target reads, not this directory.
212
+ Omit visibility=public and deprecated=false. Only report private visibility,
213
+ actual deprecation and a configured replacement when they affect reuse.
214
+ JSON is compact in all modes; --full adds data, not pretty-printing.
155
215
 
156
216
  Next:
157
- Execute any Discovery Guide command without inventing arguments.`,
158
- discover: `Usage:
159
- smallpen discover <product.smallpen> [selectors] [--offset N] [--limit N]
160
- [--locale LOCALE] [--json]
161
-
162
- ${VIEW_SELECTORS}
163
-
164
- Output:
165
- Exact selector parameters, defaults, finite valid values, localized summaries,
166
- machine operation/args, copyable commands, and pagination metadata.`,
217
+ Look at one by name: smallpen view <package> --page NAME | --component NAME.`,
167
218
  catalog: `Usage:
168
- smallpen catalog <product.smallpen> [--context AXIS=VALUE]... [--json]
219
+ smallpen catalog <product.smallpen> [--context AXIS=VALUE]... [--theme GROUP/NAME]...
220
+ [--offset N] [--limit N] [--full] [--json]
169
221
 
170
222
  Purpose:
171
- Derive legal Component variants, inherited assets, Effective Tokens, Contexts,
172
- Scenarios, Screens, and requirement coverage from the same validated workspace.`,
223
+ Return Component identities (limit=20) and domain counts. Use component for one
224
+ candidate's legal selections, tokens/search-tokens for values, or --full for
225
+ the complete Catalog. Token definitions, effective values and inventory are
226
+ not repeated in the default response. Default items use the same public
227
+ discovery fields as list; descriptions require a target read.`,
173
228
  "search-components": `Usage:
174
229
  smallpen search-components <product.smallpen> --query TEXT [--limit N] [--json]
175
230
 
176
231
  Purpose:
177
232
  Find reusable Product, public Foundation, and linked Library components before
178
233
  drawing a custom replacement. Matches stable ID, name, category, and description and returns the
179
- owning Package, source layer, legal variant selections, and an executable
234
+ owning Package, source layer, variant count, and an executable
180
235
  owner-scoped component command (argv plus a POSIX-shell command).
236
+ Default candidates use the public discovery fields; read component for
237
+ descriptions and legal choices. Private reuse restrictions remain explicit.
181
238
 
182
239
  State:
183
240
  Every query is explicit and self-contained. It never creates a current component
184
241
  or affects a later command.`,
185
- component: `Usage:
186
- smallpen component <package.smallpen> --component-id CMP [--json]
187
-
188
- Purpose:
189
- Return one exact Component Set, its legal variant selections, replacement metadata,
190
- and component-targeted Scenarios. This is read-only and uses stable Canonical IDs.
191
- The package path must identify the owning Package. For inherited components,
192
- execute the owner-scoped command returned by search-components.
193
- An HTTP(S) Library URL is also accepted and uses the normal Library cache.
194
-
195
- Next:
196
- Use a returned selection with read-view or inspect-view to review the design.`,
197
- compare: `Usage:
198
- smallpen compare <product.smallpen> --selector JSON --selector JSON
199
- [--selector JSON] [--selector JSON] [--json]
200
-
201
- Values:
202
- Exactly two to four selector objects. Example:
203
- smallpen compare product.smallpen --selector '{"presentationId":"pres_desktop"}' \\
204
- --selector '{"presentationId":"pres_mobile"}' --json`,
205
242
  tokens: `Usage:
206
- smallpen tokens <product.smallpen> [--context AXIS=VALUE]... [--json]
243
+ smallpen tokens <package.smallpen> [--theme GROUP/NAME]... [--context AXIS=VALUE]...
244
+ [--offset N] [--limit N] [--locale LOCALE] [--full] [--json]
245
+ smallpen tokens <package.smallpen> --definitions [--group GROUP]
246
+ [--offset N] [--limit N] [--full] [--json]
207
247
 
208
248
  Purpose:
209
- List Effective Tokens using Product first, Context specificity within a layer,
210
- then Foundation fallback.`,
249
+ List Effective Tokens: the active token sets of the selected themes (a later
250
+ set overrides an earlier one by name; a Product's own sets sit on top of its
251
+ Foundation's), then Context specificity for legacy contextValues. themes[]
252
+ lists every theme with active, owner (package|foundation) and its sets.
253
+ --theme selects themes for this read only (one per group); unknown groups or
254
+ names exit unknown_token_theme with validThemes. labels follow --locale or the
255
+ environment. Default: 20 items with target, Token id/path/type and resolved
256
+ value, plus page.total/hasMore. --full includes every definition and source chain.
257
+ --definitions reads exact stored Tokens in every option Set, with owning
258
+ packageId, setId, group and option. Optional --group filters the App Set group.
259
+ Missing cells have no item; they do not inherit Default. This mode cannot take
260
+ --theme or --context. Use the ordinary tokens read to resolve a combination.`,
211
261
  "search-tokens": `Usage:
212
262
  smallpen search-tokens <product.smallpen> (--color COLOR | --value JSON | --query TEXT | --type TYPE)
213
- [--context AXIS=VALUE]... [--limit N] [--json]
263
+ [--context AXIS=VALUE]... [--theme GROUP/NAME]... [--all-themes]
264
+ [--limit N] [--json]
214
265
 
215
266
  Purpose:
216
267
  Find reusable Product and Foundation Design Tokens before writing a hard-coded
@@ -218,6 +269,11 @@ Purpose:
218
269
  values. Without --context, every finite Web/Desktop/theme/etc. Context is searched
219
270
  and each resolved value reports its matching Contexts. Repeating --context restricts
220
271
  the search explicitly. Name queries match Token path, description, and type.
272
+ Token themes: by default project defaults are searched; --theme overrides only
273
+ named groups, as every resolved read does. --all-themes searches every
274
+ selection of one theme per group (at most 64) and gives each item themes[],
275
+ the selections where it has that value. themeScope {mode active|explicit|all,
276
+ selections} says which themes were searched.
221
277
 
222
278
  Examples:
223
279
  smallpen search-tokens product.smallpen --color '#6750a4' --json
@@ -228,143 +284,47 @@ State:
228
284
  Every search is self-contained. It never reads or writes a current selection,
229
285
  previous result, session, or Web UI state.`,
230
286
  "effective-token": `Usage:
231
- smallpen effective-token <product.smallpen> --token-id TOK [--package-id PKG]
232
- [--context AXIS=VALUE]... [--json]
287
+ smallpen token show <package.smallpen> --path TOKEN.NAME
288
+ [--theme GROUP/NAME]... [--json]
233
289
 
234
290
  Defaults:
235
- package-id defaults to the Product Package ID; Context axes use defaults.`,
291
+ Themes use project defaults, independently of the App's active selection.
292
+ --theme overrides only named groups.`,
236
293
  "explain-token": `Usage:
237
- smallpen explain-token <product.smallpen> --token-id TOK [--package-id PKG]
238
- [--context AXIS=VALUE]... [--json]
294
+ smallpen token explain <package.smallpen> --path TOKEN.NAME
295
+ [--theme GROUP/NAME]... [--json]
239
296
 
240
297
  Output:
241
298
  Chosen source/value plus all candidates, specificity, rejection reasons, and layer.`,
242
- render: `Usage:
243
- smallpen render <product.smallpen> [selectors] [--scale N] [--output FILE] [--json]
244
-
245
- ${DESIGN_SELECTORS}
246
-
247
- Defaults and limits:
248
- No --output: return a PNG as bare base64 in one JSON result; no image file or
249
- temporary image is created. This also applies without --json.
250
- Explicit --output FILE: write that PNG and return its absolute output path
251
- instead of base64. Existing FILE is replaced only after a successful write.
252
- scale=1, scale range (0,8], maximum dimension 8192,
253
- maximum 32M pixels. Output is deterministic PNG with revision/render hash. The
254
- PNG image itself is raster pixels only and contains no layer IDs, hierarchy,
255
- bounds, component references, or Token bindings. Use it for high-fidelity visual
256
- QA of color, typography, spacing, effects, alignment, and rendering.
257
- Decode the current response in an image-capable client; base64 text alone is
258
- not visual QA. Verify decoded bytes against renderHash (SHA-256), and use the
259
- accompanying revision and selection. On failure, never open a previous file.
260
-
261
- Wireframe:
262
- Use smallpen read-view <product.smallpen> --format wireframe --json.
263
- It returns a scaled 2D ASCII canvas and compact marker key. Use semanticTree
264
- from inspect-view when exact bounds, text, component, and Token data is needed.`,
265
- "inspect-view": `Usage:
266
- smallpen inspect-view <product.smallpen> [selectors] [--include-image]
267
- [--output FILE] [--base64] [--json]
268
-
269
- ${DESIGN_SELECTORS}
270
- --scale N Image scale; default: 1 (requires --include-image to render)
271
- --locale LOCALE Localized view labels; default: zh-TW
272
-
273
- Purpose:
274
- Return one AI-readable view bundle.
275
-
276
- Three complementary view parts:
277
- wireframe Scaled 2D ASCII canvas plus a compact layer marker key.
278
- semanticTree Exact hierarchy, bounds, text, alignment, typography, component,
279
- variant, and Token data.
280
- image Optional high-fidelity PNG and metadata; add --include-image to render it.
281
-
282
- Supporting context:
283
- Effective Tokens, component catalog, selected Context, and revision metadata.
284
- Without --output, image.base64 contains bare PNG base64 in this one JSON result
285
- (also without --json). No image file or temporary image is created.
286
- Explicit --output FILE writes the image and returns image.output instead of
287
- base64; add --base64 only when both file and inline bytes are wanted.
288
- --output and --base64 require --include-image.
289
-
290
- Agent guidance:
291
- Use this when the Agent needs both structure and visual QA. Start with wireframe
292
- for spatial relationships, query semanticTree for exact data, and inspect image
293
- only when pixel-level visual QA is needed. The PNG itself contains no layer or
294
- Token metadata. Decode this response in an image-capable client; base64 text
295
- alone is not visual QA. image.revision/selection identify the rendered snapshot;
296
- image.renderHash is the SHA-256 of the decoded PNG. Never reuse an old image
297
- after an error, or infer freshness from a filename.`,
298
- "render-matrix": `Usage:
299
- smallpen render-matrix <product.smallpen> --contexts CONTEXTS.json
300
- [--output DIRECTORY] [--scale N] [--json]
301
-
302
- Purpose:
303
- Render a deterministic PNG for every explicit design selector in CONTEXTS.json.
304
- Each selector may choose context axes, screen, presentation, and scenario.
305
- Without --output, one JSON result contains ordered images with bare base64,
306
- dimensions, exact selectors, per-image revisions, and PNG SHA-256 renderHash.
307
- No image files or temporary images are created, including if a later selector
308
- fails. This also applies without --json.
309
- Explicit --output DIRECTORY writes PNG files and returns each image.output
310
- instead of base64. Different render hashes retain separate files; there is no
311
- automatic cleanup or all-or-nothing multi-file export. Only consume images
312
- from a successful current response; never fall back to earlier matrix files.`,
313
- evidence: `Usage:
314
- smallpen evidence <product.smallpen> [selectors] [--scale N] [--output PREFIX] [--json]
315
-
316
- ${DESIGN_SELECTORS}
317
-
318
- Output:
319
- Without --output, return one JSON evidence bundle with image.base64 (bare PNG
320
- base64); no PNG, evidence JSON file, or temporary image is created. This also
321
- applies without --json. image includes the current revision/selection and
322
- renderHash (SHA-256 of decoded PNG bytes), not a cache locator.
323
- Explicit --output PREFIX writes PREFIX.png and PREFIX.json instead of base64.
324
- Evidence contains revision/package hash, resolved selectors,
325
- render hash, node-to-image regions, Semantic Tree, viewport, and diagnostics.
326
- PREFIX.png is an unannotated high-fidelity raster image. Semantic details live in
327
- PREFIX.json and are not embedded visibly in the PNG.
328
- Decode the current inline response in an image-capable client for visual QA.
329
- Failed exports do not authorize reading an old file; multi-file export is not
330
- an all-or-nothing transaction and existing files are not automatically cleaned.
331
-
332
- Wireframe:
333
- Use smallpen read-view <product.smallpen> --format wireframe --json.
334
- It returns a scaled 2D ASCII canvas with one [NN] marker per layer.`,
335
299
  "import-media": `Usage:
336
- smallpen import-media <package.smallpen> --file MEDIA_FILE [--media-id media_...]
300
+ smallpen asset media import <package.smallpen> --file MEDIA_FILE
337
301
  [--media-path PATH] [--name NAME] [--json]
338
302
 
339
303
  Import is the public binary Media entry for an existing Product: PNG, JPEG, GIF,
340
304
  WebP, and SVG are detected from bytes, dimensions are validated, and the blob is
341
305
  content-addressed (identical bytes in one package share one blob). JSON apply
342
- still forbids blob writes; media import is a file entry, not a batch operation.
343
- remove-media deletes the descriptor (metadata lifecycle is reversible); the
344
- content-addressed blob itself is immutable and shared.
306
+ still forbids blob writes; asset media import is a file entry, not a batch operation.
307
+ asset media delete --media NAME deletes the descriptor; the content-addressed blob
308
+ itself is immutable and shared.
345
309
 
346
310
  Errors and recovery:
347
311
  Corrupt or unsupported bytes exit 1 invalid_media_file and write nothing.
348
- Reusing --media-id exits duplicate_media_id with the existing descriptor.
349
-
350
- `,
351
- "remove-media": `Usage:
352
- smallpen remove-media <package.smallpen> --media-id MEDIA [--json]
353
312
 
354
- Removes one Media descriptor from the Asset Library atomically; the inverse batch
355
- restores it. Unknown ids exit missing_media.
313
+ Output:
314
+ descriptor and revision, plus batchId and the revision-guarded reverse edit (inverseBatch).
356
315
 
357
316
  `,
358
317
  "import-font": `Usage:
359
- smallpen import-font <package.smallpen> --file FONT_FILE --family NAME
360
- [--font-id font_...] [--variant-id fvar_...]
318
+ smallpen asset font import <package.smallpen> --file FONT_FILE --family NAME
361
319
  [--weight 400] [--style normal] [--name NAME] [--json]
362
320
 
363
321
  Public Font entry for an existing Product. TTF and OTF are validated by SFNT
364
322
  signature and converted to WOFF (the renderer loads ttf/otf/woff). WOFF imports
365
323
  as-is after table validation. A valid WOFF2 reaches an explicit
366
324
  unsupported_font_conversion boundary (no WOFF2 decoder is bundled); corrupt
367
- fonts fail invalid_font_blob. Either way nothing is written.
325
+ fonts fail invalid_font_blob. Either way nothing is written. Success returns
326
+ the family, files, revision, batchId, and the exact inverseBatch. Use the
327
+ family by name: "fontFamily": "Inter", or a font-family Token.
368
328
 
369
329
  `,
370
330
  "library-refresh": `Usage:
@@ -376,6 +336,13 @@ mismatch exits library_id_mismatch with expected/actual identities and leaves th
376
336
  previous verified snapshot readable offline. Success reports before/after
377
337
  revisions; failures keep the previous revision.
378
338
 
339
+ When a fetch fails and a verified cache exists, the command exits 0 with
340
+ after.cache "stale", the cached revision, and after.warning naming the cause.
341
+ With no cache the Library cannot resolve: the command exits repair_required
342
+ with a library_unavailable conflict whose message names the cause. Requests
343
+ follow at most five redirects, only within the Library's origin; each file
344
+ is capped at 50 MB.
345
+
379
346
  `,
380
347
  "import-draft": `Usage:
381
348
  smallpen import-draft <new-draft.smallpen> --kind figma|svg|png --input FILE
@@ -396,13 +363,15 @@ Next:
396
363
  "import-tokens": `Usage:
397
364
  smallpen import-tokens <package.smallpen> --input TOKENS.json [--set-name NAME]
398
365
  [--select SET/NAME]... [--apply] [--dry-run]
399
- [--batch-id ID] [--json]
366
+ [--batch-id ID] [--compact] [--inverse-out FILE] [--json]
400
367
 
401
368
  Purpose:
402
369
  Import a DTCG token file (Penpot or Tokens Studio export, single-set, multi-set
403
370
  with $themes/$metadata, or legacy value/type) into the package's Penpot-shaped
404
371
  Token Library. Without --apply the command only reviews: it prints the diff of
405
- tokens, sets, and themes that would be added, changed, or removed, with values.
372
+ tokens, sets, and themes that would be added, changed, or removed (counts and
373
+ up to 20 identities per action). --full includes the replacement library and
374
+ exact before/after values.
406
375
  --dry-run keeps the operation in no-write review mode even with --apply;
407
376
  that combination also validates the prepared Operation Batch without committing.
408
377
 
@@ -439,64 +408,47 @@ Selections:
439
408
  Output:
440
409
  A typed Operation Batch whose baseRevision is the current Canonical revision.
441
410
  This command does not apply the batch. Review it, then use smallpen apply.`,
442
- flow: `Usage:
443
- smallpen flow <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
444
- [--warning-detail compact|full] [--json]
445
-
446
- Purpose:
447
- Compile and atomically apply a declarative flowchart intent. The intent contains
448
- screenId, optional presentationId/parentId, and nodes. Each node becomes a
449
- canonical add-presentation-node operation and receives an exact inverse batch.
450
-
451
- Intent example:
452
- {"screenId":"scr_home","nodes":[{"id":"node_start","type":"ELLIPSE",
453
- "name":"Start","x":80,"y":80,"width":120,"height":56,"children":[]}]}
454
-
455
- Node types:
456
- FRAME|ELLIPSE|GROUP|PATH|RECTANGLE|TEXT. PATH nodes may carry pathData, points,
457
- strokes, and tokenBindings; arrowheads use stroke cap fields.
458
-
459
- Output:
460
- The normal atomic apply result. Follow with read-view or render to inspect it.`,
461
- page: `Usage:
462
- smallpen page <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
463
- [--warning-detail compact|full] [--json]
464
-
465
- Purpose:
466
- Compile and atomically apply declarative page nodes. The intent uses the same
467
- screenId, optional presentationId/parentId, and nodes schema as flow. Use page
468
- for general UI nodes and flow for flowchart-oriented PATH/ELLIPSE nodes.
469
- Warning output follows apply; see smallpen apply --help for the compact schema.`,
470
- token: `Usage:
471
- smallpen token <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
472
- [--warning-detail compact|full] [--json]
473
-
474
- Purpose:
475
- Apply declarative Token operations through the canonical atomic batch contract.
476
- Intent JSON contains an operations array using put-token, set-token-value,
477
- set-active-token-themes, set-token-binding, clear-token-binding, remove-token,
478
- or deprecate-token. Theme selection uses themePaths such as Product/Dark.
479
- The current package revision is read immediately before applying the intent.`,
480
411
  impact: `Usage:
481
- smallpen impact <product.smallpen> (--token-id TOK | --path TOKEN.PATH) [--package-id PKG] [--json]
412
+ smallpen token impact <package.smallpen> --path TOKEN.NAME [--json]
482
413
 
483
414
  Purpose:
484
- Find all Canonical nodes whose tokenBindings reference the selected Token. The
485
- result includes screen, presentation, component, node, and field locations so
486
- an Agent can preview the exact blast radius before changing a Token. Foundation
487
- Tokens are resolved through the Product dependency; use --package-id to
488
- disambiguate a Token ID or path that exists in both Packages.`,
415
+ Find every element whose bindings use the Token, by page, component and
416
+ element, so an Agent can see what a change touches before making it.
417
+ Foundation Tokens are resolved through the Product dependency.`,
489
418
  apply: `Usage:
490
- smallpen apply <package.smallpen> --batch BATCH.json [--dry-run]
491
- [--warning-detail compact|full] [--confirm-unmatched] [--json]
419
+ smallpen apply <package.smallpen> --batch BATCH.json [--dry-run] [--explain] [--diff]
420
+ [--warning-detail compact|full] [--confirm-unmatched]
421
+ [--compact] [--inverse-out FILE] [--json]
492
422
 
493
423
  Batch contract:
494
424
  {"baseRevision":"...","batchId":"unique-id","operations":[...]}
425
+ baseRevision is package.revision from smallpen inspect PACKAGE --json.
495
426
  The whole candidate validates and commits atomically. Success returns revision,
496
- affected stable IDs/files, and an exact inverseBatch for Undo/Redo.
427
+ affected stable IDs/files, a revision-guarded reverse edit, and changed
428
+ (false, with noChange, when the batch leaves the Package as it was).
497
429
  Revisions are content hashes, not increasing counters: restoring the exact
498
430
  Canonical content restores its previous revision.
499
431
 
432
+ Operations:
433
+ smallpen schema operations every type, purpose, Package, required fields
434
+ smallpen schema operation TYPE fields, allowed values, example, inverse
435
+ Unknown operation types and unknown fields are rejected, with a suggestion.
436
+ In the default layout everything goes to the one Package. In the
437
+ foundation-product layout Tokens, themes, Context Axes, and shared Component
438
+ Sets go to the Foundation; Screens, nodes, Instances, and Scenarios go to the
439
+ Product.
440
+ Example: {"baseRevision":"<revision>","batchId":"resize-home-1","operations":[
441
+ {"type":"update-presentation-node","screenId":"scr_home",
442
+ "presentationId":"pres_home_mobile","nodeId":"node_home_root",
443
+ "changes":{"width":360,"height":640}}]}
444
+
445
+ Component instance overrides:
446
+ set-instance-override {screenId, presentationId?, nodeId, overridePath, value}
447
+ and clear-instance-override {screenId, presentationId?, nodeId, overridePath}
448
+ change one field of one Instance node without touching the shared component.
449
+ overridePath is "<sourceNodeId>:<field>", or "<nestedInstanceId>__<sourceNodeId>:<field>"
450
+ inside a nested instance. Fields: fills, name, opacity, text, visible.
451
+
500
452
  Token references on nodes:
501
453
  tokenBindings uses package-qualified references, for example
502
454
  {"fill":{"packageId":"pkg_foundation","assetId":"tok_brand"}}.
@@ -506,7 +458,10 @@ Token references on nodes:
506
458
 
507
459
  Raw-value confirmation policy:
508
460
  Writes that resolve to no Token return a non-blocking design_token_value_unmatched
509
- warning; the write itself is not gated. --confirm-unmatched is an explicit,
461
+ warning; the write itself is not gated. Warnings are design advice, not
462
+ validity: validate does not repeat them, and warningSummary.note says so. Raw
463
+ width/height values are listed only when a Token resolves to them or lies within
464
+ 10% (at least 2); the rest are counted in warningSummary.suppressed. --confirm-unmatched is an explicit,
510
465
  machine-readable acknowledgment: the response marks those warning groups with
511
466
  "confirmed": true. Omitting the flag changes nothing about the write.
512
467
 
@@ -518,32 +473,75 @@ Warning output:
518
473
  and summary contextScope, then sort by warningIndex to recover the original list.
519
474
  Select --warning-detail full before executing to emit the original flat list.
520
475
  Do not replay an already applied batch just to expand warnings: compact output
521
- already contains every occurrence, and stale-revision checks still apply.
476
+ already contains every listed occurrence, and stale-revision checks still apply.
477
+
478
+ ${WRITE_REPLY}
522
479
 
523
480
  Errors and recovery:
524
- Each CLI call is independent; confirmed batch results are not retained across
525
- calls. Retrying a committed batch against its old base returns stale_revision,
526
- not a cached success. Batch IDs do not provide cross-process deduplication.
481
+ Committed batch identities are recorded beside the Package. Retrying the same
482
+ batchId with the same baseRevision and operations, from any process, returns the
483
+ recorded confirmation with alreadyApplied:true and writes nothing, as long as
484
+ the Package is still at the recorded revision. If the batch's effect was undone
485
+ (the Package is back at its baseRevision) the retry applies it again as a new
486
+ write. If later writes moved the Package elsewhere the retry exits
487
+ batch_superseded with committedRevision and currentRevision. The same batchId
488
+ with a different baseRevision or operations exits batch_id_conflict.
489
+ The name-based writes (page, component, flow, token, theme, asset, canvas)
490
+ and import-tokens rebuild the batch from the current
491
+ revision; rerunning one with the same --batch-id and input after success
492
+ returns alreadyApplied:true under the same rules.
527
493
  stale_revision returns actual/base revisions. Inspect current content first:
528
494
  the intended change may already be committed. Rebuild only the remaining intent
529
495
  against the actual revision and use a new unique batch ID; do not blindly replay.
530
- --dry-run validates and returns the result/inverse without writing.
496
+ --dry-run validates and returns the result/inverse without writing. --explain
497
+ adds per-operation targets and --diff a before/after field diff of every
498
+ changed entry; both are previews that never write, with or without --dry-run.
531
499
  Invalid batches never write.`,
532
500
  watch: `Usage:
533
501
  smallpen watch <package.smallpen> [--interval MS] [--max-events N] [--json]
534
502
 
535
503
  Watch is the public live mode: it polls the Canonical Package and emits one NDJSON
536
504
  {event:"revision", packageId, revision} line whenever the on-disk revision
537
- changes, and {event:"invalid"} lines when the package is temporarily unreadable.
505
+ changes. An unreadable package emits one {event:"invalid", code, message} line
506
+ (again only when the error changes); recovery emits its revision line again.
538
507
  The initial revision is emitted immediately. --max-events bounds the run for
539
508
  scripted use; Ctrl-C exits 0. Watch never writes.
540
509
 
541
510
  `,
511
+ "migrate-themes": `Usage:
512
+ smallpen migrate-themes <package.smallpen> --output NEW_PATH [--json]
513
+
514
+ Purpose:
515
+ Upgrade by copy (the original stays untouched): write a new Package whose
516
+ theme-kind Context Axes and Token contextValues are Penpot token sets and
517
+ themes. For a Product with a Foundation, both are copied: --output names the new
518
+ workspace directory, which receives the Foundation and the Product under their
519
+ current directory names, still linked. Otherwise --output is the new
520
+ <name>.smallpen path.
521
+
522
+ Conversion:
523
+ Each theme-kind axis becomes a theme group named after the axis. The set base
524
+ holds every Token's default value (Token ids, names and bindings stay as they
525
+ are); one set per non-default value holds that value's overrides as Tokens of
526
+ the same name (ids tok_<base id>__<value>); one theme per value activates
527
+ [base, its set]; the default value's theme is active. DTCG token files become
528
+ the token library. A Product stores the active Foundation theme on its
529
+ dependency. Scenario contexts on theme axes become Scenario themes. Legacy
530
+ {x, y} shadows become {offsetX, offsetY}.
531
+
532
+ Warnings:
533
+ contextValues whose when names a non-theme axis, or more than one axis, cannot
534
+ be a theme set: they are kept as they were and listed in warnings with their
535
+ Tokens. Product Token overrides (overrideOf) stay DTCG Tokens.
536
+
537
+ Next:
538
+ smallpen view NEW_PATH --theme Theme/Dark --json`,
542
539
  repair: `Usage:
543
540
  smallpen repair <product.smallpen> [--conflict N] [--action ACTION]
544
541
  [action arguments] [--batch-id ID] [--json]
545
542
 
546
543
  Actions:
544
+ select-instance-variant [--selection JSON]; default: the closest variant
547
545
  retarget-reference --replacement-package-id PKG --replacement-asset-id ID
548
546
  remove-dependent-usage no additional arguments
549
547
  choose-foundation --foundation PATH
@@ -551,70 +549,177 @@ Actions:
551
549
 
552
550
  Without --action, returns all typed conflicts and choices. Each action is checked
553
551
  against the selected conflict and committed through one atomic Operation Batch.
554
- After each step the workspace is resolved again; remaining conflicts stay explicit.`,
552
+ After each step the workspace is resolved again; remaining conflicts stay explicit.
553
+
554
+ Instances whose variant is gone:
555
+ An Instance selects variant values. When its Foundation renames those values
556
+ while the Product is open in the same Desktop or Web session, the Product's
557
+ Instances follow the variant in a batch of their own (with its own undo).
558
+ When the variant is deleted, or the rename happened elsewhere, the Product
559
+ enters Repair: the conflict is missing_variant with degraded:true, the
560
+ Instance still renders with the closest variant (a stale_instance_variant
561
+ diagnostic says so), and the choices are select-instance-variant (the closest
562
+ variant; --selection picks one of validSelections), retarget-reference or
563
+ remove-dependent-usage (delete the Instance).`,
555
564
  };
556
565
 
557
- export function printHelp(command) {
558
- if (command && HELP[command]) {
559
- process.stdout.write(`SmallPen ${command}\n\n${HELP[command]}
566
+ export function printHelp(command, { full = false, route } = {}) {
567
+ const say = (text) => printText(publicText(text));
568
+ const name = route?.name ?? command;
569
+ if (Object.hasOwn(COMMAND_GROUPS, command) || !command) {
570
+ printBriefHelp(command, undefined, { full });
571
+ say(
572
+ "\nOptions: -h, --help; --version\nUse smallpen help OBJECT ACTION for options. Common rules: smallpen help rules\n",
573
+ );
574
+ return;
575
+ }
576
+ if (!full) {
577
+ if (!command) {
578
+ printBriefHelp();
579
+ say(
580
+ "\nOptions: -h, --help; --version\nUse smallpen COMMAND --help for options. Common rules: smallpen help rules\n",
581
+ );
582
+ } else
583
+ say(
584
+ `SmallPen ${command}\n\n${commandContract(command).purpose}\n\nUsage: smallpen ${command} ${commandContract(
585
+ command,
586
+ )
587
+ .arguments.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`))
588
+ .join(
589
+ " ",
590
+ )} [options]\n\n${commandHelp(command)}\n\nInput shapes: smallpen schema command ${command} --json\n`,
591
+ );
592
+ if (command && commandOverview(command).notes)
593
+ say(commandOverview(command).notes.join("\n") + "\n");
594
+ return;
595
+ }
596
+ const guidance = commandGuidance(command, route);
597
+ if (command && guidance) {
598
+ say(`SmallPen ${name}\n\n${guidance}
599
+
600
+ ${commandHelp(name)}
560
601
 
561
602
  Common output and error contract:
562
- --json emits stable English keys; localized labels remain additional data. Success
563
- exits 0. Errors exit 1 as {error:{code,message,details}} and include exact
603
+ Default JSON is compact and bounded to 8 KiB. Large complete results are saved
604
+ to resultFile.path with bytes/SHA-256; stdout retains status and write receipts.
605
+ Search/read the JSON with jq or your file tools, and text with rg/sed.
606
+ Each call uses random filenames of a fixed format in one system temporary
607
+ folder: smallpen-TIMESTAMP-UUID-KIND.ext. Every invocation cleans CLI files
608
+ whose mtime is older than 30 minutes; active calls are protected.
609
+ --output chooses a retained file. Lists default to 20 items; --full expands data
610
+ but still uses files for large results. --stdout opts into large stdout.
611
+ --base64 explicitly requests inline image bytes; PNG paths are the default.
612
+ --json emits stable English keys; localized labels remain additional data and
613
+ follow --locale where offered, else LC_ALL, LC_MESSAGES, or LANG (zh* selects
614
+ Chinese, anything else English). Success
615
+ exits 0. Errors exit 1 as {error:{code,message,details,writeState}} and include exact
564
616
  nextOperations when recovery requires refresh, replay, Repair, or confirmation.
565
- Unknown options are not part of the command contract shown above.
617
+ Unknown options are not part of the command contract shown above. Only options
618
+ shown with ..., repeated, or described as repeatable may appear more than once;
619
+ any other option given twice exits duplicate_option.
566
620
 
567
621
  Help / next:
568
622
  smallpen --help
569
- smallpen ${command} --help
623
+ smallpen ${name} --help
570
624
  `);
571
625
  return;
572
626
  }
573
- process.stdout.write(`SmallPen Canonical Package CLI
627
+ say(`SmallPen Canonical Package CLI
574
628
 
575
629
  Model:
576
- One .smallpen directory is one Canonical Package. A Product references exactly one
577
- same-workspace Foundation by permanent Package ID and relative path. Every CLI call
630
+ One .smallpen directory is one Canonical Package. init creates one
631
+ self-contained Package (<name>.smallpen) by default: token sets and themes,
632
+ Context Axes, Component Sets, Screens, Presentations, Scenarios. Write
633
+ everything to it and pass it to view, validate, export, token list and
634
+ search. With init --layout foundation-product it creates a Foundation
635
+ (<name>-foundation.smallpen: token sets and themes, Context Axes, shared
636
+ Component Sets) and a Product (<name>.smallpen: Screens, Presentations,
637
+ Scenarios) instead: write Tokens and components to the Foundation, Screens and
638
+ nodes to the Product, and pass the Product to the reads.
639
+ Themes are Penpot token sets + themes. token theme list reads groups/options/defaults;
640
+ token theme add|rename|default|delete write them by Group/Option name. Resolved reads take
641
+ --theme GROUP/NAME for this call only: project defaults, then an explicit
642
+ Scenario, then explicit overrides of named groups. App active selection is
643
+ independent. set-active-token-themes explicitly stores the App selection.
644
+ A Product references exactly one same-workspace Foundation by permanent Package
645
+ ID and relative path. Every CLI call
578
646
  receives its Package locator and complete selectors explicitly; there is no current
579
647
  selection, current page, or cross-call session state. Packages are never merged into
580
648
  a synthetic multi-root file. CLI, Desktop, renderer, and Agents share one resolver
581
649
  and one typed Operation Batch contract. No PostgreSQL, collaboration server, MCP,
582
650
  or built-in AI Chat is required.
583
651
 
584
- Choose an Agent view:
585
- wireframe Fast 2D spatial reasoning from a scaled ASCII canvas plus a compact
586
- [NN] key containing hierarchy, layer type, name, and stable ID.
587
- semantic Exact hierarchy, bounds, text, alignment, typography, component,
588
- variant, and Token properties.
589
- PNG High-fidelity visual QA. The image is raster pixels only; it contains
590
- no layer IDs, hierarchy, component references, or Token bindings.
591
- both Use inspect-view --include-image, then reason from structure and verify
592
- appearance from the PNG.
652
+ CLI entry point:
653
+ smallpen help
654
+ smallpen help OBJECT ACTION
655
+ smallpen schema command OBJECT ACTION --json
656
+ smallpen help rules
657
+ CLI usage is complete without a Skill. Scene Skills supply task-specific rules.
658
+
659
+ Separate actions:
660
+ settings token/component/page/canvas/asset/flow actions save changes by
661
+ name; advanced apply runs an exact batch, such as an undo batch.
662
+ definitions token theme list is a cheap settings query; search finds reusable Tokens
663
+ and components.
664
+ view Compact layout outline, or a page/component/region text wireframe.
665
+ validate Problems and coverage; data validity is separate from visual QA.
666
+ export Actual text, wireframe or PNG files with size/hash/selection.
667
+ Read compact text/issues first, then text wireframes. Inspect PNG only when
668
+ useful and the caller supports image input; reuse an unchanged renderHash.
669
+ export defaults to a text wireframe; PNG requires --format png. The CLI does
670
+ not detect model image capability. Text-only models may export PNG deliverables
671
+ but must review text/wireframes and must not claim visual inspection of a PNG.
593
672
 
594
673
  Usage:
595
674
  smallpen <command> [arguments] [--json]
596
675
  smallpen <command> --help
597
- smallpen --version
676
+ smallpen version --json
598
677
 
599
678
  Commands:
600
- ${COMMANDS.map(([name, summary]) => ` ${name.padEnd(18)} ${summary}`).join("\n")}
601
-
602
- Common workflow:
603
- smallpen init ./workspace --json
604
- smallpen validate ./workspace/product.smallpen --json
605
- smallpen search-tokens ./workspace/product.smallpen --color '#6750a4' --json
606
- smallpen search-components ./workspace/product.smallpen --query button --json
607
- smallpen catalog ./workspace/product.smallpen --json
608
- smallpen read-view ./workspace/product.smallpen --json
609
- smallpen discover ./workspace/product.smallpen --json
610
- smallpen apply ./workspace/product.smallpen --batch batch.json --json
611
- smallpen evidence ./workspace/product.smallpen --output review --json
612
- smallpen import-draft ./workspace/import-1.smallpen --kind figma --input clipboard.html --json
613
- smallpen repair ./workspace/product.smallpen --json
679
+ ${PUBLIC_PATHS.map(([name, summary]) => ` ${name.padEnd(24)} ${summary}`).join("\n")}
680
+
681
+ Query examples:
682
+ smallpen init ./acme --json
683
+ smallpen themes ./acme/acme.smallpen --json
684
+ smallpen help rules --json
685
+ smallpen validate ./acme/acme.smallpen --json
686
+ smallpen search-tokens ./acme/acme.smallpen --color '#6750a4' --json
687
+ smallpen search-components ./acme/acme.smallpen --query button --json
688
+ smallpen catalog ./acme/acme.smallpen --json
689
+ smallpen view ./acme/acme.smallpen --json
690
+ smallpen apply ./acme/acme.smallpen --batch batch.json --json
691
+ smallpen export ./acme/acme.smallpen --format text --json
692
+ smallpen export ./acme/acme.smallpen --format png --json
693
+ smallpen export ./acme/acme.smallpen --design-system --json
694
+ smallpen import-draft ./acme/import-1.smallpen --kind figma --input clipboard.html --json
695
+ smallpen repair ./acme/acme.smallpen --json
614
696
 
697
+ Output:
698
+ Defaults are summaries, paginated identities, and artifact paths (8 KiB budget).
699
+ JSON is compact. Directory fields are a public CLI contract, not a dump of
700
+ internal objects. Reuse restrictions appear only when they affect a decision.
701
+ Read one target before editing. --full requests complete data; --base64 requests
702
+ image bytes. Operation schemas are available individually: smallpen schema operation TYPE.
703
+ smallpen schema returns cliVersion/contractRevision for stable contract caching.
704
+ Package reads return revision; workspace views also return dependency revisions.
705
+ Cache a view by those revisions plus its selectors, theme, context and locale.
706
+
707
+ Worked example:
708
+ smallpen schema batch --full
709
+
710
+ Every write input has a schema and a tested example: smallpen schema
711
+ (operations, node, token, theme, component-set, instance, presentation, batch, init).
615
712
  Run smallpen <command> --help for syntax, defaults, values, examples, outputs,
616
713
  errors, and relevant next commands.
617
714
  `);
618
715
  }
619
716
 
620
717
  export const COMMAND_NAMES = Object.freeze(COMMANDS.map(([name]) => name));
718
+ export function commandGuidance(command, route) {
719
+ if (route && (command === "assets" || route.fixed || !HELP[command])) {
720
+ const contract = commandContract(route.name);
721
+ const args = contract.arguments.map(({ name, required }) => required ? `<${name}>` : `[${name}]`).join(" ");
722
+ return `Usage:\n smallpen ${route.name} ${args} [options]\n\n${route.purpose}`;
723
+ }
724
+ return HELP[command] ? publicText(HELP[command]) : undefined;
725
+ }