@acsetra/runner 0.1.82 → 0.1.83
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 +40 -3
- package/lib/grammar.json +58 -1
- package/lib/grammar_info.js +57 -0
- package/lib/help.js +2 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
193
|
+
if (["version", "--version", "-V"].includes(cmd)) { console.log("runner (@acsetra/runner) 0.1.83"); 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.
|
|
3
|
+
"version": "0.1.83",
|
|
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",
|