@acsetra/runner 0.1.55 → 0.1.57

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/README.md CHANGED
@@ -27,6 +27,9 @@ Exposes `runner` and `acsetra` binaries. The token is cached at
27
27
  `~/.config/acsetra/credentials` (0600) — shared with the Python client, so you
28
28
  can use either interchangeably.
29
29
 
30
+ `runner docs pull` refreshes concise Claude Code doctrine. Use
31
+ `runner <group> <subverb> --help` for the exact installed command grammar.
32
+
30
33
  ## Environment overrides
31
34
 
32
35
  `ACSETRA_API_BASE`, `ACSETRA_TOKEN`, `ACSETRA_WORKSPACE`, `ACSETRA_SCOPE`.
package/bin/runner.js CHANGED
@@ -19,6 +19,21 @@ function emit(result) {
19
19
  }
20
20
  function fail(msg, code = 1) { console.error("REJECTED: " + msg); process.exit(code); }
21
21
  function opt(rest, flag) { const i = rest.indexOf(flag); return i >= 0 && i + 1 < rest.length ? rest[i + 1] : null; }
22
+ const MANAGED_DOC_REMOVALS = new Set([".runner/docs/commands.md"]);
23
+
24
+ function removeManagedDocs(pack) {
25
+ const removed = [];
26
+ for (const rel of (pack.remove || [])) {
27
+ if (!MANAGED_DOC_REMOVALS.has(rel)) continue;
28
+ // Never follow a server-directed parent symlink out of the current project.
29
+ if ([".runner", path.join(".runner", "docs")].some(parent => {
30
+ try { return fs.lstatSync(parent).isSymbolicLink(); } catch { return false; }
31
+ })) continue;
32
+ try { fs.unlinkSync(rel); removed.push(rel); }
33
+ catch (e) { if (e.code !== "ENOENT") throw e; }
34
+ }
35
+ return removed;
36
+ }
22
37
 
23
38
  async function signin(rest) {
24
39
  const apiArg = opt(rest, "--api");
@@ -66,8 +81,10 @@ async function docsPull(rest) {
66
81
  fs.writeFileSync(rel, content); n++;
67
82
  console.error(" " + rel);
68
83
  }
84
+ const removed = removeManagedDocs(pack);
85
+ for (const rel of removed) console.error(" removed " + rel);
69
86
  config.setProject({ context_version: pack.version, workspace: pack.workspace });
70
- console.error(`docs: wrote ${n} files (version ${pack.version})`);
87
+ console.error(`docs: wrote ${n} files, removed ${removed.length} stale files (version ${pack.version})`);
71
88
  return 0;
72
89
  }
73
90
 
@@ -137,7 +154,7 @@ async function main() {
137
154
  const argv = process.argv.slice(2);
138
155
  if (!argv.length || ["-h", "--help"].includes(argv[0])) { console.log(renderTop()); return 0; }
139
156
  const [cmd, ...rest] = argv;
140
- if (["version", "--version", "-V"].includes(cmd)) { console.log("runner (@acsetra/runner) 0.1.55"); return 0; }
157
+ if (["version", "--version", "-V"].includes(cmd)) { console.log("runner (@acsetra/runner) 0.1.57"); return 0; }
141
158
 
142
159
  // help — top-level, `help <topic>`, and per-verb `<cmd> … --help` (lexical, before toOp)
143
160
  if (cmd === "help") {
@@ -0,0 +1,1002 @@
1
+ {
2
+ "_comment": "The ONE CLI grammar + help manifest. Canonical copy: clients/grammar.json. Both clients ship a byte-identical copy (acsetra_cli/grammar.json, npm lib/grammar.json); `./d publish` re-syncs the copies and tests/test_grammar_manifest.py fails on drift. Edit the canonical file only.",
3
+ "json_args": [
4
+ "allow_hosts",
5
+ "attrs",
6
+ "config",
7
+ "config_schema",
8
+ "decls",
9
+ "input",
10
+ "meta",
11
+ "overrides",
12
+ "parts",
13
+ "props",
14
+ "schema",
15
+ "spec",
16
+ "storage",
17
+ "tree"
18
+ ],
19
+ "int_args": [
20
+ "at",
21
+ "days",
22
+ "max_bytes",
23
+ "ordinal",
24
+ "rate",
25
+ "retries",
26
+ "timeout",
27
+ "ttl_days"
28
+ ],
29
+ "float_args": [
30
+ "backoff"
31
+ ],
32
+ "bool_args": [
33
+ "auth_flow",
34
+ "autoplay",
35
+ "controls",
36
+ "enabled",
37
+ "force",
38
+ "keep",
39
+ "map",
40
+ "muted",
41
+ "probe",
42
+ "replace",
43
+ "run"
44
+ ],
45
+ "primary_json": {
46
+ "set.put": "meta",
47
+ "set.fetch_url": "meta",
48
+ "set.declare_schema": "schema",
49
+ "css.set_rule": "decls",
50
+ "css.set_styles": "styles",
51
+ "css.set_keyframes": "frames",
52
+ "css.set_breakpoints": "breakpoints",
53
+ "component.set": "tree",
54
+ "component.set_route": "props",
55
+ "behavior.attach": "config",
56
+ "head.set": "attrs"
57
+ },
58
+ "pos": {
59
+ "set.create": [
60
+ "code"
61
+ ],
62
+ "set.put": [
63
+ "set_code",
64
+ "row_code"
65
+ ],
66
+ "set.declare_schema": [
67
+ "code"
68
+ ],
69
+ "set.fetch_url": [
70
+ "set_code",
71
+ "row_code",
72
+ "url"
73
+ ],
74
+ "set.remove_row": [
75
+ "set_code",
76
+ "row_code"
77
+ ],
78
+ "set.drop": [
79
+ "set_code"
80
+ ],
81
+ "set.describe": [
82
+ "set_code"
83
+ ],
84
+ "set.rows": [
85
+ "set_code"
86
+ ],
87
+ "set.link": [
88
+ "alias",
89
+ "source"
90
+ ],
91
+ "pipe.set": [
92
+ "code"
93
+ ],
94
+ "pipe.show": [
95
+ "code"
96
+ ],
97
+ "pipe.remove": [
98
+ "code"
99
+ ],
100
+ "css.set_class": [
101
+ "code"
102
+ ],
103
+ "css.set_rule": [
104
+ "class_code",
105
+ "variant"
106
+ ],
107
+ "css.clear_rule": [
108
+ "class_code"
109
+ ],
110
+ "css.attach": [
111
+ "class_code",
112
+ "target"
113
+ ],
114
+ "css.detach": [
115
+ "code"
116
+ ],
117
+ "css.describe": [
118
+ "class_code"
119
+ ],
120
+ "css.resolve": [
121
+ "page"
122
+ ],
123
+ "css.set_keyframes": [
124
+ "name"
125
+ ],
126
+ "css.clear_keyframes": [
127
+ "name"
128
+ ],
129
+ "css.remove_class": [
130
+ "code"
131
+ ],
132
+ "asset.add": [
133
+ "code",
134
+ "kind"
135
+ ],
136
+ "asset.attach": [
137
+ "asset",
138
+ "slot"
139
+ ],
140
+ "asset.detach": [
141
+ "code"
142
+ ],
143
+ "asset.describe": [
144
+ "code"
145
+ ],
146
+ "asset.resolve": [
147
+ "page"
148
+ ],
149
+ "behavior.add": [
150
+ "code"
151
+ ],
152
+ "behavior.set_enabled": [
153
+ "code",
154
+ "enabled"
155
+ ],
156
+ "behavior.remove": [
157
+ "code"
158
+ ],
159
+ "behavior.attach": [
160
+ "behavior_code",
161
+ "target"
162
+ ],
163
+ "behavior.set_attachment_enabled": [
164
+ "code",
165
+ "enabled"
166
+ ],
167
+ "behavior.detach": [
168
+ "code"
169
+ ],
170
+ "behavior.describe": [
171
+ "code"
172
+ ],
173
+ "component.set": [
174
+ "code"
175
+ ],
176
+ "component.remove": [
177
+ "code"
178
+ ],
179
+ "component.set_route": [
180
+ "code",
181
+ "component"
182
+ ],
183
+ "component.remove_route": [
184
+ "code"
185
+ ],
186
+ "component.describe": [
187
+ "code"
188
+ ],
189
+ "head.set": [
190
+ "code"
191
+ ],
192
+ "head.remove": [
193
+ "code"
194
+ ]
195
+ },
196
+ "planes": [
197
+ "asset",
198
+ "behavior",
199
+ "component",
200
+ "css",
201
+ "head",
202
+ "set"
203
+ ],
204
+ "short": {
205
+ "w": "writes",
206
+ "k": "keys",
207
+ "u": "url",
208
+ "f": "body_from",
209
+ "H": "handler",
210
+ "X": "method",
211
+ "s": "spec",
212
+ "l": "label",
213
+ "i": "input"
214
+ },
215
+ "groups": {
216
+ "set": [
217
+ [
218
+ "create",
219
+ "set.create"
220
+ ],
221
+ [
222
+ "put",
223
+ "set.put"
224
+ ],
225
+ [
226
+ "declare_schema",
227
+ "set.declare_schema"
228
+ ],
229
+ [
230
+ "fetch_url",
231
+ "set.fetch_url"
232
+ ],
233
+ [
234
+ "remove_row",
235
+ "set.remove_row"
236
+ ],
237
+ [
238
+ "drop",
239
+ "set.drop"
240
+ ],
241
+ [
242
+ "link",
243
+ "set.link"
244
+ ],
245
+ [
246
+ "sources",
247
+ "set.sources"
248
+ ],
249
+ [
250
+ "list",
251
+ "set.list"
252
+ ],
253
+ [
254
+ "describe",
255
+ "set.describe"
256
+ ],
257
+ [
258
+ "rows",
259
+ "set.rows"
260
+ ],
261
+ [
262
+ "validate",
263
+ "set.validate"
264
+ ]
265
+ ],
266
+ "css": [
267
+ [
268
+ "init",
269
+ "css.init"
270
+ ],
271
+ [
272
+ "set_class",
273
+ "css.set_class"
274
+ ],
275
+ [
276
+ "set_rule",
277
+ "css.set_rule"
278
+ ],
279
+ [
280
+ "set_styles",
281
+ "css.set_styles"
282
+ ],
283
+ [
284
+ "clear_rule",
285
+ "css.clear_rule"
286
+ ],
287
+ [
288
+ "remove_class",
289
+ "css.remove_class"
290
+ ],
291
+ [
292
+ "set_keyframes",
293
+ "css.set_keyframes"
294
+ ],
295
+ [
296
+ "clear_keyframes",
297
+ "css.clear_keyframes"
298
+ ],
299
+ [
300
+ "set_breakpoints",
301
+ "css.set_breakpoints"
302
+ ],
303
+ [
304
+ "list_breakpoints",
305
+ "css.list_breakpoints"
306
+ ],
307
+ [
308
+ "attach",
309
+ "css.attach"
310
+ ],
311
+ [
312
+ "detach",
313
+ "css.detach"
314
+ ],
315
+ [
316
+ "list_classes",
317
+ "css.list_classes"
318
+ ],
319
+ [
320
+ "list_rules",
321
+ "css.list_rules"
322
+ ],
323
+ [
324
+ "list_attachments",
325
+ "css.list_attachments"
326
+ ],
327
+ [
328
+ "describe",
329
+ "css.describe"
330
+ ],
331
+ [
332
+ "resolve",
333
+ "css.resolve"
334
+ ],
335
+ [
336
+ "validate",
337
+ "css.validate"
338
+ ]
339
+ ],
340
+ "asset": [
341
+ [
342
+ "init",
343
+ "asset.init"
344
+ ],
345
+ [
346
+ "add",
347
+ "asset.add"
348
+ ],
349
+ [
350
+ "attach",
351
+ "asset.attach"
352
+ ],
353
+ [
354
+ "detach",
355
+ "asset.detach"
356
+ ],
357
+ [
358
+ "list",
359
+ "asset.list"
360
+ ],
361
+ [
362
+ "list_attachments",
363
+ "asset.list_attachments"
364
+ ],
365
+ [
366
+ "describe",
367
+ "asset.describe"
368
+ ],
369
+ [
370
+ "resolve",
371
+ "asset.resolve"
372
+ ],
373
+ [
374
+ "validate",
375
+ "asset.validate"
376
+ ],
377
+ [
378
+ "check",
379
+ "asset.check"
380
+ ]
381
+ ],
382
+ "behavior": [
383
+ [
384
+ "init",
385
+ "behavior.init"
386
+ ],
387
+ [
388
+ "add",
389
+ "behavior.add"
390
+ ],
391
+ [
392
+ "set_enabled",
393
+ "behavior.set_enabled"
394
+ ],
395
+ [
396
+ "remove",
397
+ "behavior.remove"
398
+ ],
399
+ [
400
+ "attach",
401
+ "behavior.attach"
402
+ ],
403
+ [
404
+ "set_attachment_enabled",
405
+ "behavior.set_attachment_enabled"
406
+ ],
407
+ [
408
+ "detach",
409
+ "behavior.detach"
410
+ ],
411
+ [
412
+ "list",
413
+ "behavior.list"
414
+ ],
415
+ [
416
+ "list_attachments",
417
+ "behavior.list_attachments"
418
+ ],
419
+ [
420
+ "describe",
421
+ "behavior.describe"
422
+ ],
423
+ [
424
+ "validate",
425
+ "behavior.validate"
426
+ ]
427
+ ],
428
+ "component": [
429
+ [
430
+ "init",
431
+ "component.init"
432
+ ],
433
+ [
434
+ "set",
435
+ "component.set"
436
+ ],
437
+ [
438
+ "remove",
439
+ "component.remove"
440
+ ],
441
+ [
442
+ "set_route",
443
+ "component.set_route"
444
+ ],
445
+ [
446
+ "remove_route",
447
+ "component.remove_route"
448
+ ],
449
+ [
450
+ "list",
451
+ "component.list"
452
+ ],
453
+ [
454
+ "list_routes",
455
+ "component.list_routes"
456
+ ],
457
+ [
458
+ "describe",
459
+ "component.describe"
460
+ ],
461
+ [
462
+ "validate",
463
+ "component.validate"
464
+ ]
465
+ ],
466
+ "head": [
467
+ [
468
+ "init",
469
+ "head.init"
470
+ ],
471
+ [
472
+ "set",
473
+ "head.set"
474
+ ],
475
+ [
476
+ "remove",
477
+ "head.remove"
478
+ ],
479
+ [
480
+ "list",
481
+ "head.list"
482
+ ],
483
+ [
484
+ "validate",
485
+ "head.validate"
486
+ ]
487
+ ]
488
+ },
489
+ "group_aliases": {
490
+ "css": {
491
+ "class": "set_class",
492
+ "rule": "set_rule"
493
+ }
494
+ },
495
+ "ops_doc": {
496
+ "set.create": {
497
+ "desc": "create a value set (mint your own plane/registry)",
498
+ "kw": [
499
+ "label",
500
+ "kind",
501
+ "schema"
502
+ ]
503
+ },
504
+ "set.put": {
505
+ "desc": "write/merge a row (--json is the row meta; MERGES unless --replace)",
506
+ "kw": [
507
+ "label",
508
+ "ordinal",
509
+ "enabled",
510
+ "replace"
511
+ ]
512
+ },
513
+ "set.declare_schema": {
514
+ "desc": "declare/replace a set's row-meta JSON-Schema contract",
515
+ "kw": []
516
+ },
517
+ "set.fetch_url": {
518
+ "desc": "import remote TEXT into a row (SSRF-guarded https GET)",
519
+ "kw": [
520
+ "key",
521
+ "label",
522
+ "max_bytes",
523
+ "allow_hosts",
524
+ "replace",
525
+ "ordinal"
526
+ ]
527
+ },
528
+ "set.remove_row": {
529
+ "desc": "delete one row from a set",
530
+ "kw": []
531
+ },
532
+ "set.drop": {
533
+ "desc": "drop a whole value set",
534
+ "kw": [
535
+ "force"
536
+ ]
537
+ },
538
+ "set.link": {
539
+ "desc": "wire another app's set in read-only under a local alias (source is '<scope-path>/<set-code>'; unlink with set drop)",
540
+ "kw": [
541
+ "label"
542
+ ]
543
+ },
544
+ "set.sources": {
545
+ "desc": "the data catalog: every linkable set outside this scope, with sampled field names",
546
+ "kw": []
547
+ },
548
+ "set.list": {
549
+ "desc": "list value sets at the scope",
550
+ "kw": []
551
+ },
552
+ "set.describe": {
553
+ "desc": "show a set's schema + metadata",
554
+ "kw": []
555
+ },
556
+ "set.rows": {
557
+ "desc": "list a set's rows",
558
+ "kw": []
559
+ },
560
+ "set.validate": {
561
+ "desc": "re-check every set (grammar, bounds, declared schema)",
562
+ "kw": []
563
+ },
564
+ "css.init": {
565
+ "desc": "bootstrap the css plane for a scope",
566
+ "kw": []
567
+ },
568
+ "css.set_class": {
569
+ "desc": "create/define a css class (identity)",
570
+ "kw": [
571
+ "label",
572
+ "description",
573
+ "enabled",
574
+ "ordinal"
575
+ ]
576
+ },
577
+ "css.set_rule": {
578
+ "desc": "set a class's declarations for a variant (--json is the decls)",
579
+ "kw": [
580
+ "ordinal",
581
+ "replace",
582
+ "context",
583
+ "relation"
584
+ ]
585
+ },
586
+ "css.set_styles": {
587
+ "desc": "set many classes/variants in one call (--json is the styles spec; flat decls beside variant blocks, \"a > b\" context keys)",
588
+ "kw": [
589
+ "replace"
590
+ ]
591
+ },
592
+ "css.clear_rule": {
593
+ "desc": "clear a class's rule (one variant or all)",
594
+ "kw": [
595
+ "variant",
596
+ "context",
597
+ "relation"
598
+ ]
599
+ },
600
+ "css.remove_class": {
601
+ "desc": "delete a class completely (row, rules, attachments)",
602
+ "kw": []
603
+ },
604
+ "css.set_keyframes": {
605
+ "desc": "author validated @keyframes (--json is the frames map)",
606
+ "kw": []
607
+ },
608
+ "css.clear_keyframes": {
609
+ "desc": "remove an authored @keyframes row",
610
+ "kw": []
611
+ },
612
+ "css.set_breakpoints": {
613
+ "desc": "declare responsive breakpoints usable as variants (--json is {name: media query})",
614
+ "kw": []
615
+ },
616
+ "css.list_breakpoints": {
617
+ "desc": "list declared breakpoints",
618
+ "kw": []
619
+ },
620
+ "css.attach": {
621
+ "desc": "attach a class to a surface (component:/instance:/part:/raw:/page:)",
622
+ "kw": [
623
+ "label",
624
+ "ordinal"
625
+ ]
626
+ },
627
+ "css.detach": {
628
+ "desc": "remove a css attachment",
629
+ "kw": []
630
+ },
631
+ "css.list_classes": {
632
+ "desc": "list css classes",
633
+ "kw": []
634
+ },
635
+ "css.list_rules": {
636
+ "desc": "list css rules",
637
+ "kw": [
638
+ "class_code"
639
+ ]
640
+ },
641
+ "css.list_attachments": {
642
+ "desc": "list css attachments",
643
+ "kw": []
644
+ },
645
+ "css.describe": {
646
+ "desc": "show a class's rules + attachments",
647
+ "kw": []
648
+ },
649
+ "css.resolve": {
650
+ "desc": "show the resolved cascade for a page",
651
+ "kw": [
652
+ "app"
653
+ ]
654
+ },
655
+ "css.validate": {
656
+ "desc": "re-check every class/rule/attachment",
657
+ "kw": []
658
+ },
659
+ "asset.init": {
660
+ "desc": "bootstrap the asset plane",
661
+ "kw": []
662
+ },
663
+ "asset.add": {
664
+ "desc": "register a media asset (image/svg/icon/video/iframe)",
665
+ "kw": [
666
+ "url",
667
+ "name",
668
+ "alt",
669
+ "provider",
670
+ "svg",
671
+ "fit",
672
+ "controls",
673
+ "muted",
674
+ "autoplay",
675
+ "poster",
676
+ "ordinal",
677
+ "storage",
678
+ "probe"
679
+ ]
680
+ },
681
+ "asset.attach": {
682
+ "desc": "fill a named slot on a target with an asset; --target takes the one durable target spelling (component:/instance:/part:/raw:/page:, the same shape css attach uses) INSTEAD of the page/component/raw/type/part flags",
683
+ "kw": [
684
+ "page",
685
+ "component",
686
+ "raw",
687
+ "type_",
688
+ "part",
689
+ "target",
690
+ "app",
691
+ "ordinal"
692
+ ]
693
+ },
694
+ "asset.detach": {
695
+ "desc": "remove an asset attachment",
696
+ "kw": []
697
+ },
698
+ "asset.list": {
699
+ "desc": "list assets",
700
+ "kw": []
701
+ },
702
+ "asset.list_attachments": {
703
+ "desc": "list asset attachments",
704
+ "kw": []
705
+ },
706
+ "asset.describe": {
707
+ "desc": "show an asset + its attachments",
708
+ "kw": []
709
+ },
710
+ "asset.resolve": {
711
+ "desc": "show resolved asset slots for a page",
712
+ "kw": [
713
+ "app"
714
+ ]
715
+ },
716
+ "asset.validate": {
717
+ "desc": "re-check every asset/attachment (shape only)",
718
+ "kw": []
719
+ },
720
+ "asset.check": {
721
+ "desc": "probe registered asset URLs (HEAD) for liveness",
722
+ "kw": [
723
+ "code",
724
+ "allow_hosts"
725
+ ]
726
+ },
727
+ "behavior.init": {
728
+ "desc": "bootstrap the behavior plane",
729
+ "kw": []
730
+ },
731
+ "behavior.add": {
732
+ "desc": "add a browser ability (mount(ctx) JS body via -b -)",
733
+ "kw": [
734
+ "label",
735
+ "config_schema",
736
+ "enabled",
737
+ "ordinal"
738
+ ],
739
+ "body": true
740
+ },
741
+ "behavior.set_enabled": {
742
+ "desc": "enable/disable a behavior definition",
743
+ "kw": []
744
+ },
745
+ "behavior.remove": {
746
+ "desc": "remove a behavior",
747
+ "kw": []
748
+ },
749
+ "behavior.attach": {
750
+ "desc": "mount a behavior on a target (--json is the config)",
751
+ "kw": [
752
+ "label",
753
+ "enabled",
754
+ "ordinal"
755
+ ]
756
+ },
757
+ "behavior.set_attachment_enabled": {
758
+ "desc": "enable/disable one placed mount",
759
+ "kw": []
760
+ },
761
+ "behavior.detach": {
762
+ "desc": "remove a behavior attachment; refused when it would leave an enabled behavior with nothing that still mounts (add + verify the replacement first, or --force)",
763
+ "kw": [
764
+ "force"
765
+ ]
766
+ },
767
+ "behavior.list": {
768
+ "desc": "list behaviors",
769
+ "kw": []
770
+ },
771
+ "behavior.list_attachments": {
772
+ "desc": "list behavior attachments",
773
+ "kw": []
774
+ },
775
+ "behavior.describe": {
776
+ "desc": "show a behavior + its mounts",
777
+ "kw": []
778
+ },
779
+ "behavior.validate": {
780
+ "desc": "re-check every behavior/attachment",
781
+ "kw": []
782
+ },
783
+ "component.init": {
784
+ "desc": "bootstrap the component+route plane",
785
+ "kw": []
786
+ },
787
+ "component.set": {
788
+ "desc": "author a component's node tree (--json is the tree); --props declares the props this component accepts (prop -> {type} or default)",
789
+ "kw": [
790
+ "parts",
791
+ "props",
792
+ "label",
793
+ "ordinal"
794
+ ]
795
+ },
796
+ "component.remove": {
797
+ "desc": "remove a component",
798
+ "kw": []
799
+ },
800
+ "component.set_route": {
801
+ "desc": "mount a component on a page (--json is route props); --page => page-scoped visibility wrapper (navigation shows/hides; without it extra routes STACK); --pattern => path pattern 'watch/{id}' mapping a served path onto the page (effective page defaults to the route code for patterned routes); --overrides patches THIS placement only, keyed 'raw:<id>' / 'part:<component>:<part>' -> {text, attrs} (the way one mount of a shared component gets its own lede)",
802
+ "kw": [
803
+ "instance",
804
+ "overrides",
805
+ "page",
806
+ "pattern",
807
+ "label",
808
+ "ordinal"
809
+ ]
810
+ },
811
+ "component.remove_route": {
812
+ "desc": "remove a route",
813
+ "kw": []
814
+ },
815
+ "component.list": {
816
+ "desc": "list components",
817
+ "kw": []
818
+ },
819
+ "component.list_routes": {
820
+ "desc": "list routes (pages)",
821
+ "kw": []
822
+ },
823
+ "component.describe": {
824
+ "desc": "show a component's refs + mounting routes",
825
+ "kw": []
826
+ },
827
+ "component.validate": {
828
+ "desc": "re-check every tree + ref",
829
+ "kw": []
830
+ },
831
+ "head.init": {
832
+ "desc": "bootstrap document_head (title/viewport/favicon)",
833
+ "kw": []
834
+ },
835
+ "head.set": {
836
+ "desc": "set/merge a <head> entry (--json is its attrs)",
837
+ "kw": [
838
+ "tag",
839
+ "text",
840
+ "enabled",
841
+ "ordinal",
842
+ "label"
843
+ ]
844
+ },
845
+ "head.remove": {
846
+ "desc": "remove a head entry",
847
+ "kw": []
848
+ },
849
+ "head.list": {
850
+ "desc": "list head entries in order",
851
+ "kw": []
852
+ },
853
+ "head.validate": {
854
+ "desc": "re-check every head entry",
855
+ "kw": []
856
+ }
857
+ },
858
+ "verbs_doc": {
859
+ "signin": {
860
+ "usage": "signin [--api URL] [--no-browser]",
861
+ "desc": "device-flow sign-in; caches a token"
862
+ },
863
+ "whoami": {
864
+ "usage": "whoami",
865
+ "desc": "show the signed-in user + workspace"
866
+ },
867
+ "workspaces": {
868
+ "usage": "workspaces [--use SLUG]",
869
+ "desc": "list workspaces; --use switches the active one"
870
+ },
871
+ "use": {
872
+ "usage": "use <slug>",
873
+ "desc": "set the active workspace for this project"
874
+ },
875
+ "logout": {
876
+ "usage": "logout",
877
+ "desc": "forget the cached token"
878
+ },
879
+ "usage": {
880
+ "usage": "usage [--days N]",
881
+ "desc": "metered usage vs your plan caps"
882
+ },
883
+ "tokens": {
884
+ "usage": "tokens [new [--name N] | list | revoke <id>]",
885
+ "desc": "manage API tokens"
886
+ },
887
+ "docs": {
888
+ "usage": "docs pull [-S scope]",
889
+ "desc": "refresh CLAUDE.md + .runner/docs/* (this app's context)"
890
+ },
891
+ "dev": {
892
+ "usage": "dev <app> [--no-browser]",
893
+ "desc": "open your app's hosted surface in the browser"
894
+ },
895
+ "open": {
896
+ "usage": "open <scope> [--reuse|--new|--background] [--device ID] [--no-fallback]",
897
+ "desc": "open or reuse a scope in paired Runner Chrome; falls back to a cold browser URL"
898
+ },
899
+ "focus": {
900
+ "usage": "focus <scope> [--device ID]",
901
+ "desc": "move to an existing scope tab, creating it when needed"
902
+ },
903
+ "notify": {
904
+ "usage": "notify <scope> [--title T] [--message M] [--new] [--device ID]",
905
+ "desc": "raise a native notification whose click opens or reuses the scope"
906
+ },
907
+ "tabs": {
908
+ "usage": "tabs [--device ID]",
909
+ "desc": "list open Runner scope tabs from the paired browser only"
910
+ },
911
+ "devices": {
912
+ "usage": "devices",
913
+ "desc": "list paired browser devices and live status"
914
+ },
915
+ "checkout": {
916
+ "usage": "checkout <app> [-o dir] [--force]",
917
+ "desc": "materialize a scope subtree as local files in .tmp/ (read-only; grep/edit, write back with the typed verbs)"
918
+ },
919
+ "app": {
920
+ "usage": "app create <name> [--label L] | app list",
921
+ "desc": "create / list app scopes"
922
+ },
923
+ "apps": {
924
+ "usage": "apps",
925
+ "desc": "list app scopes (alias of `app list`)"
926
+ },
927
+ "auth": {
928
+ "usage": "auth <app> [--off] | auth list [scope]",
929
+ "desc": "gate an app behind home.acsetra.com sign-in — signed-in visitors are enrolled as members on first visit; --off lifts the gate (no secrets, reuses the main-site realm)"
930
+ },
931
+ "ls": {
932
+ "usage": "ls [-S scope]",
933
+ "desc": "list value sets"
934
+ },
935
+ "inspect": {
936
+ "usage": "inspect [<set>] [-S scope]",
937
+ "desc": "read a set's rows (alias: i); no set => list"
938
+ },
939
+ "read": {
940
+ "usage": "read <code> [-S scope]",
941
+ "desc": "read a value set (me_* = your own per-user zone)"
942
+ },
943
+ "show": {
944
+ "usage": "show <pipeline>",
945
+ "desc": "reconstruct a pipeline from its rows"
946
+ },
947
+ "run": {
948
+ "usage": "run <pipeline> -i '{json}'",
949
+ "desc": "test-run a pipeline with input"
950
+ },
951
+ "runs": {
952
+ "usage": "runs <launch_id>",
953
+ "desc": "poll a launch's status/output"
954
+ },
955
+ "compile": {
956
+ "usage": "compile <app>",
957
+ "desc": "link the bundle + cross-plane diagnostics + content hash"
958
+ },
959
+ "doctor": {
960
+ "usage": "doctor <app>",
961
+ "desc": "full static gate over the subtree: validate every plane + link refs + compile code (non-zero exit = broken)"
962
+ },
963
+ "verify": {
964
+ "usage": "verify <app> [--map] [--run]",
965
+ "desc": "render-level check: dead wiring, behavior↔surface mismatch, ctx typos. --map = per-node design-as-data; --run executes mount(ctx) and catches throws"
966
+ },
967
+ "pipe": {
968
+ "usage": "pipe <code> -S <scope> [-w code:label] [--rate N] [--timeout N] [--keep]\n pipe show <code> | pipe list | pipe remove <code>",
969
+ "desc": "create/upsert a pipeline envelope (resets workers unless --keep)"
970
+ },
971
+ "pipes": {
972
+ "usage": "pipes",
973
+ "desc": "list pipelines (alias of `pipe list`)"
974
+ },
975
+ "w": {
976
+ "usage": "w <pipeline> <tier> [flags] [-w code:label] [--at N] [-S scope]",
977
+ "desc": "add a worker to a pipeline (alias: work)",
978
+ "extra": [
979
+ "Tiers:",
980
+ " snippet -b <code|-> DB-isolated Python (sets output / output={'rows':[…]})",
981
+ " internal -H/--handler NAME a vetted internal handler",
982
+ " httpx -u/--url URL [-X METHOD] call a service; keys injected server-side",
983
+ " [-k/--keys a,b] [-f/--body-from K]",
984
+ " read --set CODE [--mode latest] resolve a value set into the run data",
985
+ " [--key K] [--as NAME] [--read-scope S]",
986
+ " llm [--provider openai] [--model M] one model call (keys injected server-side)",
987
+ " [--system-from K] [--user-from ask] [-k/--keys a,b]"
988
+ ]
989
+ },
990
+ "op": {
991
+ "usage": "op <name> [--json '{full args bag}'] [--flag value] [-S scope]",
992
+ "desc": "call any op directly. Here --json IS the full args bag (never a single arg)."
993
+ }
994
+ },
995
+ "verb_aliases": {
996
+ "i": "inspect",
997
+ "work": "w",
998
+ "login": "signin",
999
+ "signout": "logout",
1000
+ "co": "checkout"
1001
+ }
1002
+ }
package/lib/help.js CHANGED
@@ -3,166 +3,16 @@
3
3
  // sets) so it can't drift from what `toOp` actually dispatches. Output is kept
4
4
  // byte-identical to the Python client. dispatchHelp returns text, or null when
5
5
  // the command isn't one we document (so bin/runner.js falls through unchanged).
6
- import { POS, PRIMARY_JSON, SHORT, JSON_ARGS, INT_ARGS, FLOAT_ARGS, BOOL_ARGS } from "./verbs.js";
6
+ import { GRAMMAR, POS, PRIMARY_JSON, SHORT, JSON_ARGS, INT_ARGS, FLOAT_ARGS, BOOL_ARGS } from "./verbs.js";
7
7
 
8
8
  const PROG = "r"; // docs lead with `r`; `runner` / `acsetra` are aliases
9
9
 
10
- export const GROUPS = {
11
- set: [["create", "set.create"], ["put", "set.put"],
12
- ["declare_schema", "set.declare_schema"], ["fetch_url", "set.fetch_url"],
13
- ["remove_row", "set.remove_row"], ["drop", "set.drop"],
14
- ["link", "set.link"], ["sources", "set.sources"],
15
- ["list", "set.list"], ["describe", "set.describe"],
16
- ["rows", "set.rows"], ["validate", "set.validate"]],
17
- css: [["init", "css.init"], ["set_class", "css.set_class"],
18
- ["set_rule", "css.set_rule"], ["clear_rule", "css.clear_rule"],
19
- ["attach", "css.attach"], ["detach", "css.detach"],
20
- ["list_classes", "css.list_classes"], ["list_rules", "css.list_rules"],
21
- ["list_attachments", "css.list_attachments"], ["describe", "css.describe"],
22
- ["resolve", "css.resolve"], ["validate", "css.validate"]],
23
- asset: [["init", "asset.init"], ["add", "asset.add"], ["attach", "asset.attach"],
24
- ["detach", "asset.detach"], ["list", "asset.list"],
25
- ["list_attachments", "asset.list_attachments"], ["describe", "asset.describe"],
26
- ["resolve", "asset.resolve"], ["validate", "asset.validate"],
27
- ["check", "asset.check"]],
28
- behavior: [["init", "behavior.init"], ["add", "behavior.add"],
29
- ["set_enabled", "behavior.set_enabled"], ["remove", "behavior.remove"],
30
- ["attach", "behavior.attach"],
31
- ["set_attachment_enabled", "behavior.set_attachment_enabled"],
32
- ["detach", "behavior.detach"], ["list", "behavior.list"],
33
- ["list_attachments", "behavior.list_attachments"],
34
- ["describe", "behavior.describe"], ["validate", "behavior.validate"]],
35
- component: [["init", "component.init"], ["set", "component.set"],
36
- ["remove", "component.remove"], ["set_route", "component.set_route"],
37
- ["remove_route", "component.remove_route"], ["list", "component.list"],
38
- ["list_routes", "component.list_routes"], ["describe", "component.describe"],
39
- ["validate", "component.validate"]],
40
- head: [["init", "head.init"], ["set", "head.set"], ["remove", "head.remove"],
41
- ["list", "head.list"], ["validate", "head.validate"]],
42
- };
43
-
44
- export const GROUP_ALIASES = { css: { class: "set_class", rule: "set_rule" } };
45
-
46
- export const OPS_DOC = {
47
- "set.create": { desc: "create a value set (mint your own plane/registry)", kw: ["label", "kind", "schema"] },
48
- "set.put": { desc: "write/merge a row (--json is the row meta; MERGES unless --replace)", kw: ["label", "ordinal", "enabled", "replace"] },
49
- "set.declare_schema": { desc: "declare/replace a set's row-meta JSON-Schema contract", kw: [] },
50
- "set.fetch_url": { desc: "import remote TEXT into a row (SSRF-guarded https GET)", kw: ["key", "label", "max_bytes", "allow_hosts", "replace", "ordinal"] },
51
- "set.remove_row": { desc: "delete one row from a set", kw: [] },
52
- "set.drop": { desc: "drop a whole value set", kw: ["force"] },
53
- "set.list": { desc: "list value sets at the scope", kw: [] },
54
- "set.describe": { desc: "show a set's schema + metadata", kw: [] },
55
- "set.rows": { desc: "list a set's rows", kw: [] },
56
- "set.validate": { desc: "re-check every set (grammar, bounds, declared schema)", kw: [] },
57
-
58
- "css.init": { desc: "bootstrap the css plane for a scope", kw: [] },
59
- "css.set_class": { desc: "create/define a css class (identity)", kw: ["label", "description", "enabled", "ordinal"] },
60
- "css.set_rule": { desc: "set a class's declarations for a variant (--json is the decls)", kw: ["ordinal"] },
61
- "css.clear_rule": { desc: "clear a class's rule (one variant or all)", kw: ["variant"] },
62
- "css.attach": { desc: "attach a class to a surface (component:/instance:/part:/raw:/page:)", kw: ["label", "ordinal"] },
63
- "css.detach": { desc: "remove a css attachment", kw: [] },
64
- "css.list_classes": { desc: "list css classes", kw: [] },
65
- "css.list_rules": { desc: "list css rules", kw: ["class_code"] },
66
- "css.list_attachments": { desc: "list css attachments", kw: [] },
67
- "css.describe": { desc: "show a class's rules + attachments", kw: [] },
68
- "css.resolve": { desc: "show the resolved cascade for a page", kw: ["app"] },
69
- "css.validate": { desc: "re-check every class/rule/attachment", kw: [] },
70
-
71
- "asset.init": { desc: "bootstrap the asset plane", kw: [] },
72
- "asset.add": { desc: "register a media asset (image/svg/icon/video/iframe)", kw: ["url", "name", "alt", "provider", "svg", "fit", "controls", "muted", "autoplay", "poster", "ordinal", "storage", "probe"] },
73
- "asset.attach": { desc: "fill a named slot on a target with an asset", kw: ["page", "component", "raw", "type_", "part", "app", "ordinal"] },
74
- "asset.detach": { desc: "remove an asset attachment", kw: [] },
75
- "asset.list": { desc: "list assets", kw: [] },
76
- "asset.list_attachments": { desc: "list asset attachments", kw: [] },
77
- "asset.describe": { desc: "show an asset + its attachments", kw: [] },
78
- "asset.resolve": { desc: "show resolved asset slots for a page", kw: ["app"] },
79
- "asset.validate": { desc: "re-check every asset/attachment (shape only)", kw: [] },
80
- "asset.check": { desc: "probe registered asset URLs (HEAD) for liveness", kw: ["code", "allow_hosts"] },
81
-
82
- "behavior.init": { desc: "bootstrap the behavior plane", kw: [] },
83
- "behavior.add": { desc: "add a browser ability (mount(ctx) JS body via -b -)", kw: ["label", "config_schema", "enabled", "ordinal"], body: true },
84
- "behavior.set_enabled": { desc: "enable/disable a behavior definition", kw: [] },
85
- "behavior.remove": { desc: "remove a behavior", kw: [] },
86
- "behavior.attach": { desc: "mount a behavior on a target (--json is the config)", kw: ["label", "enabled", "ordinal"] },
87
- "behavior.set_attachment_enabled": { desc: "enable/disable one placed mount", kw: [] },
88
- "behavior.detach": { desc: "remove a behavior attachment", kw: [] },
89
- "behavior.list": { desc: "list behaviors", kw: [] },
90
- "behavior.list_attachments": { desc: "list behavior attachments", kw: [] },
91
- "behavior.describe": { desc: "show a behavior + its mounts", kw: [] },
92
- "behavior.validate": { desc: "re-check every behavior/attachment", kw: [] },
93
-
94
- "component.init": { desc: "bootstrap the component+route plane", kw: [] },
95
- "component.set": { desc: "author a component's node tree (--json is the tree)", kw: ["parts", "label", "ordinal"] },
96
- "component.remove": { desc: "remove a component", kw: [] },
97
- "component.set_route": { desc: "mount a component on a page (--json is route props); --page => page-scoped visibility wrapper (navigation shows/hides; without it extra routes STACK); --pattern => path pattern 'watch/{id}' mapping a served path onto the page (effective page defaults to the route code for patterned routes)", kw: ["instance", "page", "pattern", "label", "ordinal"] },
98
- "component.remove_route": { desc: "remove a route", kw: [] },
99
- "component.list": { desc: "list components", kw: [] },
100
- "component.list_routes": { desc: "list routes (pages)", kw: [] },
101
- "component.describe": { desc: "show a component's refs + mounting routes", kw: [] },
102
- "component.validate": { desc: "re-check every tree + ref", kw: [] },
103
-
104
- "head.init": { desc: "bootstrap document_head (title/viewport/favicon)", kw: [] },
105
- "head.set": { desc: "set/merge a <head> entry (--json is its attrs)", kw: ["tag", "text", "enabled", "ordinal", "label"] },
106
- "head.remove": { desc: "remove a head entry", kw: [] },
107
- "head.list": { desc: "list head entries in order", kw: [] },
108
- "head.validate": { desc: "re-check every head entry", kw: [] },
109
- };
110
-
111
- export const VERBS_DOC = {
112
- signin: { usage: "signin [--api URL] [--no-browser]", desc: "device-flow sign-in; caches a token" },
113
- whoami: { usage: "whoami", desc: "show the signed-in user + workspace" },
114
- workspaces: { usage: "workspaces [--use SLUG]", desc: "list workspaces; --use switches the active one" },
115
- use: { usage: "use <slug>", desc: "set the active workspace for this project" },
116
- logout: { usage: "logout", desc: "forget the cached token" },
117
- usage: { usage: "usage [--days N]", desc: "metered usage vs your plan caps" },
118
- tokens: { usage: "tokens [new [--name N] | list | revoke <id>]", desc: "manage API tokens" },
119
- docs: { usage: "docs pull [-S scope]", desc: "refresh CLAUDE.md + .runner/docs/* (this app's context)" },
120
- dev: { usage: "dev <app> [--no-browser]", desc: "open your app's hosted surface in the browser" },
121
- open: { usage: "open <scope> [--reuse|--new|--background] [--device ID] [--no-fallback]", desc: "open or reuse a scope in paired Runner Chrome; falls back to a cold browser URL" },
122
- focus: { usage: "focus <scope> [--device ID]", desc: "move to an existing scope tab, creating it when needed" },
123
- notify: { usage: "notify <scope> [--title T] [--message M] [--new] [--device ID]", desc: "raise a native notification whose click opens or reuses the scope" },
124
- tabs: { usage: "tabs [--device ID]", desc: "list open Runner scope tabs from the paired browser only" },
125
- devices: { usage: "devices", desc: "list paired browser devices and live status" },
126
- checkout: { usage: "checkout <app> [-o dir] [--force]", desc: "materialize a scope subtree as local files in .tmp/ (read-only; grep/edit, write back with the typed verbs)" },
127
- app: { usage: "app create <name> [--label L] | app list", desc: "create / list app scopes" },
128
- apps: { usage: "apps", desc: "list app scopes (alias of `app list`)" },
129
- auth: { usage: "auth <app> [--off] | auth list [scope]", desc: "gate an app behind home.acsetra.com sign-in — signed-in visitors are enrolled as members on first visit; --off lifts the gate (no secrets, reuses the main-site realm)" },
130
- ls: { usage: "ls [-S scope]", desc: "list value sets" },
131
- inspect: { usage: "inspect [<set>] [-S scope]", desc: "read a set's rows (alias: i); no set => list" },
132
- read: { usage: "read <code> [-S scope]", desc: "read a value set (me_* = your own per-user zone)" },
133
- show: { usage: "show <pipeline>", desc: "reconstruct a pipeline from its rows" },
134
- run: { usage: "run <pipeline> -i '{json}'", desc: "test-run a pipeline with input" },
135
- runs: { usage: "runs <launch_id>", desc: "poll a launch's status/output" },
136
- compile: { usage: "compile <app>", desc: "link the bundle + cross-plane diagnostics + content hash" },
137
- doctor: { usage: "doctor <app>", desc: "full static gate over the subtree: validate every plane + link refs + compile code (non-zero exit = broken)" },
138
- pipe: {
139
- usage: "pipe <code> -S <scope> [-w code:label] [--rate N] [--timeout N] [--keep]\n" +
140
- " pipe show <code> | pipe list | pipe remove <code>",
141
- desc: "create/upsert a pipeline envelope (resets workers unless --keep)",
142
- },
143
- pipes: { usage: "pipes", desc: "list pipelines (alias of `pipe list`)" },
144
- w: {
145
- usage: "w <pipeline> <tier> [flags] [-w code:label] [--at N] [-S scope]",
146
- desc: "add a worker to a pipeline (alias: work)",
147
- extra: [
148
- "Tiers:",
149
- " snippet -b <code|-> DB-isolated Python (sets output / output={'rows':[…]})",
150
- " internal -H/--handler NAME a vetted internal handler",
151
- " httpx -u/--url URL [-X METHOD] call a service; keys injected server-side",
152
- " [-k/--keys a,b] [-f/--body-from K]",
153
- " read --set CODE [--mode latest] resolve a value set into the run data",
154
- " [--key K] [--as NAME] [--read-scope S]",
155
- " llm [--provider openai] [--model M] one model call (keys injected server-side)",
156
- " [--system-from K] [--user-from ask] [-k/--keys a,b]",
157
- ],
158
- },
159
- op: {
160
- usage: "op <name> [--json '{full args bag}'] [--flag value] [-S scope]",
161
- desc: "call any op directly. Here --json IS the full args bag (never a single arg).",
162
- },
163
- };
164
-
165
- export const VERB_ALIASES = { i: "inspect", work: "w", login: "signin", signout: "logout", co: "checkout" };
10
+ // --- the shared manifest (clients/grammar.json) -----------------------------
11
+ export const GROUPS = GRAMMAR.groups;
12
+ export const GROUP_ALIASES = GRAMMAR.group_aliases;
13
+ export const OPS_DOC = GRAMMAR.ops_doc;
14
+ export const VERBS_DOC = GRAMMAR.verbs_doc;
15
+ export const VERB_ALIASES = GRAMMAR.verb_aliases;
166
16
 
167
17
  const REV_SHORT = Object.fromEntries(Object.entries(SHORT).map(([s, l]) => [l, s]));
168
18
 
@@ -225,7 +75,7 @@ export function renderOp(op) {
225
75
  for (const k of kw) out.push(` ${pad(alias(k), 22)} ${vtype(k)}`);
226
76
  out.push("");
227
77
  }
228
- out.push(`Raw op: ${PROG} op ${op} … · authoritative args: .runner/docs/commands.md`);
78
+ out.push(`Raw op (advanced): ${PROG} op ${op} … · this help is authoritative`);
229
79
  return out.join("\n");
230
80
  }
231
81
 
@@ -243,7 +93,7 @@ export function renderVerb(verb) {
243
93
  const out = ["Usage:", " " + PROG + " " + doc.usage, ""];
244
94
  if (doc.desc) out.push(doc.desc, "");
245
95
  if (doc.extra) out.push(...doc.extra, "");
246
- out.push(`More: \`${PROG} help\` · grammar: .runner/docs/commands.md`);
96
+ out.push(`More: \`${PROG} help\` · exact grammar: \`${PROG} <group> <subverb> --help\``);
247
97
  return out.join("\n").replace(/\s+$/, "");
248
98
  }
249
99
 
package/lib/verbs.js CHANGED
@@ -1,35 +1,21 @@
1
1
  // The verb grammar — argv -> {op, args}. Node mirror of acsetra_cli/verbs.py.
2
+ // The tables live in grammar.json — ONE manifest shared byte-identical with the
3
+ // Python client (acsetra_cli/grammar.json; canonical copy clients/grammar.json),
4
+ // so the two CLIs can never drift apart on grammar or help. Edit the canonical
5
+ // file, never tables here.
2
6
  import fs from "node:fs";
3
7
 
4
- export const JSON_ARGS = new Set(["meta", "schema", "decls", "tree", "props", "parts", "input",
5
- "attrs", "config", "config_schema", "storage", "allow_hosts", "spec"]);
6
- export const INT_ARGS = new Set(["ordinal", "rate", "timeout", "retries", "at", "max_bytes", "ttl_days", "days"]);
7
- export const FLOAT_ARGS = new Set(["backoff"]);
8
- export const BOOL_ARGS = new Set(["replace", "force", "keep", "probe", "enabled", "muted", "autoplay", "controls", "auth_flow"]);
9
- export const PRIMARY_JSON = {
10
- "set.put": "meta", "set.fetch_url": "meta", "set.declare_schema": "schema",
11
- "css.set_rule": "decls", "component.set": "tree", "component.set_route": "props",
12
- "behavior.attach": "config", "head.set": "attrs",
13
- };
14
- export const POS = {
15
- "set.create": ["code"], "set.put": ["set_code", "row_code"], "set.declare_schema": ["code"],
16
- "set.fetch_url": ["set_code", "row_code", "url"], "set.remove_row": ["set_code", "row_code"],
17
- "set.drop": ["set_code"], "set.describe": ["set_code"], "set.rows": ["set_code"],
18
- "set.link": ["alias", "source"], "set.sources": ["scope"],
19
- "pipe.set": ["code"], "pipe.show": ["code"], "pipe.remove": ["code"],
20
- "css.set_class": ["code"], "css.set_rule": ["class_code", "variant"], "css.clear_rule": ["class_code"],
21
- "css.attach": ["class_code", "target"], "css.detach": ["code"], "css.describe": ["class_code"], "css.resolve": ["page"],
22
- "asset.add": ["code", "kind"], "asset.attach": ["asset", "slot"], "asset.detach": ["code"],
23
- "asset.describe": ["code"], "asset.resolve": ["page"],
24
- "behavior.add": ["code"], "behavior.set_enabled": ["code", "enabled"], "behavior.remove": ["code"],
25
- "behavior.attach": ["behavior_code", "target"], "behavior.set_attachment_enabled": ["code", "enabled"],
26
- "behavior.detach": ["code"], "behavior.describe": ["code"],
27
- "component.set": ["code"], "component.remove": ["code"], "component.set_route": ["code", "component"],
28
- "component.remove_route": ["code"], "component.describe": ["code"],
29
- "head.set": ["code"], "head.remove": ["code"],
30
- };
31
- export const PLANES = new Set(["set", "css", "asset", "behavior", "component", "head"]);
32
- export const SHORT = { w: "writes", k: "keys", u: "url", f: "body_from", H: "handler", X: "method", s: "spec", l: "label", i: "input" };
8
+ export const GRAMMAR = JSON.parse(
9
+ fs.readFileSync(new URL("./grammar.json", import.meta.url), "utf8"));
10
+
11
+ export const JSON_ARGS = new Set(GRAMMAR.json_args);
12
+ export const INT_ARGS = new Set(GRAMMAR.int_args);
13
+ export const FLOAT_ARGS = new Set(GRAMMAR.float_args);
14
+ export const BOOL_ARGS = new Set(GRAMMAR.bool_args);
15
+ export const PRIMARY_JSON = GRAMMAR.primary_json;
16
+ export const POS = GRAMMAR.pos;
17
+ export const PLANES = new Set(GRAMMAR.planes);
18
+ export const SHORT = GRAMMAR.short;
33
19
 
34
20
  export class VerbError extends Error {}
35
21
 
@@ -147,6 +133,15 @@ export function toOp(verb, rest) {
147
133
  if ("scope" in flags) args.scope = flags.scope;
148
134
  return [verb === "compile" ? "bundle.compile" : "doctor.check", args];
149
135
  }
136
+ if (verb === "verify") {
137
+ const { positionals, flags } = walk(rest);
138
+ const args = {};
139
+ if (positionals.length) args.scope = positionals[0];
140
+ if ("scope" in flags) args.scope = flags.scope;
141
+ if (flags.map) args.include_map = true;
142
+ if (flags.run) args.run = true;
143
+ return ["surface.verify", args];
144
+ }
150
145
  if (verb === "app") {
151
146
  if (rest[0] === "create") {
152
147
  const { positionals, flags } = walk(rest.slice(1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acsetra/runner",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "Runner CLI — author and run hosted Runner apps from your terminal (the `runner` command).",
5
5
  "keywords": ["runner", "acsetra", "cli", "agent", "low-code"],
6
6
  "license": "MIT",