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

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,5 +1,8 @@
1
+ import { workedExampleText } from "./schema.mjs";
2
+
1
3
  const COMMANDS = [
2
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"],
3
6
  ["init", "Persist a guided Brief/Proposal and create Foundation + Product"],
4
7
  ["validate", "Validate a package and its same-workspace Foundation"],
5
8
  ["inspect", "Inspect package identity, capabilities, and domain summary"],
@@ -43,6 +46,14 @@ const DESIGN_SELECTORS = `Design selectors:
43
46
  --context-profile ID Named Context profile
44
47
  --context AXIS=VALUE Repeatable finite Context selection`;
45
48
 
49
+ 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
+
46
57
  const VIEW_SELECTORS = `${DESIGN_SELECTORS}
47
58
  --format FORMAT structure|semantic|wireframe|screenshot`;
48
59
 
@@ -54,6 +65,36 @@ const HELP = {
54
65
  Output:
55
66
  The installed CLI package version, or {name,version} with --json.
56
67
  No package path is required; design files are unchanged and no services start.`,
68
+ schema: `Usage:
69
+ smallpen schema [TOPIC] [--json]
70
+ smallpen schema operation TYPE [--json]
71
+
72
+ Purpose:
73
+ Print the exact JSON that apply, token, page, flow, and init accept: fields,
74
+ types, allowed values, a valid example, and the inverse each operation
75
+ returns. No package is opened. Field lists come from the validators, and every
76
+ example is tested against a workspace made by smallpen init.
77
+
78
+ Topics:
79
+ operations every operation type, its purpose, Package, required fields
80
+ operation TYPE one operation in full (smallpen schema put-token also works)
81
+ batch batch contract, Foundation vs Product, worked example
82
+ node node fields, textStyle, tokenBindings
83
+ node-types what each node type requires
84
+ token Token definition, contextValues, bindings
85
+ token-types the value each Token type accepts
86
+ component-set Component Set, Axis roles (configuration, state), variants
87
+ instance INSTANCE nodes and overrides
88
+ presentation Presentation fields, size, and resizing
89
+ screen Screen fields
90
+ scenario Scenario fields and viewport
91
+ context Context Axes and theme values (alias: theme)
92
+ init every init question and a complete --answers file
93
+
94
+ Examples:
95
+ smallpen schema operations
96
+ smallpen schema operation update-component-node
97
+ smallpen schema component-set`,
57
98
  init: `Usage:
58
99
  smallpen init <workspace-directory> [--state FILE] [--answers FILE]
59
100
  [--answer QUESTION_ID=JSON]... [--confirm] [--locale LOCALE] [--json]
@@ -64,7 +105,13 @@ Purpose:
64
105
  Foundation and one Product; an existing target is never overwritten.
65
106
 
66
107
  Defaults and values:
67
- --state defaults to <workspace-directory>.smallpen-init.json.
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.
68
115
  Project kinds: application|motion|custom. Foundation choice: create-new.
69
116
  --answer values are JSON (for example projectKind=\"application\").
70
117
 
@@ -79,7 +126,20 @@ Output:
79
126
  <JSON> with your schema-valid answer; if no recommendation is present, supply the
80
127
  project's own purpose, audience, or deliverable. status proposal returns the
81
128
  persisted Proposal and confirmation args. Only --confirm creates the packages;
82
- status initialized returns both package paths.
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.
83
143
 
84
144
  Errors and next commands:
85
145
  Malformed --answer JSON identifies the question; schema-invalid values also return
@@ -136,7 +196,8 @@ ${VIEW_SELECTORS}
136
196
 
137
197
  Defaults:
138
198
  Base Presentation + default Context values + canonical initial state + structure.
139
- All formats resolve the same selection.
199
+ All formats resolve the same selection. --locale defaults to the environment
200
+ (zh* selects Chinese labels, anything else English).
140
201
 
141
202
  Choose a format:
142
203
  wireframe A scaled 2D ASCII canvas for fast spatial reasoning, followed by a
@@ -192,6 +253,13 @@ Purpose:
192
253
  execute the owner-scoped command returned by search-components.
193
254
  An HTTP(S) Library URL is also accepted and uses the normal Library cache.
194
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
+
195
263
  Next:
196
264
  Use a returned selection with read-view or inspect-view to review the design.`,
197
265
  compare: `Usage:
@@ -203,11 +271,11 @@ Values:
203
271
  smallpen compare product.smallpen --selector '{"presentationId":"pres_desktop"}' \\
204
272
  --selector '{"presentationId":"pres_mobile"}' --json`,
205
273
  tokens: `Usage:
206
- smallpen tokens <product.smallpen> [--context AXIS=VALUE]... [--json]
274
+ smallpen tokens <product.smallpen> [--context AXIS=VALUE]... [--locale LOCALE] [--json]
207
275
 
208
276
  Purpose:
209
277
  List Effective Tokens using Product first, Context specificity within a layer,
210
- then Foundation fallback.`,
278
+ then Foundation fallback. labels follow --locale or the environment.`,
211
279
  "search-tokens": `Usage:
212
280
  smallpen search-tokens <product.smallpen> (--color COLOR | --value JSON | --query TEXT | --type TYPE)
213
281
  [--context AXIS=VALUE]... [--limit N] [--json]
@@ -258,6 +326,12 @@ Defaults and limits:
258
326
  not visual QA. Verify decoded bytes against renderHash (SHA-256), and use the
259
327
  accompanying revision and selection. On failure, never open a previous file.
260
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
+
261
335
  Wireframe:
262
336
  Use smallpen read-view <product.smallpen> --format wireframe --json.
263
337
  It returns a scaled 2D ASCII canvas and compact marker key. Use semanticTree
@@ -268,7 +342,7 @@ Wireframe:
268
342
 
269
343
  ${DESIGN_SELECTORS}
270
344
  --scale N Image scale; default: 1 (requires --include-image to render)
271
- --locale LOCALE Localized view labels; default: zh-TW
345
+ --locale LOCALE Localized view labels; default: from the environment
272
346
 
273
347
  Purpose:
274
348
  Return one AI-readable view bundle.
@@ -347,12 +421,16 @@ Errors and recovery:
347
421
  Corrupt or unsupported bytes exit 1 invalid_media_file and write nothing.
348
422
  Reusing --media-id exits duplicate_media_id with the existing descriptor.
349
423
 
424
+ Output:
425
+ descriptor and revision, plus batchId and the exact inverseBatch for undo.
426
+
350
427
  `,
351
428
  "remove-media": `Usage:
352
429
  smallpen remove-media <package.smallpen> --media-id MEDIA [--json]
353
430
 
354
- Removes one Media descriptor from the Asset Library atomically; the inverse batch
355
- restores it. Unknown ids exit missing_media.
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.
356
434
 
357
435
  `,
358
436
  "import-font": `Usage:
@@ -364,7 +442,8 @@ Public Font entry for an existing Product. TTF and OTF are validated by SFNT
364
442
  signature and converted to WOFF (the renderer loads ttf/otf/woff). WOFF imports
365
443
  as-is after table validation. A valid WOFF2 reaches an explicit
366
444
  unsupported_font_conversion boundary (no WOFF2 decoder is bundled); corrupt
367
- fonts fail invalid_font_blob. Either way nothing is written.
445
+ fonts fail invalid_font_blob. Either way nothing is written. Success returns
446
+ fontId, variantId, files, revision, batchId, and the exact inverseBatch.
368
447
 
369
448
  `,
370
449
  "library-refresh": `Usage:
@@ -376,6 +455,13 @@ mismatch exits library_id_mismatch with expected/actual identities and leaves th
376
455
  previous verified snapshot readable offline. Success reports before/after
377
456
  revisions; failures keep the previous revision.
378
457
 
458
+ When a fetch fails and a verified cache exists, the command exits 0 with
459
+ after.cache "stale", the cached revision, and after.warning naming the cause.
460
+ With no cache the Library cannot resolve: the command exits repair_required
461
+ with a library_unavailable conflict whose message names the cause. Requests
462
+ follow at most five redirects, only within the Library's origin; each file
463
+ is capped at 50 MB.
464
+
379
465
  `,
380
466
  "import-draft": `Usage:
381
467
  smallpen import-draft <new-draft.smallpen> --kind figma|svg|png --input FILE
@@ -396,7 +482,7 @@ Next:
396
482
  "import-tokens": `Usage:
397
483
  smallpen import-tokens <package.smallpen> --input TOKENS.json [--set-name NAME]
398
484
  [--select SET/NAME]... [--apply] [--dry-run]
399
- [--batch-id ID] [--json]
485
+ [--batch-id ID] [--compact] [--inverse-out FILE] [--json]
400
486
 
401
487
  Purpose:
402
488
  Import a DTCG token file (Penpot or Tokens Studio export, single-set, multi-set
@@ -441,7 +527,8 @@ Output:
441
527
  This command does not apply the batch. Review it, then use smallpen apply.`,
442
528
  flow: `Usage:
443
529
  smallpen flow <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
444
- [--warning-detail compact|full] [--json]
530
+ [--explain] [--diff] [--warning-detail compact|full]
531
+ [--confirm-unmatched] [--compact] [--inverse-out FILE] [--json]
445
532
 
446
533
  Purpose:
447
534
  Compile and atomically apply a declarative flowchart intent. The intent contains
@@ -455,28 +542,91 @@ Intent example:
455
542
  Node types:
456
543
  FRAME|ELLIPSE|GROUP|PATH|RECTANGLE|TEXT. PATH nodes may carry pathData, points,
457
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.
458
551
 
459
- Output:
460
- The normal atomic apply result. Follow with read-view or render to inspect it.`,
552
+ ${WRITE_REPLY}`,
461
553
  page: `Usage:
462
554
  smallpen page <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
463
- [--warning-detail compact|full] [--json]
555
+ [--explain] [--diff] [--warning-detail compact|full]
556
+ [--confirm-unmatched] [--compact] [--inverse-out FILE] [--json]
464
557
 
465
558
  Purpose:
466
559
  Compile and atomically apply declarative page nodes. The intent uses the same
467
560
  screenId, optional presentationId/parentId, and nodes schema as flow. Use page
468
561
  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.`,
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}`,
470
587
  token: `Usage:
471
- smallpen token <product.smallpen> --intent INTENT.json [--batch-id ID] [--dry-run]
472
- [--warning-detail compact|full] [--json]
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.
473
598
 
474
599
  Purpose:
475
600
  Apply declarative Token operations through the canonical atomic batch contract.
476
601
  Intent JSON contains an operations array using put-token, set-token-value,
477
602
  set-active-token-themes, set-token-binding, clear-token-binding, remove-token,
478
603
  or deprecate-token. Theme selection uses themePaths such as Product/Dark.
479
- The current package revision is read immediately before applying the intent.`,
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}`,
480
630
  impact: `Usage:
481
631
  smallpen impact <product.smallpen> (--token-id TOK | --path TOKEN.PATH) [--package-id PKG] [--json]
482
632
 
@@ -487,16 +637,37 @@ Purpose:
487
637
  Tokens are resolved through the Product dependency; use --package-id to
488
638
  disambiguate a Token ID or path that exists in both Packages.`,
489
639
  apply: `Usage:
490
- smallpen apply <package.smallpen> --batch BATCH.json [--dry-run]
491
- [--warning-detail compact|full] [--confirm-unmatched] [--json]
640
+ smallpen apply <package.smallpen> --batch BATCH.json [--dry-run] [--explain] [--diff]
641
+ [--warning-detail compact|full] [--confirm-unmatched]
642
+ [--compact] [--inverse-out FILE] [--json]
492
643
 
493
644
  Batch contract:
494
645
  {"baseRevision":"...","batchId":"unique-id","operations":[...]}
646
+ baseRevision is package.revision from smallpen inspect PACKAGE --json.
495
647
  The whole candidate validates and commits atomically. Success returns revision,
496
- affected stable IDs/files, and an exact inverseBatch for Undo/Redo.
648
+ affected stable IDs/files, an exact inverseBatch for Undo/Redo, and changed
649
+ (false, with noChange, when the batch leaves the Package as it was).
497
650
  Revisions are content hashes, not increasing counters: restoring the exact
498
651
  Canonical content restores its previous revision.
499
652
 
653
+ Operations:
654
+ smallpen schema operations every type, purpose, Package, required fields
655
+ smallpen schema operation TYPE fields, allowed values, example, inverse
656
+ 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.
659
+ Example: {"baseRevision":"<revision>","batchId":"resize-home-1","operations":[
660
+ {"type":"update-presentation-node","screenId":"scr_home",
661
+ "presentationId":"pres_home_mobile","nodeId":"node_home_root",
662
+ "changes":{"width":360,"height":640}}]}
663
+
664
+ Component instance overrides:
665
+ set-instance-override {screenId, presentationId?, nodeId, overridePath, value}
666
+ and clear-instance-override {screenId, presentationId?, nodeId, overridePath}
667
+ change one field of one Instance node without touching the shared component.
668
+ overridePath is "<sourceNodeId>:<field>", or "<nestedInstanceId>__<sourceNodeId>:<field>"
669
+ inside a nested instance. Fields: fills, name, opacity, text, visible.
670
+
500
671
  Token references on nodes:
501
672
  tokenBindings uses package-qualified references, for example
502
673
  {"fill":{"packageId":"pkg_foundation","assetId":"tok_brand"}}.
@@ -506,7 +677,10 @@ Token references on nodes:
506
677
 
507
678
  Raw-value confirmation policy:
508
679
  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,
680
+ warning; the write itself is not gated. Warnings are design advice, not
681
+ validity: validate does not repeat them, and warningSummary.note says so. Raw
682
+ width/height values are listed only when a Token resolves to them or lies within
683
+ 10% (at least 2); the rest are counted in warningSummary.suppressed. --confirm-unmatched is an explicit,
510
684
  machine-readable acknowledgment: the response marks those warning groups with
511
685
  "confirmed": true. Omitting the flag changes nothing about the write.
512
686
 
@@ -518,23 +692,36 @@ Warning output:
518
692
  and summary contextScope, then sort by warningIndex to recover the original list.
519
693
  Select --warning-detail full before executing to emit the original flat list.
520
694
  Do not replay an already applied batch just to expand warnings: compact output
521
- already contains every occurrence, and stale-revision checks still apply.
695
+ already contains every listed occurrence, and stale-revision checks still apply.
696
+
697
+ ${WRITE_REPLY}
522
698
 
523
699
  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.
700
+ Committed batch identities are recorded beside the Package. Retrying the same
701
+ batchId with the same baseRevision and operations, from any process, returns the
702
+ recorded confirmation with alreadyApplied:true and writes nothing, as long as
703
+ the Package is still at the recorded revision. If the batch's effect was undone
704
+ (the Package is back at its baseRevision) the retry applies it again as a new
705
+ write. If later writes moved the Package elsewhere the retry exits
706
+ batch_superseded with committedRevision and currentRevision. The same batchId
707
+ with a different baseRevision or operations exits batch_id_conflict.
708
+ flow, page, token, and import-tokens rebuild the batch from the current
709
+ revision; rerunning one with the same --batch-id and input after success
710
+ returns alreadyApplied:true under the same rules.
527
711
  stale_revision returns actual/base revisions. Inspect current content first:
528
712
  the intended change may already be committed. Rebuild only the remaining intent
529
713
  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.
714
+ --dry-run validates and returns the result/inverse without writing. --explain
715
+ adds per-operation targets and --diff a before/after field diff of every
716
+ changed entry; both are previews that never write, with or without --dry-run.
531
717
  Invalid batches never write.`,
532
718
  watch: `Usage:
533
719
  smallpen watch <package.smallpen> [--interval MS] [--max-events N] [--json]
534
720
 
535
721
  Watch is the public live mode: it polls the Canonical Package and emits one NDJSON
536
722
  {event:"revision", packageId, revision} line whenever the on-disk revision
537
- changes, and {event:"invalid"} lines when the package is temporarily unreadable.
723
+ changes. An unreadable package emits one {event:"invalid", code, message} line
724
+ (again only when the error changes); recovery emits its revision line again.
538
725
  The initial revision is emitted immediately. --max-events bounds the run for
539
726
  scripted use; Ctrl-C exits 0. Watch never writes.
540
727
 
@@ -559,10 +746,14 @@ export function printHelp(command) {
559
746
  process.stdout.write(`SmallPen ${command}\n\n${HELP[command]}
560
747
 
561
748
  Common output and error contract:
562
- --json emits stable English keys; localized labels remain additional data. Success
749
+ --json emits stable English keys; localized labels remain additional data and
750
+ follow --locale where offered, else LC_ALL, LC_MESSAGES, or LANG (zh* selects
751
+ Chinese, anything else English). Success
563
752
  exits 0. Errors exit 1 as {error:{code,message,details}} and include exact
564
753
  nextOperations when recovery requires refresh, replay, Repair, or confirmation.
565
- Unknown options are not part of the command contract shown above.
754
+ Unknown options are not part of the command contract shown above. Only options
755
+ shown with ..., repeated, or described as repeatable may appear more than once;
756
+ any other option given twice exits duplicate_option.
566
757
 
567
758
  Help / next:
568
759
  smallpen --help
@@ -573,8 +764,16 @@ Help / next:
573
764
  process.stdout.write(`SmallPen Canonical Package CLI
574
765
 
575
766
  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
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.
775
+ A Product references exactly one same-workspace Foundation by permanent Package
776
+ ID and relative path. Every CLI call
578
777
  receives its Package locator and complete selectors explicitly; there is no current
579
778
  selection, current page, or cross-call session state. Packages are never merged into
580
779
  a synthetic multi-root file. CLI, Desktop, renderer, and Agents share one resolver
@@ -612,6 +811,11 @@ Common workflow:
612
811
  smallpen import-draft ./workspace/import-1.smallpen --kind figma --input clipboard.html --json
613
812
  smallpen repair ./workspace/product.smallpen --json
614
813
 
814
+ Worked example (tokens, component set, screen, instance override, render):
815
+ ${workedExampleText()}
816
+
817
+ Every write input has a schema and a tested example: smallpen schema
818
+ (operations, node, token, component-set, instance, presentation, batch, init).
615
819
  Run smallpen <command> --help for syntax, defaults, values, examples, outputs,
616
820
  errors, and relevant next commands.
617
821
  `);