@anna-ai/cli 0.1.33 → 0.1.35

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-DmVKWAj9.js} +3 -3
  5. package/dist/{apps-dvknZYI2.js → apps-C_Oln_om.js} +1 -1
  6. package/dist/apps-cut-MjSe2ph7.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-BWLVVMu7.js} +12 -10
  9. package/dist/{apps-grants-DnXCnkH5.js → apps-grants-lmllFB02.js} +1 -1
  10. package/dist/{apps-publish-BKqVsS1W.js → apps-publish--EmBa0zN.js} +8 -7
  11. package/dist/apps-publish-B7reDBe1.js +17 -0
  12. package/dist/{apps-push-B3wZr9B4.js → apps-push-CkJ1gtpy.js} +16 -12
  13. package/dist/{apps-release-CyAPmWPU.js → apps-release-J-Sjkoym.js} +3 -3
  14. package/dist/{apps-rename-slug-D2BrWasG.js → apps-rename-slug-DYrk4Zpt.js} +3 -3
  15. package/dist/{apps-status-DV8V_y35.js → apps-status-CcCoYu6i.js} +1 -1
  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-CL2PCMCv.js} +6 -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 +80 -44
  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-B_7k1gOf.js +11 -0
  34. package/dist/{executa-publish-Ah6J7jYd.js → executa-publish-kh4Ucd3o.js} +21 -5
  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-DURJTXRI.js +277 -0
  38. package/dist/executa-upload-binaries-DrwpzQp3.js +9 -0
  39. package/dist/{listing-meta-BlY7XMrw.js → listing-meta-CtVtyaWu.js} +13 -4
  40. package/dist/{manifest-DOMrcdCK.js → manifest-CAF3_r1T.js} +88 -11
  41. package/dist/{publish-GpvkaBIx.js → publish-DwIJWl46.js} +12 -10
  42. package/dist/{server-BMcGaUTy.js → server-BHWGGZsf.js} +1 -1
  43. package/dist/{storage-BkF4XyI0.js → storage-aii8EyPC.js} +1 -1
  44. package/dist/{token-uedeaAy7.js → token-COnYMRB-.js} +1 -1
  45. package/dist/{working-orchestration-1jzRuk34.js → working-orchestration-D7cW2Bf2.js} +7 -6
  46. package/package.json +1 -1
  47. package/dist/apps-cut-8870xpk-.js +0 -86
  48. package/dist/apps-publish-Djpw5cfG.js +0 -15
  49. package/dist/bridge-CzHB_IsQ.js +0 -3
  50. package/dist/dev-C3yWhzAp.js +0 -4
  51. package/dist/executa-install-Cyid8R7r.js +0 -7
  52. package/dist/executa-publish-DI_6M_ea.js +0 -9
  53. /package/dist/{app-cache-BqnTkxUw.js → app-cache-Cr3tKbux.js} +0 -0
  54. /package/dist/{bundled-executas-_H1A9M50.js → bundled-executas-rSIkaA6-.js} +0 -0
  55. /package/dist/{confirm-BuT56E_B.js → confirm-BSoOy1X2.js} +0 -0
  56. /package/dist/{dev-account-DqEFCmVg.js → dev-account-CfVdGa9j.js} +0 -0
  57. /package/dist/{executa-cache-Do1D7xC0.js → executa-cache-BFoUtb4J.js} +0 -0
  58. /package/dist/{executa-init-D-kWINZ3.js → executa-init-DKy7DMSD.js} +0 -0
  59. /package/dist/{executa-register-D5kvSSz_.js → executa-register-CmqKsoAU.js} +0 -0
  60. /package/dist/{executas-BhFed1B-.js → executas-CvQY_w2U.js} +0 -0
  61. /package/dist/{fixture-BUKQYR5e.js → fixture-BF1Z0Rry.js} +0 -0
  62. /package/dist/{host_upload-BVzAqvX1.js → host_upload-DrbQ-vk2.js} +0 -0
  63. /package/dist/{image-BLPyUbGQ.js → image-B4UrufYH.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,5 +1,5 @@
1
1
  import "./nexus-root-BlPwOusj.js";
2
- import { parseExecutaSpec } from "./dev-Re6NyNDS.js";
2
+ import { parseExecutaSpec } from "./dev-GUFWek03.js";
3
3
  import { isAbsolute, resolve } from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
@@ -35,7 +35,7 @@ async function runExecutaDev(opts) {
35
35
  const oneShot = !!(opts.describe || opts.health || opts.invoke);
36
36
  const quiet = oneShot && (opts.json ?? false);
37
37
  const { getAccount } = await import("./credentials-Chkoidh5.js");
38
- const { ensureDevExecutaRegistered } = await import("./dev-app-cache-Dv1dZ_bw.js");
38
+ const { ensureDevExecutaRegistered } = await import("./dev-app-cache-B3Wa5t6X.js");
39
39
  const needsRealMint = !opts.noSampling && !opts.mockSampling || !opts.noAgent && !opts.mockAgent || !opts.noImage && !opts.mockImage || !opts.noUpload && !opts.mockUpload || opts.storage === "real";
40
40
  let effectiveAppSlug = opts.appSlug;
41
41
  let autoRegistered = false;
@@ -57,7 +57,7 @@ async function runExecutaDev(opts) {
57
57
  }
58
58
  }
59
59
  }
60
- const { SamplingBridge } = await import("./sampling-XBIEYU4A.js");
60
+ const { SamplingBridge } = await import("./sampling-BpJcd6BC.js");
61
61
  const simulateUnsupportedResponseFormat = opts.samplingUnsupportedFormat;
62
62
  const sampling = opts.noSampling ? new SamplingBridge({ mode: "off" }) : opts.mockSampling ? new SamplingBridge({
63
63
  mode: "mock",
@@ -69,7 +69,7 @@ async function runExecutaDev(opts) {
69
69
  appSlug: effectiveAppSlug,
70
70
  simulateUnsupportedResponseFormat
71
71
  }) : new SamplingBridge({ mode: "off" });
72
- const { AgentBridge } = await import("./agent-CzPryLi6.js");
72
+ const { AgentBridge } = await import("./agent-DAOr_aiE.js");
73
73
  const agent = opts.noAgent ? new AgentBridge({ mode: "off" }) : opts.mockAgent ? new AgentBridge({
74
74
  mode: "mock",
75
75
  mockFile: opts.mockAgent
@@ -78,7 +78,7 @@ async function runExecutaDev(opts) {
78
78
  account: opts.agentAccount ?? opts.samplingAccount,
79
79
  appSlug: effectiveAppSlug
80
80
  }) : new AgentBridge({ mode: "off" });
81
- const { StorageBridge } = await import("./storage-BkF4XyI0.js");
81
+ const { StorageBridge } = await import("./storage-aii8EyPC.js");
82
82
  const storageMode = opts.storage ?? (opts.mockStorage ? "mock" : "memory");
83
83
  const storage = new StorageBridge({
84
84
  mode: storageMode,
@@ -88,8 +88,8 @@ async function runExecutaDev(opts) {
88
88
  scopes: opts.storageScopes ? opts.storageScopes.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
89
89
  pluginName: parsed.tool_id
90
90
  });
91
- const { ExecutaRunner } = await import("./runner-BxYjAmih.js");
92
- const { ImageBridge } = await import("./image-BLPyUbGQ.js");
91
+ const { ExecutaRunner } = await import("./runner-Bka_coxb.js");
92
+ const { ImageBridge } = await import("./image-B4UrufYH.js");
93
93
  const image = opts.noImage ? new ImageBridge({ mode: "off" }) : opts.mockImage ? new ImageBridge({
94
94
  mode: "mock",
95
95
  mockFile: opts.mockImage
@@ -98,7 +98,7 @@ async function runExecutaDev(opts) {
98
98
  account: opts.imageAccount ?? opts.samplingAccount,
99
99
  appSlug: effectiveAppSlug
100
100
  }) : new ImageBridge({ mode: "off" });
101
- const { HostUploadBridge } = await import("./host_upload-BVzAqvX1.js");
101
+ const { HostUploadBridge } = await import("./host_upload-DrbQ-vk2.js");
102
102
  const hostUpload = opts.noUpload ? new HostUploadBridge({ mode: "off" }) : opts.mockUpload ? new HostUploadBridge({
103
103
  mode: "mock",
104
104
  mockFile: opts.mockUpload
@@ -1,5 +1,5 @@
1
- import { readExecutaIdentity } from "./executa-cache-Do1D7xC0.js";
2
- import { parseExecutaSpec } from "./dev-Re6NyNDS.js";
1
+ import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
2
+ import { parseExecutaSpec } from "./dev-GUFWek03.js";
3
3
  import { isAbsolute, join, resolve } from "node:path";
4
4
  import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
5
5
  import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
@@ -0,0 +1,7 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import "./nexus-root-BlPwOusj.js";
3
+ import "./executa-cache-BFoUtb4J.js";
4
+ import "./dev-GUFWek03.js";
5
+ import { runExecutaInstall } from "./executa-install-Dz7hBUsn.js";
6
+
7
+ export { runExecutaInstall };
@@ -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-CAF3_r1T.js";
6
+ import "./executas-CvQY_w2U.js";
7
+ import { runExecutaPublish } from "./executa-publish-kh4Ucd3o.js";
8
+ import "./binary-upload-CKMU5Rfw.js";
9
+ import "./executa-upload-binaries-DURJTXRI.js";
10
+
11
+ export { runExecutaPublish };
@@ -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-CAF3_r1T.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-DURJTXRI.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";
@@ -109,7 +110,7 @@ function distributionPayload(d) {
109
110
  async function runExecutaPublish(opts) {
110
111
  return withErrorHandling(async () => {
111
112
  const cwd = resolve(opts.cwd ?? process.cwd());
112
- const manifest = loadExecutaManifest(cwd, opts.manifest);
113
+ const manifest = loadExecutaManifest(cwd, opts.manifest, opts.profile);
113
114
  const isSkill = manifest.executa_type === "skill";
114
115
  const { client, host, source } = resolveClient({ account: opts.account });
115
116
  if (!opts.json && !opts.quiet) console.log(dim(`using PAT from ${source} (host=${host})`));
@@ -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";
@@ -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,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-CAF3_r1T.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 };
@@ -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-CAF3_r1T.js";
6
+ import "./binary-upload-CKMU5Rfw.js";
7
+ import { expandArtifactPath, resolveArtifactPlan, runExecutaUploadBinaries, uploadExecutaBinaries, uploadPlannedParts } from "./executa-upload-binaries-DURJTXRI.js";
8
+
9
+ export { runExecutaUploadBinaries };
@@ -27,20 +27,29 @@ function isRemoteAsset(src) {
27
27
  /**
28
28
  * Build the metadata payload from the manifest (only defined fields). The
29
29
  * logo is handled separately and never appears here when sourced from a file.
30
+ *
31
+ * Optional listing URLs (cover/homepage/support/privacy) are *clearable*:
32
+ * `loadAppManifest` normalises an explicit `""`/`null` in the local metadata
33
+ * file to the `""` sentinel, which is forwarded on the PATCH so the server
34
+ * value is emptied (the store UI hides falsy links). An absent key stays
35
+ * `undefined` and is omitted from the payload — "leave unchanged".
30
36
  */
31
37
  function buildMeta(manifest) {
32
38
  const meta = {};
33
39
  const add = (key, value) => {
34
40
  if (value !== void 0 && value !== null && value !== "") meta[key] = value;
35
41
  };
42
+ const addClearable = (key, value) => {
43
+ if (value !== void 0) meta[key] = value;
44
+ };
36
45
  add("name", manifest.name);
37
46
  add("category", manifest.category);
38
47
  add("tagline", manifest.tagline);
39
48
  add("description", manifest.description);
40
- add("cover_url", manifest.coverUrl);
41
- add("homepage_url", manifest.homepageUrl);
42
- add("support_url", manifest.supportUrl);
43
- add("privacy_url", manifest.privacyUrl);
49
+ addClearable("cover_url", manifest.coverUrl);
50
+ addClearable("homepage_url", manifest.homepageUrl);
51
+ addClearable("support_url", manifest.supportUrl);
52
+ addClearable("privacy_url", manifest.privacyUrl);
44
53
  add("pricing_model", manifest.pricingModel);
45
54
  return meta;
46
55
  }