@acsetra/runner 0.1.82 → 0.1.84

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
@@ -70,13 +70,43 @@ async function signin(rest) {
70
70
  return 1;
71
71
  }
72
72
 
73
+ function verTuple(v) { return String(v).split(".").map(x => parseInt(x, 10) || 0); }
74
+ function verBelow(a, b) { // is version a < version b (numeric, per segment)
75
+ const [ta, tb] = [verTuple(a), verTuple(b)];
76
+ for (let i = 0; i < Math.max(ta.length, tb.length); i++) {
77
+ const d = (ta[i] || 0) - (tb[i] || 0);
78
+ if (d) return d < 0;
79
+ }
80
+ return false;
81
+ }
82
+
83
+ // The version handshake: the pack carries the server docs' grammar hash + client
84
+ // version bounds. Matching grammar → silent; old-but-compatible → one concise
85
+ // note; below the minimum → strong warning before authoring. Never a hard fail.
86
+ async function warnGrammarDrift(pack) {
87
+ const g = await import("../lib/grammar_info.js");
88
+ const server = pack.grammar_version;
89
+ if (!server || server === g.grammarHash()) return;
90
+ const minimum = pack.minimum_cli_version;
91
+ const hint = pack.recommended_cli_version ? ` (recommended: ${pack.recommended_cli_version})` : "";
92
+ if (minimum && verBelow(g.CLI_VERSION, minimum)) {
93
+ console.error(`WARNING: this CLI (${g.CLI_VERSION}) is below the server's minimum (${minimum}) — its grammar is missing arguments the server accepts. Upgrade BEFORE authoring: npm i -g @acsetra/runner${hint}`);
94
+ } else {
95
+ console.error(`note: installed CLI grammar differs from the server's docs grammar — upgrade when convenient: npm i -g @acsetra/runner${hint}`);
96
+ }
97
+ }
98
+
73
99
  async function docsPull(rest) {
74
100
  const scope = opt(rest, "-S") || opt(rest, "--scope") || config.defaultScope();
75
101
  let pack;
76
- // runtime: "npm" picks the npx/node_modules-specific wire-in block in CLAUDE.md.
77
- const params = scope ? { scope, runtime: "npm" } : { runtime: "npm" };
102
+ // runtime: "npm" picks the npx/node_modules-specific wire-in block in CLAUDE.md;
103
+ // cli_version + grammar_version are the client half of the version handshake.
104
+ const g = await import("../lib/grammar_info.js");
105
+ const params = { runtime: "npm", cli_version: g.CLI_VERSION, grammar_version: g.grammarHash() };
106
+ if (scope) params.scope = scope;
78
107
  try { pack = await api.get("/api/v1/context-pack", params); }
79
108
  catch (e) { console.error("docs pull failed: " + e.message); return 1; }
109
+ await warnGrammarDrift(pack);
80
110
  let n = 0;
81
111
  for (const [rel, content] of Object.entries(pack.files)) {
82
112
  if (path.basename(rel).endsWith(".local.md")) continue;
@@ -160,7 +190,7 @@ async function main() {
160
190
  const argv = process.argv.slice(2);
161
191
  if (!argv.length || ["-h", "--help"].includes(argv[0])) { console.log(renderTop()); return 0; }
162
192
  const [cmd, ...rest] = argv;
163
- if (["version", "--version", "-V"].includes(cmd)) { console.log("runner (@acsetra/runner) 0.1.82"); return 0; }
193
+ if (["version", "--version", "-V"].includes(cmd)) { console.log("runner (@acsetra/runner) 0.1.84"); return 0; }
164
194
 
165
195
  // help — top-level, `help <topic>`, and per-verb `<cmd> … --help` (lexical, before toOp)
166
196
  if (cmd === "help") {
@@ -177,6 +207,13 @@ async function main() {
177
207
  if (cmd === "use") { if (!rest[0]) return fail("usage: runner use <org-slug> (home org for bare-leaf scopes; full <org>.<app> paths need no `use`)"); config.setProject({ workspace: rest[0] }); console.error("home org -> " + rest[0] + " (bare-leaf default only)"); return 0; }
178
208
  if (cmd === "dev") return dev(rest);
179
209
  if (cmd === "docs") { if (rest[0] === "pull") return docsPull(rest); return fail("usage: runner docs pull [-S scope]"); }
210
+ if (cmd === "grammar") {
211
+ // local machine-readable grammar dump — reads the installed manifest, no network
212
+ const g = await import("../lib/grammar_info.js");
213
+ const name = rest.find(a => !a.startsWith("-")) || null;
214
+ try { console.log(JSON.stringify(g.payload(name), null, 2)); return 0; }
215
+ catch (e) { return fail(e.message, 2); }
216
+ }
180
217
  if (cmd === "checkout" || cmd === "co") return checkout(rest);
181
218
 
182
219
  try {
package/lib/grammar.json CHANGED
@@ -886,7 +886,11 @@
886
886
  },
887
887
  "docs": {
888
888
  "usage": "docs pull [-S scope]",
889
- "desc": "refresh CLAUDE.md + .runner/docs/* (this app's context)"
889
+ "desc": "refresh CLAUDE.md + AGENTS.md + .runner/docs/* (this app's context)"
890
+ },
891
+ "grammar": {
892
+ "usage": "grammar [<op>] [--json]",
893
+ "desc": "dump the installed CLI grammar as JSON (local manifest; no network). With <op>, just that operation."
890
894
  },
891
895
  "dev": {
892
896
  "usage": "dev <app> [--no-browser]",
@@ -1000,5 +1004,58 @@
1000
1004
  "login": "signin",
1001
1005
  "signout": "logout",
1002
1006
  "co": "checkout"
1007
+ },
1008
+ "doc_sections": {
1009
+ "_comment": "Documentation coverage map for scripts/render_hosted_cli_docs.py: every group in `groups` and every verb in `verbs_doc` must be assigned to EXACTLY ONE hosted doc (design.md or backend.md). Tests reject duplicates and omissions.",
1010
+ "design": {
1011
+ "groups": [
1012
+ "component",
1013
+ "css",
1014
+ "asset",
1015
+ "behavior",
1016
+ "head"
1017
+ ],
1018
+ "verbs": [
1019
+ "dev",
1020
+ "open",
1021
+ "focus",
1022
+ "compile",
1023
+ "doctor",
1024
+ "verify"
1025
+ ]
1026
+ },
1027
+ "backend": {
1028
+ "groups": [
1029
+ "set"
1030
+ ],
1031
+ "verbs": [
1032
+ "signin",
1033
+ "whoami",
1034
+ "workspaces",
1035
+ "use",
1036
+ "logout",
1037
+ "usage",
1038
+ "tokens",
1039
+ "docs",
1040
+ "grammar",
1041
+ "notify",
1042
+ "tabs",
1043
+ "devices",
1044
+ "checkout",
1045
+ "app",
1046
+ "apps",
1047
+ "auth",
1048
+ "ls",
1049
+ "inspect",
1050
+ "read",
1051
+ "show",
1052
+ "run",
1053
+ "runs",
1054
+ "pipe",
1055
+ "pipes",
1056
+ "w",
1057
+ "op"
1058
+ ]
1059
+ }
1003
1060
  }
1004
1061
  }
@@ -0,0 +1,57 @@
1
+ // r grammar — the machine-readable grammar dump (local; no network).
2
+ // Node mirror of acsetra_cli/grammar_info.py: reads the INSTALLED manifest
3
+ // (lib/grammar.json), renders one JSON payload with every operation's positional
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
6
+ // — the same hash the server's context pack reports.
7
+ import crypto from "node:crypto";
8
+ import fs from "node:fs";
9
+ import { GRAMMAR, JSON_ARGS, INT_ARGS, FLOAT_ARGS, BOOL_ARGS } from "./verbs.js";
10
+
11
+ const GRAMMAR_BYTES = fs.readFileSync(new URL("./grammar.json", import.meta.url));
12
+ export const CLI_VERSION = JSON.parse(
13
+ fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
14
+
15
+ export function grammarHash() {
16
+ return "sha256:" + crypto.createHash("sha256").update(GRAMMAR_BYTES).digest("hex");
17
+ }
18
+
19
+ function vtype(name) {
20
+ if (BOOL_ARGS.has(name)) return "flag";
21
+ if (JSON_ARGS.has(name)) return "json";
22
+ if (INT_ARGS.has(name)) return "int";
23
+ if (FLOAT_ARGS.has(name)) return "float";
24
+ return "str";
25
+ }
26
+
27
+ function opEntry(op) {
28
+ const doc = GRAMMAR.ops_doc[op] || {};
29
+ const rev = Object.fromEntries(Object.entries(GRAMMAR.short).map(([s, l]) => [l, s]));
30
+ const kw = doc.kw || [];
31
+ return {
32
+ desc: doc.desc || "",
33
+ pos: [...(GRAMMAR.pos[op] || [])],
34
+ json: op in GRAMMAR.primary_json ? GRAMMAR.primary_json[op] : null,
35
+ body: Boolean(doc.body),
36
+ flags: Object.fromEntries(kw.map(k => [k, vtype(k)])),
37
+ short: Object.fromEntries(kw.filter(k => k in rev)
38
+ .map(k => ["-" + rev[k], "--" + k.replace(/_/g, "-")])),
39
+ };
40
+ }
41
+
42
+ export function payload(op = null) {
43
+ if (op) {
44
+ if (!(op in GRAMMAR.ops_doc)) throw new Error(`unknown op '${op}' — run \`r grammar\` for the full manifest`);
45
+ return { cli_version: CLI_VERSION, grammar_version: grammarHash(), operation: op, ...opEntry(op) };
46
+ }
47
+ return {
48
+ cli_version: CLI_VERSION,
49
+ grammar_version: grammarHash(),
50
+ groups: Object.fromEntries(Object.entries(GRAMMAR.groups)
51
+ .map(([g, subs]) => [g, subs.map(([sub]) => sub)])),
52
+ verbs: GRAMMAR.verbs_doc,
53
+ operations: Object.fromEntries(Object.keys(GRAMMAR.ops_doc).map(name => [name, opEntry(name)])),
54
+ group_aliases: GRAMMAR.group_aliases,
55
+ verb_aliases: GRAMMAR.verb_aliases,
56
+ };
57
+ }
package/lib/help.js CHANGED
@@ -105,7 +105,7 @@ export function renderTop() {
105
105
  out.push("Session",
106
106
  " r signin / whoami / workspaces / logout / usage / tokens",
107
107
  " r use <slug> optional home org for bare-leaf scopes (full paths need none)",
108
- " r docs pull refresh CLAUDE.md + .runner/docs/*",
108
+ " r docs pull refresh CLAUDE.md + AGENTS.md + .runner/docs/*",
109
109
  " r dev <app> open your app's hosted surface in the browser",
110
110
  " r open / focus <scope> paired Chrome scope navigation",
111
111
  " r notify <scope> native notification → scope tab",
@@ -125,6 +125,7 @@ export function renderTop() {
125
125
  " r <group> --help list a plane's subcommands",
126
126
  " r <group> <subverb> --help exact usage, positional order, the --json rule",
127
127
  " r <verb> --help e.g. `r w --help`, `r op --help`",
128
+ " r grammar [<op>] machine-readable grammar dump (local JSON)",
128
129
  "");
129
130
  out.push("Env: ACSETRA_API_BASE, ACSETRA_TOKEN, ACSETRA_WORKSPACE, ACSETRA_SCOPE");
130
131
  return out.join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acsetra/runner",
3
- "version": "0.1.82",
3
+ "version": "0.1.84",
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",