antpath 0.3.1 → 0.4.1

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/README.md CHANGED
@@ -16,24 +16,26 @@ import {
16
16
  ```
17
17
 
18
18
  ```bash
19
- antpath run ./template.json --api-token ant_… --workspace … --dashboard-url … \
19
+ antpath run ./template.json --api-token ant_… \
20
20
  --anthropic-api-key sk-ant-… --follow
21
- antpath status <run-id> --api-token … --workspace … --dashboard-url …
22
- antpath events <run-id> --api-token … --workspace … --dashboard-url … --follow
23
- antpath outputs <run-id> --api-token … --workspace … --dashboard-url …
24
- antpath download <run-id> <output-id> --out ./local --api-token … --workspace … --dashboard-url …
25
- antpath cancel <run-id> --api-token … --workspace … --dashboard-url …
26
- antpath delete <run-id> --api-token … --workspace … --dashboard-url …
27
- antpath whoami --api-token … --dashboard-url …
21
+ antpath status <run-id> --api-token …
22
+ antpath events <run-id> --api-token … --follow
23
+ antpath outputs <run-id> --api-token …
24
+ antpath download <run-id> <output-id> --out ./local --api-token …
25
+ antpath cancel <run-id> --api-token …
26
+ antpath delete <run-id> --api-token …
27
+ antpath whoami --api-token …
28
28
  ```
29
29
 
30
30
  The SDK class and the CLI are backed by the same `@antpath/shared` operations module — any read or write you can do through one, you can do through the other, against the same durable run records. The same npm package also ships the in-container `antpath` CLI as its `bin` entry; the worker mounts that CLI at `/antpath/antpath` inside every run so skills can call `antpath proxy …` against the per-run manifest. See [Agent-first surface design](../../references/development-principles.md#agent-first-surface-design).
31
31
 
32
+ The dashboard URL defaults to `https://antpath.ai`. Self-hosted deployments override with `--dashboard-url` on the CLI or `baseUrl` on `AntpathClient`. The workspace is derived server-side from your API token (1:1 binding), so there is no `--workspace` flag and no `workspaceId` option.
33
+
32
34
  ## MVP boundaries
33
35
 
34
36
  - Claude Managed Agents only.
35
37
  - BYO Anthropic key + MCP credentials + skill references — passed inline on every submission, vaulted for the lifetime of a single run, destroyed at cleanup.
36
- - Workspace is the tenant boundary. Every operation is scoped to the workspace bound at client construction.
38
+ - Workspace is the tenant boundary. Workspace identity is derived server-side from the API token (1:1 binding); the SDK / CLI never name it.
37
39
  - No SDK-side storage of provider keys, MCP credentials, or output file contents.
38
40
  - Cleanup runs by default; opt into retention with `cleanup.session: "retain"`.
39
41
 
@@ -43,9 +45,8 @@ The SDK class and the CLI are backed by the same `@antpath/shared` operations mo
43
45
  import { AntpathClient, defineTemplate, string } from "antpath";
44
46
 
45
47
  const client = new AntpathClient({
46
- baseUrl: "https://antpath.example.com",
47
- apiToken: process.env.ANTPATH_API_TOKEN!,
48
- workspaceId: process.env.ANTPATH_WORKSPACE_ID!
48
+ apiToken: process.env.ANTPATH_API_TOKEN!
49
+ // baseUrl defaults to https://antpath.ai — set it for self-hosted deployments.
49
50
  });
50
51
 
51
52
  const template = defineTemplate({
@@ -84,8 +85,6 @@ The same flow from the CLI:
84
85
  ```bash
85
86
  antpath run ./template.json \
86
87
  --api-token "$ANTPATH_API_TOKEN" \
87
- --workspace "$ANTPATH_WORKSPACE_ID" \
88
- --dashboard-url https://antpath.example.com \
89
88
  --anthropic-api-key "$ANTHROPIC_API_KEY" \
90
89
  --var topic="agent-first SDK design" \
91
90
  --follow
@@ -0,0 +1,263 @@
1
+ /**
2
+ * The flat agent-first composition surface that replaces `Template`.
3
+ *
4
+ * Concepts (mirrored in references/architecture-decisions.md):
5
+ *
6
+ * - `SkillRef` is the wire-level reference to a skill — either an
7
+ * `skl_*` id pointing at a workspace-uploaded bundle, or a
8
+ * `{vendor, skillId, version}` reference to a provider built-in.
9
+ * The two shapes are discriminated by `kind` so consumers branch
10
+ * mechanically and providers can never accidentally be looked up
11
+ * in `skill_bundles`.
12
+ *
13
+ * - `McpServerRef` is the non-secret part of an MCP server declaration:
14
+ * `name` and `url`. Bearer / cookie / per-request headers travel in
15
+ * the run's vaulted `secrets.mcpServers` block keyed by the same
16
+ * `name`, and never enter the hashed submission payload or the
17
+ * run snapshot.
18
+ *
19
+ * - `Blueprint` is what the user authors. It excludes
20
+ * `secrets`/`idempotencyKey`/`signal` so it can be safely persisted
21
+ * to disk (e.g. `antpath run --config run.json`), shared between
22
+ * teams, or curried via `defineRun` without leaking credentials.
23
+ * Strings inside a Blueprint are **already resolved** — there are
24
+ * no `{{variable}}` placeholders, no template language, no late
25
+ * binding. The whole point of `defineRun` is to make the resolution
26
+ * happen at the TS call site where the IDE can type-check it.
27
+ *
28
+ * - Skill bundle validation lives here so the SDK (zipping locally),
29
+ * the BFF (server-side unzip + manifest extraction), and the worker
30
+ * (sanity-check before mounting) share a single source of truth for
31
+ * the limits, the path normaliser, and the manifest invariants. The
32
+ * DB CHECK constraints on `skill_bundles.manifest` mirror these.
33
+ *
34
+ * See `references/architecture-decisions.md` (Composition primitives,
35
+ * Skill custody) and `references/development-principles.md` (Agent-first
36
+ * surface design) for the rationale.
37
+ */
38
+ import type { JsonValue, PlatformCleanupPolicy, PlatformProxyEndpoint, PlatformTemplateEnvironment } from "./submission.js";
39
+ /**
40
+ * Mirrors the CHECK constraint
41
+ * `skill_bundles_id_format_chk = check (id ~ '^skl_[A-Za-z0-9_-]{8,128}$')`
42
+ * defined in supabase/migrations/20260512000000_skill_bundles.sql. Keep
43
+ * the two in lockstep — the DB is the ultimate authority.
44
+ */
45
+ export declare const SKILL_ID_PATTERN: RegExp;
46
+ /**
47
+ * Human-readable, workspace-scoped name. Lowercase, kebab-friendly,
48
+ * 1..128 chars. The DB enforces the length bound via
49
+ * `skill_bundles_name_len_chk`; this regex tightens the SDK/CLI input
50
+ * surface so callers fail at the boundary rather than in the BFF.
51
+ */
52
+ export declare const SKILL_NAME_PATTERN: RegExp;
53
+ /**
54
+ * Hard caps applied at upload time. The SDK enforces these before
55
+ * computing the zip hash so a clearly-too-big bundle never wastes
56
+ * bytes-on-the-wire; the BFF re-enforces server-side because the SDK
57
+ * is untrusted. Numbers are deliberately conservative for the MVP and
58
+ * can be tuned later; keep this object as the single tuning point.
59
+ */
60
+ export declare const SKILL_BUNDLE_LIMITS: {
61
+ /** Compressed (.zip) ceiling. */
62
+ readonly maxCompressedBytes: number;
63
+ /** Sum of uncompressed file sizes. */
64
+ readonly maxDecompressedBytes: number;
65
+ /** Number of regular file entries (directories don't count). */
66
+ readonly maxFiles: 1000;
67
+ /** Maximum directory nesting depth — `a/b/c/d` has depth 4. */
68
+ readonly maxDepth: 16;
69
+ /** Single-entry path length cap. */
70
+ readonly maxPathLength: 512;
71
+ /** Stored file mode for ordinary files. */
72
+ readonly defaultFileMode: 420;
73
+ /** Stored directory mode. */
74
+ readonly defaultDirMode: 493;
75
+ };
76
+ export type SkillRef = WorkspaceSkillRef | ProviderSkillRef;
77
+ export interface WorkspaceSkillRef {
78
+ readonly kind: "workspace";
79
+ readonly id: string;
80
+ }
81
+ export interface ProviderSkillRef {
82
+ readonly kind: "provider";
83
+ readonly vendor: "anthropic" | "custom";
84
+ readonly skillId: string;
85
+ readonly version?: string;
86
+ }
87
+ export declare function isWorkspaceSkillRef(ref: SkillRef): ref is WorkspaceSkillRef;
88
+ export declare function isProviderSkillRef(ref: SkillRef): ref is ProviderSkillRef;
89
+ /**
90
+ * Parse a `SkillRef` from untrusted input. Used by the BFF run parser
91
+ * and by the operations module when deserialising API responses. Throws
92
+ * with a precise path so the caller can surface a usable error.
93
+ */
94
+ export declare function parseSkillRef(input: unknown, path: string): SkillRef;
95
+ /**
96
+ * Manifest entry persisted in `skill_bundles.manifest` and
97
+ * `run_skill_snapshots.manifest`. `path` is forward-slash, relative,
98
+ * normalised. `mode` is the stored POSIX mode (sanitised, NOT the user's
99
+ * filesystem mode) — see `SKILL_BUNDLE_LIMITS.defaultFileMode`.
100
+ */
101
+ export interface SkillBundleEntry {
102
+ readonly path: string;
103
+ readonly size: number;
104
+ readonly mode: number;
105
+ }
106
+ export interface SkillBundleManifest {
107
+ readonly entries: readonly SkillBundleEntry[];
108
+ /** Total uncompressed bytes (sum of `entries[i].size`). */
109
+ readonly totalSize: number;
110
+ /** Number of file entries. Equals `entries.length` by construction. */
111
+ readonly fileCount: number;
112
+ }
113
+ export declare class SkillBundleValidationError extends Error {
114
+ constructor(message: string);
115
+ }
116
+ /**
117
+ * Reject input paths that try to escape the bundle root or smuggle
118
+ * platform-specific syntax. Returns the canonical forward-slash
119
+ * relative path; never returns paths starting or ending with `/`.
120
+ *
121
+ * Rejects:
122
+ * - empty strings and pure whitespace
123
+ * - absolute paths (`/foo`, `C:\foo`, `\\server\share`)
124
+ * - backslash separators (Windows)
125
+ * - `..` segments anywhere in the path
126
+ * - `.` segments anywhere except a leading bare `.`
127
+ * - paths whose length exceeds `SKILL_BUNDLE_LIMITS.maxPathLength`
128
+ * - paths whose depth exceeds `SKILL_BUNDLE_LIMITS.maxDepth`
129
+ * - NUL bytes
130
+ */
131
+ export declare function normaliseSkillBundlePath(input: string): string;
132
+ /**
133
+ * Validate one manifest entry: normalises the path, bounds the size,
134
+ * and sanitises the mode to one of {defaultFileMode, defaultDirMode}.
135
+ * The bundle is files-only, so any non-regular-file entry is rejected
136
+ * upstream by the caller (zip parser must skip symlinks, device files,
137
+ * etc. before reaching this function).
138
+ */
139
+ export declare function validateSkillBundleEntry(input: {
140
+ readonly path: string;
141
+ readonly size: number;
142
+ readonly mode?: number;
143
+ }): SkillBundleEntry;
144
+ /**
145
+ * Validate a full manifest. Enforces:
146
+ * - entries is a non-empty array
147
+ * - `SKILL.md` exists at the bundle root (Claude's auto-discovery key)
148
+ * - file count <= maxFiles
149
+ * - total uncompressed size <= maxDecompressedBytes
150
+ * - per-entry validation (see `validateSkillBundleEntry`)
151
+ * - no duplicate paths
152
+ *
153
+ * Returns a canonical manifest with totals computed.
154
+ */
155
+ export declare function validateSkillBundleManifest(input: ReadonlyArray<{
156
+ readonly path: string;
157
+ readonly size: number;
158
+ readonly mode?: number;
159
+ }>): SkillBundleManifest;
160
+ /**
161
+ * The non-secret half of an MCP server declaration. This is what enters
162
+ * the hashed submission, the run snapshot, and any audit log. `name`
163
+ * keys into `secrets.mcpServers` for the per-request headers.
164
+ */
165
+ export interface McpServerRef {
166
+ readonly name: string;
167
+ readonly url: string;
168
+ }
169
+ export declare const MCP_SERVER_NAME_PATTERN: RegExp;
170
+ /**
171
+ * A Blueprint-level MCP entry. The user is free to supply headers
172
+ * inline; the SDK splits the call site cleanly at submission time so
173
+ * the Authorization (or other auth-bearing) header never enters the
174
+ * non-secret wire payload.
175
+ */
176
+ export interface BlueprintMcpServer extends McpServerRef {
177
+ readonly headers?: Readonly<Record<string, string>>;
178
+ }
179
+ export declare function parseMcpServerRef(input: unknown, path: string): McpServerRef;
180
+ /**
181
+ * What the user authors and passes to `client.submitRun({...blueprint, secrets})`.
182
+ *
183
+ * Blueprint deliberately EXCLUDES `secrets`, `idempotencyKey`, and
184
+ * `signal` so:
185
+ * - `defineRun((p) => Blueprint)` can be reused across calls without
186
+ * re-injecting an API key,
187
+ * - a Blueprint can be JSON-serialised to disk (`antpath run --config
188
+ * run.json`) without ever encoding a credential,
189
+ * - audit / replay tooling can persist a Blueprint as-is.
190
+ */
191
+ export interface Blueprint {
192
+ readonly model: string;
193
+ readonly system?: string;
194
+ readonly prompt: string | readonly string[];
195
+ readonly skills?: readonly SkillRef[];
196
+ readonly mcpServers?: readonly BlueprintMcpServer[];
197
+ readonly environment?: PlatformTemplateEnvironment;
198
+ readonly cleanup?: PlatformCleanupPolicy;
199
+ readonly proxyEndpoints?: readonly PlatformProxyEndpoint[];
200
+ readonly metadata?: Readonly<Record<string, JsonValue>>;
201
+ }
202
+ /**
203
+ * Currier for parameterised Blueprints.
204
+ *
205
+ * ```ts
206
+ * const investigate = defineRun((p: { repo: string; issue: number }) => ({
207
+ * model: "claude-sonnet-4-5-20250929",
208
+ * system: `You work on ${p.repo}.`,
209
+ * prompt: `Investigate issue #${p.issue}.`,
210
+ * skills: [rules],
211
+ * }));
212
+ * await client.submitRun({
213
+ * ...investigate({ repo: "antpath", issue: 123 }),
214
+ * secrets: { anthropic: { apiKey } },
215
+ * });
216
+ * ```
217
+ *
218
+ * The returned function is referentially transparent — it just calls
219
+ * the provided producer. The wrapper exists for two reasons: (a) a
220
+ * single named entry point makes IDEs surface the type of the inner
221
+ * blueprint at the call site, and (b) it pins the "no late binding"
222
+ * contract — strings inside the Blueprint are resolved by the TS call
223
+ * site, not by a server-side template engine.
224
+ */
225
+ export declare function defineRun<TParams>(producer: (params: TParams) => Blueprint): (params: TParams) => Blueprint;
226
+ /**
227
+ * Parse a Blueprint from JSON. Defensive — used by the host CLI to
228
+ * load `--config run.json`. Throws with the JSON path that failed so
229
+ * a user can fix their file. Headers are preserved here and split out
230
+ * later by the SDK normalisation step.
231
+ */
232
+ export declare function parseBlueprint(input: unknown): Blueprint;
233
+ /**
234
+ * Result of splitting a `Blueprint` into the non-secret submission and
235
+ * the secret MCP-headers bundle. The SDK calls this just before posting
236
+ * to /api/runs: the `submission` half is what the BFF hashes for
237
+ * idempotency, the `mcpServerSecrets` half is what enters the Vault.
238
+ *
239
+ * `prompt` is normalised to `readonly string[]` (single-string callers
240
+ * get wrapped in a length-1 array) so the wire payload, the worker, and
241
+ * the audit log don't have to re-handle two shapes.
242
+ */
243
+ export interface NormalisedBlueprint {
244
+ readonly model: string;
245
+ readonly system?: string;
246
+ readonly prompt: readonly string[];
247
+ readonly skills: readonly SkillRef[];
248
+ readonly mcpServers: readonly McpServerRef[];
249
+ readonly environment?: PlatformTemplateEnvironment;
250
+ readonly cleanup?: PlatformCleanupPolicy;
251
+ readonly proxyEndpoints?: readonly PlatformProxyEndpoint[];
252
+ readonly metadata?: Readonly<Record<string, JsonValue>>;
253
+ /**
254
+ * MCP servers whose Blueprint entry carried `headers`. Keyed by the
255
+ * `name` that appears in `mcpServers` so the BFF can pair them up.
256
+ */
257
+ readonly mcpServerSecrets: ReadonlyArray<{
258
+ readonly name: string;
259
+ readonly url: string;
260
+ readonly headers: Readonly<Record<string, string>>;
261
+ }>;
262
+ }
263
+ export declare function normaliseBlueprint(blueprint: Blueprint): NormalisedBlueprint;