@acsetra/runner 0.1.88 → 0.1.90

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/runner.js CHANGED
@@ -190,7 +190,7 @@ async function main() {
190
190
  const argv = process.argv.slice(2);
191
191
  if (!argv.length || ["-h", "--help"].includes(argv[0])) { console.log(renderTop()); return 0; }
192
192
  const [cmd, ...rest] = argv;
193
- if (["version", "--version", "-V"].includes(cmd)) { console.log("runner (@acsetra/runner) 0.1.88"); return 0; }
193
+ if (["version", "--version", "-V"].includes(cmd)) { console.log("runner (@acsetra/runner) 0.1.90"); return 0; }
194
194
 
195
195
  // help — top-level, `help <topic>`, and per-verb `<cmd> … --help` (lexical, before toOp)
196
196
  if (cmd === "help") {
package/lib/grammar.json CHANGED
@@ -7,18 +7,25 @@
7
7
  "config_schema",
8
8
  "decls",
9
9
  "input",
10
+ "items",
10
11
  "meta",
11
12
  "overrides",
12
13
  "parts",
14
+ "patches",
13
15
  "props",
16
+ "replace_meta",
17
+ "requires",
14
18
  "schema",
15
19
  "spec",
16
20
  "storage",
21
+ "targets",
22
+ "tests",
17
23
  "tree"
18
24
  ],
19
25
  "int_args": [
20
26
  "at",
21
27
  "days",
28
+ "limit",
22
29
  "max_bytes",
23
30
  "ordinal",
24
31
  "rate",
@@ -30,11 +37,16 @@
30
37
  "backoff"
31
38
  ],
32
39
  "bool_args": [
40
+ "all_routes",
41
+ "atomic",
33
42
  "auth_flow",
34
43
  "autoplay",
35
44
  "controls",
45
+ "disabled",
36
46
  "enabled",
37
47
  "force",
48
+ "if_absent",
49
+ "include_deleted",
38
50
  "keep",
39
51
  "map",
40
52
  "muted",
@@ -53,7 +65,9 @@
53
65
  "component.set": "tree",
54
66
  "component.set_route": "props",
55
67
  "behavior.attach": "config",
56
- "head.set": "attrs"
68
+ "head.set": "attrs",
69
+ "record.transact": "targets",
70
+ "record.commit": "patches"
57
71
  },
58
72
  "pos": {
59
73
  "set.create": [
@@ -191,6 +205,44 @@
191
205
  ],
192
206
  "head.remove": [
193
207
  "code"
208
+ ],
209
+ "record.get": [
210
+ "set_code",
211
+ "code"
212
+ ],
213
+ "record.commit": [
214
+ "set_code",
215
+ "code"
216
+ ],
217
+ "record.transact": [
218
+ "operation_id"
219
+ ],
220
+ "record.replay": [
221
+ "operation_namespace",
222
+ "operation_id"
223
+ ],
224
+ "schedule.set": [
225
+ "code",
226
+ "pipeline",
227
+ "cron"
228
+ ],
229
+ "schedule.show": [
230
+ "code"
231
+ ],
232
+ "schedule.occurrences": [
233
+ "code"
234
+ ],
235
+ "schedule.pause": [
236
+ "code"
237
+ ],
238
+ "schedule.resume": [
239
+ "code"
240
+ ],
241
+ "schedule.run_now": [
242
+ "code"
243
+ ],
244
+ "schedule.remove": [
245
+ "code"
194
246
  ]
195
247
  },
196
248
  "planes": [
@@ -199,6 +251,8 @@
199
251
  "component",
200
252
  "css",
201
253
  "head",
254
+ "record",
255
+ "schedule",
202
256
  "set"
203
257
  ],
204
258
  "short": {
@@ -484,6 +538,58 @@
484
538
  "validate",
485
539
  "head.validate"
486
540
  ]
541
+ ],
542
+ "record": [
543
+ [
544
+ "get",
545
+ "record.get"
546
+ ],
547
+ [
548
+ "commit",
549
+ "record.commit"
550
+ ],
551
+ [
552
+ "transact",
553
+ "record.transact"
554
+ ],
555
+ [
556
+ "replay",
557
+ "record.replay"
558
+ ]
559
+ ],
560
+ "schedule": [
561
+ [
562
+ "set",
563
+ "schedule.set"
564
+ ],
565
+ [
566
+ "list",
567
+ "schedule.list"
568
+ ],
569
+ [
570
+ "show",
571
+ "schedule.show"
572
+ ],
573
+ [
574
+ "occurrences",
575
+ "schedule.occurrences"
576
+ ],
577
+ [
578
+ "pause",
579
+ "schedule.pause"
580
+ ],
581
+ [
582
+ "resume",
583
+ "schedule.resume"
584
+ ],
585
+ [
586
+ "run_now",
587
+ "schedule.run_now"
588
+ ],
589
+ [
590
+ "remove",
591
+ "schedule.remove"
592
+ ]
487
593
  ]
488
594
  },
489
595
  "group_aliases": {
@@ -494,7 +600,7 @@
494
600
  },
495
601
  "ops_doc": {
496
602
  "set.create": {
497
- "desc": "create a value set (mint your own plane/registry)",
603
+ "desc": "create a value set (mint your own plane/registry) — born a keyed record set: point reads, field patches, revisions/etags, durable receipts; never the legacy whole-collection path",
498
604
  "kw": [
499
605
  "label",
500
606
  "kind",
@@ -502,12 +608,13 @@
502
608
  ]
503
609
  },
504
610
  "set.put": {
505
- "desc": "write/merge a row (--json is the row meta; MERGES unless --replace)",
611
+ "desc": "write/merge a row (--json is the row meta). Every set you mint is a keyed record set: each sent key lands as ONE atomic field patch (disjoint edits compose, a stale edit of a changed field conflicts); MERGES unless --replace; --etag makes the write conditional",
506
612
  "kw": [
507
613
  "label",
508
614
  "ordinal",
509
615
  "enabled",
510
- "replace"
616
+ "replace",
617
+ "etag"
511
618
  ]
512
619
  },
513
620
  "set.declare_schema": {
@@ -853,6 +960,91 @@
853
960
  "head.validate": {
854
961
  "desc": "re-check every head entry",
855
962
  "kw": []
963
+ },
964
+ "record.get": {
965
+ "desc": "ONE row by code with its record_id / revision / etag (the keyed point read)",
966
+ "kw": [
967
+ "include_deleted"
968
+ ]
969
+ },
970
+ "record.commit": {
971
+ "desc": "atomic commit on one record (--json is the patches list: [{op:set|unset|increment|add_unique|remove_value|test, path:'/field', value}]); action create|patch|replace|delete|restore; expected_etag makes it conditional; items=[…] is a one-row batch with per-item outcomes",
972
+ "kw": [
973
+ "action",
974
+ "tests",
975
+ "items",
976
+ "batch_policy",
977
+ "expected_etag",
978
+ "expected_revision",
979
+ "operation_id",
980
+ "label",
981
+ "ordinal",
982
+ "replace_meta",
983
+ "if_absent"
984
+ ]
985
+ },
986
+ "record.transact": {
987
+ "desc": "several records, ONE operation: <operation_id> names it (a uuid you mint; retry = replay), --json is the targets list [{set_code, code, action?, patches?|items?, expected_etag?, requires?:[{target, item_id?}]}]; --atomic is all-or-none, else one outcome per target",
988
+ "kw": [
989
+ "atomic"
990
+ ]
991
+ },
992
+ "record.replay": {
993
+ "desc": "the stored terminal result of an operation (namespace + id)",
994
+ "kw": []
995
+ },
996
+ "schedule.set": {
997
+ "desc": "create/update an org-catalog five-field cron schedule for the explicit target scope",
998
+ "scope": "required",
999
+ "kw": [
1000
+ "label",
1001
+ "timezone",
1002
+ "input",
1003
+ "overlap",
1004
+ "misfire",
1005
+ "disabled"
1006
+ ]
1007
+ },
1008
+ "schedule.list": {
1009
+ "desc": "list schedules for this target; --all-routes lists the org catalog",
1010
+ "scope": "required",
1011
+ "kw": [
1012
+ "all_routes"
1013
+ ]
1014
+ },
1015
+ "schedule.show": {
1016
+ "desc": "show one target-addressed schedule",
1017
+ "scope": "required",
1018
+ "kw": []
1019
+ },
1020
+ "schedule.occurrences": {
1021
+ "desc": "list durable occurrence and launch receipts, newest first",
1022
+ "scope": "required",
1023
+ "kw": [
1024
+ "limit"
1025
+ ]
1026
+ },
1027
+ "schedule.pause": {
1028
+ "desc": "pause one schedule and clear its next firing",
1029
+ "scope": "required",
1030
+ "kw": []
1031
+ },
1032
+ "schedule.resume": {
1033
+ "desc": "revalidate and resume one schedule, rebinding its routed pipeline when detached",
1034
+ "scope": "required",
1035
+ "kw": []
1036
+ },
1037
+ "schedule.run_now": {
1038
+ "desc": "enqueue once without moving the cron clock; --operation-id makes retries idempotent",
1039
+ "scope": "required",
1040
+ "kw": [
1041
+ "operation_id"
1042
+ ]
1043
+ },
1044
+ "schedule.remove": {
1045
+ "desc": "delete one schedule and its occurrence history",
1046
+ "scope": "required",
1047
+ "kw": []
856
1048
  }
857
1049
  },
858
1050
  "verbs_doc": {
@@ -949,8 +1141,8 @@
949
1141
  "desc": "reconstruct a pipeline from its rows"
950
1142
  },
951
1143
  "run": {
952
- "usage": "run <pipeline> -i '{json}'",
953
- "desc": "test-run a pipeline with input"
1144
+ "usage": "run <pipeline> [-i '{json}'] [--at scope | -S scope]",
1145
+ "desc": "test-run a pipeline; --at/-S deterministically selects the deepest ancestor route, while an unscoped run selects the globally deepest route and errors on a deepest tie"
954
1146
  },
955
1147
  "runs": {
956
1148
  "usage": "runs <launch_id>",
@@ -990,7 +1182,10 @@
990
1182
  " llm [--provider openai] [--model M] one model call (keys injected server-side)",
991
1183
  " [--system-from K] [--user-from ask] [-k/--keys a,b]",
992
1184
  " pipeline -P/--calls CODE|SCOPE/CODE run an EXISTING pipeline of this org as this step",
993
- " (a real child launch; -s '{\"pick\":[\u2026],\"input\":{\u2026}}' shapes its input)"
1185
+ " (a real child launch; -s '{\"pick\":[],\"input\":{}}' shapes its input)",
1186
+ " batch -s '{\"over\":\"items\",\"each\":{\"tier\":…,\"spec\":{…}},\"concurrency\":8}'",
1187
+ " ONE run fans a sub-worker over a list: bounded (default 8, max 32), one item's",
1188
+ " failure is its own {error,index}, the answer counts ok/failed — never loop ctx.run"
994
1189
  ]
995
1190
  },
996
1191
  "op": {
@@ -1026,7 +1221,9 @@
1026
1221
  },
1027
1222
  "backend": {
1028
1223
  "groups": [
1029
- "set"
1224
+ "set",
1225
+ "record",
1226
+ "schedule"
1030
1227
  ],
1031
1228
  "verbs": [
1032
1229
  "signin",
@@ -2,7 +2,7 @@
2
2
  // Node mirror of acsetra_cli/grammar_info.py: reads the INSTALLED manifest
3
3
  // (lib/grammar.json), renders one JSON payload with every operation's positional
4
4
  // order, the named argument a bare `--json` fills, `-b` bodies, flag coercion
5
- // types, and short aliases. grammar_version is the sha256 of the manifest bytes
5
+ // types, scope requirement, and short aliases. grammar_version is the sha256 of the manifest bytes
6
6
  // — the same hash the server's context pack reports.
7
7
  import crypto from "node:crypto";
8
8
  import fs from "node:fs";
@@ -33,6 +33,7 @@ function opEntry(op) {
33
33
  pos: [...(GRAMMAR.pos[op] || [])],
34
34
  json: op in GRAMMAR.primary_json ? GRAMMAR.primary_json[op] : null,
35
35
  body: Boolean(doc.body),
36
+ scope: doc.scope || "optional",
36
37
  flags: Object.fromEntries(kw.map(k => [k, vtype(k)])),
37
38
  short: Object.fromEntries(kw.filter(k => k in rev)
38
39
  .map(k => ["-" + rev[k], "--" + k.replace(/_/g, "-")])),
package/lib/help.js CHANGED
@@ -54,7 +54,7 @@ export function renderOp(op) {
54
54
  if (prim) parts.push(`--json '{${prim}}'`);
55
55
  if (body) parts.push("-b <code|->");
56
56
  for (const k of kw) parts.push(usageFlag(k));
57
- parts.push("[-S scope]");
57
+ parts.push(doc.scope === "required" ? "-S <scope>" : "[-S scope]");
58
58
 
59
59
  const out = ["Usage:", " " + parts.join(" "), ""];
60
60
  if (doc.desc) out.push(doc.desc, "");
@@ -115,7 +115,7 @@ export function renderTop() {
115
115
  out.push("Authoring (planes — run `r <group> --help` for subcommands)",
116
116
  " r app create <name> ; r apps",
117
117
  " r auth <app> require home.acsetra.com sign-in to use an app",
118
- " r set | css | asset | behavior | component | head <subverb> …",
118
+ " r set | css | asset | behavior | component | head | schedule <subverb> …",
119
119
  " r pipe <code> -S <scope> -w c:l ; r w <code> <tier> …",
120
120
  " r ls | inspect <set> | read <code> | show <pipe>",
121
121
  " r run <pipe> -i '{…}' ; r runs <id> ; r doctor <app> ; r compile <app>",
package/lib/verbs.js CHANGED
@@ -45,7 +45,9 @@ function walk(rest) {
45
45
  if (tok === "--json") {
46
46
  const blob = rest[++i]; i++;
47
47
  const obj = blob.startsWith("@") ? JSON.parse(fs.readFileSync(blob.slice(1), "utf8")) : JSON.parse(blob);
48
- flags.__json = Object.assign(flags.__json || {}, obj); continue;
48
+ if (!Array.isArray(obj) && flags.__json && !Array.isArray(flags.__json)) Object.assign(flags.__json, obj);
49
+ else flags.__json = obj; // an object, or a LIST primary (patches / targets)
50
+ continue;
49
51
  }
50
52
  if (tok.startsWith("--") || (tok.startsWith("-") && tok.length === 2 && !/\d/.test(tok[1]))) {
51
53
  let name = tok.replace(/^-+/, "").replace(/-/g, "_");
@@ -75,7 +77,8 @@ function assemble(op, positionals, flags) {
75
77
  const blob = flags.__json; delete flags.__json;
76
78
  const prim = PRIMARY_JSON[op];
77
79
  if (prim && !(prim in args)) args[prim] = blob;
78
- else Object.assign(args, blob);
80
+ else if (!Array.isArray(blob)) Object.assign(args, blob);
81
+ else throw new VerbError(`\`${op}\` takes --json as an object of named args (a list is only the primary payload of ops like record.commit / record.transact)`);
79
82
  }
80
83
  delete flags.__body;
81
84
  for (const [k, v] of Object.entries(flags)) args[k] = coerce(k, v);
@@ -123,6 +126,12 @@ export function toOp(verb, rest) {
123
126
  const { positionals, flags } = walk(rest);
124
127
  const args = { pipeline: positionals[0] || flags.pipeline };
125
128
  if ("input" in flags) args.input = coerce("input", flags.input);
129
+ if (flags.scope !== undefined && flags.at !== undefined && flags.scope !== flags.at) {
130
+ throw new VerbError("run received different --at and -S/--scope values");
131
+ }
132
+ if (flags.scope !== undefined || flags.at !== undefined) {
133
+ args.scope = flags.scope !== undefined ? flags.scope : flags.at;
134
+ }
126
135
  return ["run", args];
127
136
  }
128
137
  if (verb === "runs") { const { positionals } = walk(rest); return ["runs", { launch: positionals[0] }]; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acsetra/runner",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
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",