@anna-ai/cli 0.1.51 → 0.1.52

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.
@@ -6,7 +6,7 @@ import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
6
6
  import { readExecutasLock, writeExecutasLock } from "./bundled-executas-BRyQkI9u.js";
7
7
  import { loadAppManifest, loadExecutaManifest } from "./manifest-BCHiC184.js";
8
8
  import { uploadExecutaBinaries } from "./executa-upload-binaries-D0JWYOtK.js";
9
- import { resolveAppBySlugOrCache } from "./working-orchestration-BRU7FV-b.js";
9
+ import { resolveAppBySlugOrCache } from "./working-orchestration-BPMFemcH.js";
10
10
  import { resolve } from "node:path";
11
11
  import { existsSync } from "node:fs";
12
12
  import { bold, cyan, dim, green, yellow } from "kleur/colors";
@@ -10,10 +10,10 @@ import "./executas-CvQY_w2U.js";
10
10
  import "./executa-publish-BuakG1RK.js";
11
11
  import "./binary-upload-DVhQ0uY0.js";
12
12
  import "./executa-upload-binaries-D0JWYOtK.js";
13
- import "./dev-BB1oS-1o.js";
14
- import "./executa-install-n1oRAoYJ.js";
13
+ import "./dev-uagVfV9Y.js";
14
+ import "./executa-install-u0nXcyy7.js";
15
15
  import "./app-cache-Cr3tKbux.js";
16
- import "./working-orchestration-BRU7FV-b.js";
17
- import { runAppsCut } from "./apps-cut-L7T8b5RS.js";
16
+ import "./working-orchestration-BPMFemcH.js";
17
+ import { runAppsCut } from "./apps-cut-BLtUBokD.js";
18
18
 
19
19
  export { runAppsCut };
@@ -10,10 +10,10 @@ import "./executas-CvQY_w2U.js";
10
10
  import "./executa-publish-BuakG1RK.js";
11
11
  import "./binary-upload-DVhQ0uY0.js";
12
12
  import "./executa-upload-binaries-D0JWYOtK.js";
13
- import "./dev-BB1oS-1o.js";
14
- import "./executa-install-n1oRAoYJ.js";
13
+ import "./dev-uagVfV9Y.js";
14
+ import "./executa-install-u0nXcyy7.js";
15
15
  import "./app-cache-Cr3tKbux.js";
16
- import { resolveAppBySlugOrCache } from "./working-orchestration-BRU7FV-b.js";
16
+ import { resolveAppBySlugOrCache } from "./working-orchestration-BPMFemcH.js";
17
17
  import { dim, green, yellow } from "kleur/colors";
18
18
 
19
19
  //#region src/commands/apps-discard.ts
@@ -49,12 +49,17 @@ async function runAppsGrants(opts) {
49
49
  const executas = perms.executas ?? [];
50
50
  if (executas.length > 0) {
51
51
  console.log(bold(cyan("bundled executa grants")));
52
+ let sawStorageGrant = false;
52
53
  for (const ex of executas) {
53
54
  console.log(` ${String(ex.name ?? ex.executa_id)}:`);
54
55
  const sections = executaGrantSections(ex);
55
56
  if (Object.keys(sections).length === 0) console.log(dim(" (no grants configured)"));
56
- else for (const [k, v] of Object.entries(sections)) console.log(` ${k}: ${JSON.stringify(v)}`);
57
+ else for (const [k, v] of Object.entries(sections)) {
58
+ if (k === "storage_grant") sawStorageGrant = true;
59
+ console.log(` ${k}: ${JSON.stringify(v)}`);
60
+ }
57
61
  }
62
+ if (sawStorageGrant) console.log(dim(" storage scopes: user, tool (platform-fixed; scope=app is Host-API only)"));
58
63
  }
59
64
  const missing = perms.missing ?? [];
60
65
  if (missing.length > 0) {
@@ -10,13 +10,13 @@ import "./executas-CvQY_w2U.js";
10
10
  import "./executa-publish-BuakG1RK.js";
11
11
  import "./binary-upload-DVhQ0uY0.js";
12
12
  import "./executa-upload-binaries-D0JWYOtK.js";
13
- import "./dev-BB1oS-1o.js";
14
- import "./executa-install-n1oRAoYJ.js";
13
+ import "./dev-uagVfV9Y.js";
14
+ import "./executa-install-u0nXcyy7.js";
15
15
  import "./app-cache-Cr3tKbux.js";
16
- import "./working-orchestration-BRU7FV-b.js";
17
- import "./apps-cut-L7T8b5RS.js";
16
+ import "./working-orchestration-BPMFemcH.js";
17
+ import "./apps-cut-BLtUBokD.js";
18
18
  import "./listing-meta-rIni8tjd.js";
19
- import "./apps-push-d1C0eePp.js";
20
- import { runAppsPublish } from "./apps-publish-CGfzN7tI.js";
19
+ import "./apps-push-DZ1LHDJW.js";
20
+ import { runAppsPublish } from "./apps-publish-BzaoSyFn.js";
21
21
 
22
22
  export { runAppsPublish };
@@ -2,8 +2,8 @@ import { listVersions } from "./apps-BRBufSyX.js";
2
2
  import { resolveClient, withErrorHandling } from "./_lifecycle-shared-_9BzQ9Y2.js";
3
3
  import { loadAppManifest } from "./manifest-BCHiC184.js";
4
4
  import { bumpVersion } from "./executa-publish-BuakG1RK.js";
5
- import { runAppsCut } from "./apps-cut-L7T8b5RS.js";
6
- import { runAppsPush } from "./apps-push-d1C0eePp.js";
5
+ import { runAppsCut } from "./apps-cut-BLtUBokD.js";
6
+ import { runAppsPush } from "./apps-push-DZ1LHDJW.js";
7
7
  import { resolve } from "node:path";
8
8
  import { bold, cyan, dim, green, yellow } from "kleur/colors";
9
9
 
@@ -3,7 +3,7 @@ import { CliError } from "./client-sMzq2PWG.js";
3
3
  import { resolveClient, withErrorHandling } from "./_lifecycle-shared-_9BzQ9Y2.js";
4
4
  import { loadAppManifest } from "./manifest-BCHiC184.js";
5
5
  import { bumpVersion, bundleHash, canonicalize, manifestHash, rewriteVersion } from "./executa-publish-BuakG1RK.js";
6
- import { installLocalBundledShims, resolveAppIdentity, resolveBundledExecutas } from "./working-orchestration-BRU7FV-b.js";
6
+ import { installLocalBundledShims, resolveAppIdentity, resolveBundledExecutas } from "./working-orchestration-BPMFemcH.js";
7
7
  import { syncAppListingMeta } from "./listing-meta-rIni8tjd.js";
8
8
  import { join, relative, resolve, sep } from "node:path";
9
9
  import { readFileSync, readdirSync, statSync } from "node:fs";
@@ -10,11 +10,11 @@ import "./executas-CvQY_w2U.js";
10
10
  import "./executa-publish-BuakG1RK.js";
11
11
  import "./binary-upload-DVhQ0uY0.js";
12
12
  import "./executa-upload-binaries-D0JWYOtK.js";
13
- import "./dev-BB1oS-1o.js";
14
- import "./executa-install-n1oRAoYJ.js";
13
+ import "./dev-uagVfV9Y.js";
14
+ import "./executa-install-u0nXcyy7.js";
15
15
  import "./app-cache-Cr3tKbux.js";
16
- import "./working-orchestration-BRU7FV-b.js";
16
+ import "./working-orchestration-BPMFemcH.js";
17
17
  import "./listing-meta-rIni8tjd.js";
18
- import { runAppsPush } from "./apps-push-d1C0eePp.js";
18
+ import { runAppsPush } from "./apps-push-DZ1LHDJW.js";
19
19
 
20
20
  export { runAppsPush };
@@ -59,7 +59,7 @@ async function runAppsRelease(opts) {
59
59
  if (!target) {
60
60
  if (opts.allowCreate) {
61
61
  if (!opts.json) console.log(yellow(`version ${opts.version} not found remotely; running 'apps publish' first (--allow-create)…`));
62
- const { runAppsPublish } = await import("./apps-publish-CUJILNOt.js");
62
+ const { runAppsPublish } = await import("./apps-publish-AdXryRYo.js");
63
63
  const code = await runAppsPublish({
64
64
  cwd: opts.cwd,
65
65
  account: opts.account,
package/dist/cli.js CHANGED
@@ -111,18 +111,132 @@ function validateManifestSchema(bundle, manifest) {
111
111
  message: `${e.message ?? "invalid"}${e.params ? " " + JSON.stringify(e.params) : ""}`
112
112
  }));
113
113
  }
114
- const ANNA_CALL_RE = /\banna\.([a-z]+)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
114
+ /**
115
+ * Blank out string-literal and comment contents (same length, newlines
116
+ * kept, so offsets/line numbers stay exact). Template interpolation bodies
117
+ * (`${…}`) are code and are preserved. Regex literals are NOT recognised
118
+ * (the `/` division ambiguity): a quote delimiter inside one can at worst
119
+ * blank the rest of that line — quoted strings cannot span lines — which
120
+ * errs toward missing a call, never toward a false report.
121
+ */
122
+ function stripStringsAndComments(source) {
123
+ const out = source.split("");
124
+ const n = source.length;
125
+ const blank = (idx) => {
126
+ const ch = out[idx];
127
+ if (ch !== "\n" && ch !== "\r") out[idx] = " ";
128
+ };
129
+ const stack = [{
130
+ kind: "code",
131
+ braces: 0
132
+ }];
133
+ let i = 0;
134
+ while (i < n) {
135
+ const top = stack[stack.length - 1];
136
+ const c = source[i];
137
+ if (top.kind === "tpl") {
138
+ if (c === "\\") {
139
+ blank(i);
140
+ if (i + 1 < n) blank(i + 1);
141
+ i += 2;
142
+ continue;
143
+ }
144
+ if (c === "`") {
145
+ blank(i);
146
+ stack.pop();
147
+ i++;
148
+ continue;
149
+ }
150
+ if (c === "$" && source[i + 1] === "{") {
151
+ i += 2;
152
+ stack.push({
153
+ kind: "code",
154
+ braces: 0
155
+ });
156
+ continue;
157
+ }
158
+ blank(i);
159
+ i++;
160
+ continue;
161
+ }
162
+ const next = source[i + 1];
163
+ if (c === "/" && next === "/") {
164
+ while (i < n && source[i] !== "\n") {
165
+ blank(i);
166
+ i++;
167
+ }
168
+ continue;
169
+ }
170
+ if (c === "/" && next === "*") {
171
+ blank(i);
172
+ blank(i + 1);
173
+ i += 2;
174
+ while (i < n && !(source[i] === "*" && source[i + 1] === "/")) {
175
+ blank(i);
176
+ i++;
177
+ }
178
+ if (i < n) {
179
+ blank(i);
180
+ blank(i + 1);
181
+ i += 2;
182
+ }
183
+ continue;
184
+ }
185
+ if (c === "'" || c === "\"") {
186
+ blank(i);
187
+ i++;
188
+ while (i < n && source[i] !== c && source[i] !== "\n") {
189
+ if (source[i] === "\\") {
190
+ blank(i);
191
+ i++;
192
+ if (i < n) blank(i);
193
+ i++;
194
+ continue;
195
+ }
196
+ blank(i);
197
+ i++;
198
+ }
199
+ if (i < n && source[i] === c) {
200
+ blank(i);
201
+ i++;
202
+ }
203
+ continue;
204
+ }
205
+ if (c === "`") {
206
+ blank(i);
207
+ stack.push({ kind: "tpl" });
208
+ i++;
209
+ continue;
210
+ }
211
+ if (c === "{") {
212
+ top.braces++;
213
+ i++;
214
+ continue;
215
+ }
216
+ if (c === "}") {
217
+ if (top.braces > 0) top.braces--;
218
+ else if (stack.length > 1) stack.pop();
219
+ i++;
220
+ continue;
221
+ }
222
+ i++;
223
+ }
224
+ return out.join("");
225
+ }
226
+ const ANNA_CALL_RE = /\banna\s*\.\s*([a-z]+)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
115
227
  function scanHostApiCalls(files) {
116
228
  const out = [];
117
229
  for (const f of files) {
118
- const lines = f.content.split("\n");
119
- for (let i = 0; i < lines.length; i++) {
120
- const line = lines[i];
121
- ANNA_CALL_RE.lastIndex = 0;
122
- let m;
123
- while ((m = ANNA_CALL_RE.exec(line)) !== null) out.push({
230
+ const code = stripStringsAndComments(f.content);
231
+ ANNA_CALL_RE.lastIndex = 0;
232
+ let m;
233
+ let line = 1;
234
+ let cursor = 0;
235
+ while ((m = ANNA_CALL_RE.exec(code)) !== null) {
236
+ for (; cursor < m.index; cursor++) if (code.charCodeAt(cursor) === 10) line++;
237
+ out.push({
124
238
  file: f.path,
125
- line: i + 1,
239
+ line,
126
240
  ns: m[1],
127
241
  method: m[2]
128
242
  });
@@ -237,7 +351,17 @@ function validateUiSectionStatic(manifest) {
237
351
  const cspOverrides = ui.csp_overrides ?? {};
238
352
  const badDirectives = Object.keys(cspOverrides).filter((d) => !CSP_OVERRIDABLE_DIRECTIVES.has(d)).sort();
239
353
  if (badDirectives.length) errors.push(`csp_overrides 含不允许的 directive: ${JSON.stringify(badDirectives)}`);
240
- for (const d of ["script-src", "style-src"]) for (const v of cspOverrides[d] ?? []) if (v !== "'self'" && !v.startsWith("'sha256-") && !v.startsWith("'nonce-")) errors.push(`csp_overrides[${d}] 仅允许 'self' / 'sha256-...' / 'nonce-...': ${v}`);
354
+ for (const d of ["script-src", "style-src"]) {
355
+ const literals = d === "script-src" ? ["'self'", "'wasm-unsafe-eval'"] : ["'self'"];
356
+ for (const v of cspOverrides[d] ?? []) if (!literals.includes(v) && !v.startsWith("'sha256-") && !v.startsWith("'nonce-")) {
357
+ const allowed = [
358
+ ...literals,
359
+ "'sha256-...'",
360
+ "'nonce-...'"
361
+ ].join(" / ");
362
+ errors.push(`csp_overrides[${d}] 仅允许 ${allowed}: ${v}`);
363
+ }
364
+ }
241
365
  return errors;
242
366
  }
243
367
 
@@ -473,8 +597,8 @@ program.command("validate").description("Run schema + ACL checks on a manifest+b
473
597
  const code = printResult(result);
474
598
  process.exit(code);
475
599
  });
476
- program.command("dev").description("Run a local harness (in-process dispatcher + iframe + SSE relay)").option("--manifest <path>", "manifest.json path", "manifest.json").option("--bundle <dir>", "bundle directory (default: ./bundle)").option("--slug <slug>", "App slug (overrides manifest.slug/name)").option("--view <name>", "View name to open (default: manifest default)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (auto-detected if omitted; can also use $ANNA_NEXUS_ROOT)").option("--port <number>", "HTTP port", "5180").option("--user-id <id>", "Harness user_id", "1").option("--cwd <dir>", "Project root (default: cwd)").option("--no-watch", "Disable bundle file watcher (default: enabled)").option("--executa <spec>", "Explicit executa registration; repeatable. Spec: comma-separated key=value (dir=<path>[,tool_id=<id>][,type=python|node|go|binary][,command=\"<argv>\"]). When only `dir=` is given, the executa is auto-detected from executa.json / pyproject.toml / package.json / go.mod. Overrides directory auto-discovery under <manifest-dir>/executas/.", (val, prev) => prev ? [...prev, val] : [val]).option("--no-llm", "Disable LLM bridge (anna.llm/agent return llm_disabled)").option("--mock-llm <fixture>", "Serve canned LLM responses from a JSONL fixture").option("--llm-account <host>", "Saved account host to use (default: current)").option("--llm-app-slug <slug>", "Override the manifest slug used to register / look up the dev AnnaApp (default: manifest.slug)").option("--storage <mode>", "Storage backend: \"legacy\" (in-memory runtime_state, default) or \"aps\" (real nexus APS via /api/v1/storage/* — requires `anna-app login`).", "legacy").option("--web <mode>", "Web backend: \"local\" (keyless ddgs + stdlib fetcher, free, default) or \"real\" (platform web service via /api/v1/copilot/app/web/* — production provider chain + normal CU billing; requires `anna-app login`).", "local").action(async (opts) => {
477
- const { runDev, parseExecutaSpec } = await import("./dev-DAkNdyJ-.js");
600
+ program.command("dev").description("Run a local harness (in-process dispatcher + iframe + SSE relay)").option("--manifest <path>", "manifest.json path", "manifest.json").option("--bundle <dir>", "bundle directory (default: ./bundle)").option("--slug <slug>", "App slug (overrides manifest.slug/name)").option("--view <name>", "View name to open (default: manifest default)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (auto-detected if omitted; can also use $ANNA_NEXUS_ROOT)").option("--port <number>", "HTTP port", "5180").option("--user-id <id>", "Harness user_id", "1").option("--cwd <dir>", "Project root (default: cwd)").option("--no-watch", "Disable bundle file watcher (default: enabled)").option("--executa <spec>", "Explicit executa registration; repeatable. Spec: comma-separated key=value (dir=<path>[,tool_id=<id>][,type=python|node|go|binary][,command=\"<argv>\"]). When only `dir=` is given, the executa is auto-detected from executa.json / pyproject.toml / package.json / go.mod. Overrides directory auto-discovery under <manifest-dir>/executas/.", (val, prev) => prev ? [...prev, val] : [val]).option("--no-llm", "Disable LLM bridge (anna.llm/agent return llm_disabled)").option("--mock-llm <fixture>", "Serve canned LLM responses from a JSONL fixture").option("--llm-account <host>", "Saved account host to use (default: current)").option("--llm-app-slug <slug>", "Override the manifest slug used to register / look up the dev AnnaApp (default: manifest.slug)").option("--storage <mode>", "Storage backend: \"legacy\" (in-memory runtime_state, default) or \"aps\" (real nexus APS via /api/v1/storage/* — requires `anna-app login`).", "legacy").option("--no-strict-caps", "Downgrade the production-parity storage capability gate to a warning (by default, storage/* fails with -32021 when host_capabilities declares no aps.* entry — exactly like production).").option("--web <mode>", "Web backend: \"local\" (keyless ddgs + stdlib fetcher, free, default) or \"real\" (platform web service via /api/v1/copilot/app/web/* — production provider chain + normal CU billing; requires `anna-app login`).", "local").action(async (opts) => {
601
+ const { runDev, parseExecutaSpec } = await import("./dev-CQIZHt7u.js");
478
602
  const cwd = opts.cwd ?? process.cwd();
479
603
  let executas;
480
604
  if (opts.executa && opts.executa.length > 0) {
@@ -504,6 +628,7 @@ program.command("dev").description("Run a local harness (in-process dispatcher +
504
628
  llmAccount: opts.llmAccount,
505
629
  llmAppSlug: opts.llmAppSlug,
506
630
  storageMode: opts.storage,
631
+ strictCaps: opts.strictCaps !== false,
507
632
  webMode: opts.web
508
633
  });
509
634
  process.exit(code);
@@ -597,7 +722,7 @@ executa.command("register").description("Register a HARNESS AnnaApp(kind=executa
597
722
  process.exit(code);
598
723
  });
599
724
  executa.command("install").description("Install a local-dev shim for an Executa under its minted tool_id so the Agent can discover it via 'Rediscover Local' (for distribution_type: local). Resolves the tool_id from .anna/executa.json (written by `executa publish` / `apps push`) unless --tool-id is given.").option("--dir <path>", "Executa project dir (default: CWD)").option("--tool-id <id>", "Install the shim under this exact id (default: minted id from .anna/executa.json, else executa.json tool_id)").option("--bin-dir <path>", "Install dir for the shim (default: ~/.anna/executa/bin)").option("--force", "Overwrite an existing shim of the same name", false).option("--json", "Emit machine-readable JSON", false).action(async (opts) => {
600
- const { runExecutaInstall } = await import("./executa-install-DKMwLq-V.js");
725
+ const { runExecutaInstall } = await import("./executa-install-Dnerah9u.js");
601
726
  const code = await runExecutaInstall({
602
727
  dir: opts.dir,
603
728
  toolId: opts.toolId,
@@ -608,7 +733,7 @@ executa.command("install").description("Install a local-dev shim for an Executa
608
733
  process.exit(code);
609
734
  });
610
735
  executa.command("dev").description("Run one Executa plugin in isolation (REPL or one-shot describe/invoke)").option("--dir <path>", "Executa project dir (default: CWD)").option("--spec <spec>", "Override discovery: comma-separated key=value (tool_id=...,type=...,command=\"...\")").option("--describe", "Print MANIFEST and exit", false).option("--health", "Print health and exit", false).option("--invoke <tool>", "Invoke one tool and exit").option("--args <json>", "JSON object passed as tool arguments", "{}").option("--json", "One-shot: emit compact JSON (no banners)", false).option("--no-sampling", "Hard-disable sampling reverse RPC (returns sampling_disabled)").option("--mock-sampling <fixture>", "Serve canned sampling responses from a JSONL fixture (offline)").option("--sampling-unsupported-format", "Simulate a model without json_schema support — exercises responseFormat onUnsupported branches (-32010 / downgrade)").option("--app-slug <slug>", "Forward sampling to nexus on behalf of this dev AnnaApp slug").option("--sampling-account <host>", "Saved account host for nexus sampling (default: current)").option("--no-agent", "Hard-disable agent reverse RPC (returns agent_not_granted)").option("--mock-agent <fixture>", "Serve canned agent/* responses from a JSONL fixture (offline)").option("--agent-account <host>", "Saved account host for nexus agent (default: --sampling-account or current)").option("--storage <mode>", "Storage backend: off | memory | mock | real (default: memory)").option("--mock-storage <fixture>", "Serve canned storage/* + files/* responses from a JSONL fixture").option("--storage-account <host>", "Saved account host for nexus storage (default: --sampling-account or current)").option("--storage-scopes <list>", "Comma-separated scopes for real storage tokens (default: user,app,tool)").option("--no-image", "Hard-disable image reverse RPC (returns image_not_granted)").option("--mock-image <fixture>", "Serve canned image/generate + image/edit responses from a JSONL fixture").option("--image-account <host>", "Saved account host for nexus image (default: --sampling-account or current)").option("--no-upload", "Hard-disable host/uploadFile reverse RPC (returns upload_not_granted)").option("--mock-upload <fixture>", "Serve canned host/uploadFile responses from a JSONL fixture").option("--upload-account <host>", "Saved account host for nexus uploads (default: --sampling-account or current)").action(async (opts) => {
611
- const { runExecutaDev } = await import("./executa-dev-ONm9di1H.js");
736
+ const { runExecutaDev } = await import("./executa-dev-BeYhrj-n.js");
612
737
  const storageMode = opts.storage === void 0 ? void 0 : (() => {
613
738
  const m = opts.storage;
614
739
  if (m === "off" || m === "memory" || m === "mock" || m === "real") return m;
@@ -671,7 +796,7 @@ apps.command("publish").description("Publish an Anna App version (composite: pus
671
796
  console.error(`✗ --bump must be patch|minor|major (got: ${opts.bump})`);
672
797
  process.exit(2);
673
798
  }
674
- const { runAppsPublish } = await import("./apps-publish-CUJILNOt.js");
799
+ const { runAppsPublish } = await import("./apps-publish-AdXryRYo.js");
675
800
  process.exit(await runAppsPublish({
676
801
  cwd: opts.cwd,
677
802
  manifest: opts.manifest,
@@ -705,7 +830,7 @@ apps.command("push").description("Upsert the mutable working draft (manifest + b
705
830
  process.exit(2);
706
831
  }
707
832
  }
708
- const { runAppsPush } = await import("./apps-push-BcYNGSYx.js");
833
+ const { runAppsPush } = await import("./apps-push-WBfD0JyE.js");
709
834
  process.exit(await runAppsPush({
710
835
  cwd: opts.cwd,
711
836
  manifest: opts.manifest,
@@ -725,7 +850,7 @@ apps.command("push").description("Upsert the mutable working draft (manifest + b
725
850
  }));
726
851
  });
727
852
  apps.command("cut <version>").description("Snapshot the working draft into an immutable version (freeze deps)").option("--changelog <text>", "Override the working draft changelog for this version").option("--slug <slug>", "App slug (default: resolve from .anna/app.json cache)").option("--cwd <dir>", "Project root for identity cache (default: cwd)").option("--dry-run", "Resolve target but don't cut", false).option("--account <host>", "Saved account host (default: current)").option("--json", "Emit machine-readable JSON", false).action(async (version, opts) => {
728
- const { runAppsCut } = await import("./apps-cut-35wivw1Z.js");
853
+ const { runAppsCut } = await import("./apps-cut-eOVb1-0y.js");
729
854
  process.exit(await runAppsCut({
730
855
  version,
731
856
  changelog: opts.changelog,
@@ -737,7 +862,7 @@ apps.command("cut <version>").description("Snapshot the working draft into an im
737
862
  }));
738
863
  });
739
864
  apps.command("discard").description("Drop the mutable working draft (leaves cut versions intact)").option("--slug <slug>", "App slug (default: resolve from .anna/app.json cache)").option("--cwd <dir>", "Project root for identity cache (default: cwd)").option("--dry-run", "Resolve target but don't discard", false).option("--account <host>", "Saved account host (default: current)").option("--json", "Emit machine-readable JSON", false).action(async (opts) => {
740
- const { runAppsDiscard } = await import("./apps-discard-CKA_m7Yw.js");
865
+ const { runAppsDiscard } = await import("./apps-discard-7ERq2pmS.js");
741
866
  process.exit(await runAppsDiscard({
742
867
  slug: opts.slug,
743
868
  cwd: opts.cwd,
@@ -747,7 +872,7 @@ apps.command("discard").description("Drop the mutable working draft (leaves cut
747
872
  }));
748
873
  });
749
874
  apps.command("release <version>").description("Freeze & publish an existing remote version (go live)").option("--slug <slug>", "App slug (default: resolve from .anna/app.json cache)").option("--cwd <dir>", "Project root for identity cache (default: cwd)").option("--allow-create", "If the version isn't on the server, run 'apps publish' first, then release", false).option("--dry-run", "Resolve target + pre-flight but don't publish", false).option("--account <host>", "Saved account host (default: current)").option("--json", "Emit machine-readable JSON", false).action(async (version, opts) => {
750
- const { runAppsRelease } = await import("./apps-release-DxRHjEYJ.js");
875
+ const { runAppsRelease } = await import("./apps-release-DAV-DB_Q.js");
751
876
  process.exit(await runAppsRelease({
752
877
  version,
753
878
  slug: opts.slug,
@@ -795,7 +920,7 @@ apps.command("versions <slug>").description("List all server versions of one Ann
795
920
  }));
796
921
  });
797
922
  apps.command("grants <slug>").description("Show currently granted scopes/quota for one Anna App").option("--account <host>", "Saved account host (default: current)").option("--json", "Emit machine-readable JSON", false).action(async (slug, opts) => {
798
- const { runAppsGrants } = await import("./apps-grants-B5MpWhSO.js");
923
+ const { runAppsGrants } = await import("./apps-grants-DVoSb6-r.js");
799
924
  process.exit(await runAppsGrants({
800
925
  slug,
801
926
  account: opts.account,
@@ -1001,7 +1126,7 @@ program.command("publish").description("Auto-detect cwd and publish (apps publis
1001
1126
  console.error(`✗ --bump must be patch|minor|major (got: ${opts.bump})`);
1002
1127
  process.exit(2);
1003
1128
  }
1004
- const { runTopLevelPublish } = await import("./publish-CIaosgo4.js");
1129
+ const { runTopLevelPublish } = await import("./publish-4C5_TgyZ.js");
1005
1130
  process.exit(await runTopLevelPublish({
1006
1131
  cwd: opts.cwd,
1007
1132
  bump: opts.bump,
@@ -0,0 +1,4 @@
1
+ import "./nexus-root-BlPwOusj.js";
2
+ import { parseExecutaSpec, runDev } from "./dev-uagVfV9Y.js";
3
+
4
+ export { parseExecutaSpec, runDev };
@@ -62,7 +62,7 @@ async function runDev(opts) {
62
62
  process.env.ANNA_APP_RUNTIME_WEB_MODE = "real";
63
63
  }
64
64
  const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-nv-le8ff.js");
65
- const { HarnessServer } = await import("./server-poDjwty-.js");
65
+ const { HarnessServer } = await import("./server-CBx61J7s.js");
66
66
  const bridge = new PythonBridge({
67
67
  mode,
68
68
  matrixNexusRoot: matrixNexusRoot ?? void 0,
@@ -125,8 +125,10 @@ async function runDev(opts) {
125
125
  if (llm === null) return 2;
126
126
  llm.storageMode = storageMode;
127
127
  llm.webMode = webMode;
128
+ llm.appManifest = manifest;
129
+ llm.strictStorageCaps = opts.strictCaps !== false;
128
130
  console.log(` llm bridge ${dim(llm.mode === "off" ? "disabled (--no-llm)" : llm.mode === "mock" ? `mock (${opts.mockLlm})` : `real${opts.llmAccount ? ` [${opts.llmAccount}]` : ""} → app_slug=${llm.appSlug}`)}`);
129
- console.log(` storage backend ${dim(storageMode === "aps" ? "aps (real nexus APS via /api/v1/storage/*)" : "legacy (in-memory runtime_state)")}`);
131
+ console.log(` storage backend ${dim(storageMode === "aps" ? `aps (real nexus APS via /api/v1/storage/*) · caps gate ${opts.strictCaps !== false ? "strict (production parity)" : "warn-only (--no-strict-caps)"}` : "legacy (in-memory runtime_state)")}`);
130
132
  console.log(` web backend ${dim(webMode === "real" ? "real (platform web service via /api/v1/copilot/app/web/* — normal CU billing)" : "local (keyless ddgs + stdlib SSRF fetcher, unbilled)")}`);
131
133
  const server = new HarnessServer({
132
134
  slug,
@@ -1,5 +1,5 @@
1
1
  import "./nexus-root-BlPwOusj.js";
2
- import { parseExecutaSpec } from "./dev-BB1oS-1o.js";
2
+ import { parseExecutaSpec } from "./dev-uagVfV9Y.js";
3
3
  import { isAbsolute, resolve } from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
@@ -0,0 +1,7 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import "./nexus-root-BlPwOusj.js";
3
+ import "./executa-cache-BFoUtb4J.js";
4
+ import "./dev-uagVfV9Y.js";
5
+ import { runExecutaInstall } from "./executa-install-u0nXcyy7.js";
6
+
7
+ export { runExecutaInstall };
@@ -1,5 +1,5 @@
1
1
  import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
2
- import { parseExecutaSpec } from "./dev-BB1oS-1o.js";
2
+ import { parseExecutaSpec } from "./dev-uagVfV9Y.js";
3
3
  import { isAbsolute, join, resolve } from "node:path";
4
4
  import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
5
5
  import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
@@ -10,14 +10,14 @@ import "./executas-CvQY_w2U.js";
10
10
  import { runExecutaPublish } from "./executa-publish-BuakG1RK.js";
11
11
  import "./binary-upload-DVhQ0uY0.js";
12
12
  import "./executa-upload-binaries-D0JWYOtK.js";
13
- import "./dev-BB1oS-1o.js";
14
- import "./executa-install-n1oRAoYJ.js";
13
+ import "./dev-uagVfV9Y.js";
14
+ import "./executa-install-u0nXcyy7.js";
15
15
  import "./app-cache-Cr3tKbux.js";
16
- import "./working-orchestration-BRU7FV-b.js";
17
- import "./apps-cut-L7T8b5RS.js";
16
+ import "./working-orchestration-BPMFemcH.js";
17
+ import "./apps-cut-BLtUBokD.js";
18
18
  import "./listing-meta-rIni8tjd.js";
19
- import "./apps-push-d1C0eePp.js";
20
- import { runAppsPublish } from "./apps-publish-CGfzN7tI.js";
19
+ import "./apps-push-DZ1LHDJW.js";
20
+ import { runAppsPublish } from "./apps-publish-BzaoSyFn.js";
21
21
  import { resolve } from "node:path";
22
22
  import { existsSync } from "node:fs";
23
23
  import { red, yellow } from "kleur/colors";
@@ -9,6 +9,71 @@ import { createServer } from "node:http";
9
9
  import { WebSocketServer } from "ws";
10
10
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
11
11
 
12
+ //#region src/executa/storage-caps.ts
13
+ /**
14
+ * APS storage declaration predicates — TS mirror of the canonical Python
15
+ * module `matrix-nexus/packages/anna-app-core/src/anna_app_core/storage_decl.py`
16
+ * (design aps-authorization-simplification.md §4.3).
17
+ *
18
+ * ⚠️ The constant tables below are diffed against the Python source by
19
+ * `scripts/check-storage-caps-sync.mjs` in CI — change BOTH sides together.
20
+ */
21
+ /** Plugin-side storage_token mint trigger set (bare "aps" = historic umbrella). */
22
+ const PLUGIN_STORAGE_CAPS = [
23
+ "aps.kv",
24
+ "aps.files",
25
+ "aps"
26
+ ];
27
+ const APS_SCOPE_PREFIX = "aps.scope.";
28
+ const STORAGE_SCOPES = new Set([
29
+ "user",
30
+ "app",
31
+ "tool"
32
+ ]);
33
+ const STORAGE_MODES = new Set([
34
+ "r",
35
+ "w",
36
+ "rw"
37
+ ]);
38
+ /** Whether a `host_capabilities` set triggers plugin-side storage_token
39
+ * minting. Wide convergence: `aps.scope.*` counts the same as
40
+ * `aps.kv`/`aps.files` (forum #284). */
41
+ function declaresPluginStorage(caps) {
42
+ for (const c of caps) {
43
+ if (PLUGIN_STORAGE_CAPS.includes(c)) return true;
44
+ if (c.startsWith(APS_SCOPE_PREFIX)) return true;
45
+ }
46
+ return false;
47
+ }
48
+ /** Extract `host_capabilities` from a manifest-like object (list or
49
+ * `{cap: bool}` dict form). */
50
+ function extractHostCapabilities(manifest) {
51
+ const raw = manifest?.host_capabilities ?? manifest?.hostCapabilities;
52
+ if (Array.isArray(raw)) return raw.map(String);
53
+ if (raw && typeof raw === "object") return Object.entries(raw).filter(([, v]) => !!v).map(([k]) => k);
54
+ return [];
55
+ }
56
+ /** Whether a full manifest declares plugin storage via EITHER form: legacy
57
+ * aps.* capability strings or the structured schema-3 `storage` entry. */
58
+ function manifestDeclaresPluginStorage(manifest) {
59
+ if (!manifest) return false;
60
+ if (declaresPluginStorage(extractHostCapabilities(manifest))) return true;
61
+ const storage = manifest.storage;
62
+ if (storage && typeof storage === "object" && !Array.isArray(storage)) {
63
+ const s = storage;
64
+ if (s.kv || s.files) return true;
65
+ if (s.scopes && typeof s.scopes === "object") {
66
+ for (const [scope, mode] of Object.entries(s.scopes)) if (STORAGE_SCOPES.has(scope) && STORAGE_MODES.has(String(mode))) return true;
67
+ }
68
+ }
69
+ return false;
70
+ }
71
+ /** Production-parity error body for an unminted storage_token (matrix host
72
+ * `src/executa/storage.py` wording, verbatim) + a local-only fix hint. */
73
+ const STORAGE_TOKEN_MISSING_ERROR = "-32021: storage_token missing — host did not authorize storage for this invoke";
74
+ const STORAGE_CAPS_HINT = "hint: declare \"aps.kv\" (KV) / \"aps.files\" (Files) in host_capabilities; the platform will not mint a plugin storage_token without them (docs: executa-storage)";
75
+
76
+ //#endregion
12
77
  //#region src/harness/llm-bridge.ts
13
78
  /** APS-mode ``storage`` methods the bridge forwards to ``/api/v1/storage/*``.
14
79
  * ``get/set/delete/list`` are the KV family; ``files_*`` are the object/blob
@@ -117,6 +182,30 @@ var LlmBridge = class {
117
182
  mintedStorage = null;
118
183
  mocks = [];
119
184
  streamCounter = 0;
185
+ warnedStorageCaps = false;
186
+ /** Production-parity storage caps gate (aps-authorization-simplification
187
+ * §4.3): in prod, no `storage_token` is minted unless the manifest
188
+ * declares storage — the invoke fails with `-32021` BEFORE scope
189
+ * evaluation. Enforced for non-self scopes (`user`/`tool`) and the
190
+ * executa reverse-RPC `files_*` family; iframe self app-scope needs no
191
+ * capability declaration in prod either. */
192
+ assertStorageCaps(method, a) {
193
+ const manifest = this.opts.appManifest;
194
+ if (!manifest) return;
195
+ const scope = String(a?.scope ?? "app");
196
+ const reverseRpcFiles = method.startsWith("files_");
197
+ if (scope === "app" && !reverseRpcFiles) return;
198
+ if (manifestDeclaresPluginStorage(manifest)) return;
199
+ if (this.opts.strictStorageCaps === false) {
200
+ if (!this.warnedStorageCaps) {
201
+ this.warnedStorageCaps = true;
202
+ console.warn(`\n⚠ [storage-caps] ${STORAGE_TOKEN_MISSING_ERROR}\n⚠ [storage-caps] ${STORAGE_CAPS_HINT}\n⚠ [storage-caps] proceeding anyway (--no-strict-caps) — this WILL fail in production
203
+ `);
204
+ }
205
+ return;
206
+ }
207
+ throw new Error(`${STORAGE_TOKEN_MISSING_ERROR}\n${STORAGE_CAPS_HINT}`);
208
+ }
120
209
  constructor(opts) {
121
210
  this.opts = opts;
122
211
  if (opts.mode === "mock" && opts.mockFile) {
@@ -740,6 +829,7 @@ var LlmBridge = class {
740
829
  };
741
830
  }
742
831
  if (args.ns === "storage") {
832
+ this.assertStorageCaps(args.method, args.args);
743
833
  const sm = await this.mintStorage();
744
834
  const a = args.args;
745
835
  const base = `${canonicalHost(acc.host)}/api/v1/storage`;
@@ -4,7 +4,7 @@ import { CliError } from "./client-sMzq2PWG.js";
4
4
  import { parseExecutaIdOverrides, readExecutasLock, substituteBundledRefs, validateBundledHandles, writeBundleToolIdSidecar, writeExecutasLock } from "./bundled-executas-BRyQkI9u.js";
5
5
  import { loadExecutaManifest } from "./manifest-BCHiC184.js";
6
6
  import { manifestSyncNote, runExecutaPublish } from "./executa-publish-BuakG1RK.js";
7
- import { runExecutaInstall } from "./executa-install-n1oRAoYJ.js";
7
+ import { runExecutaInstall } from "./executa-install-u0nXcyy7.js";
8
8
  import { appCacheMatches, readAppIdentity, writeAppIdentity } from "./app-cache-Cr3tKbux.js";
9
9
  import { join, resolve } from "node:path";
10
10
  import { dim, green, yellow } from "kleur/colors";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anna-ai/cli",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "description": "Anna App developer CLI: scaffold, validate, harness (Phase 2 MVP: init + validate).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,7 +30,8 @@
30
30
  "lint": "tsc --noEmit",
31
31
  "check:runtime-pin": "node scripts/check-runtime-pin.mjs",
32
32
  "check:sdk-pin": "node scripts/check-sdk-pin.mjs",
33
- "prepublishOnly": "pnpm lint && pnpm test && pnpm build && node scripts/check-runtime-pin.mjs && node scripts/check-sdk-pin.mjs"
33
+ "check:storage-caps": "node scripts/check-storage-caps-sync.mjs",
34
+ "prepublishOnly": "pnpm lint && pnpm test && pnpm build && node scripts/check-runtime-pin.mjs && node scripts/check-sdk-pin.mjs && node scripts/check-storage-caps-sync.mjs"
34
35
  },
35
36
  "dependencies": {
36
37
  "@anna-ai/app-runtime": "^0.16.0",
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "schema": 2,
3
- "permissions": ["tools.invoke", "storage.read", "storage.write"],
4
3
  "required_executas": [
5
4
  {
6
5
  "tool_id": "__TOOL_ID__"
@@ -1,4 +0,0 @@
1
- import "./nexus-root-BlPwOusj.js";
2
- import { parseExecutaSpec, runDev } from "./dev-BB1oS-1o.js";
3
-
4
- export { parseExecutaSpec, runDev };
@@ -1,7 +0,0 @@
1
- import "./credentials-BTv2IfUZ.js";
2
- import "./nexus-root-BlPwOusj.js";
3
- import "./executa-cache-BFoUtb4J.js";
4
- import "./dev-BB1oS-1o.js";
5
- import { runExecutaInstall } from "./executa-install-n1oRAoYJ.js";
6
-
7
- export { runExecutaInstall };