@smallpen/cli 0.1.0-alpha.5 → 0.1.0-alpha.9

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,69 +1,124 @@
1
- import { workedExampleText } from "./schema.mjs";
2
-
3
- const COMMANDS = [
4
- ["version", "Print the CLI package version without opening a workspace"],
5
- ["schema", "Print the JSON shape and a valid example of every write input"],
6
- ["init", "Persist a guided Brief/Proposal and create Foundation + Product"],
7
- ["validate", "Validate a package and its same-workspace Foundation"],
8
- ["inspect", "Inspect package identity, capabilities, and domain summary"],
9
- ["list", "List stable domain objects by kind"],
10
- ["read", "Read the complete validated Canonical Snapshot"],
11
- ["read-view", "Read the deterministic default or selected Design View"],
12
- ["discover", "Discover exact selectors and executable next reads"],
13
- ["catalog", "Derive the Design System Catalog and requirement coverage"],
14
- ["search-components", "Find reusable Product and Foundation components"],
15
- ["component", "Inspect a Component Set and its legal variants"],
16
- ["compare", "Compare two to four explicitly selected Design Views"],
17
- ["tokens", "List Effective Tokens for a Context"],
18
- ["search-tokens", "Find reusable Tokens by name, type, or design value"],
19
- ["effective-token", "Resolve one Effective Token"],
20
- ["explain-token", "Explain one Effective Token resolution decision"],
21
- ["render", "Render the selected Design View to deterministic PNG"],
22
- ["inspect-view", "Return wireframe, semanticTree, optional image, and supporting context"],
23
- ["render-matrix", "Render multiple design contexts for AI comparison"],
24
- ["evidence", "Return PNG plus deterministic review evidence; --output exports files"],
25
- ["import-draft", "Import Figma clipboard, SVG, or PNG as a separate Draft file"],
26
- ["import-media", "Import binary Media (PNG/JPEG/GIF/WebP/SVG) into a Product Asset Library"],
27
- ["remove-media", "Remove an imported Media descriptor from the Asset Library"],
28
- ["import-font", "Import a TTF/OTF/WOFF font and convert SFNT to WOFF"],
29
- ["import-tokens", "Review and import a DTCG / Tokens Studio token file into the Token Library"],
30
- ["library-refresh", "Re-fetch a declared URL Library into its verified cache"],
31
- ["draft-diff", "Compare two independent Draft files without changing Canonical"],
32
- ["draft-compile", "Compile selected Draft fields into a current-revision batch"],
33
- ["flow", "Create flowchart nodes from a declarative intent"],
34
- ["page", "Create page nodes from a declarative intent"],
35
- ["token", "Apply declarative Token operations atomically"],
36
- ["impact", "Find every node and component using a Token"],
37
- ["apply", "Atomically apply one typed Operation Batch"],
38
- ["watch", "Emit NDJSON revision events for local external changes"],
39
- ["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]),
40
19
  ];
41
-
42
- const DESIGN_SELECTORS = `Design selectors:
43
- --screen ID Screen; default: manifest defaultScreenId
44
- --presentation ID Presentation; default: Screen Base Presentation
45
- --scenario ID Reproducible Scenario; default: canonical initial state
46
- --context-profile ID Named Context profile
47
- --context AXIS=VALUE Repeatable finite Context selection`;
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);
48
26
 
49
27
  const WRITE_REPLY = `Reply size:
50
- The default reply includes inverseBatch (also repeated in guidance.undo), the
51
- exact undo for the write; it can run to thousands of lines. --inverse-out FILE
52
- saves inverseBatch to FILE (only when the batch commits) and returns
53
- inverseBatchPath. --compact drops inverseBatch and guidance from the reply and
54
- adds summary and inverseOperationCount; combine both to keep undo:
55
- smallpen apply PACKAGE --batch FILE --inverse-out FILE.undo.json`;
56
-
57
- const VIEW_SELECTORS = `${DESIGN_SELECTORS}
58
- --format FORMAT structure|semantic|wireframe|screenshot`;
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`;
59
39
 
60
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.`,
61
117
  version: `Usage:
62
- smallpen --version
63
118
  smallpen version [--json]
64
119
 
65
120
  Output:
66
- The installed CLI package version, or {name,version} with --json.
121
+ JSON {name,version} for the installed CLI package.
67
122
  No package path is required; design files are unchanged and no services start.`,
68
123
  schema: `Usage:
69
124
  smallpen schema [TOPIC] [--json]
@@ -76,209 +131,137 @@ Purpose:
76
131
  example is tested against a workspace made by smallpen init.
77
132
 
78
133
  Topics:
134
+ command NAME one command: parameters, types, defaults and constraints
135
+ commands command names and purposes
79
136
  operations every operation type, its purpose, Package, required fields
80
- operation TYPE one operation in full (smallpen schema put-token also works)
137
+ operation TYPE one operation in full
81
138
  batch batch contract, Foundation vs Product, worked example
82
139
  node node fields, textStyle, tokenBindings
83
140
  node-types what each node type requires
84
- token Token definition, contextValues, bindings
141
+ token Tokens in token sets, DTCG files, bindings
142
+ theme token sets and themes, --theme, Foundation + Product themes
85
143
  token-types the value each Token type accepts
86
144
  component-set Component Set, Axis roles (configuration, state), variants
87
145
  instance INSTANCE nodes and overrides
88
146
  presentation Presentation fields, size, and resizing
89
147
  screen Screen fields
90
148
  scenario Scenario fields and viewport
91
- context Context Axes and theme values (alias: theme)
92
- init every init question and a complete --answers file
149
+ context Context Axes (viewport, density, locale, ...)
150
+ init blank package paths, layouts and creation behavior
93
151
 
94
152
  Examples:
95
153
  smallpen schema operations
96
154
  smallpen schema operation update-component-node
97
155
  smallpen schema component-set`,
98
156
  init: `Usage:
99
- smallpen init <workspace-directory> [--state FILE] [--answers FILE]
100
- [--answer QUESTION_ID=JSON]... [--confirm] [--locale LOCALE] [--json]
157
+ smallpen project init PATH [--name NAME] [--layout single|foundation-product] [--json]
101
158
 
102
- Purpose:
103
- Persist a progressively completed Initialization Brief and validated Proposal.
104
- No hidden conversational state is used. --confirm atomically creates one local
105
- Foundation and one Product; an existing target is never overwritten.
106
-
107
- Defaults and values:
108
- --state defaults to <workspace-directory>.smallpen-init.json. Answers build up
109
- only when every call passes the same --state; a call without --state starts
110
- from no answers (the reply then carries stateNotice).
111
- --answers FILE answers every question at once: smallpen schema init prints all
112
- questions and a complete file.
113
- --locale defaults to the environment (LC_ALL, LC_MESSAGES, LANG): zh* selects
114
- Chinese labels, anything else English.
115
- Project kinds: application|motion|custom. Foundation choice: create-new.
116
- --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.
117
165
 
118
166
  Examples:
119
- smallpen init ./quincy --json
120
- smallpen init ./quincy --state ./quincy-init.json --answer projectKind=\"application\" --json
121
- 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`,
122
171
 
123
- Output:
124
- status needs_input returns a stable question id, localized label, schema,
125
- optional recommendation, and continuation args with an answer placeholder. Replace
126
- <JSON> with your schema-valid answer; if no recommendation is present, supply the
127
- project's own purpose, audience, or deliverable. status proposal returns the
128
- persisted Proposal and confirmation args. Only --confirm creates the packages;
129
- status initialized returns both package paths plus every id the first writes
130
- need: packages.{foundation,product}.{packageId,path,revision},
131
- start.{screenId,presentationId,rootNode,scenarioIds,contextAxes}, and seeded
132
- (the starter Tokens and Component Sets with ids and paths).
133
-
134
- Seeded content (from the answers):
135
- Foundation: each initialTokens name becomes a Token tok_<path with _>, for
136
- example color.brand -> tok_color_brand (#6750a4), spacing.md -> tok_spacing_md
137
- (8), radius.md -> tok_radius_md (8). Each initialComponents name becomes a
138
- Component Set cmp_<name> with one plain variant var_<name>_default, no axes
139
- (Button -> cmp_button). platforms become Context Axis axis_platform, next to
140
- your contextAxes. Product: Screen scr_<firstScreen> with Presentation
141
- pres_<firstScreen>_<platform>, root FRAME node_<firstScreen>_root, and one
142
- Scenario. put-token or put-component-set with a seeded id replaces it whole.
143
-
144
- Errors and next commands:
145
- Malformed --answer JSON identifies the question; schema-invalid values also return
146
- its schema. Correct the answer and retry with the same --state. After confirming:
147
- smallpen validate <product.smallpen> --json`,
148
172
  validate: `Usage:
149
- 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]
150
176
 
151
177
  Purpose:
152
- Validate Canonical JSON, IDs, references, Product/Foundation dependency, all Context
153
- 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.
154
183
 
155
184
  Output:
156
- status valid with Product and Foundation revisions; dependency conflicts return
157
- 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.
158
190
 
159
191
  Next:
160
- smallpen inspect <package> --json
161
- smallpen repair <product> --json`,
192
+ smallpen view <package> --as issues --json
193
+ smallpen project repair <product> --json`,
162
194
  inspect: `Usage:
163
195
  smallpen inspect <package.smallpen> [--json]
164
196
 
165
197
  Purpose:
166
- Return package identity/revision, format capabilities, Contexts, Screens,
167
- Presentations, Tokens, Component Sets, Scenarios, requirements, Flows, and
168
- machine-readable Design System preflight operations.
198
+ Return Package identity, revision, and domain counts. --full includes all
199
+ domain summaries and format capabilities.
169
200
 
170
201
  Next:
171
- smallpen list <package> --kind screens --json
172
- smallpen read-view <package> --json`,
202
+ smallpen view <package> --json`,
173
203
  list: `Usage:
174
204
  smallpen list <package.smallpen> [--kind KIND] [--offset N] [--limit N] [--json]
175
205
 
176
206
  Values:
177
207
  KIND: all|screens|presentations|contexts|tokens|components|scenarios|requirements|flows
178
- Defaults: kind=all, offset=0, limit=100. Lists include total/range pagination.
179
-
180
- Next:
181
- Use returned stable IDs with read-view, effective-token, or compare.`,
182
- read: `Usage:
183
- smallpen read <package.smallpen> [--json]
184
-
185
- Purpose:
186
- Return the complete validated Canonical Snapshot used by adapters. Binary blobs are
187
- not embedded in JSON. Prefer inspect/list/read-view for bounded Agent reads.
188
-
189
- Next:
190
- smallpen inspect <package> --json`,
191
- "read-view": `Usage:
192
- smallpen read-view <product.smallpen> [selectors] [--offset N] [--limit N]
193
- [--locale LOCALE] [--json]
194
-
195
- ${VIEW_SELECTORS}
196
-
197
- Defaults:
198
- Base Presentation + default Context values + canonical initial state + structure.
199
- All formats resolve the same selection. --locale defaults to the environment
200
- (zh* selects Chinese labels, anything else English).
201
-
202
- Choose a format:
203
- wireframe A scaled 2D ASCII canvas for fast spatial reasoning, followed by a
204
- compact marker key with hierarchy, layer type, name, and stable ID.
205
- semantic Exact machine-readable hierarchy, bounds, text, alignment, resolved
206
- typography, component references, variants, and Token bindings.
207
- screenshot Rendered PNG metadata only; use render/evidence for inline pixels,
208
- or add --output to explicitly write PNG files.
209
- The PNG image itself is raster pixels only: it has no layer IDs,
210
- hierarchy, bounds, component references, or Token bindings.
211
-
212
- Agent guidance:
213
- Use wireframe or semantic to understand and edit the design. Use PNG to inspect
214
- high-fidelity color, typography, spacing, effects, and rendering. Use inspect-view
215
- --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.
216
215
 
217
216
  Next:
218
- Execute any Discovery Guide command without inventing arguments.`,
219
- discover: `Usage:
220
- smallpen discover <product.smallpen> [selectors] [--offset N] [--limit N]
221
- [--locale LOCALE] [--json]
222
-
223
- ${VIEW_SELECTORS}
224
-
225
- Output:
226
- Exact selector parameters, defaults, finite valid values, localized summaries,
227
- machine operation/args, copyable commands, and pagination metadata.`,
217
+ Look at one by name: smallpen view <package> --page NAME | --component NAME.`,
228
218
  catalog: `Usage:
229
- 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]
230
221
 
231
222
  Purpose:
232
- Derive legal Component variants, inherited assets, Effective Tokens, Contexts,
233
- 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.`,
234
228
  "search-components": `Usage:
235
229
  smallpen search-components <product.smallpen> --query TEXT [--limit N] [--json]
236
230
 
237
231
  Purpose:
238
232
  Find reusable Product, public Foundation, and linked Library components before
239
233
  drawing a custom replacement. Matches stable ID, name, category, and description and returns the
240
- owning Package, source layer, legal variant selections, and an executable
234
+ owning Package, source layer, variant count, and an executable
241
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.
242
238
 
243
239
  State:
244
240
  Every query is explicit and self-contained. It never creates a current component
245
241
  or affects a later command.`,
246
- component: `Usage:
247
- smallpen component <package.smallpen> --component-id CMP [--json]
248
-
249
- Purpose:
250
- Return one exact Component Set, its legal variant selections, replacement metadata,
251
- and component-targeted Scenarios. This is read-only and uses stable Canonical IDs.
252
- The package path must identify the owning Package. For inherited components,
253
- execute the owner-scoped command returned by search-components.
254
- An HTTP(S) Library URL is also accepted and uses the normal Library cache.
255
-
256
- Shapes:
257
- smallpen schema component-set axes (role configuration|state) and variants
258
- smallpen schema instance place a variant on a Screen and override it
259
- Edit a variant: {"type":"update-component-node","componentSetId":"cmp_button",
260
- "variantId":"var_button_primary","nodeId":"node_button_label",
261
- "changes":{"textStyle":{"textAlign":"center"}}}
262
-
263
- Next:
264
- Use a returned selection with read-view or inspect-view to review the design.`,
265
- compare: `Usage:
266
- smallpen compare <product.smallpen> --selector JSON --selector JSON
267
- [--selector JSON] [--selector JSON] [--json]
268
-
269
- Values:
270
- Exactly two to four selector objects. Example:
271
- smallpen compare product.smallpen --selector '{"presentationId":"pres_desktop"}' \\
272
- --selector '{"presentationId":"pres_mobile"}' --json`,
273
242
  tokens: `Usage:
274
- smallpen tokens <product.smallpen> [--context AXIS=VALUE]... [--locale LOCALE] [--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]
275
247
 
276
248
  Purpose:
277
- List Effective Tokens using Product first, Context specificity within a layer,
278
- then Foundation fallback. labels follow --locale or the environment.`,
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.`,
279
261
  "search-tokens": `Usage:
280
262
  smallpen search-tokens <product.smallpen> (--color COLOR | --value JSON | --query TEXT | --type TYPE)
281
- [--context AXIS=VALUE]... [--limit N] [--json]
263
+ [--context AXIS=VALUE]... [--theme GROUP/NAME]... [--all-themes]
264
+ [--limit N] [--json]
282
265
 
283
266
  Purpose:
284
267
  Find reusable Product and Foundation Design Tokens before writing a hard-coded
@@ -286,6 +269,11 @@ Purpose:
286
269
  values. Without --context, every finite Web/Desktop/theme/etc. Context is searched
287
270
  and each resolved value reports its matching Contexts. Repeating --context restricts
288
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.
289
277
 
290
278
  Examples:
291
279
  smallpen search-tokens product.smallpen --color '#6750a4' --json
@@ -296,146 +284,38 @@ State:
296
284
  Every search is self-contained. It never reads or writes a current selection,
297
285
  previous result, session, or Web UI state.`,
298
286
  "effective-token": `Usage:
299
- smallpen effective-token <product.smallpen> --token-id TOK [--package-id PKG]
300
- [--context AXIS=VALUE]... [--json]
287
+ smallpen token show <package.smallpen> --path TOKEN.NAME
288
+ [--theme GROUP/NAME]... [--json]
301
289
 
302
290
  Defaults:
303
- 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.`,
304
293
  "explain-token": `Usage:
305
- smallpen explain-token <product.smallpen> --token-id TOK [--package-id PKG]
306
- [--context AXIS=VALUE]... [--json]
294
+ smallpen token explain <package.smallpen> --path TOKEN.NAME
295
+ [--theme GROUP/NAME]... [--json]
307
296
 
308
297
  Output:
309
298
  Chosen source/value plus all candidates, specificity, rejection reasons, and layer.`,
310
- render: `Usage:
311
- smallpen render <product.smallpen> [selectors] [--scale N] [--output FILE] [--json]
312
-
313
- ${DESIGN_SELECTORS}
314
-
315
- Defaults and limits:
316
- No --output: return a PNG as bare base64 in one JSON result; no image file or
317
- temporary image is created. This also applies without --json.
318
- Explicit --output FILE: write that PNG and return its absolute output path
319
- instead of base64. Existing FILE is replaced only after a successful write.
320
- scale=1, scale range (0,8], maximum dimension 8192,
321
- maximum 32M pixels. Output is deterministic PNG with revision/render hash. The
322
- PNG image itself is raster pixels only and contains no layer IDs, hierarchy,
323
- bounds, component references, or Token bindings. Use it for high-fidelity visual
324
- QA of color, typography, spacing, effects, alignment, and rendering.
325
- Decode the current response in an image-capable client; base64 text alone is
326
- not visual QA. Verify decoded bytes against renderHash (SHA-256), and use the
327
- accompanying revision and selection. On failure, never open a previous file.
328
-
329
- Fonts:
330
- The renderer bundles Source Sans Pro and draws fonts imported with import-font.
331
- Any other family renders in Source Sans Pro: diagnostics then carries one
332
- font_render_fallback per missing family with its nodeIds, availableFonts, and
333
- the import-font command in nextOperations.
334
-
335
- Wireframe:
336
- Use smallpen read-view <product.smallpen> --format wireframe --json.
337
- It returns a scaled 2D ASCII canvas and compact marker key. Use semanticTree
338
- from inspect-view when exact bounds, text, component, and Token data is needed.`,
339
- "inspect-view": `Usage:
340
- smallpen inspect-view <product.smallpen> [selectors] [--include-image]
341
- [--output FILE] [--base64] [--json]
342
-
343
- ${DESIGN_SELECTORS}
344
- --scale N Image scale; default: 1 (requires --include-image to render)
345
- --locale LOCALE Localized view labels; default: from the environment
346
-
347
- Purpose:
348
- Return one AI-readable view bundle.
349
-
350
- Three complementary view parts:
351
- wireframe Scaled 2D ASCII canvas plus a compact layer marker key.
352
- semanticTree Exact hierarchy, bounds, text, alignment, typography, component,
353
- variant, and Token data.
354
- image Optional high-fidelity PNG and metadata; add --include-image to render it.
355
-
356
- Supporting context:
357
- Effective Tokens, component catalog, selected Context, and revision metadata.
358
- Without --output, image.base64 contains bare PNG base64 in this one JSON result
359
- (also without --json). No image file or temporary image is created.
360
- Explicit --output FILE writes the image and returns image.output instead of
361
- base64; add --base64 only when both file and inline bytes are wanted.
362
- --output and --base64 require --include-image.
363
-
364
- Agent guidance:
365
- Use this when the Agent needs both structure and visual QA. Start with wireframe
366
- for spatial relationships, query semanticTree for exact data, and inspect image
367
- only when pixel-level visual QA is needed. The PNG itself contains no layer or
368
- Token metadata. Decode this response in an image-capable client; base64 text
369
- alone is not visual QA. image.revision/selection identify the rendered snapshot;
370
- image.renderHash is the SHA-256 of the decoded PNG. Never reuse an old image
371
- after an error, or infer freshness from a filename.`,
372
- "render-matrix": `Usage:
373
- smallpen render-matrix <product.smallpen> --contexts CONTEXTS.json
374
- [--output DIRECTORY] [--scale N] [--json]
375
-
376
- Purpose:
377
- Render a deterministic PNG for every explicit design selector in CONTEXTS.json.
378
- Each selector may choose context axes, screen, presentation, and scenario.
379
- Without --output, one JSON result contains ordered images with bare base64,
380
- dimensions, exact selectors, per-image revisions, and PNG SHA-256 renderHash.
381
- No image files or temporary images are created, including if a later selector
382
- fails. This also applies without --json.
383
- Explicit --output DIRECTORY writes PNG files and returns each image.output
384
- instead of base64. Different render hashes retain separate files; there is no
385
- automatic cleanup or all-or-nothing multi-file export. Only consume images
386
- from a successful current response; never fall back to earlier matrix files.`,
387
- evidence: `Usage:
388
- smallpen evidence <product.smallpen> [selectors] [--scale N] [--output PREFIX] [--json]
389
-
390
- ${DESIGN_SELECTORS}
391
-
392
- Output:
393
- Without --output, return one JSON evidence bundle with image.base64 (bare PNG
394
- base64); no PNG, evidence JSON file, or temporary image is created. This also
395
- applies without --json. image includes the current revision/selection and
396
- renderHash (SHA-256 of decoded PNG bytes), not a cache locator.
397
- Explicit --output PREFIX writes PREFIX.png and PREFIX.json instead of base64.
398
- Evidence contains revision/package hash, resolved selectors,
399
- render hash, node-to-image regions, Semantic Tree, viewport, and diagnostics.
400
- PREFIX.png is an unannotated high-fidelity raster image. Semantic details live in
401
- PREFIX.json and are not embedded visibly in the PNG.
402
- Decode the current inline response in an image-capable client for visual QA.
403
- Failed exports do not authorize reading an old file; multi-file export is not
404
- an all-or-nothing transaction and existing files are not automatically cleaned.
405
-
406
- Wireframe:
407
- Use smallpen read-view <product.smallpen> --format wireframe --json.
408
- It returns a scaled 2D ASCII canvas with one [NN] marker per layer.`,
409
299
  "import-media": `Usage:
410
- smallpen import-media <package.smallpen> --file MEDIA_FILE [--media-id media_...]
300
+ smallpen asset media import <package.smallpen> --file MEDIA_FILE
411
301
  [--media-path PATH] [--name NAME] [--json]
412
302
 
413
303
  Import is the public binary Media entry for an existing Product: PNG, JPEG, GIF,
414
304
  WebP, and SVG are detected from bytes, dimensions are validated, and the blob is
415
305
  content-addressed (identical bytes in one package share one blob). JSON apply
416
- still forbids blob writes; media import is a file entry, not a batch operation.
417
- remove-media deletes the descriptor (metadata lifecycle is reversible); the
418
- 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.
419
309
 
420
310
  Errors and recovery:
421
311
  Corrupt or unsupported bytes exit 1 invalid_media_file and write nothing.
422
- Reusing --media-id exits duplicate_media_id with the existing descriptor.
423
312
 
424
313
  Output:
425
- descriptor and revision, plus batchId and the exact inverseBatch for undo.
426
-
427
- `,
428
- "remove-media": `Usage:
429
- smallpen remove-media <package.smallpen> --media-id MEDIA [--json]
430
-
431
- Removes one Media descriptor from the Asset Library atomically. The result
432
- carries batchId and inverseBatch; smallpen apply with that inverseBatch restores
433
- the descriptor (the blob is retained). Unknown ids exit missing_media.
314
+ descriptor and revision, plus batchId and the revision-guarded reverse edit (inverseBatch).
434
315
 
435
316
  `,
436
317
  "import-font": `Usage:
437
- smallpen import-font <package.smallpen> --file FONT_FILE --family NAME
438
- [--font-id font_...] [--variant-id fvar_...]
318
+ smallpen asset font import <package.smallpen> --file FONT_FILE --family NAME
439
319
  [--weight 400] [--style normal] [--name NAME] [--json]
440
320
 
441
321
  Public Font entry for an existing Product. TTF and OTF are validated by SFNT
@@ -443,7 +323,8 @@ signature and converted to WOFF (the renderer loads ttf/otf/woff). WOFF imports
443
323
  as-is after table validation. A valid WOFF2 reaches an explicit
444
324
  unsupported_font_conversion boundary (no WOFF2 decoder is bundled); corrupt
445
325
  fonts fail invalid_font_blob. Either way nothing is written. Success returns
446
- fontId, variantId, files, revision, batchId, and the exact inverseBatch.
326
+ the family, files, revision, batchId, and the exact inverseBatch. Use the
327
+ family by name: "fontFamily": "Inter", or a font-family Token.
447
328
 
448
329
  `,
449
330
  "library-refresh": `Usage:
@@ -488,7 +369,9 @@ Purpose:
488
369
  Import a DTCG token file (Penpot or Tokens Studio export, single-set, multi-set
489
370
  with $themes/$metadata, or legacy value/type) into the package's Penpot-shaped
490
371
  Token Library. Without --apply the command only reviews: it prints the diff of
491
- 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.
492
375
  --dry-run keeps the operation in no-write review mode even with --apply;
493
376
  that combination also validates the prepared Operation Batch without committing.
494
377
 
@@ -525,117 +408,13 @@ Selections:
525
408
  Output:
526
409
  A typed Operation Batch whose baseRevision is the current Canonical revision.
527
410
  This command does not apply the batch. Review it, then use smallpen apply.`,
528
- flow: `Usage:
529
- smallpen flow <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
530
- [--explain] [--diff] [--warning-detail compact|full]
531
- [--confirm-unmatched] [--compact] [--inverse-out FILE] [--json]
532
-
533
- Purpose:
534
- Compile and atomically apply a declarative flowchart intent. The intent contains
535
- screenId, optional presentationId/parentId, and nodes. Each node becomes a
536
- canonical add-presentation-node operation and receives an exact inverse batch.
537
-
538
- Intent example:
539
- {"screenId":"scr_home","nodes":[{"id":"node_start","type":"ELLIPSE",
540
- "name":"Start","x":80,"y":80,"width":120,"height":56,"children":[]}]}
541
-
542
- Node types:
543
- FRAME|ELLIPSE|GROUP|PATH|RECTANGLE|TEXT. PATH nodes may carry pathData, points,
544
- strokes, and tokenBindings; arrowheads use stroke cap fields.
545
- Node fields: smallpen schema node. Every intent node is added as a sibling
546
- under parentId (default: the Presentation root); nest with a later intent.
547
- INSTANCE nodes may carry overrides, as in smallpen page --help.
548
-
549
- Output:
550
- The normal atomic apply result. Follow with read-view or render to inspect it.
551
-
552
- ${WRITE_REPLY}`,
553
- page: `Usage:
554
- smallpen page <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
555
- [--explain] [--diff] [--warning-detail compact|full]
556
- [--confirm-unmatched] [--compact] [--inverse-out FILE] [--json]
557
-
558
- Purpose:
559
- Compile and atomically apply declarative page nodes. The intent uses the same
560
- screenId, optional presentationId/parentId, and nodes schema as flow. Use page
561
- for general UI nodes and flow for flowchart-oriented PATH/ELLIPSE nodes.
562
- Warning output follows apply; see smallpen apply --help for the compact schema.
563
- Pass the Product. Each node becomes one add-presentation-node, all siblings
564
- under parentId (default: the Presentation root); x/y are relative to it.
565
-
566
- Instance overrides:
567
- An INSTANCE intent node may carry overrides {"<sourceNodeId>:<field>": value}
568
- (fields: fills, name, opacity, text, visible). Each becomes a
569
- set-instance-override after the node in the same atomic batch, so a label
570
- needs no second write; a wrong path or type rejects the whole intent.
571
-
572
- Intent example (a title and a Button Instance with its label):
573
- {"screenId":"scr_home","nodes":[
574
- {"id":"node_title","type":"TEXT","name":"Title","x":24,"y":48,"width":312,
575
- "height":32,"children":[],"text":"Tasks","textStyle":{"fontSize":24}},
576
- {"id":"node_add_button","type":"INSTANCE","name":"Add button","x":16,"y":576,
577
- "width":328,"height":48,"children":[],"instance":{"component":
578
- {"packageId":"pkg_acme_foundation","assetId":"cmp_button"},
579
- "variant":{"axis_style":"primary"}},
580
- "overrides":{"node_button_label:text":"Add task"}}]}
581
-
582
- Shapes:
583
- smallpen schema node fields, textStyle, tokenBindings
584
- smallpen schema instance INSTANCE nodes; search-components prints a ready intent
585
-
586
- ${WRITE_REPLY}`,
587
- token: `Usage:
588
- smallpen token <package.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
589
- [--explain] [--diff] [--warning-detail compact|full]
590
- [--confirm-unmatched] [--compact] [--inverse-out FILE] [--json]
591
-
592
- Which Package:
593
- Pass the Foundation (<name>-foundation.smallpen) for put-token, set-token-value,
594
- remove-token, and deprecate-token: shared Tokens live there. Pass the Product
595
- (<name>.smallpen) for set-token-binding, clear-token-binding, and
596
- set-active-token-themes, which change Product nodes and its Token Library.
597
- smallpen schema operations lists the Package of every operation.
598
-
599
- Purpose:
600
- Apply declarative Token operations through the canonical atomic batch contract.
601
- Intent JSON contains an operations array using put-token, set-token-value,
602
- set-active-token-themes, set-token-binding, clear-token-binding, remove-token,
603
- or deprecate-token. Theme selection uses themePaths such as Product/Dark.
604
- The current package revision is read immediately before applying the intent.
605
- Write shared Tokens to the Foundation package; bind them from the Product.
606
- put-token with an existing tokenId replaces that Token whole (no merge). init
607
- seeds one Token per initialTokens answer (the recommended answers give
608
- tok_color_brand, tok_spacing_md, tok_radius_md; the init reply lists them in
609
- seeded.tokens); put-token with one of those ids redefines it.
610
- Rerunning with the same --batch-id and intent after success returns
611
- alreadyApplied:true; see smallpen apply --help for replay rules.
612
-
613
- Intent example (a color with a dark value, and a typography Token):
614
- {"operations":[
615
- {"type":"put-token","filePath":"tokens/foundation.json","path":"color.surface",
616
- "tokenId":"tok_color_surface","definition":{"$type":"color","$value":"#ffffff",
617
- "$extensions":{"smallpen":{"id":"tok_color_surface","contextValues":
618
- [{"when":{"axis_theme":"dark"},"value":"#1c1b1f"}]}}}},
619
- {"type":"put-token","filePath":"tokens/foundation.json","path":"type.heading",
620
- "tokenId":"tok_type_heading","definition":{"$type":"typography","$value":
621
- {"fontFamily":"Inter","fontSize":24,"fontWeight":700},
622
- "$extensions":{"smallpen":{"id":"tok_type_heading"}}}}]}
623
-
624
- Shapes:
625
- smallpen schema token definition, contextValues, bindable fields
626
- smallpen schema token-types the value each type accepts
627
- smallpen schema operation set-token-value
628
-
629
- ${WRITE_REPLY}`,
630
411
  impact: `Usage:
631
- smallpen impact <product.smallpen> (--token-id TOK | --path TOKEN.PATH) [--package-id PKG] [--json]
412
+ smallpen token impact <package.smallpen> --path TOKEN.NAME [--json]
632
413
 
633
414
  Purpose:
634
- Find all Canonical nodes whose tokenBindings reference the selected Token. The
635
- result includes screen, presentation, component, node, and field locations so
636
- an Agent can preview the exact blast radius before changing a Token. Foundation
637
- Tokens are resolved through the Product dependency; use --package-id to
638
- 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.`,
639
418
  apply: `Usage:
640
419
  smallpen apply <package.smallpen> --batch BATCH.json [--dry-run] [--explain] [--diff]
641
420
  [--warning-detail compact|full] [--confirm-unmatched]
@@ -645,7 +424,7 @@ Batch contract:
645
424
  {"baseRevision":"...","batchId":"unique-id","operations":[...]}
646
425
  baseRevision is package.revision from smallpen inspect PACKAGE --json.
647
426
  The whole candidate validates and commits atomically. Success returns revision,
648
- affected stable IDs/files, an exact inverseBatch for Undo/Redo, and changed
427
+ affected stable IDs/files, a revision-guarded reverse edit, and changed
649
428
  (false, with noChange, when the batch leaves the Package as it was).
650
429
  Revisions are content hashes, not increasing counters: restoring the exact
651
430
  Canonical content restores its previous revision.
@@ -654,8 +433,10 @@ Operations:
654
433
  smallpen schema operations every type, purpose, Package, required fields
655
434
  smallpen schema operation TYPE fields, allowed values, example, inverse
656
435
  Unknown operation types and unknown fields are rejected, with a suggestion.
657
- Tokens, Context Axes, and shared Component Sets go to the Foundation; Screens,
658
- nodes, Instances, and Scenarios go to the Product.
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.
659
440
  Example: {"baseRevision":"<revision>","batchId":"resize-home-1","operations":[
660
441
  {"type":"update-presentation-node","screenId":"scr_home",
661
442
  "presentationId":"pres_home_mobile","nodeId":"node_home_root",
@@ -705,7 +486,8 @@ Errors and recovery:
705
486
  write. If later writes moved the Package elsewhere the retry exits
706
487
  batch_superseded with committedRevision and currentRevision. The same batchId
707
488
  with a different baseRevision or operations exits batch_id_conflict.
708
- flow, page, token, and import-tokens rebuild the batch from the current
489
+ The name-based writes (page, component, flow, token, theme, asset, canvas)
490
+ and import-tokens rebuild the batch from the current
709
491
  revision; rerunning one with the same --batch-id and input after success
710
492
  returns alreadyApplied:true under the same rules.
711
493
  stale_revision returns actual/base revisions. Inspect current content first:
@@ -726,11 +508,40 @@ The initial revision is emitted immediately. --max-events bounds the run for
726
508
  scripted use; Ctrl-C exits 0. Watch never writes.
727
509
 
728
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`,
729
539
  repair: `Usage:
730
540
  smallpen repair <product.smallpen> [--conflict N] [--action ACTION]
731
541
  [action arguments] [--batch-id ID] [--json]
732
542
 
733
543
  Actions:
544
+ select-instance-variant [--selection JSON]; default: the closest variant
734
545
  retarget-reference --replacement-package-id PKG --replacement-asset-id ID
735
546
  remove-dependent-usage no additional arguments
736
547
  choose-foundation --foundation PATH
@@ -738,18 +549,70 @@ Actions:
738
549
 
739
550
  Without --action, returns all typed conflicts and choices. Each action is checked
740
551
  against the selected conflict and committed through one atomic Operation Batch.
741
- 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).`,
742
564
  };
743
565
 
744
- export function printHelp(command) {
745
- if (command && HELP[command]) {
746
- 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)}
747
601
 
748
602
  Common output and error contract:
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.
749
612
  --json emits stable English keys; localized labels remain additional data and
750
613
  follow --locale where offered, else LC_ALL, LC_MESSAGES, or LANG (zh* selects
751
614
  Chinese, anything else English). Success
752
- exits 0. Errors exit 1 as {error:{code,message,details}} and include exact
615
+ exits 0. Errors exit 1 as {error:{code,message,details,writeState}} and include exact
753
616
  nextOperations when recovery requires refresh, replay, Repair, or confirmation.
754
617
  Unknown options are not part of the command contract shown above. Only options
755
618
  shown with ..., repeated, or described as repeatable may appear more than once;
@@ -757,21 +620,27 @@ Common output and error contract:
757
620
 
758
621
  Help / next:
759
622
  smallpen --help
760
- smallpen ${command} --help
623
+ smallpen ${name} --help
761
624
  `);
762
625
  return;
763
626
  }
764
- process.stdout.write(`SmallPen Canonical Package CLI
627
+ say(`SmallPen Canonical Package CLI
765
628
 
766
629
  Model:
767
- One .smallpen directory is one Canonical Package. init creates a Foundation
768
- (<name>-foundation.smallpen: shared Tokens, Context Axes, shared Component Sets)
769
- and a Product (<name>.smallpen: Screens, Presentations, Scenarios). Write Tokens
770
- and components to the Foundation; write Screens and nodes to the Product, and
771
- pass the Product to read-view, render, evidence, tokens, and search-*.
772
- A Presentation renders at the size of its root node: resize it with
773
- update-presentation-node; update-presentation {viewport} keeps the declared
774
- size in step.
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.
775
644
  A Product references exactly one same-workspace Foundation by permanent Package
776
645
  ID and relative path. Every CLI call
777
646
  receives its Package locator and complete selectors explicitly; there is no current
@@ -780,45 +649,77 @@ Model:
780
649
  and one typed Operation Batch contract. No PostgreSQL, collaboration server, MCP,
781
650
  or built-in AI Chat is required.
782
651
 
783
- Choose an Agent view:
784
- wireframe Fast 2D spatial reasoning from a scaled ASCII canvas plus a compact
785
- [NN] key containing hierarchy, layer type, name, and stable ID.
786
- semantic Exact hierarchy, bounds, text, alignment, typography, component,
787
- variant, and Token properties.
788
- PNG High-fidelity visual QA. The image is raster pixels only; it contains
789
- no layer IDs, hierarchy, component references, or Token bindings.
790
- both Use inspect-view --include-image, then reason from structure and verify
791
- 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.
792
672
 
793
673
  Usage:
794
674
  smallpen <command> [arguments] [--json]
795
675
  smallpen <command> --help
796
- smallpen --version
676
+ smallpen version --json
797
677
 
798
678
  Commands:
799
- ${COMMANDS.map(([name, summary]) => ` ${name.padEnd(18)} ${summary}`).join("\n")}
800
-
801
- Common workflow:
802
- smallpen init ./workspace --json
803
- smallpen validate ./workspace/product.smallpen --json
804
- smallpen search-tokens ./workspace/product.smallpen --color '#6750a4' --json
805
- smallpen search-components ./workspace/product.smallpen --query button --json
806
- smallpen catalog ./workspace/product.smallpen --json
807
- smallpen read-view ./workspace/product.smallpen --json
808
- smallpen discover ./workspace/product.smallpen --json
809
- smallpen apply ./workspace/product.smallpen --batch batch.json --json
810
- smallpen evidence ./workspace/product.smallpen --output review --json
811
- smallpen import-draft ./workspace/import-1.smallpen --kind figma --input clipboard.html --json
812
- smallpen repair ./workspace/product.smallpen --json
813
-
814
- Worked example (tokens, component set, screen, instance override, render):
815
- ${workedExampleText()}
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
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
816
709
 
817
710
  Every write input has a schema and a tested example: smallpen schema
818
- (operations, node, token, component-set, instance, presentation, batch, init).
711
+ (operations, node, token, theme, component-set, instance, presentation, batch, init).
819
712
  Run smallpen <command> --help for syntax, defaults, values, examples, outputs,
820
713
  errors, and relevant next commands.
821
714
  `);
822
715
  }
823
716
 
824
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
+ }