@anna-ai/cli 0.1.14 → 0.1.16

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-DAHcq46m.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 { PINNED_RUNTIME_VERSION, PythonBridge } from "./bridge-nqQ3-j-t.js";
2
+
3
+ export { PINNED_RUNTIME_VERSION, PythonBridge };
@@ -9,7 +9,7 @@ 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.0a3";
13
13
  var PythonBridge = class {
14
14
  proc = null;
15
15
  nextId = 1;
package/dist/cli.js CHANGED
@@ -451,7 +451,7 @@ program.command("validate").description("Run schema + ACL checks on a manifest+b
451
451
  process.exit(code);
452
452
  });
453
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");
454
+ const { runDev, parseExecutaSpec } = await import("./dev-BRlFgo2I.js");
455
455
  const cwd = opts.cwd ?? process.cwd();
456
456
  let executas;
457
457
  if (opts.executa && opts.executa.length > 0) {
@@ -509,7 +509,7 @@ fixture.command("replay <file>").description("Dry-run replay of a harness record
509
509
  process.exit(code);
510
510
  });
511
511
  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");
512
+ const { runDoctor } = await import("./doctor-C8MWfLt8.js");
513
513
  const code = await runDoctor({ matrixNexusRoot: opts.matrixNexusRoot });
514
514
  process.exit(code);
515
515
  });
@@ -535,7 +535,7 @@ program.command("whoami").description("Show the current account (and any others)
535
535
  process.exit(code);
536
536
  });
537
537
  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");
538
+ const { runAppsList } = await import("./apps-Bt9CT5Sl.js");
539
539
  const code = await runAppsList({
540
540
  account: opts.account,
541
541
  json: opts.json
@@ -569,8 +569,8 @@ executa.command("register").description("Register a standalone executa with nexu
569
569
  const code = await runExecutaRegister(opts);
570
570
  process.exit(code);
571
571
  });
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");
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)").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) => {
573
+ const { runExecutaDev } = await import("./executa-dev-4FZ7AJHR.js");
574
574
  const storageMode = opts.storage === void 0 ? void 0 : (() => {
575
575
  const m = opts.storage;
576
576
  if (m === "off" || m === "memory" || m === "mock" || m === "real") return m;
@@ -595,7 +595,13 @@ executa.command("dev").description("Run one Executa plugin in isolation (REPL or
595
595
  storage: storageMode,
596
596
  mockStorage: opts.mockStorage,
597
597
  storageAccount: opts.storageAccount,
598
- storageScopes: opts.storageScopes
598
+ storageScopes: opts.storageScopes,
599
+ noImage: opts.image === false,
600
+ mockImage: opts.mockImage,
601
+ imageAccount: opts.imageAccount,
602
+ noUpload: opts.upload === false,
603
+ mockUpload: opts.mockUpload,
604
+ uploadAccount: opts.uploadAccount
599
605
  });
600
606
  process.exit(code);
601
607
  });
@@ -0,0 +1,3 @@
1
+ import { parseExecutaSpec, runDev } from "./dev-C6v5yRV2.js";
2
+
3
+ export { parseExecutaSpec, runDev };
@@ -31,8 +31,8 @@ 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 { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-AJilXBw2.js");
35
+ const { HarnessServer } = await import("./server-D8R6ppOp.js");
36
36
  const bridge = new PythonBridge({
37
37
  mode,
38
38
  matrixNexusRoot: matrixNexusRoot ?? void 0,
@@ -123,7 +123,7 @@ async function runDev(opts) {
123
123
  */
124
124
  async function resolveRealLlm(args) {
125
125
  const { getAccount } = await import("./credentials-DDqx6XMQ.js");
126
- const { ensureDevAppRegistered } = await import("./dev-app-cache-CZ8lIKiw.js");
126
+ const { ensureDevAppRegistered } = await import("./dev-app-cache-DGF2Kuzd.js");
127
127
  const acc = getAccount(args.account);
128
128
  if (!acc) {
129
129
  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.)"));
@@ -0,0 +1,175 @@
1
+ import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { existsSync, mkdirSync, readFileSync, 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
+ /** Call POST /api/v1/anna-apps/dev/apps/register. Idempotent server-side. */
32
+ async function registerDevApp(args) {
33
+ const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps/register`;
34
+ const res = await fetch(url, {
35
+ method: "POST",
36
+ headers: { "content-type": "application/json" },
37
+ body: JSON.stringify({
38
+ pat: args.pat,
39
+ slug: args.input.slug,
40
+ name: args.input.name,
41
+ category: args.input.category,
42
+ tagline: args.input.tagline
43
+ })
44
+ });
45
+ if (!res.ok) {
46
+ const text = await res.text().catch(() => "");
47
+ throw new Error(`/dev/apps/register failed: HTTP ${res.status} ${text}`);
48
+ }
49
+ return await res.json();
50
+ }
51
+ /** Call GET /api/v1/anna-apps/dev/apps. */
52
+ async function listDevApps(args) {
53
+ const url = new URL(`${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps`);
54
+ url.searchParams.set("pat", args.pat);
55
+ const res = await fetch(url, { method: "GET" });
56
+ if (!res.ok) {
57
+ const text = await res.text().catch(() => "");
58
+ throw new Error(`/dev/apps failed: HTTP ${res.status} ${text}`);
59
+ }
60
+ const body = await res.json();
61
+ return body.apps;
62
+ }
63
+ /**
64
+ * Convenience helper for `anna-app dev`: returns a valid cached entry if
65
+ * it still matches the manifest slug, otherwise hits the server to
66
+ * (re-)register. Throws if no PAT is available on disk.
67
+ */
68
+ async function ensureDevAppRegistered(args) {
69
+ const canonical = canonicalHost(args.host);
70
+ const cached = readDevAppCache(args.cwd);
71
+ if (cached && cached.host === canonical && cached.slug === args.input.slug) return cached;
72
+ const r = await registerDevApp({
73
+ host: args.host,
74
+ pat: args.pat,
75
+ input: args.input
76
+ });
77
+ const entry = {
78
+ host: canonical,
79
+ slug: r.slug,
80
+ app_id: r.app_id,
81
+ name: r.name,
82
+ registered_at: new Date().toISOString()
83
+ };
84
+ writeDevAppCache(args.cwd, entry);
85
+ return entry;
86
+ }
87
+ const _internal = {
88
+ cachePath,
89
+ CACHE_DIR: join(CACHE_DIR, CACHE_FILE)
90
+ };
91
+ const EXECUTA_CACHE_FILE = "dev-executa.json";
92
+ /**
93
+ * Bump this whenever the server-side dev-executa grant shape changes in
94
+ * a way that requires a re-register to pick up. Cached entries written
95
+ * with a lower `cache_version` are treated as stale and re-POSTed.
96
+ *
97
+ * 1 — initial (llm_grant only)
98
+ * 2 — adds image_grant + upload_grant; fixes `allowedPurposes` to
99
+ * protocol whitelist {image_input,image_reference,user_artifact}
100
+ */
101
+ const EXECUTA_CACHE_VERSION = 2;
102
+ function executaCachePath(cwd) {
103
+ return resolve(cwd, CACHE_DIR, EXECUTA_CACHE_FILE);
104
+ }
105
+ function readDevExecutaCache(cwd) {
106
+ const p = executaCachePath(cwd);
107
+ if (!existsSync(p)) return null;
108
+ try {
109
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
110
+ if (typeof raw.host === "string" && typeof raw.slug === "string" && typeof raw.app_id === "number" && typeof raw.tool_id === "string") return {
111
+ host: raw.host,
112
+ slug: raw.slug,
113
+ app_id: raw.app_id,
114
+ tool_id: raw.tool_id,
115
+ name: raw.name ?? raw.tool_id,
116
+ registered_at: raw.registered_at ?? "",
117
+ cache_version: typeof raw.cache_version === "number" ? raw.cache_version : 0
118
+ };
119
+ } catch {}
120
+ return null;
121
+ }
122
+ function writeDevExecutaCache(cwd, entry) {
123
+ const p = executaCachePath(cwd);
124
+ mkdirSync(dirname(p), { recursive: true });
125
+ writeFileSync(p, JSON.stringify(entry, null, 2) + "\n", "utf-8");
126
+ }
127
+ /** Call `POST /api/v1/anna-apps/dev/executas/register`. Idempotent. */
128
+ async function registerDevExecuta(args) {
129
+ const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/executas/register`;
130
+ const res = await fetch(url, {
131
+ method: "POST",
132
+ headers: { "content-type": "application/json" },
133
+ body: JSON.stringify({
134
+ pat: args.pat,
135
+ tool_id: args.toolId,
136
+ slug: args.slug,
137
+ name: args.name
138
+ })
139
+ });
140
+ if (!res.ok) {
141
+ const text = await res.text().catch(() => "");
142
+ throw new Error(`/dev/executas/register failed: HTTP ${res.status} ${text}`);
143
+ }
144
+ return await res.json();
145
+ }
146
+ /**
147
+ * Convenience: return a cached executa registration if still valid for
148
+ * this host+tool_id, otherwise hit the server. Throws if no PAT.
149
+ */
150
+ async function ensureDevExecutaRegistered(args) {
151
+ const canonical = canonicalHost(args.host);
152
+ const cached = readDevExecutaCache(args.cwd);
153
+ if (cached && cached.host === canonical && cached.tool_id === args.toolId && cached.cache_version >= EXECUTA_CACHE_VERSION && (!args.slug || cached.slug === args.slug)) return cached;
154
+ const r = await registerDevExecuta({
155
+ host: args.host,
156
+ pat: args.pat,
157
+ toolId: args.toolId,
158
+ slug: args.slug,
159
+ name: args.name
160
+ });
161
+ const entry = {
162
+ host: canonical,
163
+ slug: r.slug,
164
+ app_id: r.app_id,
165
+ tool_id: r.tool_id,
166
+ name: r.name,
167
+ registered_at: new Date().toISOString(),
168
+ cache_version: EXECUTA_CACHE_VERSION
169
+ };
170
+ writeDevExecutaCache(args.cwd, entry);
171
+ return entry;
172
+ }
173
+
174
+ //#endregion
175
+ export { ensureDevAppRegistered, ensureDevExecutaRegistered, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache };
@@ -0,0 +1,4 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import { ensureDevAppRegistered, ensureDevExecutaRegistered, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache } from "./dev-app-cache-DAHcq46m.js";
3
+
4
+ export { ensureDevAppRegistered, ensureDevExecutaRegistered };
@@ -1,4 +1,4 @@
1
- import { PINNED_RUNTIME_VERSION } from "./bridge-D6YyP9DM.js";
1
+ import { PINNED_RUNTIME_VERSION } from "./bridge-nqQ3-j-t.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-C6v5yRV2.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-DGF2Kuzd.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 {
@@ -0,0 +1,136 @@
1
+ import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
2
+ import { resolve } from "node:path";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { createHash } from "node:crypto";
5
+
6
+ //#region src/executa/host_upload.ts
7
+ var HostUploadBridge = class {
8
+ mocks = [];
9
+ cachedSession = null;
10
+ constructor(opts) {
11
+ this.opts = opts;
12
+ if (opts.mode === "mock" && opts.mockFile) {
13
+ const path = resolve(opts.mockFile);
14
+ if (existsSync(path)) for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
15
+ if (!line.trim() || line.startsWith("#")) continue;
16
+ try {
17
+ this.mocks.push(JSON.parse(line));
18
+ } catch {}
19
+ }
20
+ }
21
+ }
22
+ async call(method, params) {
23
+ if (method !== "host/uploadFile") throw withCode(new Error(`unsupported host method: ${method}`), -32601);
24
+ if (this.opts.mode === "off") throw withCode(new Error("host upload disabled — rerun without `--no-upload`"), -32201);
25
+ if (this.opts.mode === "mock") return this.mockCall(params);
26
+ return this.realCall(params);
27
+ }
28
+ mockCall(params) {
29
+ const mode = String(params.mode ?? "inline");
30
+ const filename = String(params.filename ?? "");
31
+ const candidates = this.mocks.filter((m) => m.ns === "host" && m.method === "uploadFile");
32
+ const matched = candidates.find((m) => (!m.match?.modeEquals || m.match.modeEquals === mode) && (!m.match?.filenameIncludes || filename.includes(m.match.filenameIncludes))) ?? candidates[0];
33
+ if (matched && matched.result) return matched.result;
34
+ if (mode === "inline") {
35
+ const b64 = String(params.content_b64 ?? "");
36
+ const mime = String(params.mime_type ?? "application/octet-stream");
37
+ const url = `data:${mime};base64,${b64}`;
38
+ const r2Key = `mock/${hashShort(filename + ":" + b64)}/${filename || "blob"}`;
39
+ return {
40
+ download_url: url,
41
+ r2_key: r2Key,
42
+ size_bytes: Math.floor(b64.length * .75),
43
+ expires_at: new Date(Date.now() + 3600 * 1e3).toISOString()
44
+ };
45
+ }
46
+ if (mode === "negotiate") {
47
+ const key = `mock/${hashShort(filename + Date.now())}/${filename || "blob"}`;
48
+ return {
49
+ put_url: `https://mock.local/${key}?signature=mock`,
50
+ headers: { "content-type": String(params.mime_type ?? "") },
51
+ r2_key: key,
52
+ expires_at: new Date(Date.now() + 600 * 1e3).toISOString()
53
+ };
54
+ }
55
+ if (mode === "confirm") {
56
+ const key = String(params.r2_key ?? "");
57
+ return {
58
+ download_url: `https://mock.local/${key}`,
59
+ r2_key: key,
60
+ size_bytes: 0,
61
+ expires_at: new Date(Date.now() + 3600 * 1e3).toISOString()
62
+ };
63
+ }
64
+ throw withCode(new Error(`unknown upload mode: ${mode}`), -32203);
65
+ }
66
+ account() {
67
+ const acc = getAccount(this.opts.account);
68
+ if (!acc) throw withCode(new Error("no PAT on disk — run `anna-app login --host <nexus-url>` first (or pass `--mock-upload <fixture>` / `--no-upload`)"), -32201);
69
+ if (acc.expires_at && acc.expires_at < Math.floor(Date.now() / 1e3)) throw withCode(new Error("PAT expired — run `anna-app login` again"), -32201);
70
+ return acc;
71
+ }
72
+ async mint() {
73
+ if (this.cachedSession && this.cachedSession.expiresAt - 30 > Math.floor(Date.now() / 1e3)) return this.cachedSession;
74
+ const acc = this.account();
75
+ if (!this.opts.appSlug) throw withCode(new Error("host upload bridge has no app_slug — pass `--app-slug <slug>`"), -32203);
76
+ const url = `${canonicalHost(acc.host)}/api/v1/anna-apps/dev/session/mint`;
77
+ const res = await fetch(url, {
78
+ method: "POST",
79
+ headers: { "content-type": "application/json" },
80
+ body: JSON.stringify({
81
+ pat: acc.pat,
82
+ kind: "complete",
83
+ app_slug: this.opts.appSlug
84
+ })
85
+ });
86
+ if (!res.ok) {
87
+ const text = await res.text().catch(() => "");
88
+ throw withCode(new Error(`session.mint failed: HTTP ${res.status} ${text}`), -32207);
89
+ }
90
+ const body = await res.json();
91
+ this.cachedSession = {
92
+ appSessionToken: body.app_session_token,
93
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.expires_in || 600)
94
+ };
95
+ return this.cachedSession;
96
+ }
97
+ async realCall(params) {
98
+ const acc = this.account();
99
+ const session = await this.mint();
100
+ const mode = String(params.mode ?? "inline");
101
+ const path = mode === "negotiate" ? "/api/v1/copilot/app/upload/negotiate" : mode === "confirm" ? "/api/v1/copilot/app/upload/confirm" : "/api/v1/copilot/app/upload";
102
+ const res = await fetch(`${canonicalHost(acc.host)}${path}`, {
103
+ method: "POST",
104
+ headers: {
105
+ "content-type": "application/json",
106
+ authorization: `Bearer ${session.appSessionToken}`
107
+ },
108
+ body: JSON.stringify(params)
109
+ });
110
+ if (!res.ok) {
111
+ const text = await res.text().catch(() => "");
112
+ throw withCode(new Error(`HTTP ${res.status}: ${text}`), httpToUploadCode(res.status));
113
+ }
114
+ return res.json();
115
+ }
116
+ };
117
+ function withCode(err, code) {
118
+ err.rpcCode = code;
119
+ return err;
120
+ }
121
+ function hashShort(s) {
122
+ return createHash("sha256").update(s).digest("hex").slice(0, 12);
123
+ }
124
+ function httpToUploadCode(status) {
125
+ if (status === 403) return -32201;
126
+ if (status === 429) return -32202;
127
+ if (status === 400) return -32203;
128
+ if (status === 413) return -32204;
129
+ if (status === 415) return -32205;
130
+ if (status === 404) return -32212;
131
+ if (status === 504) return -32208;
132
+ return -32207;
133
+ }
134
+
135
+ //#endregion
136
+ export { HostUploadBridge };
@@ -0,0 +1,131 @@
1
+ import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
2
+ import { resolve } from "node:path";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+
5
+ //#region src/executa/image.ts
6
+ var ImageBridge = class {
7
+ mocks = [];
8
+ cachedSession = null;
9
+ constructor(opts) {
10
+ this.opts = opts;
11
+ if (opts.mode === "mock" && opts.mockFile) {
12
+ const path = resolve(opts.mockFile);
13
+ if (existsSync(path)) for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
14
+ if (!line.trim() || line.startsWith("#")) continue;
15
+ try {
16
+ this.mocks.push(JSON.parse(line));
17
+ } catch {}
18
+ }
19
+ }
20
+ }
21
+ async generate(req) {
22
+ if (this.opts.mode === "off") throw withCode(new Error("image generation disabled — rerun without `--no-image`"), -32101);
23
+ if (this.opts.mode === "mock") return this.pickMock("generate", req.prompt);
24
+ return this.realCall("/api/v1/copilot/app/image/generate", req);
25
+ }
26
+ async edit(req) {
27
+ if (this.opts.mode === "off") throw withCode(new Error("image edit disabled — rerun without `--no-image`"), -32101);
28
+ if (this.opts.mode === "mock") return this.pickMock("edit", req.prompt);
29
+ return this.realCall("/api/v1/copilot/app/image/edit", req);
30
+ }
31
+ pickMock(method, prompt) {
32
+ const wantedMethod = method === "generate" ? "generate" : "edit";
33
+ const candidates = this.mocks.filter((m) => m.ns === "image" && m.method === wantedMethod);
34
+ const matched = candidates.find((m) => m.match?.promptIncludes && prompt.includes(m.match.promptIncludes)) ?? candidates[0];
35
+ if (matched && matched.result) return normaliseImageResult(matched.result);
36
+ return {
37
+ images: [{
38
+ url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
39
+ mimeType: "image/png"
40
+ }],
41
+ model: "mock-image-model"
42
+ };
43
+ }
44
+ account() {
45
+ const acc = getAccount(this.opts.account);
46
+ if (!acc) throw withCode(new Error("no PAT on disk — run `anna-app login --host <nexus-url>` first (or pass `--mock-image <fixture>` / `--no-image`)"), -32101);
47
+ if (acc.expires_at && acc.expires_at < Math.floor(Date.now() / 1e3)) throw withCode(new Error("PAT expired — run `anna-app login` again"), -32101);
48
+ return acc;
49
+ }
50
+ async mint() {
51
+ if (this.cachedSession && this.cachedSession.expiresAt - 30 > Math.floor(Date.now() / 1e3)) return this.cachedSession;
52
+ const acc = this.account();
53
+ if (!this.opts.appSlug) throw withCode(new Error("image bridge has no app_slug — pass `--app-slug <slug>` to `anna-app executa dev` so nexus can attribute the spend"), -32104);
54
+ const url = `${canonicalHost(acc.host)}/api/v1/anna-apps/dev/session/mint`;
55
+ const res = await fetch(url, {
56
+ method: "POST",
57
+ headers: { "content-type": "application/json" },
58
+ body: JSON.stringify({
59
+ pat: acc.pat,
60
+ kind: "complete",
61
+ app_slug: this.opts.appSlug
62
+ })
63
+ });
64
+ if (!res.ok) {
65
+ const text = await res.text().catch(() => "");
66
+ throw withCode(new Error(`session.mint failed: HTTP ${res.status} ${text}`), -32103);
67
+ }
68
+ const body = await res.json();
69
+ this.cachedSession = {
70
+ appSessionToken: body.app_session_token,
71
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.expires_in || 600)
72
+ };
73
+ return this.cachedSession;
74
+ }
75
+ async realCall(path, body) {
76
+ const acc = this.account();
77
+ const session = await this.mint();
78
+ const res = await fetch(`${canonicalHost(acc.host)}${path}`, {
79
+ method: "POST",
80
+ headers: {
81
+ "content-type": "application/json",
82
+ authorization: `Bearer ${session.appSessionToken}`
83
+ },
84
+ body: JSON.stringify(body)
85
+ });
86
+ if (!res.ok) {
87
+ const text = await res.text().catch(() => "");
88
+ throw withCode(
89
+ new Error(`HTTP ${res.status}: ${text}`),
90
+ // Map common HTTP statuses to canonical image codes; otherwise
91
+ // let the host's body propagate.
92
+ httpToImageCode(res.status)
93
+ );
94
+ }
95
+ const out = await res.json();
96
+ return normaliseImageResult(out);
97
+ }
98
+ };
99
+ function withCode(err, code) {
100
+ err.rpcCode = code;
101
+ return err;
102
+ }
103
+ function httpToImageCode(status) {
104
+ if (status === 403) return -32101;
105
+ if (status === 429) return -32102;
106
+ if (status === 400) return -32104;
107
+ if (status === 504) return -32105;
108
+ return -32103;
109
+ }
110
+ function normaliseImageResult(raw) {
111
+ if (!raw || typeof raw !== "object") return {
112
+ images: [],
113
+ model: "unknown"
114
+ };
115
+ const o = raw;
116
+ const images = Array.isArray(o.images) ? o.images.map((img) => ({
117
+ url: String(img.url ?? ""),
118
+ mimeType: img.mimeType ? String(img.mimeType) : void 0,
119
+ width: typeof img.width === "number" ? img.width : void 0,
120
+ height: typeof img.height === "number" ? img.height : void 0
121
+ })) : [];
122
+ return {
123
+ images,
124
+ model: typeof o.model === "string" ? o.model : void 0,
125
+ quota_used: o.quota_used && typeof o.quota_used === "object" ? o.quota_used : void 0,
126
+ _meta: o._meta && typeof o._meta === "object" ? o._meta : void 0
127
+ };
128
+ }
129
+
130
+ //#endregion
131
+ export { ImageBridge };
@@ -8,7 +8,10 @@ const HOST_INITIALIZE_PARAMS = {
8
8
  client_capabilities: {
9
9
  sampling: {},
10
10
  agent: {},
11
- storage: {}
11
+ storage: {},
12
+ image: {},
13
+ "image.edit": {},
14
+ upload: {}
12
15
  },
13
16
  client_info: {
14
17
  name: "anna-app-cli",
@@ -248,6 +251,46 @@ var ExecutaRunner = class {
248
251
  }
249
252
  return;
250
253
  }
254
+ if (method === "image/generate" || method === "image/edit") {
255
+ if (!this.opts.image) {
256
+ respond({ error: {
257
+ code: -32101,
258
+ message: "image not configured (rerun without `--no-image`, or pass `--mock-image <fixture>` / `--app-slug`)"
259
+ } });
260
+ return;
261
+ }
262
+ try {
263
+ const result = method === "image/generate" ? await this.opts.image.generate(params) : await this.opts.image.edit(params);
264
+ respond({ result });
265
+ } catch (e) {
266
+ const code = e.rpcCode ?? -32103;
267
+ respond({ error: {
268
+ code,
269
+ message: e.message
270
+ } });
271
+ }
272
+ return;
273
+ }
274
+ if (method === "host/uploadFile") {
275
+ if (!this.opts.hostUpload) {
276
+ respond({ error: {
277
+ code: -32201,
278
+ message: "host upload not configured (rerun without `--no-upload`, or pass `--mock-upload <fixture>` / `--app-slug`)"
279
+ } });
280
+ return;
281
+ }
282
+ try {
283
+ const result = await this.opts.hostUpload.call(method, params);
284
+ respond({ result });
285
+ } catch (e) {
286
+ const code = e.rpcCode ?? -32207;
287
+ respond({ error: {
288
+ code,
289
+ message: e.message
290
+ } });
291
+ }
292
+ return;
293
+ }
251
294
  if (method.startsWith("storage/") || method.startsWith("files/")) {
252
295
  if (!this.opts.storage) {
253
296
  respond({ error: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anna-ai/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Anna App developer CLI: scaffold, validate, harness (Phase 2 MVP: init + validate).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,6 @@
1
1
  // Minimal Anna App bundle entry. Replace with real logic.
2
+ import { AnnaAppRuntime } from "/static/anna-apps/_sdk/latest/index.js";
3
+
2
4
  const TOOL_ID = "__TOOL_ID__";
3
5
 
4
6
  async function main() {
@@ -3,8 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <title>__SLUG__</title>
6
- <script src="/static/anna-apps/_sdk/0.1.0/index.js" defer></script>
7
- <script src="./app.js" defer></script>
6
+ <script src="./app.js" type="module"></script>
8
7
  </head>
9
8
  <body>
10
9
  <h1>__SLUG__</h1>
@@ -1,3 +0,0 @@
1
- import { PINNED_RUNTIME_VERSION, PythonBridge } from "./bridge-D6YyP9DM.js";
2
-
3
- export { PINNED_RUNTIME_VERSION, PythonBridge };
@@ -1,3 +0,0 @@
1
- import { parseExecutaSpec, runDev } from "./dev-BfLGxpiT.js";
2
-
3
- export { parseExecutaSpec, runDev };
@@ -1,93 +0,0 @@
1
- import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
- import { dirname, join, resolve } from "node:path";
3
- import { existsSync, mkdirSync, readFileSync, 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
- /** Call POST /api/v1/anna-apps/dev/apps/register. Idempotent server-side. */
32
- async function registerDevApp(args) {
33
- const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps/register`;
34
- const res = await fetch(url, {
35
- method: "POST",
36
- headers: { "content-type": "application/json" },
37
- body: JSON.stringify({
38
- pat: args.pat,
39
- slug: args.input.slug,
40
- name: args.input.name,
41
- category: args.input.category,
42
- tagline: args.input.tagline
43
- })
44
- });
45
- if (!res.ok) {
46
- const text = await res.text().catch(() => "");
47
- throw new Error(`/dev/apps/register failed: HTTP ${res.status} ${text}`);
48
- }
49
- return await res.json();
50
- }
51
- /** Call GET /api/v1/anna-apps/dev/apps. */
52
- async function listDevApps(args) {
53
- const url = new URL(`${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps`);
54
- url.searchParams.set("pat", args.pat);
55
- const res = await fetch(url, { method: "GET" });
56
- if (!res.ok) {
57
- const text = await res.text().catch(() => "");
58
- throw new Error(`/dev/apps failed: HTTP ${res.status} ${text}`);
59
- }
60
- const body = await res.json();
61
- return body.apps;
62
- }
63
- /**
64
- * Convenience helper for `anna-app dev`: returns a valid cached entry if
65
- * it still matches the manifest slug, otherwise hits the server to
66
- * (re-)register. Throws if no PAT is available on disk.
67
- */
68
- async function ensureDevAppRegistered(args) {
69
- const canonical = canonicalHost(args.host);
70
- const cached = readDevAppCache(args.cwd);
71
- if (cached && cached.host === canonical && cached.slug === args.input.slug) return cached;
72
- const r = await registerDevApp({
73
- host: args.host,
74
- pat: args.pat,
75
- input: args.input
76
- });
77
- const entry = {
78
- host: canonical,
79
- slug: r.slug,
80
- app_id: r.app_id,
81
- name: r.name,
82
- registered_at: new Date().toISOString()
83
- };
84
- writeDevAppCache(args.cwd, entry);
85
- return entry;
86
- }
87
- const _internal = {
88
- cachePath,
89
- CACHE_DIR: join(CACHE_DIR, CACHE_FILE)
90
- };
91
-
92
- //#endregion
93
- export { ensureDevAppRegistered, listDevApps, readDevAppCache, registerDevApp, writeDevAppCache };
@@ -1,4 +0,0 @@
1
- import "./credentials-BTv2IfUZ.js";
2
- import { ensureDevAppRegistered, listDevApps, readDevAppCache, registerDevApp, writeDevAppCache } from "./dev-app-cache-C3D1Sp_V.js";
3
-
4
- export { ensureDevAppRegistered };
File without changes