@anna-ai/cli 0.1.17 → 0.1.21

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.
Files changed (63) hide show
  1. package/dist/_lifecycle-shared-sbea9HtH.js +65 -0
  2. package/dist/{agent-DUmINbo4.js → agent-Br6zY2qw.js} +1 -1
  3. package/dist/app-bundle-upload-DuLalcSt.js +213 -0
  4. package/dist/app-cache-BEM653Th.js +53 -0
  5. package/dist/apps-B1Nd8l_t.js +221 -0
  6. package/dist/apps-BTn9EN0x.js +53 -0
  7. package/dist/apps-cut-DtEkddIk.js +83 -0
  8. package/dist/apps-destructive-DSTrcFUP.js +104 -0
  9. package/dist/apps-discard-y3_IwcbQ.js +44 -0
  10. package/dist/apps-grants-BGWlpee0.js +34 -0
  11. package/dist/apps-publish-CaTCanDu.js +265 -0
  12. package/dist/apps-publish-DfZTOxBJ.js +14 -0
  13. package/dist/apps-push-B9XT2uwF.js +127 -0
  14. package/dist/apps-release-BLH9XSxB.js +135 -0
  15. package/dist/apps-status-DQ9RvlME.js +58 -0
  16. package/dist/apps-submit-review-DLwCxeAs.js +45 -0
  17. package/dist/apps-sync-meta-D9eKMMUp.js +72 -0
  18. package/dist/apps-versions-2Tmk0nsx.js +43 -0
  19. package/dist/{bridge-Dffh9JUd.js → bridge-BuklhzeE.js} +1 -1
  20. package/dist/bridge-Id8K8gr-.js +3 -0
  21. package/dist/bundled-executas-BNOKw4kv.js +161 -0
  22. package/dist/bundled-executas-CNaV2C_O.js +5 -0
  23. package/dist/cli.js +334 -20
  24. package/dist/client-Dn9zThOd.js +150 -0
  25. package/dist/confirm-DxHkk9Wn.js +37 -0
  26. package/dist/dev-BS_8yoSm.js +3 -0
  27. package/dist/{dev-BUetXnfG.js → dev-E7mqXj5S.js} +95 -26
  28. package/dist/{dev-app-cache-3Pfesngr.js → dev-app-cache-D-r6ZpEk.js} +11 -2
  29. package/dist/{doctor-Dxkx0eqv.js → doctor-DKrt-Kda.js} +1 -1
  30. package/dist/executa-cache-BFoUtb4J.js +86 -0
  31. package/dist/executa-cache-WBkCLic7.js +4 -0
  32. package/dist/executa-destructive-COQE4Xqi.js +104 -0
  33. package/dist/{executa-dev-BzhSd_A2.js → executa-dev-BvS9zTpO.js} +11 -11
  34. package/dist/executa-publish-B88_9gbp.js +9 -0
  35. package/dist/executa-publish-Ca5V7MyA.js +258 -0
  36. package/dist/executa-reads-CQ6S8gHY.js +107 -0
  37. package/dist/executas-Cep6KEo0.js +109 -0
  38. package/dist/manifest-DGwRap2i.js +188 -0
  39. package/dist/publish-C1wcf-qI.js +58 -0
  40. package/dist/{server-6WHNkydc.js → server-_IG8Igje.js} +215 -4
  41. package/dist/{storage-EQJA_0UW.js → storage-CTkApNQ9.js} +1 -1
  42. package/dist/token-B9JUPelx.js +87 -0
  43. package/dist/working-orchestration-Dw9u1Vq0.js +190 -0
  44. package/package.json +3 -3
  45. package/templates/executa/go/executa.json +5 -0
  46. package/templates/executa/node/executa.json +5 -0
  47. package/templates/executa/python/executa.json +5 -0
  48. package/dist/apps-ClgEOdKD.js +0 -44
  49. package/dist/bridge-B1vq1oG3.js +0 -3
  50. package/dist/dev-Bi6rkb1x.js +0 -3
  51. package/dist/dev-app-cache-CZ1UjMz0.js +0 -4
  52. /package/dist/{credentials-DDqx6XMQ.js → credentials-DklPMD22.js} +0 -0
  53. /package/dist/{dev-account-DCyjamBa.js → dev-account-qRaET1Cp.js} +0 -0
  54. /package/dist/{executa-init-COEmKDOE.js → executa-init-Jp-h9OI7.js} +0 -0
  55. /package/dist/{executa-register-66WKIwQQ.js → executa-register-CulDtwYZ.js} +0 -0
  56. /package/dist/{fixture-CATHyLLI.js → fixture-CYwxbiQD.js} +0 -0
  57. /package/dist/{host_upload-C_pGOS6p.js → host_upload-GXVkDM5M.js} +0 -0
  58. /package/dist/{image-bwolX7pa.js → image-DduR91n5.js} +0 -0
  59. /package/dist/{login-CsIVbrmf.js → login-BGqFjQwH.js} +0 -0
  60. /package/dist/{logout-gfmKQxMj.js → logout-CGIRKH3y.js} +0 -0
  61. /package/dist/{runner-DmGLdat0.js → runner-B-hIqx5L.js} +0 -0
  62. /package/dist/{sampling-CJUDG-mf.js → sampling-CXke7hq1.js} +0 -0
  63. /package/dist/{whoami-BS5wy-Nh.js → whoami-BoFLEUcp.js} +0 -0
@@ -0,0 +1,58 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import "./apps-B1Nd8l_t.js";
3
+ import { CliError } from "./client-Dn9zThOd.js";
4
+ import "./bundled-executas-BNOKw4kv.js";
5
+ import "./executas-Cep6KEo0.js";
6
+ import "./executa-cache-BFoUtb4J.js";
7
+ import { runExecutaPublish } from "./executa-publish-Ca5V7MyA.js";
8
+ import "./manifest-DGwRap2i.js";
9
+ import { withErrorHandling } from "./_lifecycle-shared-sbea9HtH.js";
10
+ import "./app-cache-BEM653Th.js";
11
+ import "./app-bundle-upload-DuLalcSt.js";
12
+ import { runAppsPublish } from "./apps-publish-CaTCanDu.js";
13
+ import { resolve } from "node:path";
14
+ import { existsSync } from "node:fs";
15
+ import { red, yellow } from "kleur/colors";
16
+
17
+ //#region src/publish/detector.ts
18
+ function detectProjectKind(cwd) {
19
+ const hasManifest = existsSync(resolve(cwd, "manifest.json"));
20
+ const hasExecuta = existsSync(resolve(cwd, "executa.json"));
21
+ if (hasManifest && hasExecuta) return "ambiguous";
22
+ if (hasManifest) return "app";
23
+ if (hasExecuta) return "executa";
24
+ return "unknown";
25
+ }
26
+
27
+ //#endregion
28
+ //#region src/commands/publish.ts
29
+ async function runTopLevelPublish(opts) {
30
+ return withErrorHandling(async () => {
31
+ const cwd = resolve(opts.cwd ?? process.cwd());
32
+ const kind = detectProjectKind(cwd);
33
+ if (kind === "ambiguous") throw new CliError(`cannot auto-detect: ${cwd} has BOTH manifest.json and executa.json. Run \`anna-app apps publish\` or \`anna-app executa publish\` explicitly.`, 4);
34
+ if (kind === "unknown") throw new CliError(`no manifest.json or executa.json found in ${cwd}`, 4);
35
+ if (kind === "app") return runAppsPublish({
36
+ cwd,
37
+ bump: opts.bump,
38
+ noWrite: opts.noWrite,
39
+ dryRun: opts.dryRun,
40
+ account: opts.account,
41
+ json: opts.json,
42
+ noBundle: opts.noBundle,
43
+ bundleDir: opts.bundleDir
44
+ });
45
+ return runExecutaPublish({
46
+ cwd,
47
+ bump: opts.bump,
48
+ noWrite: opts.noWrite,
49
+ publish: opts.publish,
50
+ dryRun: opts.dryRun,
51
+ account: opts.account,
52
+ json: opts.json
53
+ });
54
+ });
55
+ }
56
+
57
+ //#endregion
58
+ export { runTopLevelPublish };
@@ -1,5 +1,5 @@
1
1
  import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
2
- import { BridgeRequestError } from "./bridge-Dffh9JUd.js";
2
+ import { BridgeRequestError } from "./bridge-BuklhzeE.js";
3
3
  import { dirname, join, normalize, resolve } from "node:path";
4
4
  import { createRequire } from "node:module";
5
5
  import { createReadStream, existsSync, readFileSync, statSync, watch } from "node:fs";
@@ -10,6 +10,90 @@ import { WebSocketServer } from "ws";
10
10
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
11
11
 
12
12
  //#region src/harness/llm-bridge.ts
13
+ /** APS-mode ``storage`` methods the bridge forwards to ``/api/v1/storage/*``.
14
+ * ``get/set/delete/list`` are the KV family; ``files_*`` are the object/blob
15
+ * (APS Files) family, forwarded from an executa's ``files/*`` reverse-RPC. */
16
+ const STORAGE_APS_METHODS = new Set([
17
+ "get",
18
+ "set",
19
+ "delete",
20
+ "list",
21
+ "files_init",
22
+ "files_finalize",
23
+ "files_url",
24
+ "files_list",
25
+ "files_delete"
26
+ ]);
27
+ /** APS Files method → ``[httpMethod, subPath, payloadKind]``. Mirrors
28
+ * `anna-app-cli/src/executa/storage.ts` ROUTING and matrix `storage.py`
29
+ * so the app-harness path round-trips identically to `executa dev`. */
30
+ const FILES_ROUTES = {
31
+ files_init: [
32
+ "POST",
33
+ "/files/init",
34
+ "body"
35
+ ],
36
+ files_finalize: [
37
+ "POST",
38
+ "/files/finalize",
39
+ "body"
40
+ ],
41
+ files_url: [
42
+ "GET",
43
+ "/files/url",
44
+ "query"
45
+ ],
46
+ files_list: [
47
+ "GET",
48
+ "/files",
49
+ "query"
50
+ ],
51
+ files_delete: [
52
+ "DELETE",
53
+ "/files",
54
+ "query"
55
+ ]
56
+ };
57
+ /** App-direct ``anna.files.*`` method → ``[httpMethod, subPath, payloadKind]``.
58
+ * These are the iframe-facing method names (ns=``"files"``) the browser SDK
59
+ * emits via ``rt.call("files", method, args)``. Production nexus serves them
60
+ * through ``anna_app_rpc_handlers/files.py`` with ``scope="app"`` /
61
+ * ``owner_id=app_id`` (the handler ignores ``args.scope``); we force the same
62
+ * scope here so the harness round-trips identically. Distinct from
63
+ * ``FILES_ROUTES`` above, which is the executa reverse-RPC method naming
64
+ * (``files_init`` …) forwarded with the executa's own (default ``user``) scope. */
65
+ const FILES_APP_ROUTES = {
66
+ upload_init: [
67
+ "POST",
68
+ "/files/init",
69
+ "body"
70
+ ],
71
+ upload_finalize: [
72
+ "POST",
73
+ "/files/finalize",
74
+ "body"
75
+ ],
76
+ download_url: [
77
+ "GET",
78
+ "/files/url",
79
+ "query"
80
+ ],
81
+ list: [
82
+ "GET",
83
+ "/files",
84
+ "query"
85
+ ],
86
+ delete: [
87
+ "DELETE",
88
+ "/files",
89
+ "query"
90
+ ]
91
+ };
92
+ const VALID_STORAGE_SCOPES = new Set([
93
+ "user",
94
+ "app",
95
+ "tool"
96
+ ]);
13
97
  var LlmBridge = class {
14
98
  mintedAuto = new Map();
15
99
  mintedAgent = new Map();
@@ -44,18 +128,19 @@ var LlmBridge = class {
44
128
  * - ``aps``: the bridge claims storage and forwards to real nexus APS
45
129
  * via ``/api/v1/storage/*`` with a Bearer ``storage_token``. */
46
130
  handles(ns, method) {
47
- if (ns === "llm" && method === "complete") return true;
131
+ if (ns === "llm" && (method === "complete" || method === "embed")) return true;
48
132
  if (ns === "agent" && method.startsWith("session.")) return true;
49
133
  if (ns === "image" && (method === "generate" || method === "edit")) return true;
50
134
  if (ns === "upload" && (method === "inline" || method === "negotiate" || method === "confirm")) return true;
51
- if (ns === "storage" && this.opts.storageMode === "aps" && this.opts.mode === "real" && (method === "get" || method === "set" || method === "delete" || method === "list")) return true;
135
+ if (ns === "storage" && this.opts.storageMode === "aps" && this.opts.mode === "real" && STORAGE_APS_METHODS.has(method)) return true;
136
+ if (ns === "files" && this.opts.storageMode === "aps" && this.opts.mode === "real" && Object.prototype.hasOwnProperty.call(FILES_APP_ROUTES, method)) return true;
52
137
  return false;
53
138
  }
54
139
  /** Back-compat static alias — older code paths still call
55
140
  * ``LlmBridge.handles(ns, method)`` without an instance. Returns the
56
141
  * legacy (no-storage) decision since options aren't available here. */
57
142
  static handles(ns, method) {
58
- if (ns === "llm" && method === "complete") return true;
143
+ if (ns === "llm" && (method === "complete" || method === "embed")) return true;
59
144
  if (ns === "agent" && method.startsWith("session.")) return true;
60
145
  if (ns === "image" && (method === "generate" || method === "edit")) return true;
61
146
  if (ns === "upload" && (method === "inline" || method === "negotiate" || method === "confirm")) return true;
@@ -285,6 +370,14 @@ var LlmBridge = class {
285
370
  result
286
371
  };
287
372
  }
373
+ if (args.ns === "llm" && args.method === "embed") {
374
+ const ms = await this.mintComplete(args.windowUuid);
375
+ const result = await this.postJson(`${canonicalHost(acc.host)}/api/v1/copilot/app/embed`, ms.appSessionToken, args.args);
376
+ return {
377
+ ok: true,
378
+ result
379
+ };
380
+ }
288
381
  if (args.ns === "agent") switch (args.method) {
289
382
  case "session.create": {
290
383
  const ms = await this.mintAgent(args.args);
@@ -495,6 +588,94 @@ var LlmBridge = class {
495
588
  result: await res.json()
496
589
  };
497
590
  }
591
+ const froute = FILES_ROUTES[args.method];
592
+ if (froute) {
593
+ const [httpMethod, subPath, payloadKind] = froute;
594
+ const scope = typeof a.scope === "string" && VALID_STORAGE_SCOPES.has(a.scope) ? a.scope : "user";
595
+ const url = new URL(`${base}${subPath}`);
596
+ url.searchParams.set("scope", scope);
597
+ let jsonBody;
598
+ if (payloadKind === "query") for (const [k, v] of Object.entries(a)) {
599
+ if (k === "scope" || v === void 0 || v === null) continue;
600
+ url.searchParams.set(k, typeof v === "string" ? v : JSON.stringify(v));
601
+ }
602
+ else {
603
+ const inner = {};
604
+ for (const [k, v] of Object.entries(a)) {
605
+ if (k === "scope" || v === void 0) continue;
606
+ inner[k] = v;
607
+ }
608
+ jsonBody = JSON.stringify(inner);
609
+ }
610
+ const headers = { authorization: `Bearer ${sm.storageToken}` };
611
+ if (jsonBody !== void 0) headers["content-type"] = "application/json";
612
+ const res = await fetch(url, {
613
+ method: httpMethod,
614
+ headers,
615
+ body: jsonBody
616
+ });
617
+ if (!res.ok) {
618
+ const text = await res.text().catch(() => "");
619
+ throw new Error(`storage.${args.method} HTTP ${res.status}: ${text}`);
620
+ }
621
+ const ct = res.headers.get("content-type") ?? "";
622
+ if (args.method === "files_delete" && (res.status === 204 || !ct.includes("json"))) return {
623
+ ok: true,
624
+ result: { deleted: true }
625
+ };
626
+ return {
627
+ ok: true,
628
+ result: await res.json()
629
+ };
630
+ }
631
+ }
632
+ if (args.ns === "files") {
633
+ const froute = FILES_APP_ROUTES[args.method];
634
+ if (froute) {
635
+ const sm = await this.mintStorage();
636
+ const a = { ...args.args };
637
+ if (args.method === "upload_init" && a.size !== void 0 && a.size_bytes === void 0) {
638
+ a.size_bytes = a.size;
639
+ delete a.size;
640
+ }
641
+ const base = `${canonicalHost(this.account().host)}/api/v1/storage`;
642
+ const [httpMethod, subPath, payloadKind] = froute;
643
+ const url = new URL(`${base}${subPath}`);
644
+ url.searchParams.set("scope", "app");
645
+ let jsonBody;
646
+ if (payloadKind === "query") for (const [k, v] of Object.entries(a)) {
647
+ if (k === "scope" || v === void 0 || v === null) continue;
648
+ url.searchParams.set(k, typeof v === "string" ? v : JSON.stringify(v));
649
+ }
650
+ else {
651
+ const inner = {};
652
+ for (const [k, v] of Object.entries(a)) {
653
+ if (k === "scope" || v === void 0) continue;
654
+ inner[k] = v;
655
+ }
656
+ jsonBody = JSON.stringify(inner);
657
+ }
658
+ const headers = { authorization: `Bearer ${sm.storageToken}` };
659
+ if (jsonBody !== void 0) headers["content-type"] = "application/json";
660
+ const res = await fetch(url, {
661
+ method: httpMethod,
662
+ headers,
663
+ body: jsonBody
664
+ });
665
+ if (!res.ok) {
666
+ const text = await res.text().catch(() => "");
667
+ throw new Error(`files.${args.method} HTTP ${res.status}: ${text}`);
668
+ }
669
+ const ct = res.headers.get("content-type") ?? "";
670
+ if (args.method === "delete" && (res.status === 204 || !ct.includes("json"))) return {
671
+ ok: true,
672
+ result: { deleted: true }
673
+ };
674
+ return {
675
+ ok: true,
676
+ result: await res.json()
677
+ };
678
+ }
498
679
  }
499
680
  return {
500
681
  ok: false,
@@ -646,6 +827,11 @@ var HarnessServer = class {
646
827
  "llm",
647
828
  "complete"
648
829
  ],
830
+ [
831
+ "host.llm.embed",
832
+ "llm",
833
+ "embed"
834
+ ],
649
835
  [
650
836
  "host.agent.session.create",
651
837
  "agent",
@@ -715,6 +901,31 @@ var HarnessServer = class {
715
901
  "host.storage.list",
716
902
  "storage",
717
903
  "list"
904
+ ],
905
+ [
906
+ "host.storage.files_init",
907
+ "storage",
908
+ "files_init"
909
+ ],
910
+ [
911
+ "host.storage.files_finalize",
912
+ "storage",
913
+ "files_finalize"
914
+ ],
915
+ [
916
+ "host.storage.files_url",
917
+ "storage",
918
+ "files_url"
919
+ ],
920
+ [
921
+ "host.storage.files_list",
922
+ "storage",
923
+ "files_list"
924
+ ],
925
+ [
926
+ "host.storage.files_delete",
927
+ "storage",
928
+ "files_delete"
718
929
  ]
719
930
  ];
720
931
  for (const [hostMethod, ns, dispatchMethod] of HOST_OUTBOUND_ROUTES) this.bridge.onRequest(hostMethod, async (params) => {
@@ -1,5 +1,5 @@
1
1
  import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
- import { hostOf, requireAccount, withCode } from "./dev-account-DCyjamBa.js";
2
+ import { hostOf, requireAccount, withCode } from "./dev-account-qRaET1Cp.js";
3
3
  import { resolve } from "node:path";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
 
@@ -0,0 +1,87 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import "./client-Dn9zThOd.js";
3
+ import { resolveClient, withErrorHandling } from "./_lifecycle-shared-sbea9HtH.js";
4
+ import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
5
+
6
+ //#region src/api/tokens.ts
7
+ async function listTokens(client, includeRevoked = false) {
8
+ const r = await client.request({
9
+ path: "/api/v1/anna-apps/dev/tokens",
10
+ query: { include_revoked: includeRevoked ? "true" : void 0 }
11
+ });
12
+ return r.data?.tokens ?? [];
13
+ }
14
+ async function revokeToken(client, id) {
15
+ await client.request({
16
+ method: "POST",
17
+ path: `/api/v1/anna-apps/dev/tokens/${id}/revoke`,
18
+ allowStatuses: [204]
19
+ });
20
+ }
21
+ async function listScopes(client) {
22
+ const r = await client.request({ path: "/api/v1/anna-apps/dev/tokens/scopes" });
23
+ return r.data?.scopes ?? [];
24
+ }
25
+
26
+ //#endregion
27
+ //#region src/commands/token.ts
28
+ async function runTokenList(opts) {
29
+ return withErrorHandling(async () => {
30
+ const { client, host } = resolveClient({ account: opts.account });
31
+ const tokens = await listTokens(client, opts.includeRevoked ?? false);
32
+ if (opts.json) {
33
+ console.log(JSON.stringify({
34
+ host,
35
+ tokens
36
+ }, null, 2));
37
+ return 0;
38
+ }
39
+ console.log(bold(cyan(`PATs for ${host}`)));
40
+ if (tokens.length === 0) {
41
+ console.log(dim(` (no tokens — run \`anna-app login\` to mint one)`));
42
+ return 0;
43
+ }
44
+ for (const t of tokens) {
45
+ const scopes = Array.isArray(t.scopes) ? t.scopes.join(",") : t.scopes ?? "";
46
+ const tag = t.revoked_at ? red("[revoked]") : green("[active]");
47
+ const label = t.label ? ` "${t.label}"` : "";
48
+ console.log(` ${tag} #${t.id}${label} ${dim(`(scopes=[${scopes}], jti=${t.jti})`)}`);
49
+ }
50
+ return 0;
51
+ });
52
+ }
53
+ async function runTokenRevoke(opts) {
54
+ return withErrorHandling(async () => {
55
+ const { client, host } = resolveClient({ account: opts.account });
56
+ await revokeToken(client, opts.id);
57
+ if (opts.json) {
58
+ console.log(JSON.stringify({
59
+ host,
60
+ token_id: opts.id,
61
+ revoked: true
62
+ }, null, 2));
63
+ return 0;
64
+ }
65
+ console.log(yellow(`✓ token #${opts.id} revoked on ${host}`));
66
+ return 0;
67
+ });
68
+ }
69
+ async function runTokenScopes(opts) {
70
+ return withErrorHandling(async () => {
71
+ const { client, host } = resolveClient({ account: opts.account });
72
+ const scopes = await listScopes(client);
73
+ if (opts.json) {
74
+ console.log(JSON.stringify({
75
+ host,
76
+ scopes
77
+ }, null, 2));
78
+ return 0;
79
+ }
80
+ console.log(bold(cyan(`available PAT scopes`)) + " " + dim(`(${host})`));
81
+ for (const s of scopes) console.log(` ${green(s.name.padEnd(18))} ${dim(s.description)}`);
82
+ return 0;
83
+ });
84
+ }
85
+
86
+ //#endregion
87
+ export { runTokenList, runTokenRevoke, runTokenScopes };
@@ -0,0 +1,190 @@
1
+ import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
+ import { createApp, findAppBySlug, getApp } from "./apps-B1Nd8l_t.js";
3
+ import { CliError } from "./client-Dn9zThOd.js";
4
+ import { parseExecutaIdOverrides, readExecutasLock, substituteBundledRefs, validateBundledHandles, writeBundleToolIdSidecar, writeExecutasLock } from "./bundled-executas-BNOKw4kv.js";
5
+ import { runExecutaPublish } from "./executa-publish-Ca5V7MyA.js";
6
+ import { appCacheMatches, readAppIdentity, writeAppIdentity } from "./app-cache-BEM653Th.js";
7
+ import { resolve } from "node:path";
8
+ import { dim, green, yellow } from "kleur/colors";
9
+
10
+ //#region src/publish/working-orchestration.ts
11
+ async function resolveBundledExecutas(params) {
12
+ const { manifest, cwd, host, dryRun = false, freeze, json = false } = params;
13
+ const overrides = parseExecutaIdOverrides(params.executaId);
14
+ const active = !params.noBundledExecutas && (manifest.bundledExecutas.length > 0 || overrides.length > 0);
15
+ if (!active) return {
16
+ publishManifestRaw: manifest.raw,
17
+ resolved: {}
18
+ };
19
+ const { referenced } = validateBundledHandles(manifest.raw, manifest.bundledExecutas, overrides);
20
+ const overrideMap = new Map(overrides.map((o) => [o.handle, o.toolId]));
21
+ const lock = readExecutasLock(cwd, host);
22
+ const resolved = {};
23
+ const lockEntries = {};
24
+ for (const decl of manifest.bundledExecutas) {
25
+ if (overrideMap.has(decl.handle)) {
26
+ const id = overrideMap.get(decl.handle);
27
+ resolved[decl.handle] = id;
28
+ lockEntries[decl.handle] = {
29
+ tool_id: id,
30
+ path: decl.path,
31
+ source: "override"
32
+ };
33
+ if (!json) console.log(dim(` bundled:${decl.handle} → ${id} (override)`));
34
+ continue;
35
+ }
36
+ if (params.skipExecutaPublish) {
37
+ const cachedId = lock?.executas[decl.handle]?.tool_id;
38
+ if (!cachedId) throw new CliError(`--skip-executa-publish set but no cached tool_id for "${decl.handle}" in .anna/executas.lock.json (run without --skip-executa-publish first, or pass --executa-id ${decl.handle}=<tool-id>)`, 4);
39
+ resolved[decl.handle] = cachedId;
40
+ lockEntries[decl.handle] = {
41
+ tool_id: cachedId,
42
+ path: decl.path,
43
+ source: "cache"
44
+ };
45
+ if (!json) console.log(dim(` bundled:${decl.handle} → ${cachedId} (cache)`));
46
+ continue;
47
+ }
48
+ const execCwd = resolve(cwd, decl.path);
49
+ if (dryRun) {
50
+ if (!json) console.log(yellow(` [dry-run] would publish bundled executa "${decl.handle}" from ${decl.path}`));
51
+ continue;
52
+ }
53
+ if (!json) console.log(dim(` ${freeze ? "publishing" : "registering"} bundled executa "${decl.handle}" (${decl.path})${freeze ? "" : " [no-freeze]"}…`));
54
+ let captured;
55
+ const code = await runExecutaPublish({
56
+ cwd: execCwd,
57
+ account: params.account,
58
+ quiet: true,
59
+ freeze,
60
+ capture: (r) => {
61
+ captured = r;
62
+ }
63
+ });
64
+ if (code !== 0 || !captured) throw new CliError(`failed to ${freeze ? "publish" : "register"} bundled executa "${decl.handle}" (${decl.path})`, code === 0 ? 4 : code);
65
+ resolved[decl.handle] = captured.tool_id;
66
+ lockEntries[decl.handle] = {
67
+ tool_id: captured.tool_id,
68
+ path: decl.path,
69
+ source: params.lockSource ?? (freeze ? "published" : "working")
70
+ };
71
+ if (!json) console.log(green(` ✓ bundled:${decl.handle} → ${captured.tool_id}`) + dim(` (v${captured.version}${captured.idempotent_hit ? ", unchanged" : ""})`));
72
+ }
73
+ for (const o of overrides) if (!(o.handle in resolved)) {
74
+ resolved[o.handle] = o.toolId;
75
+ lockEntries[o.handle] = {
76
+ tool_id: o.toolId,
77
+ source: "override"
78
+ };
79
+ if (!json) console.log(dim(` bundled:${o.handle} → ${o.toolId} (override)`));
80
+ }
81
+ const missing = [...referenced].filter((h) => !(h in resolved));
82
+ if (missing.length > 0 && !dryRun) throw new CliError(`could not resolve bundled handle(s): ${missing.sort().join(", ")}`, 4);
83
+ let publishManifestRaw = manifest.raw;
84
+ if (!dryRun) {
85
+ const merged = {
86
+ $schema: "anna-executas-lock/v1",
87
+ host: canonicalHost(host),
88
+ executas: {
89
+ ...lock?.executas ?? {},
90
+ ...lockEntries
91
+ }
92
+ };
93
+ writeExecutasLock(cwd, merged);
94
+ publishManifestRaw = substituteBundledRefs(manifest.raw, resolved);
95
+ const bundleEntry = manifest.raw.ui?.bundle?.entry;
96
+ if (bundleEntry && Object.keys(resolved).length > 0) {
97
+ const sidecar = writeBundleToolIdSidecar(resolve(cwd, "bundle"), resolved);
98
+ if (!json) console.log(dim(` wrote ${sidecar.replace(cwd + "/", "")}`));
99
+ }
100
+ }
101
+ return {
102
+ publishManifestRaw,
103
+ resolved
104
+ };
105
+ }
106
+ /**
107
+ * Resolve (creating if absent) the `AnnaApp` row, persisting the identity
108
+ * cache before any subsequent network call. Returns `null` only in dry-run
109
+ * mode when the app does not exist yet (nothing to create).
110
+ */
111
+ async function resolveAppIdentity(params) {
112
+ const { client, cwd, host, manifest, dryRun = false, json = false } = params;
113
+ let cached = readAppIdentity(cwd);
114
+ if (cached && cached.host !== canonicalHost(host)) cached = null;
115
+ if (cached && appCacheMatches(cached, host, manifest.slug)) try {
116
+ const live = await getApp(client, cached.app_id);
117
+ return {
118
+ appId: live.id,
119
+ serverSlug: live.slug,
120
+ firstPublish: false,
121
+ fromCache: true
122
+ };
123
+ } catch (e) {
124
+ if (!(e instanceof CliError && e.status === 404)) throw e;
125
+ if (!json) console.log(yellow(`↻ cached app_id=${cached.app_id} (slug=${cached.slug}) no longer exists on ${host}; re-resolving by slug…`));
126
+ }
127
+ const found = await findAppBySlug(client, manifest.slug);
128
+ let appId;
129
+ let serverSlug;
130
+ let firstPublish = false;
131
+ if (found) {
132
+ appId = found.id;
133
+ serverSlug = found.slug;
134
+ } else {
135
+ if (dryRun) {
136
+ if (!json) console.log(yellow(`[dry-run] would create AnnaApp slug=${manifest.slug}`));
137
+ return null;
138
+ }
139
+ const created = await createApp(client, {
140
+ slug: manifest.slug,
141
+ name: manifest.name,
142
+ category: manifest.category,
143
+ tagline: manifest.tagline,
144
+ description: manifest.description
145
+ });
146
+ appId = created.id;
147
+ serverSlug = created.slug;
148
+ firstPublish = true;
149
+ }
150
+ writeAppIdentity(cwd, {
151
+ $schema: "anna-app-identity/v1",
152
+ host: canonicalHost(host),
153
+ app_id: appId,
154
+ slug: serverSlug,
155
+ first_published_at: cached?.first_published_at ?? new Date().toISOString()
156
+ });
157
+ return {
158
+ appId,
159
+ serverSlug,
160
+ firstPublish,
161
+ fromCache: false
162
+ };
163
+ }
164
+ /**
165
+ * Resolve an EXISTING app via an explicit `slug` or the local
166
+ * `.anna/app.json` identity cache (no create). Used by verbs that operate on
167
+ * an already-pushed app (`cut`, `discard`). Throws `CliError(…, 5)` when the
168
+ * app cannot be resolved.
169
+ */
170
+ async function resolveAppBySlugOrCache(client, host, opts) {
171
+ if (opts.slug) {
172
+ const app = await findAppBySlug(client, opts.slug);
173
+ if (!app) throw new CliError(`no app with slug "${opts.slug}" on ${host}`, 5);
174
+ return getApp(client, app.id);
175
+ }
176
+ const cwd = resolve(opts.cwd ?? process.cwd());
177
+ const cached = readAppIdentity(cwd);
178
+ if (cached && cached.host === canonicalHost(host)) try {
179
+ return await getApp(client, cached.app_id);
180
+ } catch (e) {
181
+ if (e instanceof CliError && e.status === 404) {
182
+ const app = await findAppBySlug(client, cached.slug);
183
+ if (app) return getApp(client, app.id);
184
+ } else throw e;
185
+ }
186
+ throw new CliError("cannot resolve app — pass --slug <slug> or run from a project dir with a .anna/app.json cache (run `anna-app apps push` first)", 5);
187
+ }
188
+
189
+ //#endregion
190
+ export { resolveAppBySlugOrCache, resolveAppIdentity, resolveBundledExecutas };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anna-ai/cli",
3
- "version": "0.1.17",
3
+ "version": "0.1.21",
4
4
  "description": "Anna App developer CLI: scaffold, validate, harness (Phase 2 MVP: init + validate).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,8 +32,8 @@
32
32
  "prepublishOnly": "pnpm lint && pnpm test && pnpm build"
33
33
  },
34
34
  "dependencies": {
35
- "@anna-ai/app-runtime": "^0.2.0",
36
- "@anna-ai/app-schema": "^0.4.0",
35
+ "@anna-ai/app-runtime": "^0.5.0",
36
+ "@anna-ai/app-schema": "^0.8.0",
37
37
  "ajv": "^8.17.1",
38
38
  "ajv-formats": "^3.0.1",
39
39
  "commander": "^12.1.0",
@@ -1,4 +1,9 @@
1
1
  {
2
+ "slug": "__SLUG__",
3
+ "name": "__SLUG__",
4
+ "version": "0.1.0",
5
+ "executa_type": "tool",
6
+ "description": "",
2
7
  "tool_id": "__TOOL_ID__",
3
8
  "type": "go"
4
9
  }
@@ -1,4 +1,9 @@
1
1
  {
2
+ "slug": "__SLUG__",
3
+ "name": "__SLUG__",
4
+ "version": "0.1.0",
5
+ "executa_type": "tool",
6
+ "description": "",
2
7
  "tool_id": "__TOOL_ID__",
3
8
  "type": "node"
4
9
  }
@@ -1,4 +1,9 @@
1
1
  {
2
+ "slug": "__SLUG__",
3
+ "name": "__SLUG__",
4
+ "version": "0.1.0",
5
+ "executa_type": "tool",
6
+ "description": "",
2
7
  "tool_id": "__TOOL_ID__",
3
8
  "type": "python"
4
9
  }