@aexhq/cli 0.25.1 → 0.26.0

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 (93) hide show
  1. package/README.md +12 -0
  2. package/dist/index.d.ts +2 -7
  3. package/dist/index.js +233 -7
  4. package/package.json +26 -29
  5. package/LICENSE +0 -201
  6. package/dist/cli.d.ts +0 -1
  7. package/dist/cli.js +0 -51
  8. package/dist/cli.js.map +0 -1
  9. package/dist/cli.mjs +0 -4657
  10. package/dist/cli.mjs.sha256 +0 -1
  11. package/dist/host/cancel.d.ts +0 -3
  12. package/dist/host/cancel.js +0 -30
  13. package/dist/host/cancel.js.map +0 -1
  14. package/dist/host/common.d.ts +0 -135
  15. package/dist/host/common.js +0 -281
  16. package/dist/host/common.js.map +0 -1
  17. package/dist/host/delete-asset.d.ts +0 -3
  18. package/dist/host/delete-asset.js +0 -34
  19. package/dist/host/delete-asset.js.map +0 -1
  20. package/dist/host/delete.d.ts +0 -3
  21. package/dist/host/delete.js +0 -30
  22. package/dist/host/delete.js.map +0 -1
  23. package/dist/host/download.d.ts +0 -3
  24. package/dist/host/download.js +0 -80
  25. package/dist/host/download.js.map +0 -1
  26. package/dist/host/events.d.ts +0 -3
  27. package/dist/host/events.js +0 -97
  28. package/dist/host/events.js.map +0 -1
  29. package/dist/host/index.d.ts +0 -17
  30. package/dist/host/index.js +0 -18
  31. package/dist/host/index.js.map +0 -1
  32. package/dist/host/outputs.d.ts +0 -3
  33. package/dist/host/outputs.js +0 -36
  34. package/dist/host/outputs.js.map +0 -1
  35. package/dist/host/run-cmd.d.ts +0 -3
  36. package/dist/host/run-cmd.js +0 -549
  37. package/dist/host/run-cmd.js.map +0 -1
  38. package/dist/host/skills-cmd.d.ts +0 -3
  39. package/dist/host/skills-cmd.js +0 -304
  40. package/dist/host/skills-cmd.js.map +0 -1
  41. package/dist/host/status.d.ts +0 -3
  42. package/dist/host/status.js +0 -31
  43. package/dist/host/status.js.map +0 -1
  44. package/dist/host/wait.d.ts +0 -3
  45. package/dist/host/wait.js +0 -88
  46. package/dist/host/wait.js.map +0 -1
  47. package/dist/host/whoami.d.ts +0 -3
  48. package/dist/host/whoami.js +0 -33
  49. package/dist/host/whoami.js.map +0 -1
  50. package/dist/index.js.map +0 -1
  51. package/dist/internal.d.ts +0 -53
  52. package/dist/internal.js +0 -10
  53. package/dist/internal.js.map +0 -1
  54. package/dist/outputs-sync.d.ts +0 -24
  55. package/dist/outputs-sync.js +0 -70
  56. package/dist/outputs-sync.js.map +0 -1
  57. package/dist/proxy.d.ts +0 -16
  58. package/dist/proxy.js +0 -316
  59. package/dist/proxy.js.map +0 -1
  60. package/dist/run.d.ts +0 -10
  61. package/dist/run.js +0 -166
  62. package/dist/run.js.map +0 -1
  63. package/dist/runtime.d.ts +0 -5
  64. package/dist/runtime.js +0 -4
  65. package/dist/runtime.js.map +0 -1
  66. package/scripts/finalize-bundle.mjs +0 -72
  67. package/src/cli.ts +0 -51
  68. package/src/host/cancel.ts +0 -39
  69. package/src/host/common.ts +0 -329
  70. package/src/host/delete-asset.ts +0 -43
  71. package/src/host/delete.ts +0 -39
  72. package/src/host/download.ts +0 -97
  73. package/src/host/events.ts +0 -113
  74. package/src/host/index.ts +0 -25
  75. package/src/host/outputs.ts +0 -45
  76. package/src/host/run-cmd.ts +0 -578
  77. package/src/host/skills-cmd.ts +0 -317
  78. package/src/host/status.ts +0 -40
  79. package/src/host/wait.ts +0 -110
  80. package/src/host/whoami.ts +0 -42
  81. package/src/index.ts +0 -7
  82. package/src/internal.ts +0 -63
  83. package/src/outputs-sync.ts +0 -76
  84. package/src/proxy.ts +0 -359
  85. package/src/run.ts +0 -187
  86. package/src/runtime.ts +0 -15
  87. package/test/cli.test.ts +0 -519
  88. package/test/host.test.ts +0 -1154
  89. package/test/no-env-vars.test.ts +0 -59
  90. package/test/outputs-sync.test.ts +0 -107
  91. package/tsconfig.build.json +0 -9
  92. package/tsconfig.json +0 -4
  93. package/vitest.config.ts +0 -14
@@ -1,72 +0,0 @@
1
- // Finalize the CLI bundle:
2
- // 1. esbuild bundles dist/cli.js (the tsc-built entrypoint) and its
3
- // workspace deps into a single ESM file.
4
- // 2. Prepend the Node shebang.
5
- // 3. Write to dist/cli.mjs so the `bin` field works on Unix and the
6
- // ESM extension is unambiguous.
7
- // 4. Compute a sha256 digest and write a sibling cli.mjs.sha256
8
- // used by release/package integrity checks.
9
- //
10
- // The published SDK copies ONLY dist/cli.mjs as the user-facing `aex`
11
- // bin. Managed run internals use the separate runtime bridge artifact, so
12
- // this host-install bundle stays independent of platform injection.
13
- import { build } from "esbuild";
14
- import { readFile, writeFile, chmod } from "node:fs/promises";
15
- import { createHash } from "node:crypto";
16
- import { dirname, resolve } from "node:path";
17
- import { fileURLToPath } from "node:url";
18
-
19
- const here = dirname(fileURLToPath(import.meta.url));
20
- const pkgRoot = resolve(here, "..");
21
- const distDir = resolve(pkgRoot, "dist");
22
- const inputPath = resolve(distDir, "cli.js");
23
- const outputPath = resolve(distDir, "cli.mjs");
24
- const digestPath = resolve(distDir, "cli.mjs.sha256");
25
-
26
- const result = await build({
27
- entryPoints: [inputPath],
28
- bundle: true,
29
- platform: "node",
30
- format: "esm",
31
- target: "node20",
32
- write: false,
33
- // Mark node built-ins external; we only bundle our own + workspace deps.
34
- external: [
35
- "node:*",
36
- "fs",
37
- "fs/promises",
38
- "path",
39
- "url",
40
- "crypto",
41
- "buffer",
42
- "stream",
43
- "util"
44
- ],
45
- // The bundle is loaded as ESM at runtime; suppress sourcemaps so the
46
- // shipped artifact stays minimal and free of file-path leakage.
47
- sourcemap: false,
48
- legalComments: "none",
49
- minify: false
50
- });
51
-
52
- if (result.outputFiles.length !== 1) {
53
- throw new Error(`unexpected esbuild output count: ${result.outputFiles.length}`);
54
- }
55
- const bundled = result.outputFiles[0].text;
56
-
57
- const shebang = "#!/usr/bin/env node\n";
58
- const final = shebang + bundled;
59
- await writeFile(outputPath, final, "utf8");
60
-
61
- try {
62
- // mode 0755; harmless on Windows.
63
- await chmod(outputPath, 0o755);
64
- } catch {
65
- /* ignore */
66
- }
67
-
68
- const digest = createHash("sha256").update(final).digest("hex");
69
- await writeFile(digestPath, `${digest} cli.mjs\n`, "utf8");
70
-
71
- console.log(`built ${outputPath} (${final.length} bytes)`);
72
- console.log(`sha256 ${digest}`);
package/src/cli.ts DELETED
@@ -1,51 +0,0 @@
1
- /**
2
- * aex CLI entrypoint. Wires the real IO surface and calls
3
- * {@link runCli}. The shipped bundle has a `#!/usr/bin/env node` line
4
- * prepended by `scripts/finalize-bundle.mjs`.
5
- *
6
- * NO `process.env.AEX_*` reads here — paths to the manifest and
7
- * token file are fixed constants in `internal.ts`. The mechanical
8
- * enforcement test in Phase 9 greps the built artifact to confirm.
9
- */
10
- import { readFile, writeFile, readdir, stat } from "node:fs/promises";
11
- import { resolve as resolvePath } from "node:path";
12
- import { runCli } from "./run.js";
13
- import type { CliIO, OutputsSyncFileEntry } from "./internal.js";
14
-
15
- async function walkDirectory(root: string): Promise<readonly OutputsSyncFileEntry[] | null> {
16
- try {
17
- const rootStat = await stat(root);
18
- if (!rootStat.isDirectory()) return null;
19
- } catch {
20
- return null;
21
- }
22
- const out: OutputsSyncFileEntry[] = [];
23
- async function visit(dir: string): Promise<void> {
24
- const entries = await readdir(dir, { withFileTypes: true });
25
- for (const entry of entries) {
26
- const full = resolvePath(dir, entry.name);
27
- if (entry.isDirectory()) {
28
- await visit(full);
29
- } else if (entry.isFile()) {
30
- const s = await stat(full);
31
- out.push({ path: full, sizeBytes: s.size });
32
- }
33
- }
34
- }
35
- await visit(root);
36
- return out;
37
- }
38
-
39
- const io: CliIO = {
40
- readFile: (path) => readFile(path, "utf8"),
41
- writeFile: (path, data) => writeFile(path, data),
42
- fetchImpl: fetch,
43
- stdout: (chunk) => process.stdout.write(chunk),
44
- stderr: (chunk) => process.stderr.write(chunk),
45
- exit: (code) => process.exit(code),
46
- argv: process.argv,
47
- cwd: () => process.cwd(),
48
- walkDirectory
49
- };
50
-
51
- await runCli(io);
@@ -1,39 +0,0 @@
1
- /**
2
- * `aex cancel <run-id>` — POST /api/runs/{id}/cancel.
3
- */
4
- import { operations } from "@aexhq/contracts";
5
- import type { CliIO } from "../internal.js";
6
- import {
7
- type CliExitCode,
8
- SUCCESS,
9
- USAGE_ERR,
10
- emitJsonError,
11
- makeHttpClient,
12
- parseCommonHostFlags,
13
- refuseInsideManagedRun
14
- } from "./common.js";
15
-
16
- export async function runCancelCmd(io: CliIO, argv: readonly string[]): Promise<CliExitCode> {
17
- if (await refuseInsideManagedRun(io, "cancel")) return USAGE_ERR;
18
-
19
- const common = parseCommonHostFlags(argv);
20
- if (!common.ok) {
21
- io.stderr(`${common.reason}\n`);
22
- return USAGE_ERR;
23
- }
24
- const positional = common.rest.filter((arg) => !arg.startsWith("--"));
25
- if (positional.length !== 1) {
26
- io.stderr("usage: aex cancel <run-id> [common flags]\n");
27
- return USAGE_ERR;
28
- }
29
- const runId = positional[0]!;
30
-
31
- const http = makeHttpClient(io, common.flags);
32
- try {
33
- await operations.cancelRun(http, runId);
34
- io.stdout(JSON.stringify({ runId, status: "cancel_requested" }) + "\n");
35
- return SUCCESS;
36
- } catch (err) {
37
- return emitJsonError(io, "cancel_failed", (err as Error).message ?? "cancel failed", { runId });
38
- }
39
- }
@@ -1,329 +0,0 @@
1
- /**
2
- * Shared helpers for every host-side aex subcommand. Common flag
3
- * parsing, HttpClient construction, manifest detection so we can refuse
4
- * to run host commands inside a managed run container, and exit codes
5
- * shared with the in-container proxy command.
6
- */
7
- import { AEX_DEFAULT_BASE_URL, HttpClient, type FetchLike } from "@aexhq/contracts";
8
- import { AEX_INDEX_PATH, type CliIO } from "../internal.js";
9
-
10
- export interface CliExitCode {
11
- readonly code: number;
12
- }
13
-
14
- export const SUCCESS: CliExitCode = { code: 0 };
15
- export const USAGE_ERR: CliExitCode = { code: 2 };
16
- export const RUNTIME_ERR: CliExitCode = { code: 1 };
17
- /**
18
- * Distinct exit code for "the wait/follow deadline elapsed before the
19
- * run reached a terminal status". Separated from RUNTIME_ERR (1) so a
20
- * script can tell a timeout apart from a run that finished non-succeeded
21
- * (which is also RUNTIME_ERR). Mirrors the SDK's `waitForRun` throwing a
22
- * dedicated timeout error.
23
- */
24
- export const TIMEOUT_ERR: CliExitCode = { code: 3 };
25
-
26
- export interface CommonHostFlags {
27
- readonly apiToken: string;
28
- readonly aexUrl: string;
29
- /** `--debug`: print a redacted per-request trace to stderr. Uploads nothing. */
30
- readonly debug: boolean;
31
- }
32
-
33
- export type ParseCommonResult =
34
- | { readonly ok: true; readonly flags: CommonHostFlags; readonly rest: readonly string[] }
35
- | { readonly ok: false; readonly reason: string };
36
-
37
- /**
38
- * Parse and remove the common flags every host-side subcommand needs,
39
- * leaving the rest for the caller.
40
- *
41
- * The CLI is `flags_only` — no `AEX_*` env reads. `--api-token` is
42
- * required; `--aex-url` defaults to `AEX_DEFAULT_BASE_URL`
43
- * (`https://api.aex.dev`) so SaaS users never need to supply it.
44
- *
45
- * There is no `--workspace` flag: workspace identity is derived
46
- * server-side from the API token.
47
- */
48
- export function parseCommonHostFlags(argv: readonly string[]): ParseCommonResult {
49
- let apiToken: string | null = null;
50
- let aexUrl: string | null = null;
51
- let debug = false;
52
- const rest: string[] = [];
53
-
54
- for (let i = 0; i < argv.length; i++) {
55
- const arg = argv[i]!;
56
- if (arg === "--debug") {
57
- debug = true;
58
- continue;
59
- }
60
- if (arg === "--api-token") {
61
- const v = argv[++i];
62
- if (v === undefined) return { ok: false, reason: "--api-token requires a value" };
63
- apiToken = v;
64
- continue;
65
- }
66
- if (arg === "--aex-url") {
67
- const v = argv[++i];
68
- if (v === undefined) return { ok: false, reason: "--aex-url requires a value" };
69
- aexUrl = v;
70
- continue;
71
- }
72
- if (arg === "--workspace" || arg === "--workspace-id") {
73
- // Removed in favor of server-side derivation from the API token.
74
- // Bail out early with an actionable message instead of letting
75
- // the flag fall through and produce a confusing positional-arg
76
- // count error from the calling subcommand.
77
- return {
78
- ok: false,
79
- reason: `unknown flag ${arg}: workspace is derived from --api-token on the server; drop this flag`
80
- };
81
- }
82
- rest.push(arg);
83
- }
84
-
85
- if (!apiToken) return { ok: false, reason: "--api-token is required" };
86
- return {
87
- ok: true,
88
- flags: { apiToken, aexUrl: aexUrl ?? AEX_DEFAULT_BASE_URL, debug },
89
- rest
90
- };
91
- }
92
-
93
- export function makeHttpClient(io: CliIO, flags: CommonHostFlags): HttpClient {
94
- return new HttpClient({
95
- baseUrl: flags.aexUrl,
96
- apiToken: flags.apiToken,
97
- fetch: io.fetchImpl as FetchLike,
98
- // `--debug`: route the transport's redacted per-request traces to stderr.
99
- ...(flags.debug ? { debug: (line: string) => io.stderr(`${line}\n`) } : {})
100
- });
101
- }
102
-
103
- /**
104
- * Host subcommands refuse to run inside a managed run container. The
105
- * heuristic: presence of the per-run manifest at AEX_INDEX_PATH
106
- * (`/mnt/session/uploads/aex/index.json`) means we're inside a
107
- * run and should expose `proxy`, not the platform-management verbs.
108
- *
109
- * Fails *closed* on read errors that are not "file not found": if the
110
- * manifest exists but is unreadable for any other reason (permissions,
111
- * IO error, etc.) we refuse to run the host verb rather than risk
112
- * leaking workspace-management calls into a sandboxed container.
113
- */
114
- export async function refuseInsideManagedRun(io: CliIO, verb: string): Promise<boolean> {
115
- try {
116
- await io.readFile(AEX_INDEX_PATH);
117
- io.stderr(
118
- `\`aex ${verb}\` is a host command and cannot run inside a managed run container.\n` +
119
- "Use `aex proxy ...` to call your declared upstream endpoints from inside the run.\n"
120
- );
121
- return true;
122
- } catch (err) {
123
- const code = (err as NodeJS.ErrnoException | undefined)?.code;
124
- if (code === "ENOENT" || code === "ENOTDIR") {
125
- // Manifest definitively absent; we are on a host. Allow the verb.
126
- return false;
127
- }
128
- io.stderr(
129
- `\`aex ${verb}\` could not determine whether it is running inside a managed run ` +
130
- `container (error reading ${AEX_INDEX_PATH}: ${(err as Error).message ?? "unknown"}). ` +
131
- `Refusing to proceed.\n`
132
- );
133
- return true;
134
- }
135
- }
136
-
137
- /**
138
- * Emit a JSON error body and return RUNTIME_ERR. The shape mirrors the
139
- * in-container proxy error envelope so a script-shaped consumer can
140
- * parse both surfaces the same way.
141
- */
142
- export function emitJsonError(io: CliIO, code: string, message: string, extra: Record<string, unknown> = {}): CliExitCode {
143
- io.stderr(JSON.stringify({ error: code, message, ...extra }) + "\n");
144
- return RUNTIME_ERR;
145
- }
146
-
147
- /**
148
- * Repeatable `--var key=value` / `--mcp-secret name=value` /
149
- * `--proxy-auth name=value`-style flag parser. Returns the values in
150
- * insertion order (last-wins on duplicate keys).
151
- */
152
- export function collectRepeatedKv(rest: readonly string[], flag: string): {
153
- readonly entries: Record<string, string>;
154
- readonly remaining: readonly string[];
155
- readonly error: string | null;
156
- } {
157
- const entries: Record<string, string> = {};
158
- const remaining: string[] = [];
159
- for (let i = 0; i < rest.length; i++) {
160
- const arg = rest[i]!;
161
- if (arg === flag) {
162
- const kv = rest[++i];
163
- if (kv === undefined) {
164
- return { entries, remaining, error: `${flag} requires a KEY=VALUE argument` };
165
- }
166
- const eq = kv.indexOf("=");
167
- if (eq <= 0) {
168
- return { entries, remaining, error: `${flag} must be in the form KEY=VALUE (got: ${kv})` };
169
- }
170
- entries[kv.slice(0, eq)] = kv.slice(eq + 1);
171
- continue;
172
- }
173
- remaining.push(arg);
174
- }
175
- return { entries, remaining, error: null };
176
- }
177
-
178
- /**
179
- * Repeatable `--flag <value>` collector. Each occurrence consumes one
180
- * argv slot. Returns the collected values in insertion order alongside
181
- * the remaining argv.
182
- */
183
- export function collectRepeated(rest: readonly string[], flag: string): {
184
- readonly values: readonly string[];
185
- readonly remaining: readonly string[];
186
- readonly error: string | null;
187
- } {
188
- const values: string[] = [];
189
- const remaining: string[] = [];
190
- for (let i = 0; i < rest.length; i++) {
191
- const arg = rest[i]!;
192
- if (arg === flag) {
193
- const v = rest[++i];
194
- if (v === undefined) {
195
- return { values, remaining, error: `${flag} requires a value` };
196
- }
197
- values.push(v);
198
- continue;
199
- }
200
- remaining.push(arg);
201
- }
202
- return { values, remaining, error: null };
203
- }
204
-
205
- /**
206
- * Repeatable `--flag KEY=VALUE` collector that PRESERVES every
207
- * occurrence (no key-collision collapse). Use this when the same key
208
- * may legitimately appear multiple times — for example,
209
- * `--mcp-auth github=Authorization:Bearer t --mcp-auth github=X-Api-Key:k`
210
- * needs to register both headers, not just the last one.
211
- *
212
- * Returns `[key, value]` pairs in argv order alongside the remaining
213
- * argv with the consumed flags removed.
214
- */
215
- export function collectRepeatedKvList(rest: readonly string[], flag: string): {
216
- readonly entries: ReadonlyArray<readonly [string, string]>;
217
- readonly remaining: readonly string[];
218
- readonly error: string | null;
219
- } {
220
- const entries: Array<readonly [string, string]> = [];
221
- const remaining: string[] = [];
222
- for (let i = 0; i < rest.length; i++) {
223
- const arg = rest[i]!;
224
- if (arg === flag) {
225
- const kv = rest[++i];
226
- if (kv === undefined) {
227
- return { entries, remaining, error: `${flag} requires a KEY=VALUE argument` };
228
- }
229
- const eq = kv.indexOf("=");
230
- if (eq <= 0) {
231
- return { entries, remaining, error: `${flag} must be in the form KEY=VALUE (got: ${kv})` };
232
- }
233
- entries.push([kv.slice(0, eq), kv.slice(eq + 1)] as const);
234
- continue;
235
- }
236
- remaining.push(arg);
237
- }
238
- return { entries, remaining, error: null };
239
- }
240
-
241
- export function takeFlagValue(
242
- rest: readonly string[],
243
- flag: string
244
- ): { value: string | null; remaining: readonly string[]; error: string | null } {
245
- let value: string | null = null;
246
- const remaining: string[] = [];
247
- for (let i = 0; i < rest.length; i++) {
248
- const arg = rest[i]!;
249
- if (arg === flag) {
250
- const v = rest[++i];
251
- if (v === undefined) {
252
- return { value, remaining, error: `${flag} requires a value` };
253
- }
254
- value = v;
255
- continue;
256
- }
257
- remaining.push(arg);
258
- }
259
- return { value, remaining, error: null };
260
- }
261
-
262
- /**
263
- * Parse a human-friendly duration into milliseconds. Accepts a bare
264
- * integer (interpreted as milliseconds) or `<number><unit>` where unit
265
- * is one of `ms`, `s`, `m`, `h` — e.g. `500ms`, `30s`, `8m`, `1h`.
266
- *
267
- * Returns the millisecond count, or an `error` string describing the
268
- * malformed input (the value is never silently coerced to 0). Rejects
269
- * negatives and non-finite values so a `--timeout` can never disable the
270
- * deadline by accident.
271
- */
272
- export function parseDuration(input: string): { readonly ms: number | null; readonly error: string | null } {
273
- const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(input.trim());
274
- if (!match) {
275
- return { ms: null, error: `invalid duration "${input}" (expected e.g. 500ms, 30s, 8m, 1h, or a bare ms integer)` };
276
- }
277
- const value = Number(match[1]);
278
- if (!Number.isFinite(value) || value < 0) {
279
- return { ms: null, error: `invalid duration "${input}" (must be a non-negative number)` };
280
- }
281
- const unit = match[2] ?? "ms";
282
- const factor = unit === "h" ? 3_600_000 : unit === "m" ? 60_000 : unit === "s" ? 1_000 : 1;
283
- return { ms: Math.round(value * factor), error: null };
284
- }
285
-
286
- export function takeBooleanFlag(rest: readonly string[], flag: string): {
287
- readonly present: boolean;
288
- readonly remaining: readonly string[];
289
- } {
290
- const remaining: string[] = [];
291
- let present = false;
292
- for (const arg of rest) {
293
- if (arg === flag) {
294
- present = true;
295
- continue;
296
- }
297
- remaining.push(arg);
298
- }
299
- return { present, remaining };
300
- }
301
-
302
- /**
303
- * Take an option flag in either `--flag value` or `--flag=value` form.
304
- * Returns the trailing value (or undefined when the flag is absent)
305
- * plus the remaining args. Unlike `takeFlagValue`, this is permissive
306
- * about the `=` form which CLI users frequently expect.
307
- */
308
- export function takeOptionFlag(
309
- rest: readonly string[],
310
- flag: string
311
- ): { readonly value: string | undefined; readonly remaining: readonly string[] } {
312
- const remaining: string[] = [];
313
- let value: string | undefined;
314
- const prefix = `${flag}=`;
315
- for (let i = 0; i < rest.length; i++) {
316
- const arg = rest[i]!;
317
- if (arg === flag) {
318
- const next = rest[++i];
319
- if (next !== undefined) value = next;
320
- continue;
321
- }
322
- if (arg.startsWith(prefix)) {
323
- value = arg.slice(prefix.length);
324
- continue;
325
- }
326
- remaining.push(arg);
327
- }
328
- return { value, remaining };
329
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * `aex delete-asset <assetId|hash>` — DELETE /assets/{assetId}.
3
- *
4
- * Removes a workspace asset blob from the shared content-addressed store.
5
- * Runs that already snapshotted the asset into their own prefix are
6
- * unaffected. Accepts `sha256:<hex>` or a bare 64-hex digest.
7
- */
8
- import { operations } from "@aexhq/contracts";
9
- import type { CliIO } from "../internal.js";
10
- import {
11
- type CliExitCode,
12
- SUCCESS,
13
- USAGE_ERR,
14
- emitJsonError,
15
- makeHttpClient,
16
- parseCommonHostFlags,
17
- refuseInsideManagedRun
18
- } from "./common.js";
19
-
20
- export async function runDeleteAssetCmd(io: CliIO, argv: readonly string[]): Promise<CliExitCode> {
21
- if (await refuseInsideManagedRun(io, "delete-asset")) return USAGE_ERR;
22
-
23
- const common = parseCommonHostFlags(argv);
24
- if (!common.ok) {
25
- io.stderr(`${common.reason}\n`);
26
- return USAGE_ERR;
27
- }
28
- const positional = common.rest.filter((arg) => !arg.startsWith("--"));
29
- if (positional.length !== 1) {
30
- io.stderr("usage: aex delete-asset <hash> [common flags]\n");
31
- return USAGE_ERR;
32
- }
33
- const hash = positional[0]!;
34
-
35
- const http = makeHttpClient(io, common.flags);
36
- try {
37
- await operations.deleteWorkspaceAsset(http, hash);
38
- io.stdout(JSON.stringify({ hash, deleted: true }) + "\n");
39
- return SUCCESS;
40
- } catch (err) {
41
- return emitJsonError(io, "delete_asset_failed", (err as Error).message ?? "delete-asset failed", { hash });
42
- }
43
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * `aex delete <run-id>` — DELETE /api/runs/{id}.
3
- */
4
- import { operations } from "@aexhq/contracts";
5
- import type { CliIO } from "../internal.js";
6
- import {
7
- type CliExitCode,
8
- SUCCESS,
9
- USAGE_ERR,
10
- emitJsonError,
11
- makeHttpClient,
12
- parseCommonHostFlags,
13
- refuseInsideManagedRun
14
- } from "./common.js";
15
-
16
- export async function runDeleteCmd(io: CliIO, argv: readonly string[]): Promise<CliExitCode> {
17
- if (await refuseInsideManagedRun(io, "delete")) return USAGE_ERR;
18
-
19
- const common = parseCommonHostFlags(argv);
20
- if (!common.ok) {
21
- io.stderr(`${common.reason}\n`);
22
- return USAGE_ERR;
23
- }
24
- const positional = common.rest.filter((arg) => !arg.startsWith("--"));
25
- if (positional.length !== 1) {
26
- io.stderr("usage: aex delete <run-id> [common flags]\n");
27
- return USAGE_ERR;
28
- }
29
- const runId = positional[0]!;
30
-
31
- const http = makeHttpClient(io, common.flags);
32
- try {
33
- await operations.deleteRun(http, runId);
34
- io.stdout(JSON.stringify({ runId, deleted: true }) + "\n");
35
- return SUCCESS;
36
- } catch (err) {
37
- return emitJsonError(io, "delete_failed", (err as Error).message ?? "delete failed", { runId });
38
- }
39
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * `aex download <run-id> [--only namespace] [--out path]` — download
3
- * a run's content as a zip, assembled client-side from the public read
4
- * endpoints (no per-output id required).
5
- *
6
- * Without `--only`, downloads everything public — organised into
7
- * `metadata/run.json`, typed `events/events.jsonl`, `outputs/<rel>`
8
- * (deliverables), plus `manifest.json`.
9
- *
10
- * `--only outputs|events|metadata` downloads just that one
11
- * namespace (files at the zip root).
12
- *
13
- * `--out` resolves relative to the host CWD; if omitted the file is
14
- * written to `aex-run-<run-id>[-<namespace>].zip` in the current
15
- * directory.
16
- */
17
- import { resolve as resolvePath } from "node:path";
18
- import { operations } from "@aexhq/contracts";
19
- import type { CliIO } from "../internal.js";
20
- import {
21
- type CliExitCode,
22
- SUCCESS,
23
- USAGE_ERR,
24
- emitJsonError,
25
- makeHttpClient,
26
- parseCommonHostFlags,
27
- refuseInsideManagedRun,
28
- takeFlagValue
29
- } from "./common.js";
30
-
31
- type Namespace = "outputs" | "events" | "metadata";
32
-
33
- const NAMESPACE_DOWNLOADERS = {
34
- outputs: operations.downloadOutputs,
35
- events: operations.downloadEvents,
36
- metadata: operations.downloadMetadata
37
- } satisfies Record<Namespace, typeof operations.download>;
38
-
39
- export async function runDownloadCmd(io: CliIO, argv: readonly string[]): Promise<CliExitCode> {
40
- if (await refuseInsideManagedRun(io, "download")) return USAGE_ERR;
41
-
42
- const common = parseCommonHostFlags(argv);
43
- if (!common.ok) {
44
- io.stderr(`${common.reason}\n`);
45
- return USAGE_ERR;
46
- }
47
- const outFlag = takeFlagValue(common.rest, "--out");
48
- if (outFlag.error) {
49
- io.stderr(`${outFlag.error}\n`);
50
- return USAGE_ERR;
51
- }
52
- const onlyFlag = takeFlagValue(outFlag.remaining, "--only");
53
- if (onlyFlag.error) {
54
- io.stderr(`${onlyFlag.error}\n`);
55
- return USAGE_ERR;
56
- }
57
- if (onlyFlag.value !== null && !(onlyFlag.value in NAMESPACE_DOWNLOADERS)) {
58
- io.stderr(`--only must be one of: ${Object.keys(NAMESPACE_DOWNLOADERS).join(", ")}\n`);
59
- return USAGE_ERR;
60
- }
61
- const namespace = onlyFlag.value as Namespace | null;
62
-
63
- const positional = onlyFlag.remaining.filter((arg) => !arg.startsWith("--"));
64
- if (positional.length !== 1) {
65
- io.stderr("usage: aex download <run-id> [--only outputs|events|metadata] [--out path] [common flags]\n");
66
- return USAGE_ERR;
67
- }
68
- const runId = positional[0]!;
69
-
70
- const http = makeHttpClient(io, common.flags);
71
- const downloader = namespace ? NAMESPACE_DOWNLOADERS[namespace] : operations.download;
72
-
73
- let bytes: Uint8Array;
74
- try {
75
- bytes = await downloader(http, runId);
76
- } catch (err) {
77
- return emitJsonError(io, "download_failed", (err as Error).message ?? "download failed", { runId });
78
- }
79
-
80
- const destination = resolveDestination(io, outFlag.value, runId, namespace);
81
- try {
82
- await io.writeFile(destination, bytes);
83
- } catch (err) {
84
- return emitJsonError(io, "write_failed", `failed to write archive: ${(err as Error).message}`, { destination });
85
- }
86
-
87
- io.stdout(JSON.stringify({ runId, namespace: namespace ?? "all", path: destination, bytes: bytes.byteLength }) + "\n");
88
- return SUCCESS;
89
- }
90
-
91
- function resolveDestination(io: CliIO, out: string | null, runId: string, namespace: Namespace | null): string {
92
- if (out) {
93
- return resolvePath(io.cwd(), out);
94
- }
95
- const suffix = namespace ? `-${namespace}` : "";
96
- return resolvePath(io.cwd(), `aex-run-${runId}${suffix}.zip`);
97
- }