@anna-ai/cli 0.1.32 → 0.1.34

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 (68) hide show
  1. package/dist/{_lifecycle-shared-CUK2CE76.js → _lifecycle-shared-RyiRITI5.js} +4 -2
  2. package/dist/{account-bqao7Gp5.js → account-B4rdVDyg.js} +1 -1
  3. package/dist/{agent-CzPryLi6.js → agent-DAOr_aiE.js} +1 -1
  4. package/dist/{app-bundle-upload-CqT9KUOl.js → app-bundle-upload-CrBRnzf4.js} +3 -3
  5. package/dist/{apps-dvknZYI2.js → apps-C_Oln_om.js} +1 -1
  6. package/dist/apps-cut-C14A3V93.js +159 -0
  7. package/dist/{apps-destructive-B6RFjokU.js → apps-destructive-a2YBmWFm.js} +2 -2
  8. package/dist/{apps-discard-hVZxAQyj.js → apps-discard-B49iiOX6.js} +12 -10
  9. package/dist/{apps-grants-DnXCnkH5.js → apps-grants-lmllFB02.js} +1 -1
  10. package/dist/apps-publish-B9CnifEx.js +17 -0
  11. package/dist/{apps-publish-BKqVsS1W.js → apps-publish-qc_E4b_R.js} +7 -7
  12. package/dist/{apps-push-B3wZr9B4.js → apps-push-DnINd0Cx.js} +14 -12
  13. package/dist/{apps-release-DZK30eF_.js → apps-release--7R2cmPg.js} +7 -6
  14. package/dist/{apps-rename-slug-D2BrWasG.js → apps-rename-slug-CGykNHM5.js} +3 -3
  15. package/dist/{apps-status-Ca6MdXZd.js → apps-status-CcCoYu6i.js} +2 -2
  16. package/dist/{apps-submit-review-DMFQI8JZ.js → apps-submit-review-DNrQc2ws.js} +2 -2
  17. package/dist/{apps-sync-meta-BOb6J_wz.js → apps-sync-meta-dy9j-5n8.js} +4 -4
  18. package/dist/{apps-versions-D1lixrmM.js → apps-versions-2BR8xo7C.js} +1 -1
  19. package/dist/binary-upload-CKMU5Rfw.js +181 -0
  20. package/dist/bridge-CzeSuRRL.js +3 -0
  21. package/dist/{bridge-CbjqAm0H.js → bridge-Dvck0e9c.js} +1 -1
  22. package/dist/{bundled-executas-06keEPP_.js → bundled-executas-Bp9Z6aiR.js} +1 -1
  23. package/dist/cli.js +74 -41
  24. package/dist/dev-CZLOk2qE.js +4 -0
  25. package/dist/{dev-Re6NyNDS.js → dev-GUFWek03.js} +5 -5
  26. package/dist/{dev-app-cache-Dv1dZ_bw.js → dev-app-cache-B3Wa5t6X.js} +18 -3
  27. package/dist/{doctor-VrfEkedz.js → doctor-C3Nx8E2L.js} +1 -1
  28. package/dist/{executa-cache-B0O79Hpb.js → executa-cache-WBkCLic7.js} +1 -1
  29. package/dist/{executa-destructive-Bz7vWxEO.js → executa-destructive-DCfQ1ez5.js} +3 -3
  30. package/dist/{executa-dev-B5Uz982M.js → executa-dev-CAw5X64R.js} +8 -8
  31. package/dist/{executa-install-BW9jjY7u.js → executa-install-Dz7hBUsn.js} +2 -2
  32. package/dist/executa-install-nOFLBwxv.js +7 -0
  33. package/dist/{executa-publish-Ah6J7jYd.js → executa-publish-CGz5wM1Q.js} +20 -4
  34. package/dist/executa-publish-DHICU6S0.js +11 -0
  35. package/dist/{executa-reads-BXDJWWw6.js → executa-reads-BmwT_Yjp.js} +2 -2
  36. package/dist/executa-trusted-publisher-tVwzjI2d.js +91 -0
  37. package/dist/executa-upload-binaries-CYMtWJU7.js +9 -0
  38. package/dist/executa-upload-binaries-Do5Sq64E.js +277 -0
  39. package/dist/{manifest-DOMrcdCK.js → manifest-DlIcwLQs.js} +56 -1
  40. package/dist/{publish-GpvkaBIx.js → publish-DW_D3uQ4.js} +12 -10
  41. package/dist/{server-BMcGaUTy.js → server-BHWGGZsf.js} +1 -1
  42. package/dist/{storage-BkF4XyI0.js → storage-aii8EyPC.js} +1 -1
  43. package/dist/{token-uedeaAy7.js → token-COnYMRB-.js} +1 -1
  44. package/dist/{working-orchestration-1jzRuk34.js → working-orchestration-B8KdAh5S.js} +5 -5
  45. package/package.json +1 -1
  46. package/dist/apps-cut-8870xpk-.js +0 -86
  47. package/dist/apps-publish-Djpw5cfG.js +0 -15
  48. package/dist/bridge-CzHB_IsQ.js +0 -3
  49. package/dist/dev-C3yWhzAp.js +0 -4
  50. package/dist/executa-install-Cyid8R7r.js +0 -7
  51. package/dist/executa-publish-DI_6M_ea.js +0 -9
  52. /package/dist/{app-cache-BqnTkxUw.js → app-cache-Cr3tKbux.js} +0 -0
  53. /package/dist/{bundled-executas-_H1A9M50.js → bundled-executas-rSIkaA6-.js} +0 -0
  54. /package/dist/{confirm-BuT56E_B.js → confirm-BSoOy1X2.js} +0 -0
  55. /package/dist/{dev-account-DqEFCmVg.js → dev-account-CfVdGa9j.js} +0 -0
  56. /package/dist/{executa-cache-Do1D7xC0.js → executa-cache-BFoUtb4J.js} +0 -0
  57. /package/dist/{executa-init-D-kWINZ3.js → executa-init-DKy7DMSD.js} +0 -0
  58. /package/dist/{executa-register-D5kvSSz_.js → executa-register-CmqKsoAU.js} +0 -0
  59. /package/dist/{executas-BhFed1B-.js → executas-CvQY_w2U.js} +0 -0
  60. /package/dist/{fixture-BUKQYR5e.js → fixture-BF1Z0Rry.js} +0 -0
  61. /package/dist/{host_upload-BVzAqvX1.js → host_upload-DrbQ-vk2.js} +0 -0
  62. /package/dist/{image-BLPyUbGQ.js → image-B4UrufYH.js} +0 -0
  63. /package/dist/{listing-meta-BlY7XMrw.js → listing-meta-C0HsNjIB.js} +0 -0
  64. /package/dist/{login-DhgBgsfx.js → login-DbETuUxy.js} +0 -0
  65. /package/dist/{logout-VYDkLYAx.js → logout-BzIEf-HU.js} +0 -0
  66. /package/dist/{runner-BxYjAmih.js → runner-Bka_coxb.js} +0 -0
  67. /package/dist/{sampling-XBIEYU4A.js → sampling-BpJcd6BC.js} +0 -0
  68. /package/dist/{whoami-C_x26b_k.js → whoami-Bn38ns8r.js} +0 -0
@@ -1,9 +1,10 @@
1
1
  import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
2
  import { CliError } from "./client-D-_z1ALk.js";
3
- import { resolveClient, withErrorHandling } from "./_lifecycle-shared-CUK2CE76.js";
4
- import { commitDraft, createDraft, getMySkill, getMyTool, listToolVersions, publishToolVersion, setSkillVisibility, setToolVisibility, updateMySkill, updateMyTool } from "./executas-BhFed1B-.js";
5
- import { executaCacheMatches, invalidateExecutaCache, mintIdempotencyKey, readExecutaIdentity, writeExecutaIdentity } from "./executa-cache-Do1D7xC0.js";
6
- import { loadExecutaManifest } from "./manifest-DOMrcdCK.js";
3
+ import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
4
+ import { executaCacheMatches, invalidateExecutaCache, mintIdempotencyKey, readExecutaIdentity, writeExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
5
+ import { loadExecutaManifest } from "./manifest-DlIcwLQs.js";
6
+ import { commitDraft, createDraft, getMySkill, getMyTool, listToolVersions, publishToolVersion, setSkillVisibility, setToolVisibility, updateMySkill, updateMyTool } from "./executas-CvQY_w2U.js";
7
+ import { uploadExecutaBinaries } from "./executa-upload-binaries-Do5Sq64E.js";
7
8
  import { join, relative, resolve, sep } from "node:path";
8
9
  import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
9
10
  import { bold, cyan, dim, green, yellow } from "kleur/colors";
@@ -206,6 +207,21 @@ async function runExecutaPublish(opts) {
206
207
  description: manifest.description,
207
208
  ...distributionPayload(manifest.distribution)
208
209
  });
210
+ if (!isSkill && manifest.distribution?.binaryArtifacts && opts.freeze !== false) {
211
+ const upLog = (msg) => {
212
+ if (!opts.json && !opts.quiet) console.log(msg);
213
+ };
214
+ const up = await uploadExecutaBinaries({
215
+ client,
216
+ host,
217
+ dir: cwd,
218
+ manifest,
219
+ toolId,
220
+ log: upLog
221
+ });
222
+ upLog(green(`✓ binary artifacts published `) + dim(`(${up.uploaded.length} uploaded, ${up.skipped.length} cached)`));
223
+ }
224
+ if (!isSkill && manifest.distribution?.type === "binary" && manifest.distribution?.binaryUrls && !opts.json && !opts.quiet) console.log(yellow("! distribution.binary_urls uses the pull-mirror track: the platform fetches these URLs at cut/release, which requires them to be PUBLICLY downloadable. Private source URLs are deprecated — switch to distribution.binary_artifacts (direct upload) if this source is (or may become) private."));
209
225
  const preFreeze = isSkill ? await getMySkill(client, executaId) : await getMyTool(client, executaId);
210
226
  const currentVis = preFreeze.visibility ?? "private";
211
227
  const targetVis = opts.publish ? "public" : currentVis === "public" ? "public" : "app_bundled";
@@ -0,0 +1,11 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import "./client-D-_z1ALk.js";
3
+ import "./_lifecycle-shared-RyiRITI5.js";
4
+ import "./executa-cache-BFoUtb4J.js";
5
+ import "./manifest-DlIcwLQs.js";
6
+ import "./executas-CvQY_w2U.js";
7
+ import { runExecutaPublish } from "./executa-publish-CGz5wM1Q.js";
8
+ import "./binary-upload-CKMU5Rfw.js";
9
+ import "./executa-upload-binaries-Do5Sq64E.js";
10
+
11
+ export { runExecutaPublish };
@@ -1,7 +1,7 @@
1
1
  import "./credentials-BTv2IfUZ.js";
2
2
  import { CliError } from "./client-D-_z1ALk.js";
3
- import { resolveClient, withErrorHandling } from "./_lifecycle-shared-CUK2CE76.js";
4
- import { getMyTool, listMyTools, listToolVersions } from "./executas-BhFed1B-.js";
3
+ import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
4
+ import { getMyTool, listMyTools, listToolVersions } from "./executas-CvQY_w2U.js";
5
5
  import { bold, cyan, dim, green } from "kleur/colors";
6
6
 
7
7
  //#region src/commands/executa-reads.ts
@@ -0,0 +1,91 @@
1
+ import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
+ import { CliError } from "./client-D-_z1ALk.js";
3
+ import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
4
+ import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
5
+ import { addTrustedPublisher, listTrustedPublishers, removeTrustedPublisher } from "./binary-upload-CKMU5Rfw.js";
6
+ import { resolve } from "node:path";
7
+ import { bold, cyan, dim, green } from "kleur/colors";
8
+
9
+ //#region src/commands/executa-trusted-publisher.ts
10
+ function resolveToolId(opts, host) {
11
+ if (opts.toolId) return opts.toolId;
12
+ const dir = resolve(opts.dir ?? process.cwd());
13
+ const identity = readExecutaIdentity(dir);
14
+ if (!identity) throw new CliError(`no .anna/executa.json identity cache in ${dir} — run \`anna-app executa publish\` / \`anna-app apps push\` once, or pass --tool-id`, 4);
15
+ if (identity.host !== canonicalHost(host)) throw new CliError(`identity cache in ${dir} belongs to ${identity.host}, but the current PAT targets ${canonicalHost(host)} — use --account or pass --tool-id`, 4);
16
+ return identity.tool_id;
17
+ }
18
+ function setup(opts) {
19
+ const { client, host } = resolveClient({
20
+ account: opts.account,
21
+ fetchImpl: opts.fetchImpl
22
+ });
23
+ return {
24
+ client,
25
+ host,
26
+ toolId: resolveToolId(opts, host)
27
+ };
28
+ }
29
+ function printPublisher(p) {
30
+ console.log(` #${p.id} ${bold(p.repository)} ${cyan(p.workflow)}` + (p.environment ? dim(` env=${p.environment}`) : dim(" env=(any)")) + (p.last_used_at ? dim(` last used ${p.last_used_at}`) : dim(" never used")));
31
+ }
32
+ async function runTrustedPublisherAdd(opts) {
33
+ return withErrorHandling(async () => {
34
+ if (!/^[^/\s]+\/[^/\s]+$/.test(opts.repository)) throw new CliError(`--repository must be "owner/repo" (got "${opts.repository}")`, 2);
35
+ const { client, toolId } = setup(opts);
36
+ const created = await addTrustedPublisher(client, toolId, {
37
+ repository: opts.repository,
38
+ workflow: opts.workflow,
39
+ environment: opts.environment
40
+ });
41
+ if (opts.json) {
42
+ console.log(JSON.stringify({
43
+ tool_id: toolId,
44
+ trusted_publisher: created
45
+ }, null, 2));
46
+ return 0;
47
+ }
48
+ console.log(green(`✓ trusted publisher registered for ${toolId}:`));
49
+ printPublisher(created);
50
+ console.log(dim("→ that workflow can now run ") + cyan("anna-app executa upload-binaries --oidc") + dim(" with `permissions: id-token: write` and no PAT"));
51
+ return 0;
52
+ });
53
+ }
54
+ async function runTrustedPublisherList(opts) {
55
+ return withErrorHandling(async () => {
56
+ const { client, toolId } = setup(opts);
57
+ const rows = await listTrustedPublishers(client, toolId);
58
+ if (opts.json) {
59
+ console.log(JSON.stringify({
60
+ tool_id: toolId,
61
+ trusted_publishers: rows
62
+ }, null, 2));
63
+ return 0;
64
+ }
65
+ if (rows.length === 0) {
66
+ console.log(dim(`(no trusted publishers registered for ${toolId})`));
67
+ return 0;
68
+ }
69
+ console.log(bold(`trusted publishers for ${toolId}:`));
70
+ for (const p of rows) printPublisher(p);
71
+ return 0;
72
+ });
73
+ }
74
+ async function runTrustedPublisherRemove(opts) {
75
+ return withErrorHandling(async () => {
76
+ const { client, toolId } = setup(opts);
77
+ await removeTrustedPublisher(client, toolId, opts.publisherId);
78
+ if (opts.json) {
79
+ console.log(JSON.stringify({
80
+ tool_id: toolId,
81
+ removed: opts.publisherId
82
+ }, null, 2));
83
+ return 0;
84
+ }
85
+ console.log(green(`✓ removed trusted publisher #${opts.publisherId} from ${toolId}`));
86
+ return 0;
87
+ });
88
+ }
89
+
90
+ //#endregion
91
+ export { runTrustedPublisherAdd, runTrustedPublisherList, runTrustedPublisherRemove };
@@ -0,0 +1,9 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import "./client-D-_z1ALk.js";
3
+ import "./_lifecycle-shared-RyiRITI5.js";
4
+ import "./executa-cache-BFoUtb4J.js";
5
+ import "./manifest-DlIcwLQs.js";
6
+ import "./binary-upload-CKMU5Rfw.js";
7
+ import { expandArtifactPath, resolveArtifactPlan, runExecutaUploadBinaries, uploadExecutaBinaries, uploadPlannedParts } from "./executa-upload-binaries-Do5Sq64E.js";
8
+
9
+ export { runExecutaUploadBinaries };
@@ -0,0 +1,277 @@
1
+ import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
+ import { CliError } from "./client-D-_z1ALk.js";
3
+ import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
4
+ import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
5
+ import { loadExecutaManifest } from "./manifest-DlIcwLQs.js";
6
+ import { fetchGitHubIdToken, finalizeBinaryUpload, initBinaryUpload, mintUploadSession, mintUploadSessionViaOidc, uploadBinaryPart } from "./binary-upload-CKMU5Rfw.js";
7
+ import { isAbsolute, resolve } from "node:path";
8
+ import { closeSync, createReadStream, existsSync, openSync, readSync, statSync } from "node:fs";
9
+ import { bold, cyan, dim, green, yellow } from "kleur/colors";
10
+ import { createHash } from "node:crypto";
11
+
12
+ //#region src/commands/executa-upload-binaries.ts
13
+ const MAX_ARTIFACT_BYTES = 1024 * 1024 * 1024;
14
+ const PART_RETRIES = 3;
15
+ /** Expand `{version}` / `{platform}` / `{tool_id}` placeholders. */
16
+ function expandArtifactPath(template, vars) {
17
+ return template.replaceAll("{version}", vars.version).replaceAll("{platform}", vars.platform).replaceAll("{tool_id}", vars.toolId);
18
+ }
19
+ async function sha256File(path) {
20
+ const hash = createHash("sha256");
21
+ for await (const chunk of createReadStream(path)) hash.update(chunk);
22
+ return hash.digest("hex");
23
+ }
24
+ /**
25
+ * Resolve `distribution.binary_artifacts` into a validated, hashed upload
26
+ * plan. Throws `CliError(4)` on missing files / oversize artifacts.
27
+ */
28
+ async function resolveArtifactPlan(manifest, dir, toolId) {
29
+ const artifacts = manifest.distribution?.binaryArtifacts;
30
+ if (!artifacts || Object.keys(artifacts).length === 0) throw new CliError("executa.json declares no `distribution.binary_artifacts` — this command pushes LOCAL build artifacts directly to the platform. Add e.g.\n \"distribution\": { \"type\": \"binary\", \"binary_artifacts\": {\n \"darwin-arm64\": { \"path\": \"dist/my-tool-{version}-darwin-arm64.tar.gz\", \"entrypoint\": \"my-tool\" }\n } }\n(projects using public `binary_urls` are pull-mirrored at `apps cut` instead)", 4);
31
+ const out = [];
32
+ for (const [platform, art] of Object.entries(artifacts)) {
33
+ const expanded = expandArtifactPath(art.path, {
34
+ version: manifest.version,
35
+ platform,
36
+ toolId
37
+ });
38
+ const absPath = isAbsolute(expanded) ? expanded : resolve(dir, expanded);
39
+ if (!existsSync(absPath)) throw new CliError(`binary_artifacts["${platform}"]: ${absPath} does not exist — build the artifact first (path placeholders: {version} {platform} {tool_id})`, 4);
40
+ const st = statSync(absPath);
41
+ if (!st.isFile()) throw new CliError(`binary_artifacts["${platform}"]: ${absPath} is not a file`, 4);
42
+ if (st.size <= 0 || st.size > MAX_ARTIFACT_BYTES) throw new CliError(`binary_artifacts["${platform}"]: ${absPath} is ${st.size} bytes — must be 1..${MAX_ARTIFACT_BYTES} (1 GiB)`, 4);
43
+ const filename = absPath.split(/[\\/]/).pop();
44
+ out.push({
45
+ platform,
46
+ absPath,
47
+ decl: {
48
+ filename,
49
+ sha256: await sha256File(absPath),
50
+ byte_size: st.size,
51
+ format: art.format,
52
+ entrypoint: art.entrypoint
53
+ }
54
+ });
55
+ }
56
+ out.sort((a, b) => a.platform.localeCompare(b.platform));
57
+ return out;
58
+ }
59
+ /** Read one part (byte range) of a file into a fresh buffer. */
60
+ function readFilePart(path, start, length) {
61
+ const buf = Buffer.alloc(length);
62
+ const fd = openSync(path, "r");
63
+ try {
64
+ let off = 0;
65
+ while (off < length) {
66
+ const n = readSync(fd, buf, off, length - off, start + off);
67
+ if (n <= 0) break;
68
+ off += n;
69
+ }
70
+ if (off !== length) throw new CliError(`short read on ${path} (${off}/${length} bytes)`, 4);
71
+ return buf;
72
+ } finally {
73
+ closeSync(fd);
74
+ }
75
+ }
76
+ /**
77
+ * Drive parts for every platform the init plan marked `upload`.
78
+ * Sequential parts with per-part retry — resume comes from `uploaded_parts`.
79
+ */
80
+ async function uploadPlannedParts(t, toolId, plan, init, log, retryDelayMs = 500) {
81
+ const outcome = {
82
+ uploadId: init.upload_id,
83
+ skipped: [],
84
+ uploaded: [],
85
+ partsSent: 0
86
+ };
87
+ for (const entry of plan) {
88
+ const asset = init.assets[entry.platform];
89
+ if (!asset) throw new CliError(`server plan is missing platform ${entry.platform}`, 6);
90
+ if (asset.status === "exists") {
91
+ outcome.skipped.push(entry.platform);
92
+ log(dim(`⏭ ${entry.platform}: skipped (content already published)`));
93
+ continue;
94
+ }
95
+ const partSize = asset.part_size;
96
+ const partCount = asset.part_count;
97
+ const already = new Set(asset.uploaded_parts ?? []);
98
+ if (already.size > 0) log(dim(`↩ ${entry.platform}: resuming — ${already.size}/${partCount} part(s) already received`));
99
+ for (let index = 0; index < partCount; index++) {
100
+ if (already.has(index)) continue;
101
+ const start = index * partSize;
102
+ const length = Math.min(partSize, entry.decl.byte_size - start);
103
+ const bytes = readFilePart(entry.absPath, start, length);
104
+ let lastError;
105
+ let sent = false;
106
+ for (let attempt = 1; attempt <= PART_RETRIES; attempt++) try {
107
+ await uploadBinaryPart(t, toolId, init.upload_id, entry.platform, index, bytes);
108
+ sent = true;
109
+ break;
110
+ } catch (e) {
111
+ lastError = e;
112
+ const status = e.status;
113
+ if (status && status < 500 && status !== 429) throw e;
114
+ if (attempt < PART_RETRIES && retryDelayMs > 0) await new Promise((r) => setTimeout(r, retryDelayMs * attempt));
115
+ }
116
+ if (!sent) throw lastError;
117
+ outcome.partsSent += 1;
118
+ log(dim(`↑ ${entry.platform}: part ${index + 1}/${partCount} (${length} bytes)`));
119
+ }
120
+ outcome.uploaded.push(entry.platform);
121
+ }
122
+ return outcome;
123
+ }
124
+ /**
125
+ * Reusable direct-upload flow: hash artifacts → mint upload token → init →
126
+ * push missing parts → finalize. Shared by the standalone
127
+ * `executa upload-binaries` command, `executa publish` (pre-freeze upload)
128
+ * and `apps cut` (bundled-executa pre-upload).
129
+ *
130
+ * Throws `CliError` on any failure; all-`exists` plans finalize instantly
131
+ * (content-addressed idempotency), so re-runs are cheap.
132
+ */
133
+ async function uploadExecutaBinaries(opts) {
134
+ const log = opts.log ?? (() => {});
135
+ const { host, dir, manifest, toolId } = opts;
136
+ log(dim(`hashing artifacts for ${toolId} (v${manifest.version})…`));
137
+ const plan = await resolveArtifactPlan(manifest, dir, toolId);
138
+ let uploadToken = opts.uploadToken;
139
+ if (!uploadToken) {
140
+ if (!opts.client) throw new CliError("uploadExecutaBinaries needs either a PAT client or a pre-minted uploadToken", 2);
141
+ ({upload_token: uploadToken} = await mintUploadSession(opts.client, toolId));
142
+ }
143
+ const transport = {
144
+ host,
145
+ uploadToken,
146
+ fetchImpl: opts.fetchImpl
147
+ };
148
+ const assets = {};
149
+ for (const e of plan) assets[e.platform] = e.decl;
150
+ const init = await initBinaryUpload(transport, toolId, {
151
+ executa_version: manifest.version,
152
+ assets,
153
+ dry_run: opts.dryRun === true
154
+ });
155
+ const planOut = {};
156
+ for (const e of plan) planOut[e.platform] = {
157
+ ...e.decl,
158
+ status: init.assets[e.platform]?.status
159
+ };
160
+ if (opts.dryRun) return {
161
+ toolId,
162
+ version: manifest.version,
163
+ uploadId: init.upload_id,
164
+ dryRun: true,
165
+ skipped: plan.filter((e) => init.assets[e.platform]?.status === "exists").map((e) => e.platform),
166
+ uploaded: [],
167
+ plan: planOut,
168
+ assets: {}
169
+ };
170
+ const outcome = await uploadPlannedParts(transport, toolId, plan, init, log);
171
+ const finalized = await finalizeBinaryUpload(transport, toolId, init.upload_id);
172
+ return {
173
+ toolId,
174
+ version: manifest.version,
175
+ uploadId: init.upload_id,
176
+ dryRun: false,
177
+ skipped: outcome.skipped,
178
+ uploaded: outcome.uploaded,
179
+ plan: planOut,
180
+ assets: finalized.assets
181
+ };
182
+ }
183
+ async function runExecutaUploadBinaries(opts) {
184
+ return withErrorHandling(async () => {
185
+ const dir = resolve(opts.dir ?? process.cwd());
186
+ const manifest = loadExecutaManifest(dir);
187
+ const log = (msg) => {
188
+ if (!opts.json) console.log(msg);
189
+ };
190
+ let client;
191
+ let host;
192
+ let uploadToken;
193
+ if (opts.oidc) {
194
+ const rawHost = opts.host ?? process.env.ANNA_APP_HOST;
195
+ if (!rawHost) throw new CliError("--oidc needs the Nexus host — pass --host or set ANNA_APP_HOST", 2);
196
+ host = canonicalHost(rawHost);
197
+ log(dim(`using GitHub OIDC trusted publishing (host=${host})`));
198
+ } else {
199
+ const resolved = resolveClient({
200
+ account: opts.account,
201
+ host: opts.host,
202
+ fetchImpl: opts.fetchImpl
203
+ });
204
+ client = resolved.client;
205
+ host = resolved.host;
206
+ log(dim(`using PAT from ${resolved.source} (host=${host})`));
207
+ }
208
+ let toolId = opts.toolId;
209
+ if (!toolId) {
210
+ const identity = readExecutaIdentity(dir);
211
+ if (!identity) throw new CliError(`no .anna/executa.json identity cache in ${dir} — run \`anna-app executa publish\` (standalone) or \`anna-app apps push\` (bundled) once to mint the executa, or pass --tool-id`, 4);
212
+ if (identity.host !== canonicalHost(host)) throw new CliError(`identity cache in ${dir} belongs to ${identity.host}, but the current target is ${canonicalHost(host)} — use --account/--host or re-publish against this host`, 4);
213
+ toolId = identity.tool_id;
214
+ }
215
+ if (opts.oidc) {
216
+ const idToken = await fetchGitHubIdToken({ fetchImpl: opts.fetchImpl });
217
+ ({upload_token: uploadToken} = await mintUploadSessionViaOidc({
218
+ host,
219
+ toolId,
220
+ oidcToken: idToken,
221
+ fetchImpl: opts.fetchImpl
222
+ }));
223
+ log(dim("✓ upload token minted via OIDC exchange (no PAT involved)"));
224
+ }
225
+ const result = await uploadExecutaBinaries({
226
+ client,
227
+ host,
228
+ dir,
229
+ manifest,
230
+ toolId,
231
+ uploadToken,
232
+ dryRun: opts.dryRun === true,
233
+ fetchImpl: opts.fetchImpl,
234
+ log
235
+ });
236
+ if (opts.dryRun) {
237
+ if (opts.json) {
238
+ console.log(JSON.stringify({
239
+ tool_id: toolId,
240
+ version: manifest.version,
241
+ dry_run: true,
242
+ upload_id: result.uploadId,
243
+ assets: result.plan
244
+ }, null, 2));
245
+ return 0;
246
+ }
247
+ console.log(yellow(`[dry-run] upload plan for ${toolId} v${manifest.version}:`));
248
+ for (const [platform, decl] of Object.entries(result.plan)) {
249
+ const status = decl.status ?? "?";
250
+ console.log(` ${platform}: ${status === "exists" ? green("exists (skip)") : cyan("upload")} ` + dim(`${decl.byte_size} bytes, sha256=${decl.sha256.slice(0, 12)}…`));
251
+ }
252
+ return 0;
253
+ }
254
+ if (opts.json) {
255
+ console.log(JSON.stringify({
256
+ tool_id: toolId,
257
+ version: manifest.version,
258
+ host,
259
+ upload_id: result.uploadId,
260
+ skipped: result.skipped,
261
+ uploaded: result.uploaded,
262
+ assets: result.assets
263
+ }, null, 2));
264
+ return 0;
265
+ }
266
+ console.log(green(`✓ ${toolId}: binaries published `) + dim(`(${result.uploaded.length} uploaded, ${result.skipped.length} cached)`));
267
+ for (const [platform, a] of Object.entries(result.assets)) {
268
+ console.log(` ${bold(platform)} ${dim(`sha256=${a.sha256.slice(0, 12)}…`)}`);
269
+ console.log(` ${cyan(a.url)}`);
270
+ }
271
+ console.log(dim("→ binaries are served from the platform CDN; `apps cut` / release will pin these hashes"));
272
+ return 0;
273
+ });
274
+ }
275
+
276
+ //#endregion
277
+ export { expandArtifactPath, resolveArtifactPlan, runExecutaUploadBinaries, uploadExecutaBinaries, uploadPlannedParts };
@@ -206,15 +206,19 @@ function parseDistributionProfile(d, ctx) {
206
206
  const executableName = pickString(d, "executable_name");
207
207
  const supportsProtocol = pickBool(d, "supports_protocol");
208
208
  const binaryUrls = parseBinaryUrls(d["binary_urls"], ctx);
209
+ const binaryArtifacts = parseBinaryArtifacts(d["binary_artifacts"], ctx);
209
210
  const capabilities = parseCapabilities(d["capabilities"], ctx);
210
211
  if (PACKAGE_BACKED.has(distType) && !packageName) throw new CliError(`executa.json \`${ctx}.package_name\` is required for \`${ctx}.type: "${distType}"\``, 4);
211
- if (distType === "binary" && (!binaryUrls || Object.keys(binaryUrls).length === 0)) throw new CliError(`executa.json \`${ctx}.binary_urls\` is required (and non-empty) for \`${ctx}.type: "binary"\``, 4);
212
+ if (binaryArtifacts && distType !== "binary") throw new CliError(`executa.json \`${ctx}.binary_artifacts\` is only valid for \`${ctx}.type: "binary"\``, 4);
213
+ if (binaryArtifacts && binaryUrls) throw new CliError(`executa.json \`${ctx}\` declares BOTH \`binary_urls\` and \`binary_artifacts\` — pick exactly one: \`binary_urls\` (a public GET-able source the platform pull-mirrors at cut) or \`binary_artifacts\` (local files pushed directly via \`anna-app executa upload-binaries\`; works for private repos)`, 4);
214
+ if (distType === "binary" && (!binaryUrls || Object.keys(binaryUrls).length === 0) && (!binaryArtifacts || Object.keys(binaryArtifacts).length === 0)) throw new CliError(`executa.json \`${ctx}\` requires exactly one of \`binary_urls\` (non-empty) or \`binary_artifacts\` (non-empty) for \`${ctx}.type: "binary"\``, 4);
212
215
  return {
213
216
  type: distType,
214
217
  packageName,
215
218
  executableName,
216
219
  supportsProtocol,
217
220
  binaryUrls,
221
+ binaryArtifacts,
218
222
  capabilities
219
223
  };
220
224
  }
@@ -238,6 +242,57 @@ function parseBinaryUrls(value, ctx = "distribution") {
238
242
  }
239
243
  return out;
240
244
  }
245
+ /**
246
+ * Canonical `<os>-<arch>` platform keys accepted for `binary_artifacts`
247
+ * (mirrors ALLOWED_PLATFORMS in matrix-nexus
248
+ * src/services/executa_binary_upload.py — both ends validate independently).
249
+ */
250
+ const BINARY_ARTIFACT_PLATFORMS = new Set([
251
+ "darwin-arm64",
252
+ "darwin-x86_64",
253
+ "linux-x86_64",
254
+ "linux-arm64",
255
+ "linux-aarch64",
256
+ "windows-x86_64",
257
+ "windows-arm64"
258
+ ]);
259
+ const MAX_BINARY_ARTIFACT_PLATFORMS = 8;
260
+ /** Infer the archive format from a path suffix (`.tar.gz`/`.tgz`/`.zip`). */
261
+ function inferArtifactFormat(path) {
262
+ const lower = path.toLowerCase();
263
+ if (lower.endsWith(".tar.gz") || lower.endsWith(".tgz")) return "tar.gz";
264
+ if (lower.endsWith(".zip")) return "zip";
265
+ return void 0;
266
+ }
267
+ function parseBinaryArtifacts(value, ctx = "distribution") {
268
+ if (value === void 0 || value === null) return void 0;
269
+ if (typeof value !== "object" || Array.isArray(value)) throw new CliError(`executa.json \`${ctx}.binary_artifacts\` must be an object mapping platform → { path, entrypoint?, format? }`, 4);
270
+ const entries = Object.entries(value);
271
+ if (entries.length > MAX_BINARY_ARTIFACT_PLATFORMS) throw new CliError(`executa.json \`${ctx}.binary_artifacts\` declares more than ${MAX_BINARY_ARTIFACT_PLATFORMS} platforms`, 4);
272
+ const out = {};
273
+ for (const [platform, decl] of entries) {
274
+ if (!BINARY_ARTIFACT_PLATFORMS.has(platform)) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` — unknown platform key; expected one of ` + [...BINARY_ARTIFACT_PLATFORMS].join(" | "), 4);
275
+ if (decl === null || typeof decl !== "object" || Array.isArray(decl)) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` must be an object \`{ path, entrypoint?, format? }\``, 4);
276
+ const d = decl;
277
+ const path = pickString(d, "path");
278
+ if (!path || !path.trim()) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` missing required \`path\``, 4);
279
+ const declared = pickString(d, "format");
280
+ let format;
281
+ if (declared !== void 0) if (declared === "tar.gz" || declared === "tgz") format = "tar.gz";
282
+ else if (declared === "zip") format = "zip";
283
+ else throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"].format\` "${declared}" is invalid — expected tar.gz | tgz | zip (bare executables must be archived)`, 4);
284
+ else {
285
+ format = inferArtifactFormat(path);
286
+ if (!format) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` — cannot infer archive format from "${path}"; use a .tar.gz/.tgz/.zip suffix or set \`format\` explicitly`, 4);
287
+ }
288
+ out[platform] = {
289
+ path,
290
+ entrypoint: pickString(d, "entrypoint"),
291
+ format
292
+ };
293
+ }
294
+ return out;
295
+ }
241
296
  function parseCapabilities(value, ctx = "distribution") {
242
297
  if (value === void 0 || value === null) return void 0;
243
298
  if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) throw new CliError(`executa.json \`${ctx}.capabilities\` must be an array of strings`, 4);
@@ -1,16 +1,18 @@
1
1
  import "./credentials-BTv2IfUZ.js";
2
2
  import "./apps-DlVsVHQ5.js";
3
3
  import { CliError } from "./client-D-_z1ALk.js";
4
- import { withErrorHandling } from "./_lifecycle-shared-CUK2CE76.js";
5
- import "./bundled-executas-_H1A9M50.js";
6
- import "./executas-BhFed1B-.js";
7
- import "./executa-cache-Do1D7xC0.js";
8
- import { runExecutaPublish } from "./executa-publish-Ah6J7jYd.js";
9
- import "./manifest-DOMrcdCK.js";
10
- import "./app-cache-BqnTkxUw.js";
11
- import "./listing-meta-BlY7XMrw.js";
12
- import "./app-bundle-upload-CqT9KUOl.js";
13
- import { runAppsPublish } from "./apps-publish-BKqVsS1W.js";
4
+ import { withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
5
+ import "./executa-cache-BFoUtb4J.js";
6
+ import "./bundled-executas-rSIkaA6-.js";
7
+ import "./manifest-DlIcwLQs.js";
8
+ import "./executas-CvQY_w2U.js";
9
+ import { runExecutaPublish } from "./executa-publish-CGz5wM1Q.js";
10
+ import "./binary-upload-CKMU5Rfw.js";
11
+ import "./executa-upload-binaries-Do5Sq64E.js";
12
+ import "./app-cache-Cr3tKbux.js";
13
+ import "./listing-meta-C0HsNjIB.js";
14
+ import "./app-bundle-upload-CrBRnzf4.js";
15
+ import { runAppsPublish } from "./apps-publish-qc_E4b_R.js";
14
16
  import { resolve } from "node:path";
15
17
  import { existsSync } from "node:fs";
16
18
  import { red, yellow } from "kleur/colors";
@@ -1,5 +1,5 @@
1
1
  import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
2
- import { BridgeRequestError } from "./bridge-CbjqAm0H.js";
2
+ import { BridgeRequestError } from "./bridge-Dvck0e9c.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";
@@ -1,5 +1,5 @@
1
1
  import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
- import { hostOf, requireAccount, withCode } from "./dev-account-DqEFCmVg.js";
2
+ import { hostOf, requireAccount, withCode } from "./dev-account-CfVdGa9j.js";
3
3
  import { resolve } from "node:path";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
 
@@ -1,6 +1,6 @@
1
1
  import "./credentials-BTv2IfUZ.js";
2
2
  import "./client-D-_z1ALk.js";
3
- import { resolveClient, withErrorHandling } from "./_lifecycle-shared-CUK2CE76.js";
3
+ import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
4
4
  import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
5
5
 
6
6
  //#region src/api/tokens.ts
@@ -1,11 +1,11 @@
1
1
  import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
2
  import { createApp, findAppBySlug, getApp } from "./apps-DlVsVHQ5.js";
3
3
  import { CliError } from "./client-D-_z1ALk.js";
4
- import { parseExecutaIdOverrides, readExecutasLock, substituteBundledRefs, validateBundledHandles, writeBundleToolIdSidecar, writeExecutasLock } from "./bundled-executas-_H1A9M50.js";
5
- import { runExecutaPublish } from "./executa-publish-Ah6J7jYd.js";
6
- import { loadExecutaManifest } from "./manifest-DOMrcdCK.js";
7
- import { runExecutaInstall } from "./executa-install-BW9jjY7u.js";
8
- import { appCacheMatches, readAppIdentity, writeAppIdentity } from "./app-cache-BqnTkxUw.js";
4
+ import { parseExecutaIdOverrides, readExecutasLock, substituteBundledRefs, validateBundledHandles, writeBundleToolIdSidecar, writeExecutasLock } from "./bundled-executas-rSIkaA6-.js";
5
+ import { loadExecutaManifest } from "./manifest-DlIcwLQs.js";
6
+ import { runExecutaPublish } from "./executa-publish-CGz5wM1Q.js";
7
+ import { runExecutaInstall } from "./executa-install-Dz7hBUsn.js";
8
+ import { appCacheMatches, readAppIdentity, writeAppIdentity } from "./app-cache-Cr3tKbux.js";
9
9
  import { join, resolve } from "node:path";
10
10
  import { dim, green, yellow } from "kleur/colors";
11
11
  import { homedir } from "node:os";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anna-ai/cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Anna App developer CLI: scaffold, validate, harness (Phase 2 MVP: init + validate).",
5
5
  "license": "MIT",
6
6
  "type": "module",