antpath 0.7.0 → 0.8.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.
- package/dist/_shared/operations.d.ts +33 -0
- package/dist/_shared/operations.js +43 -0
- package/dist/_shared/proxy-protocol.d.ts +10 -2
- package/dist/_shared/proxy-protocol.js +3 -2
- package/dist/_shared/runtime-types.d.ts +19 -0
- package/dist/_shared/submission.d.ts +21 -0
- package/dist/_shared/submission.js +93 -3
- package/dist/cli.mjs +116 -35
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +69 -0
- package/dist/client.js +67 -1
- package/dist/client.js.map +1 -1
- package/dist/proxy-endpoint.d.ts +26 -7
- package/dist/proxy-endpoint.js +19 -3
- package/dist/proxy-endpoint.js.map +1 -1
- package/dist/skill.d.ts +41 -0
- package/dist/skill.js +52 -0
- package/dist/skill.js.map +1 -1
- package/docs/credentials.md +34 -0
- package/docs/events.md +7 -0
- package/docs/mcp.md +28 -0
- package/docs/outputs.md +92 -12
- package/docs/quickstart.md +37 -0
- package/docs/skills.md +49 -0
- package/package.json +1 -1
|
@@ -22,6 +22,19 @@ export declare function createOutputLink(http: HttpClient, runId: string, output
|
|
|
22
22
|
export declare function cancelRun(http: HttpClient, runId: string): Promise<void>;
|
|
23
23
|
export declare function deleteRun(http: HttpClient, runId: string): Promise<void>;
|
|
24
24
|
export declare function whoami(http: HttpClient): Promise<WhoAmI>;
|
|
25
|
+
/**
|
|
26
|
+
* Stream the per-run archive zip from the BFF. Returns the raw
|
|
27
|
+
* `Response` so callers can pipe the body to disk without buffering
|
|
28
|
+
* the whole archive in memory.
|
|
29
|
+
*
|
|
30
|
+
* The archive lifecycle contract lives in
|
|
31
|
+
* `apps/dashboard/src/server/run-archive.ts` and
|
|
32
|
+
* `packages/sdk/docs/outputs.md`. Pre-session runs reject with HTTP
|
|
33
|
+
* 409 `run_not_started`; mid-session and terminal both produce the
|
|
34
|
+
* same archive layout and differ only in `manifest.json`'s `source`
|
|
35
|
+
* + `partial` fields.
|
|
36
|
+
*/
|
|
37
|
+
export declare function downloadRunArchive(http: HttpClient, runId: string): Promise<Response>;
|
|
25
38
|
export declare function submitRunFlat(http: HttpClient, request: PlatformFlatRunSubmissionInput): Promise<Run>;
|
|
26
39
|
/**
|
|
27
40
|
* Multipart variant of `submitRunFlat` for runs that carry transient
|
|
@@ -59,3 +72,23 @@ export declare function createSkillBundle(http: HttpClient, args: {
|
|
|
59
72
|
export declare function listSkills(http: HttpClient): Promise<readonly Skill[]>;
|
|
60
73
|
export declare function getSkill(http: HttpClient, skillId: string): Promise<Skill>;
|
|
61
74
|
export declare function deleteSkill(http: HttpClient, skillId: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Lookup a live workspace skill by `(name, contentHash)`. Returns the
|
|
77
|
+
* matching `Skill` record or null when no live row carries that hash.
|
|
78
|
+
*
|
|
79
|
+
* `contentHash` is the wire format `sha256:<hex>` as returned by
|
|
80
|
+
* `hashSkillBundle`. This powers `Skill.uploadIfChanged` — the SDK
|
|
81
|
+
* computes the hash locally and calls this function to skip the upload
|
|
82
|
+
* when the bytes already exist.
|
|
83
|
+
*/
|
|
84
|
+
export declare function findSkillByHash(http: HttpClient, args: {
|
|
85
|
+
readonly name: string;
|
|
86
|
+
readonly contentHash: string;
|
|
87
|
+
}): Promise<Skill | null>;
|
|
88
|
+
/**
|
|
89
|
+
* Lookup a live workspace skill by `name`. Returns the matching `Skill`
|
|
90
|
+
* record or null when no live row carries that name. Implemented as a
|
|
91
|
+
* list-and-filter on the existing `/api/skills` endpoint — the
|
|
92
|
+
* indexed by-hash route is reserved for `uploadIfChanged`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function findSkillByName(http: HttpClient, name: string): Promise<Skill | null>;
|
|
@@ -41,6 +41,22 @@ export async function deleteRun(http, runId) {
|
|
|
41
41
|
export async function whoami(http) {
|
|
42
42
|
return http.request("/api/whoami");
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Stream the per-run archive zip from the BFF. Returns the raw
|
|
46
|
+
* `Response` so callers can pipe the body to disk without buffering
|
|
47
|
+
* the whole archive in memory.
|
|
48
|
+
*
|
|
49
|
+
* The archive lifecycle contract lives in
|
|
50
|
+
* `apps/dashboard/src/server/run-archive.ts` and
|
|
51
|
+
* `packages/sdk/docs/outputs.md`. Pre-session runs reject with HTTP
|
|
52
|
+
* 409 `run_not_started`; mid-session and terminal both produce the
|
|
53
|
+
* same archive layout and differ only in `manifest.json`'s `source`
|
|
54
|
+
* + `partial` fields.
|
|
55
|
+
*/
|
|
56
|
+
export async function downloadRunArchive(http, runId) {
|
|
57
|
+
const { response } = await http.download(`/api/runs/${encodeURIComponent(runId)}/download`);
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
44
60
|
// ===========================================================================
|
|
45
61
|
// Flat (Skill / McpServer / Blueprint) operations
|
|
46
62
|
// ===========================================================================
|
|
@@ -125,6 +141,33 @@ export async function deleteSkill(http, skillId) {
|
|
|
125
141
|
method: "DELETE"
|
|
126
142
|
});
|
|
127
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Lookup a live workspace skill by `(name, contentHash)`. Returns the
|
|
146
|
+
* matching `Skill` record or null when no live row carries that hash.
|
|
147
|
+
*
|
|
148
|
+
* `contentHash` is the wire format `sha256:<hex>` as returned by
|
|
149
|
+
* `hashSkillBundle`. This powers `Skill.uploadIfChanged` — the SDK
|
|
150
|
+
* computes the hash locally and calls this function to skip the upload
|
|
151
|
+
* when the bytes already exist.
|
|
152
|
+
*/
|
|
153
|
+
export async function findSkillByHash(http, args) {
|
|
154
|
+
const params = new URLSearchParams({
|
|
155
|
+
name: args.name,
|
|
156
|
+
content_hash: args.contentHash
|
|
157
|
+
});
|
|
158
|
+
const result = await http.request(`/api/skills/by-hash?${params.toString()}`);
|
|
159
|
+
return result.skill ?? null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Lookup a live workspace skill by `name`. Returns the matching `Skill`
|
|
163
|
+
* record or null when no live row carries that name. Implemented as a
|
|
164
|
+
* list-and-filter on the existing `/api/skills` endpoint — the
|
|
165
|
+
* indexed by-hash route is reserved for `uploadIfChanged`.
|
|
166
|
+
*/
|
|
167
|
+
export async function findSkillByName(http, name) {
|
|
168
|
+
const skills = await listSkills(http);
|
|
169
|
+
return skills.find((skill) => skill.name === name) ?? null;
|
|
170
|
+
}
|
|
128
171
|
function unwrapSkill(result) {
|
|
129
172
|
if (result && typeof result === "object" && "skill" in result) {
|
|
130
173
|
return result.skill;
|
|
@@ -67,8 +67,16 @@ export interface ProxyIndexEntry {
|
|
|
67
67
|
* The actual auth value lives in the run's Vault bundle under
|
|
68
68
|
* `secrets.proxyEndpointAuth[i].value` and is never reflected back
|
|
69
69
|
* into the container or index file.
|
|
70
|
+
*
|
|
71
|
+
* The `none` variant declares an upstream that takes no auth (public
|
|
72
|
+
* APIs like Wikimedia Commons or NASA Images). It still routes through
|
|
73
|
+
* the proxy for unified egress, audit, and budget enforcement, but
|
|
74
|
+
* carries no `proxyEndpointAuth[]` entry and the BFF injects no
|
|
75
|
+
* header or query value.
|
|
70
76
|
*/
|
|
71
77
|
export type ProxyAuthShape = {
|
|
78
|
+
readonly type: "none";
|
|
79
|
+
} | {
|
|
72
80
|
readonly type: "bearer";
|
|
73
81
|
} | {
|
|
74
82
|
readonly type: "basic";
|
|
@@ -82,7 +90,7 @@ export type ProxyAuthShape = {
|
|
|
82
90
|
export type ProxyAuthType = ProxyAuthShape["type"];
|
|
83
91
|
/**
|
|
84
92
|
* Header name (lowercase) that an upstream auth shape uses as its
|
|
85
|
-
* carrier. Returns `undefined` for query-based auth.
|
|
93
|
+
* carrier. Returns `undefined` for query-based and keyless auth.
|
|
86
94
|
*
|
|
87
95
|
* Used by the submission parser to forbid `allowHeaders` from listing
|
|
88
96
|
* the auth header (avoids leaks via caller-supplied headers), and by
|
|
@@ -92,7 +100,7 @@ export type ProxyAuthType = ProxyAuthShape["type"];
|
|
|
92
100
|
export declare function authShapeHeaderName(shape: ProxyAuthShape): string | undefined;
|
|
93
101
|
/**
|
|
94
102
|
* Query-string key that an upstream query-based auth shape uses as its
|
|
95
|
-
* carrier. Returns `undefined` for non-query shapes.
|
|
103
|
+
* carrier. Returns `undefined` for non-query shapes (including "none").
|
|
96
104
|
*/
|
|
97
105
|
export declare function authShapeQueryName(shape: ProxyAuthShape): string | undefined;
|
|
98
106
|
/**
|
|
@@ -66,7 +66,7 @@ export const PROXY_ERROR_CODES = [
|
|
|
66
66
|
];
|
|
67
67
|
/**
|
|
68
68
|
* Header name (lowercase) that an upstream auth shape uses as its
|
|
69
|
-
* carrier. Returns `undefined` for query-based auth.
|
|
69
|
+
* carrier. Returns `undefined` for query-based and keyless auth.
|
|
70
70
|
*
|
|
71
71
|
* Used by the submission parser to forbid `allowHeaders` from listing
|
|
72
72
|
* the auth header (avoids leaks via caller-supplied headers), and by
|
|
@@ -81,12 +81,13 @@ export function authShapeHeaderName(shape) {
|
|
|
81
81
|
case "header":
|
|
82
82
|
return shape.name.toLowerCase();
|
|
83
83
|
case "query":
|
|
84
|
+
case "none":
|
|
84
85
|
return undefined;
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
/**
|
|
88
89
|
* Query-string key that an upstream query-based auth shape uses as its
|
|
89
|
-
* carrier. Returns `undefined` for non-query shapes.
|
|
90
|
+
* carrier. Returns `undefined` for non-query shapes (including "none").
|
|
90
91
|
*/
|
|
91
92
|
export function authShapeQueryName(shape) {
|
|
92
93
|
return shape.type === "query" ? shape.name : undefined;
|
|
@@ -71,6 +71,25 @@ export interface WhoAmI {
|
|
|
71
71
|
readonly tokenId?: string;
|
|
72
72
|
readonly tokenName?: string | null;
|
|
73
73
|
readonly scopes?: readonly string[];
|
|
74
|
+
/**
|
|
75
|
+
* Workspace-level caps the BFF will enforce on subsequent calls.
|
|
76
|
+
* Surfaced so consumers (e.g. broll's app-side admission gate) can
|
|
77
|
+
* decide whether to keep their own gate or rely on platform headers.
|
|
78
|
+
* All fields optional — older BFFs may omit. Numbers are concrete
|
|
79
|
+
* snapshots at the time of the `whoami` call.
|
|
80
|
+
*/
|
|
81
|
+
readonly caps?: {
|
|
82
|
+
/** Token-bucket cap on POST /api/runs per minute, per workspace. */
|
|
83
|
+
readonly runSubmitPerMinute?: number;
|
|
84
|
+
/** Hard cap on concurrent non-terminal runs the workspace may hold. */
|
|
85
|
+
readonly maxConcurrentRuns?: number;
|
|
86
|
+
/** Storage cap (bytes) on captured output objects, workspace-wide. */
|
|
87
|
+
readonly storageCapBytes?: number;
|
|
88
|
+
/** Current captured-output usage in bytes. */
|
|
89
|
+
readonly storageUsedBytes?: number;
|
|
90
|
+
/** Wall-clock ceiling on a single run before forced termination. */
|
|
91
|
+
readonly maxRunDurationMs?: number;
|
|
92
|
+
};
|
|
74
93
|
readonly [key: string]: unknown;
|
|
75
94
|
}
|
|
76
95
|
/**
|
|
@@ -175,6 +175,27 @@ export interface PlatformFlatSubmission {
|
|
|
175
175
|
readonly mcpServers: readonly McpServerRef[];
|
|
176
176
|
readonly environment?: PlatformTemplateEnvironment;
|
|
177
177
|
readonly metadata?: Record<string, JsonValue>;
|
|
178
|
+
/**
|
|
179
|
+
* Opt-in container paths to capture as `output_objects` at session
|
|
180
|
+
* terminal. When omitted, the worker still persists run metadata
|
|
181
|
+
* (status, events, snapshots, cleanup state) but does not capture
|
|
182
|
+
* any container file bytes. When present, the worker drives a
|
|
183
|
+
* synthetic agent turn at session terminal that instructs the agent
|
|
184
|
+
* to register every file under these paths via the Anthropic Files
|
|
185
|
+
* API, then walks the resulting list and copies bytes into private
|
|
186
|
+
* Supabase Storage.
|
|
187
|
+
*
|
|
188
|
+
* Validation:
|
|
189
|
+
* - Absolute UNIX paths only (starts with `/`).
|
|
190
|
+
* - No `..` segments, no NUL bytes, no embedded newlines.
|
|
191
|
+
* - Max 32 entries.
|
|
192
|
+
* - Max 512 bytes per entry.
|
|
193
|
+
*
|
|
194
|
+
* Entries are normalised (collapse `/+`, drop trailing `/` except
|
|
195
|
+
* for `/`) and deduplicated. The normalised list is what travels in
|
|
196
|
+
* the idempotency hash and the run snapshot.
|
|
197
|
+
*/
|
|
198
|
+
readonly outputDirs?: readonly string[];
|
|
178
199
|
}
|
|
179
200
|
export interface PlatformFlatRunSubmissionRequest {
|
|
180
201
|
readonly workspaceId: string;
|
|
@@ -272,6 +272,9 @@ function parseProxyAuthShape(input, field) {
|
|
|
272
272
|
const value = requireRecord(input, field);
|
|
273
273
|
const type = requireString(value.type, `${field}.type`);
|
|
274
274
|
switch (type) {
|
|
275
|
+
case "none":
|
|
276
|
+
assertOnlyKeys(value, field, ["type"]);
|
|
277
|
+
return { type: "none" };
|
|
275
278
|
case "bearer":
|
|
276
279
|
assertOnlyKeys(value, field, ["type"]);
|
|
277
280
|
return { type: "bearer" };
|
|
@@ -293,7 +296,7 @@ function parseProxyAuthShape(input, field) {
|
|
|
293
296
|
return { type: "query", name };
|
|
294
297
|
}
|
|
295
298
|
default:
|
|
296
|
-
throw new Error(`${field}.type must be one of: bearer, basic, header, query`);
|
|
299
|
+
throw new Error(`${field}.type must be one of: none, bearer, basic, header, query`);
|
|
297
300
|
}
|
|
298
301
|
}
|
|
299
302
|
function parseProxyMethods(input, field) {
|
|
@@ -382,6 +385,16 @@ function crossValidateProxyEndpointsAndAuth(endpoints, auth) {
|
|
|
382
385
|
const authByName = new Map(authList.map((a) => [a.name, a]));
|
|
383
386
|
for (const endpoint of endpointsList) {
|
|
384
387
|
const authEntry = authByName.get(endpoint.name);
|
|
388
|
+
if (endpoint.authShape.type === "none") {
|
|
389
|
+
// Keyless endpoints carry no auth value. Reject any matching
|
|
390
|
+
// auth entry so callers don't accidentally ship a secret bound
|
|
391
|
+
// to a "none" endpoint (which would be silently ignored at
|
|
392
|
+
// request time — confusing and a leak risk).
|
|
393
|
+
if (authEntry) {
|
|
394
|
+
throw new Error(`proxyEndpoints[${endpoint.name}] has authShape "none" but a matching secrets.proxyEndpointAuth entry was supplied; remove the auth entry`);
|
|
395
|
+
}
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
385
398
|
if (!authEntry) {
|
|
386
399
|
throw new Error(`proxyEndpoints[${endpoint.name}] has no matching secrets.proxyEndpointAuth entry`);
|
|
387
400
|
}
|
|
@@ -742,7 +755,8 @@ function parseFlatSubmission(input) {
|
|
|
742
755
|
"skills",
|
|
743
756
|
"mcpServers",
|
|
744
757
|
"environment",
|
|
745
|
-
"metadata"
|
|
758
|
+
"metadata",
|
|
759
|
+
"outputDirs"
|
|
746
760
|
]);
|
|
747
761
|
for (const key of Object.keys(value)) {
|
|
748
762
|
if (!allowed.has(key)) {
|
|
@@ -756,6 +770,7 @@ function parseFlatSubmission(input) {
|
|
|
756
770
|
const mcpServers = parseFlatMcpServers(value.mcpServers);
|
|
757
771
|
const environment = parseTemplateEnvironment(value.environment);
|
|
758
772
|
const metadata = optionalJsonRecord(value.metadata, "submission.metadata");
|
|
773
|
+
const outputDirs = parseOutputDirs(value.outputDirs);
|
|
759
774
|
return {
|
|
760
775
|
model,
|
|
761
776
|
...(system ? { system } : {}),
|
|
@@ -763,9 +778,84 @@ function parseFlatSubmission(input) {
|
|
|
763
778
|
skills,
|
|
764
779
|
mcpServers,
|
|
765
780
|
...(environment ? { environment } : {}),
|
|
766
|
-
...(metadata ? { metadata } : {})
|
|
781
|
+
...(metadata ? { metadata } : {}),
|
|
782
|
+
...(outputDirs ? { outputDirs } : {})
|
|
767
783
|
};
|
|
768
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* Maximum number of `outputDirs` entries accepted per submission.
|
|
787
|
+
*
|
|
788
|
+
* 32 is enough room for the typical "one or two capture roots" pattern
|
|
789
|
+
* plus a generous margin for legitimate multi-root use cases (per-tool
|
|
790
|
+
* output directory + scratch state + logs, repeated across a few
|
|
791
|
+
* subdirectories), without inviting abuse of the synthetic-turn path
|
|
792
|
+
* the worker drives at session terminal.
|
|
793
|
+
*/
|
|
794
|
+
const MAX_OUTPUT_DIRS = 32;
|
|
795
|
+
/**
|
|
796
|
+
* Maximum byte length of a single `outputDirs` entry (after UTF-8
|
|
797
|
+
* encoding). 512 bytes comfortably covers `/very/long/nested/path`
|
|
798
|
+
* style entries without letting a misuse smuggle large blobs through
|
|
799
|
+
* the field.
|
|
800
|
+
*/
|
|
801
|
+
const MAX_OUTPUT_DIR_BYTES = 512;
|
|
802
|
+
function parseOutputDirs(input) {
|
|
803
|
+
if (input === undefined) {
|
|
804
|
+
return undefined;
|
|
805
|
+
}
|
|
806
|
+
if (!Array.isArray(input)) {
|
|
807
|
+
throw new Error("submission.outputDirs must be an array of absolute UNIX paths");
|
|
808
|
+
}
|
|
809
|
+
if (input.length === 0) {
|
|
810
|
+
// Treat an empty array as omission so the idempotency hash matches
|
|
811
|
+
// the "no outputDirs" case.
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
814
|
+
if (input.length > MAX_OUTPUT_DIRS) {
|
|
815
|
+
throw new Error(`submission.outputDirs has ${input.length} entries; max is ${MAX_OUTPUT_DIRS}`);
|
|
816
|
+
}
|
|
817
|
+
const seen = new Set();
|
|
818
|
+
const normalised = [];
|
|
819
|
+
for (let i = 0; i < input.length; i++) {
|
|
820
|
+
const item = input[i];
|
|
821
|
+
if (typeof item !== "string") {
|
|
822
|
+
throw new Error(`submission.outputDirs[${i}] must be a string`);
|
|
823
|
+
}
|
|
824
|
+
if (item.length === 0) {
|
|
825
|
+
throw new Error(`submission.outputDirs[${i}] must be a non-empty absolute UNIX path`);
|
|
826
|
+
}
|
|
827
|
+
const bytes = new TextEncoder().encode(item).length;
|
|
828
|
+
if (bytes > MAX_OUTPUT_DIR_BYTES) {
|
|
829
|
+
throw new Error(`submission.outputDirs[${i}] exceeds ${MAX_OUTPUT_DIR_BYTES} bytes (got ${bytes})`);
|
|
830
|
+
}
|
|
831
|
+
if (!item.startsWith("/")) {
|
|
832
|
+
throw new Error(`submission.outputDirs[${i}] must be an absolute UNIX path (start with '/')`);
|
|
833
|
+
}
|
|
834
|
+
if (item.includes("\0")) {
|
|
835
|
+
throw new Error(`submission.outputDirs[${i}] must not contain NUL bytes`);
|
|
836
|
+
}
|
|
837
|
+
if (item.includes("\n") || item.includes("\r")) {
|
|
838
|
+
throw new Error(`submission.outputDirs[${i}] must not contain newline characters`);
|
|
839
|
+
}
|
|
840
|
+
const segments = item.split("/");
|
|
841
|
+
if (segments.includes("..")) {
|
|
842
|
+
throw new Error(`submission.outputDirs[${i}] must not contain '..' segments`);
|
|
843
|
+
}
|
|
844
|
+
const collapsed = segments
|
|
845
|
+
.filter((seg, idx) => seg.length > 0 || idx === 0)
|
|
846
|
+
.join("/");
|
|
847
|
+
const stripped = collapsed.length > 1 && collapsed.endsWith("/")
|
|
848
|
+
? collapsed.slice(0, -1)
|
|
849
|
+
: collapsed;
|
|
850
|
+
const canonical = stripped.length === 0 ? "/" : stripped;
|
|
851
|
+
if (seen.has(canonical)) {
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
seen.add(canonical);
|
|
855
|
+
normalised.push(canonical);
|
|
856
|
+
}
|
|
857
|
+
return normalised;
|
|
858
|
+
}
|
|
769
859
|
function parseFlatPrompt(input) {
|
|
770
860
|
if (typeof input === "string") {
|
|
771
861
|
if (input.length === 0) {
|
package/dist/cli.mjs
CHANGED
|
@@ -6,7 +6,12 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// dist/cli.js
|
|
9
|
-
import { readFile as readFile2, writeFile } from "node:fs/promises";
|
|
9
|
+
import { readFile as readFile2, writeFile, readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
|
10
|
+
import { resolve as resolvePath4 } from "node:path";
|
|
11
|
+
|
|
12
|
+
// dist/internal.js
|
|
13
|
+
var ANTPATH_INDEX_PATH = "/antpath/index.json";
|
|
14
|
+
var ANTPATH_RUN_TOKEN_PATH = "/antpath/run-token";
|
|
10
15
|
|
|
11
16
|
// ../shared/dist/config.js
|
|
12
17
|
var DEFAULT_CAPS = {
|
|
@@ -527,6 +532,9 @@ __export(operations_exports, {
|
|
|
527
532
|
createSkillBundle: () => createSkillBundle,
|
|
528
533
|
deleteRun: () => deleteRun,
|
|
529
534
|
deleteSkill: () => deleteSkill,
|
|
535
|
+
downloadRunArchive: () => downloadRunArchive,
|
|
536
|
+
findSkillByHash: () => findSkillByHash,
|
|
537
|
+
findSkillByName: () => findSkillByName,
|
|
530
538
|
getRun: () => getRun,
|
|
531
539
|
getSkill: () => getSkill,
|
|
532
540
|
listOutputs: () => listOutputs,
|
|
@@ -567,6 +575,10 @@ async function deleteRun(http, runId) {
|
|
|
567
575
|
async function whoami(http) {
|
|
568
576
|
return http.request("/api/whoami");
|
|
569
577
|
}
|
|
578
|
+
async function downloadRunArchive(http, runId) {
|
|
579
|
+
const { response } = await http.download(`/api/runs/${encodeURIComponent(runId)}/download`);
|
|
580
|
+
return response;
|
|
581
|
+
}
|
|
570
582
|
async function submitRunFlat(http, request) {
|
|
571
583
|
return http.request("/api/runs", {
|
|
572
584
|
method: "POST",
|
|
@@ -623,6 +635,18 @@ async function deleteSkill(http, skillId) {
|
|
|
623
635
|
method: "DELETE"
|
|
624
636
|
});
|
|
625
637
|
}
|
|
638
|
+
async function findSkillByHash(http, args) {
|
|
639
|
+
const params = new URLSearchParams({
|
|
640
|
+
name: args.name,
|
|
641
|
+
content_hash: args.contentHash
|
|
642
|
+
});
|
|
643
|
+
const result = await http.request(`/api/skills/by-hash?${params.toString()}`);
|
|
644
|
+
return result.skill ?? null;
|
|
645
|
+
}
|
|
646
|
+
async function findSkillByName(http, name) {
|
|
647
|
+
const skills = await listSkills(http);
|
|
648
|
+
return skills.find((skill) => skill.name === name) ?? null;
|
|
649
|
+
}
|
|
626
650
|
function unwrapSkill(result) {
|
|
627
651
|
if (result && typeof result === "object" && "skill" in result) {
|
|
628
652
|
return result.skill;
|
|
@@ -669,10 +693,6 @@ function validateProxyAuth(endpoints, auth) {
|
|
|
669
693
|
}
|
|
670
694
|
}
|
|
671
695
|
|
|
672
|
-
// dist/internal.js
|
|
673
|
-
var ANTPATH_INDEX_PATH = "/antpath/index.json";
|
|
674
|
-
var ANTPATH_RUN_TOKEN_PATH = "/antpath/run-token";
|
|
675
|
-
|
|
676
696
|
// dist/host/common.js
|
|
677
697
|
var SUCCESS = { code: 0 };
|
|
678
698
|
var USAGE_ERR = { code: 2 };
|
|
@@ -830,6 +850,52 @@ function takeBooleanFlag(rest, flag) {
|
|
|
830
850
|
return { present, remaining };
|
|
831
851
|
}
|
|
832
852
|
|
|
853
|
+
// dist/outputs-sync.js
|
|
854
|
+
async function runOutputsSyncCmd(io2, dirs) {
|
|
855
|
+
if (dirs.length === 0) {
|
|
856
|
+
io2.stderr("usage: antpath outputs sync <dir> [<dir> ...]\n");
|
|
857
|
+
return USAGE_ERR;
|
|
858
|
+
}
|
|
859
|
+
try {
|
|
860
|
+
await io2.readFile(ANTPATH_INDEX_PATH);
|
|
861
|
+
} catch {
|
|
862
|
+
io2.stderr("`antpath outputs sync` is an in-container internal command and cannot run on the host.\n");
|
|
863
|
+
return USAGE_ERR;
|
|
864
|
+
}
|
|
865
|
+
if (!io2.walkDirectory) {
|
|
866
|
+
io2.stderr("antpath outputs sync: walkDirectory IO is not available\n");
|
|
867
|
+
return RUNTIME_ERR;
|
|
868
|
+
}
|
|
869
|
+
let scanned = 0;
|
|
870
|
+
let missing = 0;
|
|
871
|
+
for (const dir of dirs) {
|
|
872
|
+
if (!dir.startsWith("/")) {
|
|
873
|
+
io2.stderr(JSON.stringify({ dir, error: "non_absolute_path", message: "skipping non-absolute output dir" }) + "\n");
|
|
874
|
+
missing++;
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
let entries;
|
|
878
|
+
try {
|
|
879
|
+
entries = await io2.walkDirectory(dir);
|
|
880
|
+
} catch (err2) {
|
|
881
|
+
io2.stderr(JSON.stringify({ dir, error: "walk_failed", message: err2.message ?? "walk failed" }) + "\n");
|
|
882
|
+
missing++;
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (entries === null) {
|
|
886
|
+
io2.stderr(JSON.stringify({ dir, error: "missing_or_unreadable" }) + "\n");
|
|
887
|
+
missing++;
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
for (const entry of entries) {
|
|
891
|
+
io2.stdout(JSON.stringify({ dir, path: entry.path, sizeBytes: entry.sizeBytes }) + "\n");
|
|
892
|
+
scanned++;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
io2.stdout(JSON.stringify({ summary: { dirs: dirs.length, files: scanned, missing } }) + "\n");
|
|
896
|
+
return SUCCESS;
|
|
897
|
+
}
|
|
898
|
+
|
|
833
899
|
// dist/proxy.js
|
|
834
900
|
function parseProxyFlags(rest) {
|
|
835
901
|
let endpointName = null;
|
|
@@ -2644,7 +2710,7 @@ async function runOutputsCmd(io2, argv) {
|
|
|
2644
2710
|
}
|
|
2645
2711
|
|
|
2646
2712
|
// dist/host/download.js
|
|
2647
|
-
import { resolve as resolvePath3
|
|
2713
|
+
import { resolve as resolvePath3 } from "node:path";
|
|
2648
2714
|
async function runDownloadCmd(io2, argv) {
|
|
2649
2715
|
if (await refuseInsideManagedRun(io2, "download"))
|
|
2650
2716
|
return USAGE_ERR;
|
|
@@ -2661,51 +2727,38 @@ async function runDownloadCmd(io2, argv) {
|
|
|
2661
2727
|
return USAGE_ERR;
|
|
2662
2728
|
}
|
|
2663
2729
|
const positional = outFlag.remaining.filter((arg) => !arg.startsWith("--"));
|
|
2664
|
-
if (positional.length !==
|
|
2665
|
-
io2.stderr("usage: antpath download <run-id>
|
|
2730
|
+
if (positional.length !== 1) {
|
|
2731
|
+
io2.stderr("usage: antpath download <run-id> [--out path] [common flags]\n");
|
|
2666
2732
|
return USAGE_ERR;
|
|
2667
2733
|
}
|
|
2668
2734
|
const runId = positional[0];
|
|
2669
|
-
const outputId = positional[1];
|
|
2670
2735
|
const http = makeHttpClient(io2, common.flags);
|
|
2671
|
-
let
|
|
2736
|
+
let response;
|
|
2672
2737
|
try {
|
|
2673
|
-
|
|
2738
|
+
response = await operations_exports.downloadRunArchive(http, runId);
|
|
2674
2739
|
} catch (err2) {
|
|
2675
|
-
return emitJsonError(io2, "
|
|
2740
|
+
return emitJsonError(io2, "download_failed", err2.message ?? "download failed", { runId });
|
|
2676
2741
|
}
|
|
2677
|
-
let
|
|
2742
|
+
let bytes;
|
|
2678
2743
|
try {
|
|
2679
|
-
|
|
2744
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
2680
2745
|
} catch (err2) {
|
|
2681
|
-
return emitJsonError(io2, "download_failed", `download
|
|
2682
|
-
}
|
|
2683
|
-
if (!response.ok) {
|
|
2684
|
-
return emitJsonError(io2, "download_failed", `download HTTP ${response.status}`, { runId, outputId });
|
|
2746
|
+
return emitJsonError(io2, "download_failed", `download read failed: ${err2.message}`, { runId });
|
|
2685
2747
|
}
|
|
2686
|
-
const
|
|
2687
|
-
const destination = resolveDestination(io2, outFlag.value, outputId, link.url);
|
|
2748
|
+
const destination = resolveDestination(io2, outFlag.value, runId);
|
|
2688
2749
|
try {
|
|
2689
|
-
await io2.writeFile(destination,
|
|
2750
|
+
await io2.writeFile(destination, bytes);
|
|
2690
2751
|
} catch (err2) {
|
|
2691
|
-
return emitJsonError(io2, "write_failed", `failed to write
|
|
2752
|
+
return emitJsonError(io2, "write_failed", `failed to write archive: ${err2.message}`, { destination });
|
|
2692
2753
|
}
|
|
2693
|
-
io2.stdout(JSON.stringify({ runId,
|
|
2754
|
+
io2.stdout(JSON.stringify({ runId, path: destination, bytes: bytes.byteLength }) + "\n");
|
|
2694
2755
|
return SUCCESS;
|
|
2695
2756
|
}
|
|
2696
|
-
function resolveDestination(io2, out,
|
|
2757
|
+
function resolveDestination(io2, out, runId) {
|
|
2697
2758
|
if (out) {
|
|
2698
2759
|
return resolvePath3(io2.cwd(), out);
|
|
2699
2760
|
}
|
|
2700
|
-
|
|
2701
|
-
try {
|
|
2702
|
-
const url = new URL(signedUrl);
|
|
2703
|
-
const tail = basename2(url.pathname);
|
|
2704
|
-
if (tail)
|
|
2705
|
-
fileName = tail;
|
|
2706
|
-
} catch {
|
|
2707
|
-
}
|
|
2708
|
-
return resolvePath3(io2.cwd(), fileName);
|
|
2761
|
+
return resolvePath3(io2.cwd(), `antpath-run-${runId}.zip`);
|
|
2709
2762
|
}
|
|
2710
2763
|
|
|
2711
2764
|
// dist/host/cancel.js
|
|
@@ -2815,6 +2868,9 @@ async function dispatch(io2, args) {
|
|
|
2815
2868
|
case "events":
|
|
2816
2869
|
return runEventsCmd(io2, rest);
|
|
2817
2870
|
case "outputs":
|
|
2871
|
+
if (rest[0] === "sync") {
|
|
2872
|
+
return runOutputsSyncCmd(io2, rest.slice(1));
|
|
2873
|
+
}
|
|
2818
2874
|
return runOutputsCmd(io2, rest);
|
|
2819
2875
|
case "download":
|
|
2820
2876
|
return runDownloadCmd(io2, rest);
|
|
@@ -2864,7 +2920,7 @@ Protocol version: ${manifest.protocolVersion}
|
|
|
2864
2920
|
io2.stdout(" antpath status <run-id> --api-token T\n");
|
|
2865
2921
|
io2.stdout(" antpath events <run-id> [--follow] --api-token T\n");
|
|
2866
2922
|
io2.stdout(" antpath outputs <run-id> --api-token T\n");
|
|
2867
|
-
io2.stdout(" antpath download <run-id>
|
|
2923
|
+
io2.stdout(" antpath download <run-id> [--out path] --api-token T\n");
|
|
2868
2924
|
io2.stdout(" antpath cancel <run-id> --api-token T\n");
|
|
2869
2925
|
io2.stdout(" antpath delete <run-id> --api-token T\n");
|
|
2870
2926
|
io2.stdout(" antpath whoami --api-token T\n");
|
|
@@ -2892,6 +2948,30 @@ Protocol version: ${manifest.protocolVersion}
|
|
|
2892
2948
|
}
|
|
2893
2949
|
|
|
2894
2950
|
// dist/cli.js
|
|
2951
|
+
async function walkDirectory(root) {
|
|
2952
|
+
try {
|
|
2953
|
+
const rootStat = await stat2(root);
|
|
2954
|
+
if (!rootStat.isDirectory())
|
|
2955
|
+
return null;
|
|
2956
|
+
} catch {
|
|
2957
|
+
return null;
|
|
2958
|
+
}
|
|
2959
|
+
const out = [];
|
|
2960
|
+
async function visit(dir) {
|
|
2961
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
2962
|
+
for (const entry of entries) {
|
|
2963
|
+
const full = resolvePath4(dir, entry.name);
|
|
2964
|
+
if (entry.isDirectory()) {
|
|
2965
|
+
await visit(full);
|
|
2966
|
+
} else if (entry.isFile()) {
|
|
2967
|
+
const s = await stat2(full);
|
|
2968
|
+
out.push({ path: full, sizeBytes: s.size });
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
await visit(root);
|
|
2973
|
+
return out;
|
|
2974
|
+
}
|
|
2895
2975
|
var io = {
|
|
2896
2976
|
readFile: (path) => readFile2(path, "utf8"),
|
|
2897
2977
|
writeFile: (path, data) => writeFile(path, data),
|
|
@@ -2900,6 +2980,7 @@ var io = {
|
|
|
2900
2980
|
stderr: (chunk) => process.stderr.write(chunk),
|
|
2901
2981
|
exit: (code) => process.exit(code),
|
|
2902
2982
|
argv: process.argv,
|
|
2903
|
-
cwd: () => process.cwd()
|
|
2983
|
+
cwd: () => process.cwd(),
|
|
2984
|
+
walkDirectory
|
|
2904
2985
|
};
|
|
2905
2986
|
await runCli(io);
|
package/dist/cli.mjs.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e7ee04a6c02d7ddfef1546abddda3b7031c92b8f21471a1e1cec10478a30fa50 cli.mjs
|