antpath 0.2.1 → 0.3.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.
Files changed (105) hide show
  1. package/README.md +55 -40
  2. package/dist/_shared/cleanup-policy.d.ts +8 -0
  3. package/dist/_shared/cleanup-policy.js +24 -0
  4. package/dist/_shared/config.d.ts +47 -0
  5. package/dist/_shared/config.js +150 -0
  6. package/dist/_shared/dev-stack.d.ts +19 -0
  7. package/dist/_shared/dev-stack.js +105 -0
  8. package/dist/_shared/errors.d.ts +8 -0
  9. package/dist/_shared/errors.js +18 -0
  10. package/dist/_shared/http.d.ts +20 -0
  11. package/dist/_shared/http.js +94 -0
  12. package/dist/_shared/index.d.ts +17 -0
  13. package/dist/_shared/index.js +20 -0
  14. package/dist/{providers → _shared}/known-events.d.ts +10 -10
  15. package/dist/{providers → _shared}/known-events.js +9 -9
  16. package/dist/_shared/operations.d.ts +19 -0
  17. package/dist/_shared/operations.js +42 -0
  18. package/dist/_shared/proxy-protocol.d.ts +148 -0
  19. package/dist/_shared/proxy-protocol.js +113 -0
  20. package/dist/_shared/proxy-validation.d.ts +19 -0
  21. package/dist/_shared/proxy-validation.js +51 -0
  22. package/dist/_shared/runtime-types.d.ts +90 -0
  23. package/dist/_shared/runtime-types.js +2 -0
  24. package/dist/{errors.d.ts → _shared/sdk-errors.d.ts} +10 -1
  25. package/dist/{errors.js → _shared/sdk-errors.js} +15 -2
  26. package/dist/{utils/secrets.js → _shared/sdk-secrets.js} +1 -1
  27. package/dist/_shared/secrets.d.ts +7 -0
  28. package/dist/_shared/secrets.js +20 -0
  29. package/dist/_shared/status.d.ts +8 -0
  30. package/dist/_shared/status.js +46 -0
  31. package/dist/_shared/submission.d.ts +142 -0
  32. package/dist/_shared/submission.js +681 -0
  33. package/dist/{template → _shared/template}/compiler.js +3 -3
  34. package/dist/{template/index.d.ts → _shared/template/helpers.d.ts} +0 -2
  35. package/dist/{template/index.js → _shared/template/helpers.js} +1 -2
  36. package/dist/_shared/template/index.d.ts +4 -0
  37. package/dist/_shared/template/index.js +4 -0
  38. package/dist/_shared/template/mapper.d.ts +11 -0
  39. package/dist/_shared/template/mapper.js +70 -0
  40. package/dist/cli.mjs +1234 -64
  41. package/dist/cli.mjs.sha256 +1 -1
  42. package/dist/client.d.ts +93 -8
  43. package/dist/client.js +192 -30
  44. package/dist/client.js.map +1 -1
  45. package/dist/index.d.ts +16 -10
  46. package/dist/index.js +16 -7
  47. package/dist/index.js.map +1 -1
  48. package/docs/cleanup.md +7 -4
  49. package/docs/credentials.md +12 -12
  50. package/docs/events.md +19 -82
  51. package/docs/outputs.md +15 -4
  52. package/docs/quickstart.md +42 -5
  53. package/docs/skills.md +1 -1
  54. package/docs/templates.md +1 -1
  55. package/docs/testing.md +11 -8
  56. package/examples/mcp-static-bearer.ts +14 -9
  57. package/examples/quickstart.ts +8 -6
  58. package/package.json +4 -5
  59. package/references/implementation-plan.md +1 -1
  60. package/dist/credentials.d.ts +0 -3
  61. package/dist/credentials.js +0 -56
  62. package/dist/credentials.js.map +0 -1
  63. package/dist/errors.js.map +0 -1
  64. package/dist/files/downloader.d.ts +0 -3
  65. package/dist/files/downloader.js +0 -43
  66. package/dist/files/downloader.js.map +0 -1
  67. package/dist/platform/client.d.ts +0 -204
  68. package/dist/platform/client.js +0 -203
  69. package/dist/platform/client.js.map +0 -1
  70. package/dist/platform/index.d.ts +0 -1
  71. package/dist/platform/index.js +0 -2
  72. package/dist/platform/index.js.map +0 -1
  73. package/dist/providers/anthropic/provider.d.ts +0 -36
  74. package/dist/providers/anthropic/provider.js +0 -380
  75. package/dist/providers/anthropic/provider.js.map +0 -1
  76. package/dist/providers/known-events.js.map +0 -1
  77. package/dist/providers/types.d.ts +0 -42
  78. package/dist/providers/types.js.map +0 -1
  79. package/dist/run/controller.d.ts +0 -30
  80. package/dist/run/controller.js +0 -314
  81. package/dist/run/controller.js.map +0 -1
  82. package/dist/skills/packager.d.ts +0 -11
  83. package/dist/skills/packager.js +0 -76
  84. package/dist/skills/packager.js.map +0 -1
  85. package/dist/template/compiler.js.map +0 -1
  86. package/dist/template/index.js.map +0 -1
  87. package/dist/template/types.js +0 -2
  88. package/dist/template/types.js.map +0 -1
  89. package/dist/types.d.ts +0 -149
  90. package/dist/types.js +0 -2
  91. package/dist/types.js.map +0 -1
  92. package/dist/utils/events.d.ts +0 -27
  93. package/dist/utils/events.js +0 -120
  94. package/dist/utils/events.js.map +0 -1
  95. package/dist/utils/paths.d.ts +0 -3
  96. package/dist/utils/paths.js +0 -27
  97. package/dist/utils/paths.js.map +0 -1
  98. package/dist/utils/secrets.js.map +0 -1
  99. package/dist/utils/stable.js.map +0 -1
  100. /package/dist/{utils/secrets.d.ts → _shared/sdk-secrets.d.ts} +0 -0
  101. /package/dist/{utils → _shared}/stable.d.ts +0 -0
  102. /package/dist/{utils → _shared}/stable.js +0 -0
  103. /package/dist/{template → _shared/template}/compiler.d.ts +0 -0
  104. /package/dist/{template → _shared/template}/types.d.ts +0 -0
  105. /package/dist/{providers → _shared/template}/types.js +0 -0
package/README.md CHANGED
@@ -4,41 +4,48 @@ title: antpath
4
4
 
5
5
  # antpath
6
6
 
7
- antpath is a TypeScript-first SDK for running autonomous Claude Managed Agents sessions from code-defined, secret-free Templates.
8
-
9
- Everything ships from a single import path:
7
+ antpath is a TypeScript-first SDK + CLI for running autonomous Claude Managed Agents sessions through a managed antpath dashboard. Everything an agent or human needs is reachable through **one** import and **one** binary.
10
8
 
11
9
  ```ts
12
10
  import {
13
- AntpathClient, // direct in-process Claude runs (caller-held key)
14
- AntpathPlatformClient, // submit durable runs to an antpath dashboard
11
+ AntpathClient, // the only public class — submits durable runs to a dashboard
15
12
  defineTemplate,
16
13
  string,
17
- validateProxyAuth // helper for per-run proxy endpoint auth
14
+ validateProxyAuth // helper for per-run proxy endpoint auth
18
15
  } from "antpath";
19
16
  ```
20
17
 
21
- There is no `antpath/platform`, `antpath/proxy`, or other sub-path. The two
22
- clients are distinct classes for distinct use cases but live behind the same
23
- single agent-visible surface — see [Agent-first surface design](../../references/development-principles.md#agent-first-surface-design).
18
+ ```bash
19
+ antpath run ./template.json --api-token ant_… --workspace --dashboard-url \
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 …
28
+ ```
24
29
 
25
- - **Direct (`AntpathClient`)**runs Claude in your own process with a caller-held key. This README covers the direct surface.
26
- - **Platform (`AntpathPlatformClient`)** — submits durable runs to an antpath dashboard. Every submission carries an inline `secrets` bundle (Anthropic key, optional MCP credentials, optional skill references, and optional per-run proxy endpoint auth); the dashboard vaults the bundle for the lifetime of one run and deletes it at cleanup. Per-run named secrets accessed through the managed HTTP proxy are declared via the `proxyEndpoints` submission field and authenticated via `secrets.proxyEndpointAuth` — see [Credentials](docs/credentials.md). The same npm package also ships the `antpath` CLI as its `bin` entry; the worker mounts that CLI at `/antpath/antpath` in every run for skills to invoke (`node /antpath/antpath proxy …`).
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).
27
31
 
28
- ## MVP boundaries (direct SDK)
32
+ ## MVP boundaries
29
33
 
30
34
  - Claude Managed Agents only.
31
- - Caller-held provider key — the direct SDK never persists keys.
35
+ - 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.
32
37
  - No SDK-side storage of provider keys, MCP credentials, or output file contents.
33
- - Manual cleanup by default.
38
+ - Cleanup runs by default; opt into retention with `cleanup.session: "retain"`.
34
39
 
35
- ## Quickstart
40
+ ## Quickstart (SDK)
36
41
 
37
42
  ```ts
38
43
  import { AntpathClient, defineTemplate, string } from "antpath";
39
44
 
40
45
  const client = new AntpathClient({
41
- anthropicApiKey: process.env.ANTHROPIC_API_KEY
46
+ baseUrl: "https://antpath.example.com",
47
+ apiToken: process.env.ANTPATH_API_TOKEN!,
48
+ workspaceId: process.env.ANTPATH_WORKSPACE_ID!
42
49
  });
43
50
 
44
51
  const template = defineTemplate({
@@ -46,47 +53,54 @@ const template = defineTemplate({
46
53
  model: "claude-haiku-4-5",
47
54
  system: "You are a concise automation agent.",
48
55
  messages: ["Write a short answer about {{topic}}."],
49
- variables: {
50
- topic: string()
51
- }
56
+ variables: { topic: string() }
52
57
  });
53
58
 
54
- const handle = await client.run(template, {
55
- variables: { topic: "test-first SDK design" }
59
+ const ref = await client.submitRun(template, {
60
+ variables: { topic: "agent-first SDK design" },
61
+ secrets: { anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! } }
56
62
  });
57
63
 
58
- const result = await handle.wait();
59
- console.log(result.status, await handle.usage());
60
- await handle.downloadOutputs("./outputs");
61
- await handle.cleanup();
64
+ const run = await ref.wait();
65
+ console.log(run.status);
66
+
67
+ for (const output of await ref.outputs()) {
68
+ console.log(output.id, output.path);
69
+ }
62
70
  ```
63
71
 
64
- Observe events live by passing `onEvent` in `RunOptions`:
72
+ Stream events live with `ref.stream()`:
65
73
 
66
74
  ```ts
67
- const handle = await client.run(template, {
68
- onEvent: (event) => {
69
- if (event.type === "provider.event") {
70
- // event.event is a ProviderEvent (raw type stays `unknown`).
71
- }
75
+ for await (const event of ref.stream()) {
76
+ if (event.type === "agent.message") {
77
+ // typed event helpers live under `antpath`'s event guard exports.
72
78
  }
73
- });
74
- await handle.wait();
79
+ }
75
80
  ```
76
81
 
77
- By the time `wait()` returns, every `onEvent` invocation triggered before
78
- terminal status has settled. See [Events](docs/events.md) for the full
79
- contract and a list of typed helpers.
82
+ The same flow from the CLI:
83
+
84
+ ```bash
85
+ antpath run ./template.json \
86
+ --api-token "$ANTPATH_API_TOKEN" \
87
+ --workspace "$ANTPATH_WORKSPACE_ID" \
88
+ --dashboard-url https://antpath.example.com \
89
+ --anthropic-api-key "$ANTHROPIC_API_KEY" \
90
+ --var topic="agent-first SDK design" \
91
+ --follow
92
+ ```
80
93
 
81
94
  ## Test commands
82
95
 
83
96
  ```text
84
- npm run test:unit
85
- npm run test:unit:recorded
86
- npm run test:e2e:live
97
+ pnpm test # unit (deterministic; uses fakes/snapshots)
98
+ pnpm test:e2e:live # full top-to-bottom against a real dashboard + Anthropic
99
+ pnpm test:user # offline; exercises the published package via npm install
100
+ pnpm test:user:live # live; same but hits the real platform
87
101
  ```
88
102
 
89
- Unit tests are deterministic and may use fakes or sanitized recorded snapshots. Live e2e tests run when `pnpm test:e2e:live` is invoked with `.env.local` containing `ANTHROPIC_API_KEY`.
103
+ `pnpm test:user*` requires `ANTPATH_USER_TEST_TARBALL`/`ANTPATH_USER_TEST_VERSION` (+ live target vars for `:live`). Missing required env is a hard error there is no silent skip.
90
104
 
91
105
  ## Guides
92
106
 
@@ -100,3 +114,4 @@ Unit tests are deterministic and may use fakes or sanitized recorded snapshots.
100
114
  - [Cleanup](docs/cleanup.md)
101
115
  - [Testing](docs/testing.md)
102
116
  - [Release](docs/release.md)
117
+
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Provider-neutral cleanup-policy resolution.
3
+ *
4
+ * Reads `cleanup.session` (canonical) and falls back to legacy `cleanup.claudeSession`.
5
+ * Returns the worker default for any malformed shape — never throws.
6
+ */
7
+ export type SessionCleanupPolicy = "retain" | "delete";
8
+ export declare function resolveCleanupPolicyFromSnapshot(snapshot: unknown, fallback: SessionCleanupPolicy): SessionCleanupPolicy;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Provider-neutral cleanup-policy resolution.
3
+ *
4
+ * Reads `cleanup.session` (canonical) and falls back to legacy `cleanup.claudeSession`.
5
+ * Returns the worker default for any malformed shape — never throws.
6
+ */
7
+ export function resolveCleanupPolicyFromSnapshot(snapshot, fallback) {
8
+ if (snapshot === null || snapshot === undefined || typeof snapshot !== "object" || Array.isArray(snapshot)) {
9
+ return fallback;
10
+ }
11
+ const cleanup = snapshot.cleanup;
12
+ if (!cleanup || typeof cleanup !== "object" || Array.isArray(cleanup)) {
13
+ return fallback;
14
+ }
15
+ const policy = cleanup;
16
+ if (policy.session === "retain" || policy.session === "delete") {
17
+ return policy.session;
18
+ }
19
+ if (policy.claudeSession === "retain" || policy.claudeSession === "delete") {
20
+ return policy.claudeSession;
21
+ }
22
+ return fallback;
23
+ }
24
+ //# sourceMappingURL=cleanup-policy.js.map
@@ -0,0 +1,47 @@
1
+ export interface PlatformRequiredConfig {
2
+ readonly databaseUrl: string;
3
+ readonly supabaseUrl: string;
4
+ readonly supabaseServiceRoleKey: string;
5
+ readonly supabaseStorageBucket: string;
6
+ readonly supabaseVaultSchema: string;
7
+ readonly authSecret: string;
8
+ readonly sdkTokenPepper: string;
9
+ readonly workerId: string;
10
+ readonly providerApiBaseUrl: string;
11
+ readonly providerApiVersion: string;
12
+ readonly proxyPublicBaseUrl: string;
13
+ readonly proxyTokenPepper: string;
14
+ }
15
+ export interface PlatformProxyConfig {
16
+ readonly rateLimitPerRunPerMinute: number;
17
+ readonly defaultResponseByteBudget: number;
18
+ readonly sweeperIntervalMs: number;
19
+ }
20
+ export interface PlatformCaps {
21
+ readonly maxRunDurationMs: number;
22
+ readonly maxActiveRunsPerWorkspace: number;
23
+ readonly maxActiveRunsPerUserOrToken: number;
24
+ readonly pollingBaseIntervalMs: number;
25
+ readonly pollingMaxIntervalMs: number;
26
+ readonly pollingJitterRatio: number;
27
+ readonly providerCreateTokensPerMinute: number;
28
+ readonly providerDeleteTokensPerMinute: number;
29
+ readonly providerPollTokensPerMinute: number;
30
+ readonly providerRetryBackoffMs: number;
31
+ readonly leaseDurationMs: number;
32
+ readonly leaseRenewalThresholdMs: number;
33
+ readonly maxProviderAttempts: number;
34
+ readonly cleanupRetryCount: number;
35
+ readonly cleanupRetryBackoffMs: number;
36
+ readonly outputDownloadSafetyCapBytes: number;
37
+ readonly workspaceStorageCapBytes: number;
38
+ readonly signedUrlTtlSeconds: number;
39
+ }
40
+ export interface PlatformConfig extends PlatformRequiredConfig {
41
+ readonly caps: PlatformCaps;
42
+ readonly proxy: PlatformProxyConfig;
43
+ }
44
+ type Env = Record<string, string | undefined>;
45
+ export declare function parsePlatformConfig(env: Env): PlatformConfig;
46
+ export declare function snapshotRunCaps(config: PlatformConfig): PlatformCaps;
47
+ export {};
@@ -0,0 +1,150 @@
1
+ const DEFAULT_CAPS = {
2
+ maxRunDurationMs: 5 * 60 * 1000,
3
+ maxActiveRunsPerWorkspace: 1,
4
+ maxActiveRunsPerUserOrToken: 1,
5
+ pollingBaseIntervalMs: 5_000,
6
+ pollingMaxIntervalMs: 60_000,
7
+ pollingJitterRatio: 0.2,
8
+ providerCreateTokensPerMinute: 20,
9
+ providerDeleteTokensPerMinute: 30,
10
+ providerPollTokensPerMinute: 60,
11
+ providerRetryBackoffMs: 5_000,
12
+ leaseDurationMs: 60_000,
13
+ leaseRenewalThresholdMs: 20_000,
14
+ maxProviderAttempts: 1,
15
+ cleanupRetryCount: 3,
16
+ cleanupRetryBackoffMs: 10_000,
17
+ outputDownloadSafetyCapBytes: 1024 * 1024 * 1024,
18
+ workspaceStorageCapBytes: 5 * 1024 * 1024 * 1024,
19
+ signedUrlTtlSeconds: 300
20
+ };
21
+ const requiredEnvNames = [
22
+ "ANTPATH_DATABASE_URL",
23
+ "ANTPATH_SUPABASE_URL",
24
+ "ANTPATH_SUPABASE_SERVICE_ROLE_KEY",
25
+ "ANTPATH_SUPABASE_STORAGE_BUCKET",
26
+ "ANTPATH_SUPABASE_VAULT_SCHEMA",
27
+ "ANTPATH_AUTH_SECRET",
28
+ "ANTPATH_SDK_TOKEN_PEPPER",
29
+ "ANTPATH_WORKER_ID",
30
+ "ANTPATH_PROVIDER_API_BASE_URL",
31
+ "ANTPATH_PROVIDER_API_VERSION",
32
+ "ANTPATH_PROXY_PUBLIC_BASE_URL",
33
+ "ANTPATH_PROXY_TOKEN_PEPPER"
34
+ ];
35
+ const DEFAULT_PROXY_RATE_LIMIT_PER_RUN_PER_MINUTE = 60;
36
+ const DEFAULT_PROXY_RESPONSE_BYTE_BUDGET = 1_048_576;
37
+ const DEFAULT_PROXY_SWEEPER_INTERVAL_MS = 60_000;
38
+ export function parsePlatformConfig(env) {
39
+ const missing = requiredEnvNames.filter((name) => !env[name]);
40
+ if (missing.length > 0) {
41
+ throw new Error(`Missing required environment variables: ${missing.join(", ")}`);
42
+ }
43
+ const caps = {
44
+ maxRunDurationMs: optionalPositiveInt(env, "ANTPATH_MAX_RUN_DURATION_MS", DEFAULT_CAPS.maxRunDurationMs),
45
+ maxActiveRunsPerWorkspace: optionalPositiveInt(env, "ANTPATH_MAX_ACTIVE_RUNS_PER_WORKSPACE", DEFAULT_CAPS.maxActiveRunsPerWorkspace),
46
+ maxActiveRunsPerUserOrToken: optionalPositiveInt(env, "ANTPATH_MAX_ACTIVE_RUNS_PER_USER_OR_TOKEN", DEFAULT_CAPS.maxActiveRunsPerUserOrToken),
47
+ pollingBaseIntervalMs: optionalPositiveInt(env, "ANTPATH_POLLING_BASE_INTERVAL_MS", DEFAULT_CAPS.pollingBaseIntervalMs),
48
+ pollingMaxIntervalMs: optionalPositiveInt(env, "ANTPATH_POLLING_MAX_INTERVAL_MS", DEFAULT_CAPS.pollingMaxIntervalMs),
49
+ pollingJitterRatio: optionalRatio(env, "ANTPATH_POLLING_JITTER_RATIO", DEFAULT_CAPS.pollingJitterRatio),
50
+ providerCreateTokensPerMinute: optionalPositiveInt(env, "ANTPATH_PROVIDER_CREATE_TOKENS_PER_MINUTE", DEFAULT_CAPS.providerCreateTokensPerMinute),
51
+ providerDeleteTokensPerMinute: optionalPositiveInt(env, "ANTPATH_PROVIDER_DELETE_TOKENS_PER_MINUTE", DEFAULT_CAPS.providerDeleteTokensPerMinute),
52
+ providerPollTokensPerMinute: optionalPositiveInt(env, "ANTPATH_PROVIDER_POLL_TOKENS_PER_MINUTE", DEFAULT_CAPS.providerPollTokensPerMinute),
53
+ providerRetryBackoffMs: optionalPositiveInt(env, "ANTPATH_PROVIDER_RETRY_BACKOFF_MS", DEFAULT_CAPS.providerRetryBackoffMs),
54
+ leaseDurationMs: optionalPositiveInt(env, "ANTPATH_LEASE_DURATION_MS", DEFAULT_CAPS.leaseDurationMs),
55
+ leaseRenewalThresholdMs: optionalPositiveInt(env, "ANTPATH_LEASE_RENEWAL_THRESHOLD_MS", DEFAULT_CAPS.leaseRenewalThresholdMs),
56
+ maxProviderAttempts: optionalPositiveInt(env, "ANTPATH_MAX_PROVIDER_ATTEMPTS", DEFAULT_CAPS.maxProviderAttempts),
57
+ cleanupRetryCount: optionalNonNegativeInt(env, "ANTPATH_CLEANUP_RETRY_COUNT", DEFAULT_CAPS.cleanupRetryCount),
58
+ cleanupRetryBackoffMs: optionalPositiveInt(env, "ANTPATH_CLEANUP_RETRY_BACKOFF_MS", DEFAULT_CAPS.cleanupRetryBackoffMs),
59
+ outputDownloadSafetyCapBytes: optionalPositiveInt(env, "ANTPATH_OUTPUT_DOWNLOAD_SAFETY_CAP_BYTES", DEFAULT_CAPS.outputDownloadSafetyCapBytes),
60
+ workspaceStorageCapBytes: optionalPositiveInt(env, "ANTPATH_WORKSPACE_STORAGE_CAP_BYTES", DEFAULT_CAPS.workspaceStorageCapBytes),
61
+ signedUrlTtlSeconds: optionalPositiveInt(env, "ANTPATH_SIGNED_URL_TTL_SECONDS", DEFAULT_CAPS.signedUrlTtlSeconds)
62
+ };
63
+ validateCaps(caps);
64
+ const proxy = {
65
+ rateLimitPerRunPerMinute: optionalPositiveInt(env, "ANTPATH_PROXY_RATE_LIMIT_PER_RUN_PER_MINUTE", DEFAULT_PROXY_RATE_LIMIT_PER_RUN_PER_MINUTE),
66
+ defaultResponseByteBudget: optionalPositiveInt(env, "ANTPATH_PROXY_DEFAULT_RESPONSE_BYTE_BUDGET", DEFAULT_PROXY_RESPONSE_BYTE_BUDGET),
67
+ sweeperIntervalMs: optionalPositiveInt(env, "ANTPATH_PROXY_SWEEPER_INTERVAL_MS", DEFAULT_PROXY_SWEEPER_INTERVAL_MS)
68
+ };
69
+ const proxyPublicBaseUrl = requireProxyBaseUrl(env, "ANTPATH_PROXY_PUBLIC_BASE_URL");
70
+ return {
71
+ databaseUrl: requireEnv(env, "ANTPATH_DATABASE_URL"),
72
+ supabaseUrl: requireEnv(env, "ANTPATH_SUPABASE_URL"),
73
+ supabaseServiceRoleKey: requireEnv(env, "ANTPATH_SUPABASE_SERVICE_ROLE_KEY"),
74
+ supabaseStorageBucket: requireEnv(env, "ANTPATH_SUPABASE_STORAGE_BUCKET"),
75
+ supabaseVaultSchema: requireEnv(env, "ANTPATH_SUPABASE_VAULT_SCHEMA"),
76
+ authSecret: requireEnv(env, "ANTPATH_AUTH_SECRET"),
77
+ sdkTokenPepper: requireEnv(env, "ANTPATH_SDK_TOKEN_PEPPER"),
78
+ workerId: requireEnv(env, "ANTPATH_WORKER_ID"),
79
+ providerApiBaseUrl: requireEnv(env, "ANTPATH_PROVIDER_API_BASE_URL"),
80
+ providerApiVersion: requireEnv(env, "ANTPATH_PROVIDER_API_VERSION"),
81
+ proxyPublicBaseUrl,
82
+ proxyTokenPepper: requireEnv(env, "ANTPATH_PROXY_TOKEN_PEPPER"),
83
+ caps,
84
+ proxy
85
+ };
86
+ }
87
+ export function snapshotRunCaps(config) {
88
+ return { ...config.caps };
89
+ }
90
+ function requireEnv(env, name) {
91
+ const value = env[name];
92
+ if (!value) {
93
+ throw new Error(`Missing required environment variable: ${name}`);
94
+ }
95
+ return value;
96
+ }
97
+ function optionalPositiveInt(env, name, fallback) {
98
+ return optionalInt(env, name, fallback, 1);
99
+ }
100
+ function optionalNonNegativeInt(env, name, fallback) {
101
+ return optionalInt(env, name, fallback, 0);
102
+ }
103
+ function optionalInt(env, name, fallback, minimum) {
104
+ const raw = env[name];
105
+ if (raw === undefined || raw === "") {
106
+ return fallback;
107
+ }
108
+ const value = Number(raw);
109
+ if (!Number.isSafeInteger(value) || value < minimum) {
110
+ throw new Error(`${name} must be a safe integer greater than or equal to ${minimum}`);
111
+ }
112
+ return value;
113
+ }
114
+ function optionalRatio(env, name, fallback) {
115
+ const raw = env[name];
116
+ if (raw === undefined || raw === "") {
117
+ return fallback;
118
+ }
119
+ const value = Number(raw);
120
+ if (!Number.isFinite(value) || value < 0 || value > 1) {
121
+ throw new Error(`${name} must be a number between 0 and 1`);
122
+ }
123
+ return value;
124
+ }
125
+ function validateCaps(caps) {
126
+ if (caps.leaseRenewalThresholdMs >= caps.leaseDurationMs) {
127
+ throw new Error("ANTPATH_LEASE_RENEWAL_THRESHOLD_MS must be less than ANTPATH_LEASE_DURATION_MS");
128
+ }
129
+ }
130
+ function requireProxyBaseUrl(env, name) {
131
+ const raw = requireEnv(env, name);
132
+ let parsed;
133
+ try {
134
+ parsed = new URL(raw);
135
+ }
136
+ catch {
137
+ throw new Error(`${name} must be a valid absolute URL`);
138
+ }
139
+ if (parsed.protocol !== "https:") {
140
+ throw new Error(`${name} must use https://`);
141
+ }
142
+ if (parsed.username || parsed.password) {
143
+ throw new Error(`${name} must not embed credentials`);
144
+ }
145
+ if (parsed.search || parsed.hash) {
146
+ throw new Error(`${name} must not include a query string or fragment`);
147
+ }
148
+ return `${parsed.origin}${parsed.pathname.replace(/\/+$/, "")}`;
149
+ }
150
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,19 @@
1
+ export interface DevStackConfig {
2
+ readonly supabaseUrl: string;
3
+ readonly supabaseDbUrl: string;
4
+ readonly supabaseServiceRoleKey: string;
5
+ readonly authSecret: string;
6
+ readonly sdkTokenPepper: string;
7
+ readonly storageBucket: string;
8
+ readonly authProvider: "email" | "google" | "none";
9
+ readonly dashboardPort: number;
10
+ readonly workerPort: number;
11
+ readonly workerId: string;
12
+ }
13
+ export type AntpathRole = "worker" | "dashboard";
14
+ type Env = Record<string, string | undefined>;
15
+ export declare function collectRoleEnvErrors(env: Env, role: AntpathRole): readonly string[];
16
+ export declare function assertRoleEnv(env: Env, role: AntpathRole): void;
17
+ export declare function parseDevStackConfig(env: Env): DevStackConfig;
18
+ export declare function collectDevStackConfigErrors(env: Env): readonly string[];
19
+ export {};
@@ -0,0 +1,105 @@
1
+ const ROLE_REQUIREMENTS = {
2
+ worker: [
3
+ { names: ["SUPABASE_DB_URL", "DATABASE_URL", "DB_URL"], label: "Supabase database URL" },
4
+ { names: ["ANTPATH_WORKER_ID"], label: "worker ID" },
5
+ { names: ["ANTPATH_PROXY_PUBLIC_BASE_URL"], label: "antpath proxy public base URL" }
6
+ ],
7
+ dashboard: [
8
+ { names: ["SUPABASE_DB_URL", "DATABASE_URL", "DB_URL"], label: "Supabase database URL" },
9
+ { names: ["SUPABASE_URL", "API_URL"], label: "Supabase API URL" },
10
+ { names: ["SUPABASE_SECRET_KEY", "SUPABASE_SERVICE_ROLE_KEY", "SERVICE_ROLE_KEY", "SECRET_KEY"], label: "Supabase secret key" },
11
+ { names: ["AUTH_SECRET"], label: "Auth.js secret" },
12
+ { names: ["ANTPATH_SDK_TOKEN_PEPPER"], label: "antpath SDK token pepper" },
13
+ { names: ["ANTPATH_PROXY_PUBLIC_BASE_URL"], label: "antpath proxy public base URL" },
14
+ { names: ["ANTPATH_PROXY_TOKEN_PEPPER"], label: "antpath proxy token pepper" }
15
+ ]
16
+ };
17
+ export function collectRoleEnvErrors(env, role) {
18
+ const errors = [];
19
+ for (const requirement of ROLE_REQUIREMENTS[role]) {
20
+ if (!requirement.names.some((name) => Boolean(env[name]))) {
21
+ errors.push(`missing ${requirement.label}: set one of ${requirement.names.join(", ")}`);
22
+ }
23
+ }
24
+ return errors;
25
+ }
26
+ export function assertRoleEnv(env, role) {
27
+ const errors = collectRoleEnvErrors(env, role);
28
+ if (errors.length > 0) {
29
+ throw new Error(`Missing required environment variables for antpath ${role}:\n${errors.map((error) => ` - ${error}`).join("\n")}`);
30
+ }
31
+ }
32
+ export function parseDevStackConfig(env) {
33
+ const errors = collectDevStackConfigErrors(env);
34
+ if (errors.length > 0) {
35
+ throw new Error(`Invalid dev stack configuration:\n${errors.map((error) => `- ${error}`).join("\n")}`);
36
+ }
37
+ return {
38
+ supabaseUrl: requireOne(env, ["SUPABASE_URL", "API_URL"]),
39
+ supabaseDbUrl: requireOne(env, ["SUPABASE_DB_URL", "DATABASE_URL", "DB_URL"]),
40
+ supabaseServiceRoleKey: requireOne(env, ["SUPABASE_SECRET_KEY", "SUPABASE_SERVICE_ROLE_KEY", "SERVICE_ROLE_KEY", "SECRET_KEY"]),
41
+ authSecret: requireOne(env, ["AUTH_SECRET"]),
42
+ sdkTokenPepper: requireOne(env, ["ANTPATH_SDK_TOKEN_PEPPER"]),
43
+ storageBucket: env.ANTPATH_SUPABASE_STORAGE_BUCKET ?? "antpath-outputs",
44
+ authProvider: authProviderFromEnv(env),
45
+ dashboardPort: optionalPort(env, "ANTPATH_DASHBOARD_PORT", 3000),
46
+ workerPort: optionalPort(env, "ANTPATH_WORKER_PORT", optionalPort(env, "PORT", 8787)),
47
+ workerId: env.ANTPATH_WORKER_ID ?? "local-worker"
48
+ };
49
+ }
50
+ export function collectDevStackConfigErrors(env) {
51
+ const errors = [];
52
+ requireAny(errors, env, ["SUPABASE_URL", "API_URL"], "local Supabase API URL");
53
+ requireAny(errors, env, ["SUPABASE_DB_URL", "DATABASE_URL", "DB_URL"], "local Supabase database URL");
54
+ requireAny(errors, env, ["SUPABASE_SECRET_KEY", "SUPABASE_SERVICE_ROLE_KEY", "SERVICE_ROLE_KEY", "SECRET_KEY"], "local Supabase secret key");
55
+ requireAny(errors, env, ["AUTH_SECRET"], "Auth.js secret");
56
+ requireAny(errors, env, ["ANTPATH_SDK_TOKEN_PEPPER"], "SDK token pepper");
57
+ if (env.ANTPATH_DEV_STACK_REQUIRE_AUTH_PROVIDER === "1" && authProviderFromEnv(env) === "none") {
58
+ errors.push("configure either AUTH_GOOGLE_ID/AUTH_GOOGLE_SECRET or AUTH_POSTMARK_KEY/AUTH_EMAIL_FROM");
59
+ }
60
+ validatePort(errors, env, "ANTPATH_DASHBOARD_PORT");
61
+ validatePort(errors, env, "ANTPATH_WORKER_PORT");
62
+ validatePort(errors, env, "PORT");
63
+ return errors;
64
+ }
65
+ function requireAny(errors, env, names, label) {
66
+ if (!names.some((name) => Boolean(env[name]))) {
67
+ errors.push(`missing ${label}: set one of ${names.join(", ")}`);
68
+ }
69
+ }
70
+ function requireOne(env, names) {
71
+ for (const name of names) {
72
+ const value = env[name];
73
+ if (value) {
74
+ return value;
75
+ }
76
+ }
77
+ throw new Error(`Missing required environment variable: ${names.join(" or ")}`);
78
+ }
79
+ function hasAll(env, names) {
80
+ return names.every((name) => Boolean(env[name]));
81
+ }
82
+ function authProviderFromEnv(env) {
83
+ if (hasAll(env, ["AUTH_GOOGLE_ID", "AUTH_GOOGLE_SECRET"])) {
84
+ return "google";
85
+ }
86
+ if (hasAll(env, ["AUTH_POSTMARK_KEY", "AUTH_EMAIL_FROM"])) {
87
+ return "email";
88
+ }
89
+ return "none";
90
+ }
91
+ function optionalPort(env, name, fallback) {
92
+ const value = env[name];
93
+ return value ? Number(value) : fallback;
94
+ }
95
+ function validatePort(errors, env, name) {
96
+ const raw = env[name];
97
+ if (!raw) {
98
+ return;
99
+ }
100
+ const value = Number(raw);
101
+ if (!Number.isSafeInteger(value) || value < 1 || value > 65_535) {
102
+ errors.push(`${name} must be an integer port between 1 and 65535`);
103
+ }
104
+ }
105
+ //# sourceMappingURL=dev-stack.js.map
@@ -0,0 +1,8 @@
1
+ export declare const ERROR_CLASSES: readonly ["transient_provider", "provider_permanent", "tenant_permanent", "antpath_bug", "cancelled_by_user"];
2
+ export type ErrorClass = typeof ERROR_CLASSES[number];
3
+ export interface ErrorPolicy {
4
+ readonly retryable: boolean;
5
+ readonly terminal: boolean;
6
+ }
7
+ export declare const ERROR_POLICIES: Record<ErrorClass, ErrorPolicy>;
8
+ export declare function isRetryableErrorClass(errorClass: ErrorClass): boolean;
@@ -0,0 +1,18 @@
1
+ export const ERROR_CLASSES = [
2
+ "transient_provider",
3
+ "provider_permanent",
4
+ "tenant_permanent",
5
+ "antpath_bug",
6
+ "cancelled_by_user"
7
+ ];
8
+ export const ERROR_POLICIES = {
9
+ transient_provider: { retryable: true, terminal: false },
10
+ provider_permanent: { retryable: false, terminal: true },
11
+ tenant_permanent: { retryable: false, terminal: true },
12
+ antpath_bug: { retryable: false, terminal: true },
13
+ cancelled_by_user: { retryable: false, terminal: true }
14
+ };
15
+ export function isRetryableErrorClass(errorClass) {
16
+ return ERROR_POLICIES[errorClass].retryable;
17
+ }
18
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,20 @@
1
+ export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
2
+ export interface HttpClientOptions {
3
+ readonly baseUrl: string;
4
+ readonly apiToken: string;
5
+ readonly fetch?: FetchLike;
6
+ }
7
+ /**
8
+ * Thin transport used by every BFF-bound operation. The SDK class and
9
+ * the CLI subcommands BOTH build an `HttpClient` and pass it to the
10
+ * operations module — so they cannot drift in how they auth, encode
11
+ * query parameters, or decode error responses.
12
+ */
13
+ export declare class HttpClient {
14
+ #private;
15
+ constructor(options: HttpClientOptions);
16
+ request<T>(path: string, init?: RequestInit, query?: Record<string, string>): Promise<T>;
17
+ download(path: string, init?: RequestInit, query?: Record<string, string>): Promise<{
18
+ readonly response: Response;
19
+ }>;
20
+ }
@@ -0,0 +1,94 @@
1
+ import { AntpathApiError } from "./sdk-errors.js";
2
+ /**
3
+ * Thin transport used by every BFF-bound operation. The SDK class and
4
+ * the CLI subcommands BOTH build an `HttpClient` and pass it to the
5
+ * operations module — so they cannot drift in how they auth, encode
6
+ * query parameters, or decode error responses.
7
+ */
8
+ export class HttpClient {
9
+ #baseUrl;
10
+ #apiToken;
11
+ #fetch;
12
+ constructor(options) {
13
+ if (!options.baseUrl) {
14
+ throw new Error("HttpClient: baseUrl is required");
15
+ }
16
+ if (!options.apiToken) {
17
+ throw new Error("HttpClient: apiToken is required");
18
+ }
19
+ const normalized = options.baseUrl.endsWith("/") ? options.baseUrl : `${options.baseUrl}/`;
20
+ this.#baseUrl = new URL(normalized);
21
+ this.#apiToken = options.apiToken;
22
+ this.#fetch = options.fetch ?? fetch;
23
+ }
24
+ async request(path, init = {}, query = {}) {
25
+ const url = new URL(path.replace(/^\//, ""), this.#baseUrl);
26
+ for (const [key, value] of Object.entries(query)) {
27
+ url.searchParams.set(key, value);
28
+ }
29
+ const headers = {
30
+ accept: "application/json",
31
+ authorization: `Bearer ${this.#apiToken}`,
32
+ ...normalizeHeaders(init.headers)
33
+ };
34
+ if (init.body !== undefined && init.body !== null && !headers["content-type"]) {
35
+ headers["content-type"] = "application/json";
36
+ }
37
+ const response = await this.#fetch(url, { ...init, headers });
38
+ const body = await readJson(response);
39
+ if (!response.ok) {
40
+ throw new AntpathApiError(response.status, extractErrorMessage(body), body);
41
+ }
42
+ return body;
43
+ }
44
+ async download(path, init = {}, query = {}) {
45
+ const url = new URL(path.replace(/^\//, ""), this.#baseUrl);
46
+ for (const [key, value] of Object.entries(query)) {
47
+ url.searchParams.set(key, value);
48
+ }
49
+ const headers = {
50
+ authorization: `Bearer ${this.#apiToken}`,
51
+ ...normalizeHeaders(init.headers)
52
+ };
53
+ const response = await this.#fetch(url, { ...init, headers });
54
+ if (!response.ok) {
55
+ const body = await readJson(response);
56
+ throw new AntpathApiError(response.status, extractErrorMessage(body), body);
57
+ }
58
+ return { response };
59
+ }
60
+ }
61
+ function normalizeHeaders(headers) {
62
+ if (!headers)
63
+ return {};
64
+ if (headers instanceof Headers)
65
+ return Object.fromEntries(headers.entries());
66
+ if (Array.isArray(headers))
67
+ return Object.fromEntries(headers);
68
+ return headers;
69
+ }
70
+ async function readJson(response) {
71
+ const text = await response.text();
72
+ if (text.length === 0)
73
+ return {};
74
+ try {
75
+ return JSON.parse(text);
76
+ }
77
+ catch {
78
+ return { raw: text };
79
+ }
80
+ }
81
+ function extractErrorMessage(body) {
82
+ if (body && typeof body === "object" && "error" in body) {
83
+ const error = body.error;
84
+ if (typeof error === "string")
85
+ return error;
86
+ if (error && typeof error === "object" && "message" in error) {
87
+ const message = error.message;
88
+ if (typeof message === "string")
89
+ return message;
90
+ }
91
+ }
92
+ return "antpath API request failed";
93
+ }
94
+ //# sourceMappingURL=http.js.map