@anna-ai/cli 0.1.14 → 0.1.17

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.
@@ -1,5 +1,5 @@
1
1
  import { getAccount } from "./credentials-BTv2IfUZ.js";
2
- import { listDevApps } from "./dev-app-cache-C3D1Sp_V.js";
2
+ import { listDevApps } from "./dev-app-cache-3Pfesngr.js";
3
3
  import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
4
4
 
5
5
  //#region src/commands/apps.ts
@@ -0,0 +1,3 @@
1
+ import { BridgeRequestError, PINNED_RUNTIME_VERSION, PythonBridge } from "./bridge-Dffh9JUd.js";
2
+
3
+ export { PINNED_RUNTIME_VERSION, PythonBridge };
@@ -9,17 +9,39 @@ import { createInterface } from "node:readline";
9
9
  * `uvx <pkg>@<version>` so end users always run the dispatcher version
10
10
  * the CLI was tested against.
11
11
  */
12
- const PINNED_RUNTIME_VERSION = "0.2.0a2";
12
+ const PINNED_RUNTIME_VERSION = "0.2.0a4";
13
+ /**
14
+ * Throwable from a {@link RequestHandler} to send a structured JSON-RPC
15
+ * error back to the python bridge with a stable string ``code`` (e.g.
16
+ * ``"llm_disabled"``, ``"provider_error"``). The python side decodes the
17
+ * code from ``error.data.errorCode``.
18
+ */
19
+ var BridgeRequestError = class extends Error {
20
+ constructor(code, message, details) {
21
+ super(message);
22
+ this.code = code;
23
+ this.details = details;
24
+ }
25
+ };
13
26
  var PythonBridge = class {
14
27
  proc = null;
15
28
  nextId = 1;
16
29
  pending = new Map();
30
+ requestHandlers = new Map();
17
31
  readyPromise = null;
18
32
  closed = false;
19
33
  constructor(opts) {
20
34
  this.opts = opts;
21
35
  }
22
36
  /**
37
+ * Register a handler for an outbound python→node RPC method (e.g.
38
+ * ``host.llm.complete``). Subsequent registrations for the same method
39
+ * replace the previous handler.
40
+ */
41
+ onRequest(method, handler) {
42
+ this.requestHandlers.set(method, handler);
43
+ }
44
+ /**
23
45
  * Build the default launch command from `opts.mode`. uvx by default; for
24
46
  * `nexus-source` mode, `matrixNexusRoot` must be set.
25
47
  */
@@ -93,6 +115,10 @@ var PythonBridge = class {
93
115
  resolve$1();
94
116
  return;
95
117
  }
118
+ if (typeof env$1.method === "string") {
119
+ this.handleIncomingRequest(env$1);
120
+ return;
121
+ }
96
122
  this.handleResponse(env$1);
97
123
  });
98
124
  this.proc.on("error", (e) => reject(e));
@@ -102,6 +128,58 @@ var PythonBridge = class {
102
128
  });
103
129
  await this.readyPromise;
104
130
  }
131
+ async handleIncomingRequest(env) {
132
+ const method = env.method;
133
+ const id = env.id;
134
+ if (id == null) return;
135
+ const params = env.params ?? {};
136
+ const handler = this.requestHandlers.get(method);
137
+ if (!handler) {
138
+ this.writeFrame({
139
+ jsonrpc: "2.0",
140
+ id,
141
+ error: {
142
+ code: -32601,
143
+ message: `method not found: ${method}`
144
+ }
145
+ });
146
+ return;
147
+ }
148
+ try {
149
+ const result = await handler(params);
150
+ this.writeFrame({
151
+ jsonrpc: "2.0",
152
+ id,
153
+ result: result ?? {}
154
+ });
155
+ } catch (e) {
156
+ if (e instanceof BridgeRequestError) this.writeFrame({
157
+ jsonrpc: "2.0",
158
+ id,
159
+ error: {
160
+ code: -32e3,
161
+ message: e.message,
162
+ data: {
163
+ errorCode: e.code,
164
+ ...e.details ?? {}
165
+ }
166
+ }
167
+ });
168
+ else this.writeFrame({
169
+ jsonrpc: "2.0",
170
+ id,
171
+ error: {
172
+ code: -32603,
173
+ message: e.message,
174
+ data: { errorCode: "internal_error" }
175
+ }
176
+ });
177
+ }
178
+ }
179
+ writeFrame(env) {
180
+ if (!this.proc) return;
181
+ this.proc.stdin.write(`${JSON.stringify(env)}\n`);
182
+ }
105
183
  handleResponse(env) {
106
184
  const id = env.id;
107
185
  if (id == null) return;
@@ -143,4 +221,4 @@ var PythonBridge = class {
143
221
  };
144
222
 
145
223
  //#endregion
146
- export { PINNED_RUNTIME_VERSION, PythonBridge };
224
+ export { BridgeRequestError, PINNED_RUNTIME_VERSION, PythonBridge };
package/dist/cli.js CHANGED
@@ -450,8 +450,8 @@ program.command("validate").description("Run schema + ACL checks on a manifest+b
450
450
  const code = printResult(result);
451
451
  process.exit(code);
452
452
  });
453
- 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)").action(async (opts) => {
454
- const { runDev, parseExecutaSpec } = await import("./dev-C81H9c9_.js");
453
+ 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").action(async (opts) => {
454
+ const { runDev, parseExecutaSpec } = await import("./dev-Bi6rkb1x.js");
455
455
  const cwd = opts.cwd ?? process.cwd();
456
456
  let executas;
457
457
  if (opts.executa && opts.executa.length > 0) {
@@ -479,7 +479,8 @@ program.command("dev").description("Run a local harness (in-process dispatcher +
479
479
  noLlm: opts.llm === false,
480
480
  mockLlm: opts.mockLlm,
481
481
  llmAccount: opts.llmAccount,
482
- llmAppSlug: opts.llmAppSlug
482
+ llmAppSlug: opts.llmAppSlug,
483
+ storageMode: opts.storage
483
484
  });
484
485
  process.exit(code);
485
486
  });
@@ -509,7 +510,7 @@ fixture.command("replay <file>").description("Dry-run replay of a harness record
509
510
  process.exit(code);
510
511
  });
511
512
  program.command("doctor").description("Check environment for `anna-app dev` (uv, matrix-nexus, dev key)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (optional)").action(async (opts) => {
512
- const { runDoctor } = await import("./doctor-B3u0edUg.js");
513
+ const { runDoctor } = await import("./doctor-Dxkx0eqv.js");
513
514
  const code = await runDoctor({ matrixNexusRoot: opts.matrixNexusRoot });
514
515
  process.exit(code);
515
516
  });
@@ -535,7 +536,7 @@ program.command("whoami").description("Show the current account (and any others)
535
536
  process.exit(code);
536
537
  });
537
538
  program.command("apps:list").description("List dev apps installed for the current PAT").option("--account <host>", "Saved account host (default: current)").option("--json", "Emit machine-readable JSON", false).action(async (opts) => {
538
- const { runAppsList } = await import("./apps-BEJUn9Ws.js");
539
+ const { runAppsList } = await import("./apps-ClgEOdKD.js");
539
540
  const code = await runAppsList({
540
541
  account: opts.account,
541
542
  json: opts.json
@@ -569,8 +570,8 @@ executa.command("register").description("Register a standalone executa with nexu
569
570
  const code = await runExecutaRegister(opts);
570
571
  process.exit(code);
571
572
  });
572
- 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("--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)").action(async (opts) => {
573
- const { runExecutaDev } = await import("./executa-dev-BhouP8jh.js");
573
+ 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("--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) => {
574
+ const { runExecutaDev } = await import("./executa-dev-BzhSd_A2.js");
574
575
  const storageMode = opts.storage === void 0 ? void 0 : (() => {
575
576
  const m = opts.storage;
576
577
  if (m === "off" || m === "memory" || m === "mock" || m === "real") return m;
@@ -595,7 +596,13 @@ executa.command("dev").description("Run one Executa plugin in isolation (REPL or
595
596
  storage: storageMode,
596
597
  mockStorage: opts.mockStorage,
597
598
  storageAccount: opts.storageAccount,
598
- storageScopes: opts.storageScopes
599
+ storageScopes: opts.storageScopes,
600
+ noImage: opts.image === false,
601
+ mockImage: opts.mockImage,
602
+ imageAccount: opts.imageAccount,
603
+ noUpload: opts.upload === false,
604
+ mockUpload: opts.mockUpload,
605
+ uploadAccount: opts.uploadAccount
599
606
  });
600
607
  process.exit(code);
601
608
  });
@@ -31,8 +31,20 @@ async function runDev(opts) {
31
31
  }
32
32
  const matrixNexusRoot = await resolveMatrixNexusRoot(opts.matrixNexusRoot, cwd);
33
33
  const mode = matrixNexusRoot ? "nexus-source" : "uvx";
34
- const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-C0DWb5eQ.js");
35
- const { HarnessServer } = await import("./server-q6nKCeEV.js");
34
+ const storageMode = opts.storageMode === "aps" ? "aps" : "legacy";
35
+ if (opts.storageMode && opts.storageMode !== "legacy" && opts.storageMode !== "aps") {
36
+ console.error(red(`✗ --storage must be "legacy" or "aps", got "${opts.storageMode}"`));
37
+ return 2;
38
+ }
39
+ if (storageMode === "aps") {
40
+ if (opts.noLlm || opts.mockLlm) {
41
+ console.error(red("✗ --storage aps requires a real LLM bridge (PAT on disk); drop --no-llm / --mock-llm or switch back to --storage legacy."));
42
+ return 2;
43
+ }
44
+ process.env.ANNA_APP_RUNTIME_STORAGE_MODE = "aps";
45
+ }
46
+ const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-B1vq1oG3.js");
47
+ const { HarnessServer } = await import("./server-6WHNkydc.js");
36
48
  const bridge = new PythonBridge({
37
49
  mode,
38
50
  matrixNexusRoot: matrixNexusRoot ?? void 0,
@@ -75,7 +87,9 @@ async function runDev(opts) {
75
87
  manifest
76
88
  });
77
89
  if (llm === null) return 2;
90
+ llm.storageMode = storageMode;
78
91
  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}`)}`);
92
+ console.log(` storage backend ${dim(storageMode === "aps" ? "aps (real nexus APS via /api/v1/storage/*)" : "legacy (in-memory runtime_state)")}`);
79
93
  const server = new HarnessServer({
80
94
  slug,
81
95
  manifest,
@@ -123,7 +137,7 @@ async function runDev(opts) {
123
137
  */
124
138
  async function resolveRealLlm(args) {
125
139
  const { getAccount } = await import("./credentials-DDqx6XMQ.js");
126
- const { ensureDevAppRegistered } = await import("./dev-app-cache-CZ8lIKiw.js");
140
+ const { ensureDevAppRegistered } = await import("./dev-app-cache-CZ1UjMz0.js");
127
141
  const acc = getAccount(args.account);
128
142
  if (!acc) {
129
143
  console.error(red("✗ no developer PAT on disk — run `anna-app login --host <nexus-url>` first.\n (or use `--no-llm` / `--mock-llm <fixture>` to develop offline.)"));
@@ -134,22 +148,35 @@ async function resolveRealLlm(args) {
134
148
  return null;
135
149
  }
136
150
  const manifest = args.manifest;
151
+ const registerInput = {
152
+ slug: args.appSlug,
153
+ name: manifest.name,
154
+ category: manifest.category,
155
+ tagline: manifest.tagline
156
+ };
137
157
  try {
138
158
  const entry = await ensureDevAppRegistered({
139
159
  cwd: args.cwd,
140
160
  host: acc.host,
141
161
  pat: acc.pat,
142
- input: {
143
- slug: args.appSlug,
144
- name: manifest.name,
145
- category: manifest.category,
146
- tagline: manifest.tagline
147
- }
162
+ input: registerInput
148
163
  });
149
164
  return {
150
165
  mode: "real",
151
166
  account: args.account,
152
- appSlug: entry.slug
167
+ appSlug: entry.slug,
168
+ onAppSlugNotFound: async () => {
169
+ const { invalidateDevAppCache } = await import("./dev-app-cache-CZ1UjMz0.js");
170
+ invalidateDevAppCache(args.cwd);
171
+ const fresh = getAccount(args.account);
172
+ if (!fresh) throw new Error("PAT not found while re-registering dev app — run `anna-app login` again");
173
+ await ensureDevAppRegistered({
174
+ cwd: args.cwd,
175
+ host: fresh.host,
176
+ pat: fresh.pat,
177
+ input: registerInput
178
+ });
179
+ }
153
180
  };
154
181
  } catch (e) {
155
182
  console.error(red(`✗ failed to register dev app on nexus: ${e.message}`));
@@ -0,0 +1,3 @@
1
+ import { parseExecutaSpec, runDev } from "./dev-BUetXnfG.js";
2
+
3
+ export { parseExecutaSpec, runDev };
@@ -0,0 +1,189 @@
1
+ import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
4
+
5
+ //#region src/dev-app-cache.ts
6
+ const CACHE_DIR = ".anna";
7
+ const CACHE_FILE = "dev-app.json";
8
+ function cachePath(cwd) {
9
+ return resolve(cwd, CACHE_DIR, CACHE_FILE);
10
+ }
11
+ function readDevAppCache(cwd) {
12
+ const p = cachePath(cwd);
13
+ if (!existsSync(p)) return null;
14
+ try {
15
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
16
+ if (typeof raw.host === "string" && typeof raw.slug === "string" && typeof raw.app_id === "number") return {
17
+ host: raw.host,
18
+ slug: raw.slug,
19
+ app_id: raw.app_id,
20
+ name: raw.name ?? raw.slug,
21
+ registered_at: raw.registered_at ?? ""
22
+ };
23
+ } catch {}
24
+ return null;
25
+ }
26
+ function writeDevAppCache(cwd, entry) {
27
+ const p = cachePath(cwd);
28
+ mkdirSync(dirname(p), { recursive: true });
29
+ writeFileSync(p, JSON.stringify(entry, null, 2) + "\n", "utf-8");
30
+ }
31
+ /** Delete the local dev-app cache file.
32
+ *
33
+ * Used by the LLM bridge's recovery flow: when `/dev/session/mint`
34
+ * returns 404 with "app slug ... not found", the cache is stale (the
35
+ * nexus DB no longer has the row this cache asserts is registered).
36
+ * Invalidating forces the next `ensureDevAppRegistered` call to
37
+ * re-POST `/dev/apps/register`. Idempotent: a missing file is a no-op.
38
+ */
39
+ function invalidateDevAppCache(cwd) {
40
+ const p = cachePath(cwd);
41
+ if (existsSync(p)) try {
42
+ unlinkSync(p);
43
+ } catch {}
44
+ }
45
+ /** Call POST /api/v1/anna-apps/dev/apps/register. Idempotent server-side. */
46
+ async function registerDevApp(args) {
47
+ const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps/register`;
48
+ const res = await fetch(url, {
49
+ method: "POST",
50
+ headers: { "content-type": "application/json" },
51
+ body: JSON.stringify({
52
+ pat: args.pat,
53
+ slug: args.input.slug,
54
+ name: args.input.name,
55
+ category: args.input.category,
56
+ tagline: args.input.tagline
57
+ })
58
+ });
59
+ if (!res.ok) {
60
+ const text = await res.text().catch(() => "");
61
+ throw new Error(`/dev/apps/register failed: HTTP ${res.status} ${text}`);
62
+ }
63
+ return await res.json();
64
+ }
65
+ /** Call GET /api/v1/anna-apps/dev/apps. */
66
+ async function listDevApps(args) {
67
+ const url = new URL(`${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps`);
68
+ url.searchParams.set("pat", args.pat);
69
+ const res = await fetch(url, { method: "GET" });
70
+ if (!res.ok) {
71
+ const text = await res.text().catch(() => "");
72
+ throw new Error(`/dev/apps failed: HTTP ${res.status} ${text}`);
73
+ }
74
+ const body = await res.json();
75
+ return body.apps;
76
+ }
77
+ /**
78
+ * Convenience helper for `anna-app dev`: returns a valid cached entry if
79
+ * it still matches the manifest slug, otherwise hits the server to
80
+ * (re-)register. Throws if no PAT is available on disk.
81
+ */
82
+ async function ensureDevAppRegistered(args) {
83
+ const canonical = canonicalHost(args.host);
84
+ const cached = readDevAppCache(args.cwd);
85
+ if (cached && cached.host === canonical && cached.slug === args.input.slug) return cached;
86
+ const r = await registerDevApp({
87
+ host: args.host,
88
+ pat: args.pat,
89
+ input: args.input
90
+ });
91
+ const entry = {
92
+ host: canonical,
93
+ slug: r.slug,
94
+ app_id: r.app_id,
95
+ name: r.name,
96
+ registered_at: new Date().toISOString()
97
+ };
98
+ writeDevAppCache(args.cwd, entry);
99
+ return entry;
100
+ }
101
+ const _internal = {
102
+ cachePath,
103
+ CACHE_DIR: join(CACHE_DIR, CACHE_FILE)
104
+ };
105
+ const EXECUTA_CACHE_FILE = "dev-executa.json";
106
+ /**
107
+ * Bump this whenever the server-side dev-executa grant shape changes in
108
+ * a way that requires a re-register to pick up. Cached entries written
109
+ * with a lower `cache_version` are treated as stale and re-POSTed.
110
+ *
111
+ * 1 — initial (llm_grant only)
112
+ * 2 — adds image_grant + upload_grant; fixes `allowedPurposes` to
113
+ * protocol whitelist {image_input,image_reference,user_artifact}
114
+ */
115
+ const EXECUTA_CACHE_VERSION = 2;
116
+ function executaCachePath(cwd) {
117
+ return resolve(cwd, CACHE_DIR, EXECUTA_CACHE_FILE);
118
+ }
119
+ function readDevExecutaCache(cwd) {
120
+ const p = executaCachePath(cwd);
121
+ if (!existsSync(p)) return null;
122
+ try {
123
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
124
+ if (typeof raw.host === "string" && typeof raw.slug === "string" && typeof raw.app_id === "number" && typeof raw.tool_id === "string") return {
125
+ host: raw.host,
126
+ slug: raw.slug,
127
+ app_id: raw.app_id,
128
+ tool_id: raw.tool_id,
129
+ name: raw.name ?? raw.tool_id,
130
+ registered_at: raw.registered_at ?? "",
131
+ cache_version: typeof raw.cache_version === "number" ? raw.cache_version : 0
132
+ };
133
+ } catch {}
134
+ return null;
135
+ }
136
+ function writeDevExecutaCache(cwd, entry) {
137
+ const p = executaCachePath(cwd);
138
+ mkdirSync(dirname(p), { recursive: true });
139
+ writeFileSync(p, JSON.stringify(entry, null, 2) + "\n", "utf-8");
140
+ }
141
+ /** Call `POST /api/v1/anna-apps/dev/executas/register`. Idempotent. */
142
+ async function registerDevExecuta(args) {
143
+ const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/executas/register`;
144
+ const res = await fetch(url, {
145
+ method: "POST",
146
+ headers: { "content-type": "application/json" },
147
+ body: JSON.stringify({
148
+ pat: args.pat,
149
+ tool_id: args.toolId,
150
+ slug: args.slug,
151
+ name: args.name
152
+ })
153
+ });
154
+ if (!res.ok) {
155
+ const text = await res.text().catch(() => "");
156
+ throw new Error(`/dev/executas/register failed: HTTP ${res.status} ${text}`);
157
+ }
158
+ return await res.json();
159
+ }
160
+ /**
161
+ * Convenience: return a cached executa registration if still valid for
162
+ * this host+tool_id, otherwise hit the server. Throws if no PAT.
163
+ */
164
+ async function ensureDevExecutaRegistered(args) {
165
+ const canonical = canonicalHost(args.host);
166
+ const cached = readDevExecutaCache(args.cwd);
167
+ if (cached && cached.host === canonical && cached.tool_id === args.toolId && cached.cache_version >= EXECUTA_CACHE_VERSION && (!args.slug || cached.slug === args.slug)) return cached;
168
+ const r = await registerDevExecuta({
169
+ host: args.host,
170
+ pat: args.pat,
171
+ toolId: args.toolId,
172
+ slug: args.slug,
173
+ name: args.name
174
+ });
175
+ const entry = {
176
+ host: canonical,
177
+ slug: r.slug,
178
+ app_id: r.app_id,
179
+ tool_id: r.tool_id,
180
+ name: r.name,
181
+ registered_at: new Date().toISOString(),
182
+ cache_version: EXECUTA_CACHE_VERSION
183
+ };
184
+ writeDevExecutaCache(args.cwd, entry);
185
+ return entry;
186
+ }
187
+
188
+ //#endregion
189
+ export { ensureDevAppRegistered, ensureDevExecutaRegistered, invalidateDevAppCache, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache };
@@ -0,0 +1,4 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import { ensureDevAppRegistered, ensureDevExecutaRegistered, invalidateDevAppCache, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache } from "./dev-app-cache-3Pfesngr.js";
3
+
4
+ export { ensureDevAppRegistered, ensureDevExecutaRegistered, invalidateDevAppCache };
@@ -1,4 +1,4 @@
1
- import { PINNED_RUNTIME_VERSION } from "./bridge-D6YyP9DM.js";
1
+ import { PINNED_RUNTIME_VERSION } from "./bridge-Dffh9JUd.js";
2
2
  import { dirname, isAbsolute, resolve } from "node:path";
3
3
  import { existsSync, statSync } from "node:fs";
4
4
  import { spawnSync } from "node:child_process";
@@ -1,4 +1,4 @@
1
- import { parseExecutaSpec } from "./dev-BfLGxpiT.js";
1
+ import { parseExecutaSpec } from "./dev-BUetXnfG.js";
2
2
  import { isAbsolute, resolve } from "node:path";
3
3
  import { existsSync } from "node:fs";
4
4
  import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
@@ -33,54 +33,101 @@ async function runExecutaDev(opts) {
33
33
  }
34
34
  const oneShot = !!(opts.describe || opts.health || opts.invoke);
35
35
  const quiet = oneShot && (opts.json ?? false);
36
- const { SamplingBridge } = await import("./sampling-3EfSlDHM.js");
36
+ const { getAccount } = await import("./credentials-DDqx6XMQ.js");
37
+ const { ensureDevExecutaRegistered } = await import("./dev-app-cache-CZ1UjMz0.js");
38
+ const needsRealMint = !opts.noSampling && !opts.mockSampling || !opts.noAgent && !opts.mockAgent || !opts.noImage && !opts.mockImage || !opts.noUpload && !opts.mockUpload || opts.storage === "real";
39
+ let effectiveAppSlug = opts.appSlug;
40
+ let autoRegistered = false;
41
+ if (!effectiveAppSlug && needsRealMint) {
42
+ const acc = getAccount(opts.samplingAccount);
43
+ if (acc) try {
44
+ const reg = await ensureDevExecutaRegistered({
45
+ cwd: parsed.project_dir,
46
+ host: acc.host,
47
+ pat: acc.pat,
48
+ toolId: parsed.tool_id
49
+ });
50
+ effectiveAppSlug = reg.slug;
51
+ autoRegistered = true;
52
+ } catch (e) {
53
+ if (!quiet) {
54
+ console.warn(yellow(`! executa auto-register failed: ${e.message}`));
55
+ console.warn(yellow(" reverse-RPC bridges (sampling/agent/image/upload) will run in disabled mode. Pass `--app-slug <slug>` to override, or `--no-sampling --no-agent --no-image --no-upload` to silence."));
56
+ }
57
+ }
58
+ }
59
+ const { SamplingBridge } = await import("./sampling-CJUDG-mf.js");
37
60
  const sampling = opts.noSampling ? new SamplingBridge({ mode: "off" }) : opts.mockSampling ? new SamplingBridge({
38
61
  mode: "mock",
39
62
  mockFile: opts.mockSampling
40
- }) : opts.appSlug ? new SamplingBridge({
63
+ }) : effectiveAppSlug ? new SamplingBridge({
41
64
  mode: "real",
42
65
  account: opts.samplingAccount,
43
- appSlug: opts.appSlug
66
+ appSlug: effectiveAppSlug
44
67
  }) : new SamplingBridge({ mode: "off" });
45
68
  const { AgentBridge } = await import("./agent-DUmINbo4.js");
46
69
  const agent = opts.noAgent ? new AgentBridge({ mode: "off" }) : opts.mockAgent ? new AgentBridge({
47
70
  mode: "mock",
48
71
  mockFile: opts.mockAgent
49
- }) : opts.appSlug ? new AgentBridge({
72
+ }) : effectiveAppSlug ? new AgentBridge({
50
73
  mode: "real",
51
74
  account: opts.agentAccount ?? opts.samplingAccount,
52
- appSlug: opts.appSlug
75
+ appSlug: effectiveAppSlug
53
76
  }) : new AgentBridge({ mode: "off" });
54
- const { StorageBridge } = await import("./storage-CnWTZqq_.js");
77
+ const { StorageBridge } = await import("./storage-EQJA_0UW.js");
55
78
  const storageMode = opts.storage ?? (opts.mockStorage ? "mock" : "memory");
56
79
  const storage = new StorageBridge({
57
80
  mode: storageMode,
58
81
  mockFile: opts.mockStorage,
59
82
  account: opts.storageAccount ?? opts.samplingAccount,
60
- appSlug: opts.appSlug,
83
+ appSlug: effectiveAppSlug,
61
84
  scopes: opts.storageScopes ? opts.storageScopes.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
62
85
  pluginName: parsed.tool_id
63
86
  });
64
- const { ExecutaRunner } = await import("./runner-Bral1LFW.js");
87
+ const { ExecutaRunner } = await import("./runner-DmGLdat0.js");
88
+ const { ImageBridge } = await import("./image-bwolX7pa.js");
89
+ const image = opts.noImage ? new ImageBridge({ mode: "off" }) : opts.mockImage ? new ImageBridge({
90
+ mode: "mock",
91
+ mockFile: opts.mockImage
92
+ }) : effectiveAppSlug ? new ImageBridge({
93
+ mode: "real",
94
+ account: opts.imageAccount ?? opts.samplingAccount,
95
+ appSlug: effectiveAppSlug
96
+ }) : new ImageBridge({ mode: "off" });
97
+ const { HostUploadBridge } = await import("./host_upload-C_pGOS6p.js");
98
+ const hostUpload = opts.noUpload ? new HostUploadBridge({ mode: "off" }) : opts.mockUpload ? new HostUploadBridge({
99
+ mode: "mock",
100
+ mockFile: opts.mockUpload
101
+ }) : effectiveAppSlug ? new HostUploadBridge({
102
+ mode: "real",
103
+ account: opts.uploadAccount ?? opts.samplingAccount,
104
+ appSlug: effectiveAppSlug
105
+ }) : new HostUploadBridge({ mode: "off" });
65
106
  const runner = new ExecutaRunner({
66
107
  command: parsed.command,
67
108
  cwd: parsed.project_dir,
68
109
  sampling,
69
110
  agent,
70
111
  storage,
112
+ image,
113
+ hostUpload,
71
114
  onStderr: (line) => {
72
115
  const text = quiet ? `${line}\n` : dim(`[executa] ${line}\n`);
73
116
  writeStderrCooperative(text);
74
117
  }
75
118
  });
76
119
  if (!quiet) {
120
+ const slugTag = (mode) => effectiveAppSlug ? `${mode} → app_slug=${effectiveAppSlug}${autoRegistered ? " (auto)" : ""}` : "disabled (no fixture, no --app-slug, no PAT)";
77
121
  console.log(bold(cyan("anna-app executa dev")));
78
122
  console.log(` tool_id ${dim(parsed.tool_id)}`);
79
123
  console.log(` dir ${dim(parsed.project_dir)}`);
80
124
  console.log(` command ${dim(parsed.command.join(" "))}`);
81
- console.log(` sampling ${dim(opts.noSampling ? "disabled (--no-sampling)" : opts.mockSampling ? `mock (${opts.mockSampling})` : opts.appSlug ? `real → app_slug=${opts.appSlug}` : "disabled (no fixture, no --app-slug)")}`);
82
- console.log(` agent ${dim(opts.noAgent ? "disabled (--no-agent)" : opts.mockAgent ? `mock (${opts.mockAgent})` : opts.appSlug ? `real → app_slug=${opts.appSlug}` : "disabled (no fixture, no --app-slug)")}`);
83
- console.log(` storage ${dim(storageMode === "mock" ? `mock (${opts.mockStorage ?? "<no-fixture>"})` : storageMode === "real" ? `real → app_slug=${opts.appSlug ?? "<unset>"}` : storageMode)}`);
125
+ if (autoRegistered && effectiveAppSlug) console.log(` executa ${dim(`auto-registered as ${effectiveAppSlug}`)}`);
126
+ console.log(` sampling ${dim(opts.noSampling ? "disabled (--no-sampling)" : opts.mockSampling ? `mock (${opts.mockSampling})` : slugTag("real"))}`);
127
+ console.log(` agent ${dim(opts.noAgent ? "disabled (--no-agent)" : opts.mockAgent ? `mock (${opts.mockAgent})` : slugTag("real"))}`);
128
+ console.log(` storage ${dim(storageMode === "mock" ? `mock (${opts.mockStorage ?? "<no-fixture>"})` : storageMode === "real" ? `real → app_slug=${effectiveAppSlug ?? "<unset>"}` : storageMode)}`);
129
+ console.log(` image ${dim(opts.noImage ? "disabled (--no-image)" : opts.mockImage ? `mock (${opts.mockImage})` : slugTag("real"))}`);
130
+ console.log(` upload ${dim(opts.noUpload ? "disabled (--no-upload)" : opts.mockUpload ? `mock (${opts.mockUpload})` : slugTag("real"))}`);
84
131
  }
85
132
  let init;
86
133
  try {