@promigence/sdk 0.1.0-beta.12

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 (104) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +54 -0
  3. package/dist/blake3.d.ts +10 -0
  4. package/dist/blake3.js +111 -0
  5. package/dist/bundle.d.ts +96 -0
  6. package/dist/bundle.js +227 -0
  7. package/dist/ca-guard.d.ts +1 -0
  8. package/dist/ca-guard.js +166 -0
  9. package/dist/caller.d.ts +73 -0
  10. package/dist/caller.js +47 -0
  11. package/dist/cjs/blake3.d.ts +10 -0
  12. package/dist/cjs/blake3.js +116 -0
  13. package/dist/cjs/bundle.d.ts +96 -0
  14. package/dist/cjs/bundle.js +242 -0
  15. package/dist/cjs/ca-guard.d.ts +1 -0
  16. package/dist/cjs/ca-guard.js +174 -0
  17. package/dist/cjs/caller.d.ts +73 -0
  18. package/dist/cjs/caller.js +57 -0
  19. package/dist/cjs/client.d.ts +3015 -0
  20. package/dist/cjs/client.js +2617 -0
  21. package/dist/cjs/command-errors.d.ts +41 -0
  22. package/dist/cjs/command-errors.js +43 -0
  23. package/dist/cjs/context.d.ts +119 -0
  24. package/dist/cjs/context.js +598 -0
  25. package/dist/cjs/core.d.ts +285 -0
  26. package/dist/cjs/core.js +290 -0
  27. package/dist/cjs/credstore.d.ts +90 -0
  28. package/dist/cjs/credstore.js +219 -0
  29. package/dist/cjs/dispatcher.d.ts +58 -0
  30. package/dist/cjs/dispatcher.js +91 -0
  31. package/dist/cjs/github.d.ts +158 -0
  32. package/dist/cjs/github.js +84 -0
  33. package/dist/cjs/gitlab.d.ts +104 -0
  34. package/dist/cjs/gitlab.js +24 -0
  35. package/dist/cjs/handles.d.ts +82 -0
  36. package/dist/cjs/handles.js +139 -0
  37. package/dist/cjs/hint.d.ts +1 -0
  38. package/dist/cjs/hint.js +10 -0
  39. package/dist/cjs/http2.d.ts +206 -0
  40. package/dist/cjs/http2.js +1932 -0
  41. package/dist/cjs/index.d.ts +34 -0
  42. package/dist/cjs/index.js +31 -0
  43. package/dist/cjs/keyed.gen.d.ts +1 -0
  44. package/dist/cjs/keyed.gen.js +10 -0
  45. package/dist/cjs/models.gen.d.ts +6162 -0
  46. package/dist/cjs/models.gen.js +2 -0
  47. package/dist/cjs/package.json +1 -0
  48. package/dist/cjs/proxy.d.ts +40 -0
  49. package/dist/cjs/proxy.js +257 -0
  50. package/dist/cjs/pty.d.ts +171 -0
  51. package/dist/cjs/pty.js +504 -0
  52. package/dist/cjs/sandbox.d.ts +537 -0
  53. package/dist/cjs/sandbox.js +736 -0
  54. package/dist/cjs/ssh.d.ts +25 -0
  55. package/dist/cjs/ssh.js +117 -0
  56. package/dist/cjs/trace.d.ts +33 -0
  57. package/dist/cjs/trace.js +88 -0
  58. package/dist/cjs/transport-worker.d.ts +1 -0
  59. package/dist/cjs/transport-worker.js +468 -0
  60. package/dist/cjs/warm.d.ts +1 -0
  61. package/dist/cjs/warm.js +43 -0
  62. package/dist/client.d.ts +3015 -0
  63. package/dist/client.js +2583 -0
  64. package/dist/command-errors.d.ts +41 -0
  65. package/dist/command-errors.js +36 -0
  66. package/dist/context.d.ts +119 -0
  67. package/dist/context.js +588 -0
  68. package/dist/core.d.ts +285 -0
  69. package/dist/core.js +266 -0
  70. package/dist/credstore.d.ts +90 -0
  71. package/dist/credstore.js +206 -0
  72. package/dist/dispatcher.d.ts +58 -0
  73. package/dist/dispatcher.js +86 -0
  74. package/dist/github.d.ts +158 -0
  75. package/dist/github.js +78 -0
  76. package/dist/gitlab.d.ts +104 -0
  77. package/dist/gitlab.js +20 -0
  78. package/dist/handles.d.ts +82 -0
  79. package/dist/handles.js +135 -0
  80. package/dist/hint.d.ts +1 -0
  81. package/dist/hint.js +7 -0
  82. package/dist/http2.d.ts +206 -0
  83. package/dist/http2.js +1921 -0
  84. package/dist/index.d.ts +34 -0
  85. package/dist/index.js +15 -0
  86. package/dist/keyed.gen.d.ts +1 -0
  87. package/dist/keyed.gen.js +7 -0
  88. package/dist/models.gen.d.ts +6162 -0
  89. package/dist/models.gen.js +1 -0
  90. package/dist/proxy.d.ts +40 -0
  91. package/dist/proxy.js +249 -0
  92. package/dist/pty.d.ts +171 -0
  93. package/dist/pty.js +496 -0
  94. package/dist/sandbox.d.ts +537 -0
  95. package/dist/sandbox.js +725 -0
  96. package/dist/ssh.d.ts +25 -0
  97. package/dist/ssh.js +114 -0
  98. package/dist/trace.d.ts +33 -0
  99. package/dist/trace.js +80 -0
  100. package/dist/transport-worker.d.ts +1 -0
  101. package/dist/transport-worker.js +466 -0
  102. package/dist/warm.d.ts +1 -0
  103. package/dist/warm.js +40 -0
  104. package/package.json +54 -0
@@ -0,0 +1,3015 @@
1
+ /** `PromigenceClient` — the native client for the Promigence API. */
2
+ import { inspect, type InspectOptions } from "node:util";
3
+ import { PromigenceError, type CommandResult, type NetworkMode, type OptimizeFor, type Tier } from "./core.js";
4
+ import { type Bundle } from "./bundle.js";
5
+ import { type PtyExit, type PtySession } from "./pty.js";
6
+ import { type SshTunnel } from "./ssh.js";
7
+ import type * as M from "./models.gen.js";
8
+ import { type ConnectRequest, type ConnectTicket, type GithubConnect, type GithubDisconnected, type GithubRepos, type GithubStatus, type GithubUntracked } from "./github.js";
9
+ import { type GitlabConnect, type GitlabDisconnected, type GitlabRepos, type GitlabStatus, type GitlabUntracked } from "./gitlab.js";
10
+ export type FetchLike = typeof globalThis.fetch;
11
+ /**
12
+ * Default deadline for one `files get|put`.
13
+ *
14
+ * A transfer is bounded by the artefact's size, not by the 30 s that is generous for a control
15
+ * message: a 2 GB trace over a shaped link is minutes, and timing it out at 30 s would make the
16
+ * route useless for exactly the payload it exists to carry.
17
+ */
18
+ export declare const FILE_TIMEOUT_MS: number;
19
+ export type RunState = M.RunState;
20
+ /**
21
+ * `queued` and `awaiting_capacity` are both waiting, and the difference is the whole point.
22
+ *
23
+ * `queued` = waiting for capacity that **exists**; typically milliseconds to a few seconds.
24
+ * `awaiting_capacity` = there is no free capacity and more is being **provisioned for you**; the
25
+ * run's `capacity` block carries an ETA. A waiting sandbox may also say why it waits: see
26
+ * {@link QueuedInfo}.
27
+ *
28
+ */
29
+ export type SandboxState = "queued" | "awaiting_capacity" | "ready" | "running" | "paused" | "ended";
30
+ /** A sandbox is still waiting for capacity, for either reason. */
31
+ export declare const isWaiting: (s: SandboxState | string) => boolean;
32
+ /**
33
+ * Why a waiting sandbox is waiting.
34
+ *
35
+ * `capacity_arriving` — more capacity is being added for the run; `eta_s` says when it is
36
+ * expected, and `one_time` whether it covers everything waiting. `snapshot_held` — the sandbox can
37
+ * only start where its snapshot is held, and there is no room there yet; it starts when a sandbox
38
+ * there ends. `capacity_limit` — the service is at its capacity limit; it starts when room frees.
39
+ * `preparing` — the snapshot is being made ready to start; its first start takes a few seconds.
40
+ */
41
+ export type QueuedReason = M.QueuedReason;
42
+ /**
43
+ * Why a waiting sandbox is waiting and how long that should take — on every sandbox record a run
44
+ * or sandbox route returns, and on the validity report's rows. All four fields are present
45
+ * together, or none: absent while the sandbox is not waiting, and while nothing more is known
46
+ * than that it is queued.
47
+ */
48
+ export interface QueuedInfo {
49
+ queued_reason?: QueuedReason;
50
+ /**
51
+ * Seconds until the capacity it waits for is expected to be ready, with `capacity_arriving`.
52
+ * `null` with the other reasons, and once the sandbox has waited past the estimate it was first
53
+ * given (`queued_message` then says it is taking longer than expected).
54
+ */
55
+ eta_s?: number | null;
56
+ /**
57
+ * A one-time wait: the capacity being added covers what is waiting, so once it is ready the
58
+ * rest of the run starts at once. An agent can decide to wait on this.
59
+ */
60
+ one_time?: boolean;
61
+ /** The same, in words, ready to show a person. */
62
+ queued_message?: string;
63
+ }
64
+ /**
65
+ * Capacity while this run waits. Attached to `GET /runs/:id` and `GET /runs/:id/sandboxes`, so
66
+ * the request a client is already long-polling carries the answer.
67
+ */
68
+ export interface Capacity {
69
+ /** `ok` there is free capacity · `arriving` more is on its way for you · `blocked` no more is available right now. */
70
+ state: "ok" | "arriving" | "blocked";
71
+ awaiting_capacity: number;
72
+ /** Soonest a sandbox can start, in seconds. `null` when nothing is on its way. */
73
+ eta_s: number | null;
74
+ /** Why no more capacity is available right now. Not an ETA. */
75
+ blocked?: {
76
+ code: string;
77
+ reason: string;
78
+ at: string;
79
+ } | null;
80
+ /** Why this run's waiting sandboxes are waiting: the `queued_reason` they carry. */
81
+ queued_reason?: QueuedReason;
82
+ /** The same, in words, with the time it should take: the `queued_message` they carry. */
83
+ queued_message?: string;
84
+ }
85
+ export type Outcome = M.Outcome;
86
+ /** `GET /me/outcomes`: what the organization got this month, measured. */
87
+ export type Outcomes = M.Outcomes;
88
+ export interface Percentiles {
89
+ p50: number;
90
+ p95: number;
91
+ p99?: number;
92
+ }
93
+ /** Reply to `sandboxes.pause`. `bytes.allocated` is the disk space the paused sandbox occupies. */
94
+ export interface PauseResult {
95
+ ok: boolean;
96
+ sandbox_id: string;
97
+ state: "paused";
98
+ /** An opaque name for this pause. Resume by the sandbox id. */
99
+ paused_id: string;
100
+ /** Milliseconds the pause took, from the request to the answer. */
101
+ took_ms?: number;
102
+ bytes?: {
103
+ allocated: number;
104
+ };
105
+ /** False if the pause happened but could not be written down; worth retrying. */
106
+ recorded?: boolean;
107
+ /** True when the paused sandbox is stored so that it survives a hardware failure. */
108
+ durable?: boolean;
109
+ /** The pause was a no-op: it was already parked. */
110
+ already_paused?: boolean;
111
+ /**
112
+ * Background execs (started with `background: true`) this pause ended. Their exec ids answer
113
+ * not found after resume; a process a foreground command detached is kept.
114
+ */
115
+ background_execs_killed?: number;
116
+ /**
117
+ * `code: sentence` lines, present only when there is something to say. Today:
118
+ * `secrets_in_saved_state` — the sandbox was given the value of a secret, so its saved state
119
+ * can contain that value while it is paused.
120
+ */
121
+ warnings?: string[];
122
+ }
123
+ /**
124
+ * Reply to `sandboxes.snapshot` — a snapshot taken from a sandbox that is still running.
125
+ *
126
+ * `still_running` is sent explicitly rather than implied by the absence of an error, because it
127
+ * is the whole difference between this call and `pause`.
128
+ */
129
+ export interface SandboxSnapshotResult {
130
+ ok: boolean;
131
+ /** An ordinary snapshot id. Fork it like any other. */
132
+ snapshot_id: string;
133
+ sandbox_id: string;
134
+ alias?: string | null;
135
+ tier: string;
136
+ /** What the image occupies on disk. */
137
+ bytes: {
138
+ allocated: number;
139
+ };
140
+ /** Milliseconds the snapshot took, from the request to the answer. */
141
+ took_ms?: number;
142
+ /** Background processes ended to take the snapshot. */
143
+ background_execs_killed: number;
144
+ still_running: boolean;
145
+ /**
146
+ * Present only when there is something to say. Today: `secrets_in_saved_state` on a snapshot
147
+ * taken with `force` from a sandbox that was given a secret — the snapshot, and every sandbox
148
+ * started from it, can contain that value.
149
+ */
150
+ warnings?: string[];
151
+ }
152
+ /** Reply to `sandboxes.resume`. `took_ms` is the time it took. */
153
+ export interface ResumeResult {
154
+ ok: boolean;
155
+ sandbox_id: string;
156
+ state: "ready";
157
+ /** The `paused_id` of the pause this restored. */
158
+ paused_id?: string;
159
+ /** Milliseconds the resume took, from the request to the answer. */
160
+ took_ms?: number;
161
+ recorded?: boolean;
162
+ }
163
+ export interface SandboxRef extends QueuedInfo {
164
+ sandbox_id: string;
165
+ state: SandboxState;
166
+ /**
167
+ * `runs.get` only: the verdict on the sandbox's terminal command when the sandbox outlived it.
168
+ * `completed` | `task_failed` with `killOnExit: false`: the command is over, the sandbox is live
169
+ * and billed until it is killed or expires. `cap_killed`: the run's spend cap (or the trial's
170
+ * credits) stopped the command and the sandbox was paused with its files and state; it goes on
171
+ * once the cap is raised (`sandboxes.resume(id, { spendCap })`) or credits are added. Absent
172
+ * otherwise.
173
+ */
174
+ command_outcome?: "completed" | "task_failed" | "cap_killed";
175
+ [extra: string]: unknown;
176
+ }
177
+ /** How a grader's result is read. */
178
+ export type ScoreMode = M.ScoreMode;
179
+ /**
180
+ * Score the agent's work after the episode, in a clean copy of the snapshot with the agent's
181
+ * changes applied. `holdout_cmd` is your own second test set; the report carries the gap
182
+ * between the two pass rates. It runs in a copy of the same snapshot, so any file it reads from
183
+ * the snapshot was readable by the agent too — keep held-out tests out of the snapshot.
184
+ *
185
+ * The answer is a verdict and a score. What the grader prints is kept only when the run asks for
186
+ * it (`gradeOutput`): then its last 16 KiB come back in the report (`ReportSandbox.grade`).
187
+ */
188
+ export interface GradeSpec {
189
+ cmd: string;
190
+ holdout_cmd?: string;
191
+ timeout_ms?: number;
192
+ score?: ScoreMode;
193
+ }
194
+ /**
195
+ * What a sandbox's grader printed, in the run report when the run asked for it (`gradeOutput`):
196
+ * the end of its output, stdout and stderr together, at most its last 16 KiB. Never a held-out
197
+ * grader's.
198
+ */
199
+ export interface GradeOutput {
200
+ output: string;
201
+ /** The grader printed more than `output` holds; the beginning was left out. */
202
+ output_truncated: boolean;
203
+ }
204
+ /** Per run: the two pass rates and the gap between them, in points. */
205
+ export interface GradeStats {
206
+ graded: number;
207
+ passed: number;
208
+ pass_pct: number;
209
+ holdout_graded?: number;
210
+ holdout_passed?: number;
211
+ holdout_pass_pct?: number;
212
+ /** `pass_pct − holdout_pass_pct`. Absent without a held-out set. */
213
+ gap_pct?: number;
214
+ grader_failed?: number;
215
+ }
216
+ export interface RunStartResponse {
217
+ run_id: string;
218
+ sandboxes: SandboxRef[];
219
+ /** Set by the server when an `Idempotency-Key` replayed an existing run. */
220
+ idempotent_replay?: boolean;
221
+ /** With `idempotent_replay`: how many seconds ago that run's answer was first given. */
222
+ idempotent_replay_age_s?: number;
223
+ [extra: string]: unknown;
224
+ }
225
+ export interface RunRecord {
226
+ run_id: string;
227
+ state: RunState;
228
+ snapshot_id?: string;
229
+ sandboxes?: SandboxRef[];
230
+ [extra: string]: unknown;
231
+ }
232
+ export interface ForkStats {
233
+ n: number;
234
+ queued_ms?: Percentiles;
235
+ ready_ms?: Percentiles;
236
+ e2e_ms?: Percentiles;
237
+ verify_each_ms?: Percentiles;
238
+ warm_retries: number;
239
+ first_attempt_success_pct: number;
240
+ final_success_pct: number;
241
+ percentile_method?: string;
242
+ replay?: {
243
+ n: number;
244
+ success_pct: number;
245
+ };
246
+ }
247
+ export interface Totals {
248
+ env_verified: number;
249
+ completed: number;
250
+ task_failed: number;
251
+ env_failed: number;
252
+ /** The grade could not be computed. Never counted as a task failure. */
253
+ grader_failed?: number;
254
+ snapshot_failed: number;
255
+ aborted_by_client: number;
256
+ /**
257
+ * Sandboxes Promigence support stopped (outcome `stopped_by_support`), each with its reason in its
258
+ * failure's message. Never counted as your own kill (`aborted_by_client`). Absent from an older API.
259
+ */
260
+ stopped_by_support?: number;
261
+ expired: number;
262
+ cap_killed: number;
263
+ not_started: number;
264
+ evidence_missing: number;
265
+ }
266
+ export interface ExecRecord {
267
+ exec_id: string;
268
+ argv: string[];
269
+ exit_code?: number;
270
+ signal?: number;
271
+ wall_ms?: number;
272
+ cpu_ms?: number;
273
+ stdout_ref?: string;
274
+ stderr_ref?: string;
275
+ truncated?: boolean;
276
+ [extra: string]: unknown;
277
+ }
278
+ export interface ReportSandbox extends QueuedInfo {
279
+ sandbox_id: string;
280
+ state: SandboxState;
281
+ attempt?: number;
282
+ queued_ms?: number;
283
+ ready_ms?: number;
284
+ e2e_ms?: number;
285
+ env_verified?: boolean;
286
+ execs?: ExecRecord[];
287
+ outcome: Outcome;
288
+ /** The grader ran in a clean environment and produced a verdict. */
289
+ graded?: boolean;
290
+ /** The visible test set's verdict. This one decides the sandbox's outcome. */
291
+ passed?: boolean;
292
+ score?: number;
293
+ /** The held-out set, run in its own clean environment. Reported, never attributed. */
294
+ holdout_passed?: boolean;
295
+ holdout_score?: number;
296
+ /** What the grader printed, when the run asked for it (`gradeOutput`). Absent otherwise. */
297
+ grade?: GradeOutput;
298
+ failure?: {
299
+ category: "env" | "task" | "snapshot" | "grader" | "cap";
300
+ code: string;
301
+ evidence?: Record<string, unknown>;
302
+ };
303
+ warnings?: string[];
304
+ /** Seconds from hand-over to end minus paused time, rounded up — whatever the outcome. */
305
+ used_seconds?: number;
306
+ /** Seconds this sandbox bills: `used_seconds` capped at `committed_seconds`, 0 for env failures. */
307
+ billable_seconds?: number;
308
+ /** Seconds the run's cap reserved for it (raised only by `PATCH /sandboxes/:id`). */
309
+ committed_seconds?: number;
310
+ /** USD: `billable_seconds × per_second_usd`. */
311
+ cost?: number;
312
+ [extra: string]: unknown;
313
+ }
314
+ export interface RunReport {
315
+ schema: string;
316
+ run_id: string;
317
+ state: RunState;
318
+ snapshot_id?: string;
319
+ alias?: string;
320
+ labels?: Record<string, string>;
321
+ requested?: {
322
+ count: number;
323
+ timeout_ms: number;
324
+ verify_each: boolean;
325
+ network: NetworkMode;
326
+ allow_partial: boolean;
327
+ secrets?: string[];
328
+ grade?: GradeSpec;
329
+ grade_output?: boolean;
330
+ };
331
+ quote?: Quote;
332
+ spend_cap?: number;
333
+ started_at?: string;
334
+ finished_at?: string;
335
+ fork: ForkStats;
336
+ totals: Totals;
337
+ cost?: {
338
+ quoted: number;
339
+ charged: number;
340
+ unbilled: number;
341
+ cap_killed: boolean;
342
+ billable_seconds?: number;
343
+ per_second_usd?: number;
344
+ };
345
+ /** The two pass rates and the gap between them. Absent when the run was not graded. */
346
+ grade?: GradeStats;
347
+ sandboxes: ReportSandbox[];
348
+ [extra: string]: unknown;
349
+ }
350
+ /** A build of a snapshot (`POST /snapshots`, `GET /snapshots/builds/:id`). */
351
+ /** `built`: finished and stored with no verification command to run; done, like `verified`. */
352
+ export type BuildState = "queued" | "running" | "verified" | "built" | "rejected" | "failed";
353
+ export interface SecretRecord {
354
+ name: string;
355
+ /** Length of the stored value, so a caller can sanity-check without reading it. */
356
+ bytes: number;
357
+ /** On `secrets.set` only: `false` when an existing secret of that name was overwritten. */
358
+ created?: boolean;
359
+ /** On `secrets.list` only. */
360
+ created_at?: string;
361
+ updated_at?: string;
362
+ /**
363
+ * The hosts the secret is bound to. Absent when it is not bound: a sandbox that names it is
364
+ * then given the value. When present, requests the sandbox sends to the host's address in
365
+ * `urls` are authenticated with the secret, a request made straight to `https://<host>/` is not,
366
+ * and the value stays out of the sandbox unless a run asks for it (`raw`).
367
+ */
368
+ hosts?: string[];
369
+ /**
370
+ * For each of `hosts`, in order, the address a sandbox sends its requests to, with the
371
+ * request's path added. On `set` and `list` of a bound secret.
372
+ */
373
+ urls?: string[];
374
+ /**
375
+ * The variables a sandbox that names this secret is also given, each set to its host's
376
+ * address in `urls`: `OPENAI_BASE_URL` for `api.openai.com`, `ANTHROPIC_BASE_URL` for
377
+ * `api.anthropic.com`. Absent when there is none. A variable the caller sets itself wins.
378
+ */
379
+ env?: Record<string, string>;
380
+ /** The request headers the secret authenticates: on `secrets.list`, when `hosts` is present. */
381
+ headers?: string[];
382
+ /** What the variable in a sandbox that names it holds instead of the value (on `set` of a bound secret). */
383
+ placeholder?: string;
384
+ /** `refused` when the value itself can never be given to a sandbox (present when `hosts` is). */
385
+ raw?: "allowed" | "refused";
386
+ /** Whether the value is kept out of the bound hosts' answers (present when `hosts` is). */
387
+ scan_responses?: boolean;
388
+ }
389
+ /**
390
+ * `secrets.set` options: bind the secret to hosts. Requests a sandbox sends to the host's address
391
+ * the answer gives in `urls` (and to `$OPENAI_BASE_URL` / `$ANTHROPIC_BASE_URL` for those
392
+ * providers) are authenticated with it, one sent straight to the host is not, and the value stays
393
+ * out of the sandbox unless a run asks for it (`raw: "refused"` forbids that; the default allows it).
394
+ *
395
+ * Every option left out keeps what the secret already has, so setting a bound secret again with
396
+ * a new value (a rotation) keeps its binding. `hosts: []` removes it.
397
+ */
398
+ export interface SecretSetOpts {
399
+ /** Host names, e.g. `["api.openai.com"]` or `["*.openai.azure.com"]`. Never an address. */
400
+ hosts?: string[];
401
+ /** The request headers the key is sent in. Default: authorization, x-api-key, api-key, x-goog-api-key. */
402
+ headers?: string[];
403
+ /** `refused`: the value itself can never be given to a sandbox (`{ name, mode: "raw" }` is refused). */
404
+ raw?: "allowed" | "refused";
405
+ /** Mask the value out of responses from the bound hosts (default true). */
406
+ scanResponses?: boolean;
407
+ }
408
+ export interface SnapshotSource {
409
+ kind: "image" | "repo" | (string & {});
410
+ image?: string;
411
+ digest?: string;
412
+ [extra: string]: unknown;
413
+ }
414
+ export interface SnapshotBuild {
415
+ build_id: string;
416
+ state: BuildState;
417
+ snapshot_id?: string | null;
418
+ /** The name the snapshot holds now; `null` once it is deleted (see `snapshot_deleted`). */
419
+ alias?: string | null;
420
+ /** `true` when the snapshot this build made has since been deleted. */
421
+ snapshot_deleted?: boolean;
422
+ tier_assigned?: string;
423
+ /**
424
+ * The most the build can cost, USD, said before it runs: `max_seconds` at `per_second_usd`. A build
425
+ * is billed per second for the time it runs; what it cost is `cost_usd` once it has ended.
426
+ */
427
+ build_price?: number;
428
+ /** What one second of the build costs, USD: its tier's per-second rate. */
429
+ per_second_usd?: number;
430
+ /** The longest the build may run, in seconds: it is billed for at most this long. */
431
+ max_seconds?: number;
432
+ /** Once it has ended, the seconds it is billed for; 0 when it failed on our side. */
433
+ billable_seconds?: number;
434
+ /** Once it has ended, what it cost, USD; 0 when nothing was charged. */
435
+ cost_usd?: number;
436
+ repo?: string;
437
+ commit?: string;
438
+ verification?: Record<string, unknown>;
439
+ /** A rejected build names the guard and the measured value; nothing was stored. */
440
+ error?: {
441
+ code?: string;
442
+ guard?: string;
443
+ message?: string;
444
+ values?: number[];
445
+ stored?: boolean;
446
+ } | null;
447
+ /** Set when an identical build already existed. */
448
+ deduped?: boolean;
449
+ /**
450
+ * Set when an identical build was already IN PROGRESS and this answer is that build: wait on
451
+ * it like your own. Unlike `deduped`, nothing is finished yet.
452
+ */
453
+ joined?: boolean;
454
+ /** Server notes on this answer, e.g. that a joined build's alias was not applied. */
455
+ warnings?: string[];
456
+ /**
457
+ * `true` when this answer repeats an earlier create's (the same `Idempotency-Key`): nothing new
458
+ * was built or charged, whatever `build_price` says.
459
+ */
460
+ idempotent_replay?: boolean;
461
+ /** With `idempotent_replay`: how many seconds ago that first answer was given. */
462
+ idempotent_replay_age_s?: number;
463
+ /** Image or repo. */
464
+ source?: SnapshotSource;
465
+ /** `false` for an image snapshot built without `verify` — stored, but nothing was verified. */
466
+ verified?: boolean;
467
+ next?: string[];
468
+ [extra: string]: unknown;
469
+ }
470
+ export interface SnapshotRecord {
471
+ snapshot_id: string;
472
+ alias?: string | null;
473
+ visibility?: string;
474
+ repo?: string;
475
+ commit?: string;
476
+ setup?: string[];
477
+ verify?: {
478
+ cmd: string;
479
+ allow_fast?: boolean;
480
+ };
481
+ tier?: string;
482
+ tier_assigned?: string;
483
+ build_id?: string;
484
+ verification?: Record<string, unknown>;
485
+ /** Image or repo. */
486
+ source?: SnapshotSource;
487
+ /** `false` for an image snapshot built without `verify` — stored, but nothing was verified. */
488
+ verified?: boolean;
489
+ created_at?: string;
490
+ /**
491
+ * Set when the snapshot can no longer be started, saying why (it was lost before it could be
492
+ * kept); a fork of it is refused `snapshot_unavailable`. Absent while it can be started.
493
+ */
494
+ unavailable?: string;
495
+ /**
496
+ * The user commands, terminals and SSH sessions in its sandboxes run as when they name none:
497
+ * its image's own `USER`, or the `user` it was created with. Absent: root.
498
+ */
499
+ user?: string | null;
500
+ [extra: string]: unknown;
501
+ }
502
+ /** `GET /snapshots/:id/manifest`: the snapshot's manifest, sealed (`buildBundle` turns it into a `.promigence` bundle). */
503
+ export interface SnapshotManifestResponse {
504
+ snapshot_id: string;
505
+ layers?: number;
506
+ apparent_bytes?: number;
507
+ transferable_bytes?: number;
508
+ cached_at?: string;
509
+ /**
510
+ * What a scrub of these bytes can verify. `verifiable: false` means
511
+ * the manifest records no digest for at least one artefact — the snapshot was built before
512
+ * digests were recorded, and no scrub can tell rot from the real bytes. `summary` is the line
513
+ * `promigence snapshot inspect` prints.
514
+ */
515
+ integrity?: {
516
+ verifiable?: boolean;
517
+ chunked?: string[];
518
+ whole_file?: string[];
519
+ unverifiable?: string[];
520
+ bytes?: number;
521
+ summary?: string;
522
+ };
523
+ manifest: Record<string, unknown>;
524
+ [extra: string]: unknown;
525
+ }
526
+ export interface ExecResponse {
527
+ exec_id: string;
528
+ /**
529
+ * The command's exit code. `null` only when the exit carried neither a code nor a signal: the
530
+ * result is unknown, which is never success (a signal is reported as `128 + signal`).
531
+ */
532
+ exit_code: number | null;
533
+ stdout: string;
534
+ stderr: string;
535
+ truncated?: boolean;
536
+ /** True when the command was killed at its own timeout. */
537
+ timed_out?: boolean;
538
+ /** True when the command was killed for running out of memory. */
539
+ oom_killed?: boolean;
540
+ /** Why it ended without an exit code of its own: `timeout`, `out_of_memory` or `signal`. */
541
+ reason?: "timeout" | "out_of_memory" | "signal";
542
+ /** With `encoding: "base64"`: the exact bytes of standard output (see `execOutputBytes`). */
543
+ stdout_b64?: string;
544
+ /** With `encoding: "base64"`: the exact bytes of standard error. */
545
+ stderr_b64?: string;
546
+ [extra: string]: unknown;
547
+ }
548
+ /**
549
+ * A command's output as bytes: the exact bytes when it was asked for with `encoding: "base64"`,
550
+ * otherwise the UTF-8 of its text.
551
+ */
552
+ export declare function execOutputBytes(r: ExecResponse, stream?: "stdout" | "stderr"): Uint8Array;
553
+ export interface SandboxRecord extends QueuedInfo {
554
+ sandbox_id: string;
555
+ state: SandboxState;
556
+ /**
557
+ * Why it is paused, when a limit paused it rather than a request: `"spend_cap"` (its run
558
+ * reached its spend cap; resume with `spendCap` raised) or `"credits_exhausted"` (the
559
+ * organization's credits ran out; add a card, then resume). Absent otherwise.
560
+ */
561
+ pause_reason?: "spend_cap" | "credits_exhausted";
562
+ run_id?: string;
563
+ /**
564
+ * The snapshot it was started from, as its run names it (`runs.get(runId).snapshot_id`): the
565
+ * snapshot's id, or `promigence/base` for the built-in base.
566
+ */
567
+ snapshot_id?: string;
568
+ /** When the sandbox was created, RFC 3339. */
569
+ created_at?: string;
570
+ outcome?: Outcome;
571
+ /**
572
+ * The labels this sandbox inherited from its run — the same map `runs.start({ labels })` took.
573
+ *
574
+ * Reported so that a caller who found a sandbox with `list({ labels })` can see what it
575
+ * matched, and so that one who did not can learn what a sandbox is tagged with at all.
576
+ */
577
+ labels?: Record<string, string>;
578
+ /**
579
+ * Seconds of idleness after which this sandbox pauses itself; `0` when it never does.
580
+ * Set it with `sandboxes.patch(id, { autoPauseS })`.
581
+ */
582
+ auto_pause_s?: number;
583
+ /**
584
+ * What counts as idle: `"requests"` (the default) or `"activity"`. Set it with
585
+ * `sandboxes.patch(id, { autoPauseMode })`.
586
+ */
587
+ auto_pause_mode?: AutoPauseMode;
588
+ /** What this sandbox asks for on every start and resume (`runs.start({ optimizeFor })`). */
589
+ optimize_for?: OptimizeFor;
590
+ /**
591
+ * What its latest start or resume actually got; `null` before the first. It can differ from
592
+ * `optimize_for`, and the next resume asks again.
593
+ */
594
+ optimize_for_applied?: OptimizeFor | null;
595
+ /**
596
+ * The lifetime this sandbox is bound to, in milliseconds of running time: time spent paused
597
+ * does not count. `setTimeout` / `sandboxes.patch(id, { timeoutMs })` extend it.
598
+ */
599
+ timeout_ms?: number | null;
600
+ /**
601
+ * Milliseconds of running time since hand-over, paused time excluded: what `timeout_ms` is
602
+ * measured against, so `timeout_ms - elapsed_ms` is the time left. `null` before hand-over and
603
+ * once it has ended.
604
+ */
605
+ elapsed_ms?: number | null;
606
+ [extra: string]: unknown;
607
+ }
608
+ /** `POST /images/resolve`. */
609
+ export interface ResolvedImage {
610
+ /** The pinned reference: your own name with `@sha256:…` on the end. Pass it to `snapshots.create`. */
611
+ image: string;
612
+ /** The digest `image` carries — the **index**'s, for a multi-arch tag. */
613
+ digest: string;
614
+ registry: string;
615
+ repository: string;
616
+ tag: string | null;
617
+ /** The media type at `digest`. Empty when the reference was already pinned. */
618
+ media_type: string;
619
+ /** Always `linux/amd64`: the one platform a sandbox runs. */
620
+ platform: string;
621
+ /** The `linux/amd64` child of an index; `null` for a single-platform manifest. */
622
+ platform_digest: string | null;
623
+ /** Every `os/arch` an index offered. Empty for a single manifest. */
624
+ platforms: string[];
625
+ /** `true` when the reference was already a digest and no registry was contacted. */
626
+ already_pinned: boolean;
627
+ ms?: number;
628
+ }
629
+ /** Options for {@link PromigenceClient.sandboxes}`.list`. */
630
+ export interface SandboxListOptions {
631
+ limit?: number;
632
+ /**
633
+ * Only sandboxes whose run carries **every** one of these labels.
634
+ *
635
+ * This is a server-side filter, and that is the point: `limit` bounds the rows the server
636
+ * *reads*, so filtering in your own code cannot find a sandbox older than the last page.
637
+ * It is how two replicas of one service converge on a single sandbox instead of each
638
+ * forking its own.
639
+ */
640
+ labels?: Record<string, string>;
641
+ /** States to keep, server-side: `["running", "ready"]`. Empty = every state. */
642
+ state?: string[];
643
+ /** A `nextCursor` from an earlier page. Opaque: a client only ever echoes it back. */
644
+ cursor?: string;
645
+ }
646
+ /**
647
+ * One page of a list route. `nextCursor` is `null` on the last page, **and that is the only
648
+ * reliable stop signal**: a short page is not one, because a server-side filter can empty a page
649
+ * that still has successors.
650
+ */
651
+ export interface Page<T> {
652
+ items: T[];
653
+ nextCursor: string | null;
654
+ /** What the server actually used, which is not always what was asked for (it caps at 500). */
655
+ limit: number;
656
+ }
657
+ /** `GET /health` — unauthenticated: `ok`, `version`, `serving` and `location`; `build_sha` to a caller with a key. */
658
+ export interface HealthResponse {
659
+ ok?: boolean;
660
+ version?: string;
661
+ build_sha?: string;
662
+ /** Whether the service can start new sandboxes right now. */
663
+ serving?: boolean;
664
+ /** Where sandboxes run, in plain words (for example "US East Coast"); absent when not said. */
665
+ location?: string;
666
+ [extra: string]: unknown;
667
+ }
668
+ /** One service's row in `GET /status` (`api`, `sandboxes`): `up`, `degraded` or `down`. */
669
+ export interface ServiceStatus {
670
+ name: string;
671
+ state?: string;
672
+ last_health_at?: string | null;
673
+ [extra: string]: unknown;
674
+ }
675
+ /** `GET /status` — unauthenticated; the synthetic check window plus one row per service. */
676
+ export interface StatusResponse {
677
+ checks?: unknown;
678
+ services?: ServiceStatus[];
679
+ [extra: string]: unknown;
680
+ }
681
+ /** One API key (`KeyView` in the API reference). `api_key` is present only in the answer that minted it. */
682
+ export interface KeyRecord {
683
+ key_id: string;
684
+ prefix: string;
685
+ name: string;
686
+ created_at: string;
687
+ created_by?: string | null;
688
+ last_used_at?: string | null;
689
+ revoked?: boolean | null;
690
+ revoked_at?: string | null;
691
+ /** When it stops working; `null` or absent = never. */
692
+ expires_at?: string | null;
693
+ /** The key that minted it; revoking that key revokes this one. */
694
+ minted_by?: string | null;
695
+ /** True for the key making this call. */
696
+ current?: boolean | null;
697
+ /** The secret, in the create answer only; never retrievable again. */
698
+ api_key?: string | null;
699
+ /** On a revoke: the keys this one minted, revoked with it. */
700
+ also_revoked?: string[] | null;
701
+ }
702
+ /** A key minted in an organization's project (`OrgKeyView`). `api_key` is shown once. */
703
+ export interface OrgKeyRecord {
704
+ key_id: string;
705
+ prefix: string;
706
+ name: string;
707
+ org_id: string;
708
+ project_id: string;
709
+ service: boolean;
710
+ created_at: string;
711
+ created_by?: string | null;
712
+ expires_at?: string | null;
713
+ api_key: string;
714
+ }
715
+ /** A key's lifetime: days from now, or a date (`2027-01-31`) / UTC time ending in `Z`. Neither = never expires. */
716
+ export interface KeyExpiry {
717
+ expiresInDays?: number;
718
+ expiresAt?: string;
719
+ }
720
+ export interface OrgRecord {
721
+ org_id: string;
722
+ name: string;
723
+ role: string;
724
+ default: boolean;
725
+ card_on_file: boolean;
726
+ projects: number;
727
+ members: number;
728
+ created_at: string;
729
+ }
730
+ export interface ProjectRecord {
731
+ project_id: string;
732
+ org_id: string;
733
+ name: string;
734
+ default: boolean;
735
+ keys: number;
736
+ /** The most the project may bill in a calendar month (UTC); `null` = no cap. */
737
+ monthly_cap_usd?: number | null;
738
+ created_at: string;
739
+ }
740
+ export interface MemberRecord {
741
+ user_id: string;
742
+ email: string;
743
+ role: string;
744
+ joined_at: string;
745
+ /** True for the person this call is made as. */
746
+ you: boolean;
747
+ }
748
+ export interface InviteRecord {
749
+ invite_id: string;
750
+ org_id: string;
751
+ org_name: string;
752
+ email: string;
753
+ role: string;
754
+ invited_by: string;
755
+ created_at: string;
756
+ }
757
+ /** `GET /credits/offer`: the account's free credit. It is claimed in a browser, never by an API call. */
758
+ export interface CreditOffer {
759
+ /** `claimable` | `claimed` | `pending_review` | `needs_card` | `not_eligible` | `offer_ended` | `none` */
760
+ state: string;
761
+ credit_usd: number;
762
+ card_bonus_usd: number;
763
+ /** USD charged to confirm Apple Pay, Google Pay or Amazon Pay when one is added (added to the credit). */
764
+ method_check_usd?: number;
765
+ expires_at?: string | null;
766
+ campaign?: string | null;
767
+ message: string;
768
+ }
769
+ /** `GET /billing` — the customer's own month to date (`promigence billing usage`). */
770
+ /** `GET /referrals` (`ReferralStatus` in the API reference). */
771
+ export interface ReferralStatus {
772
+ /** `"on"` while an offer runs, else `"off"`. */
773
+ program: "on" | "off" | string;
774
+ /** Your code; `null` while the program is off. */
775
+ code: string | null;
776
+ offer: {
777
+ /** E.g. `"Give $50, get $100"`. */
778
+ headline: string;
779
+ give_usd: number;
780
+ get_usd: number;
781
+ credit_expiry_days: number | null;
782
+ hold_days: number;
783
+ qualifying_plans: string[];
784
+ ends_at: string | null;
785
+ } | null;
786
+ referrals: {
787
+ pending: number;
788
+ held: number;
789
+ rewarded: number;
790
+ under_review: number;
791
+ other: number;
792
+ };
793
+ earned_usd: number;
794
+ referred_by: {
795
+ status: string;
796
+ reward_usd: number;
797
+ rewarded_at: string | null;
798
+ } | null;
799
+ expiring_credits: Array<{
800
+ remaining_usd: number;
801
+ granted_usd: number;
802
+ expires_at: string;
803
+ }>;
804
+ }
805
+ /** `POST /referrals/apply` (`ReferralApplied`), and a first sign-in's `referral`. */
806
+ export interface ReferralApplied {
807
+ /** `pending` | `under_review` | `not_applied` (sign-in only). */
808
+ status: string;
809
+ detail?: string;
810
+ code: string;
811
+ reward_usd: number;
812
+ qualifying_plans: string[];
813
+ }
814
+ /** One sandbox shape and its price in a {@link PricingView}. */
815
+ export interface PricingTier {
816
+ /** The name `--tier` takes. */
817
+ name: string;
818
+ vcpus: number;
819
+ mem_gib: number;
820
+ disk_gib: number;
821
+ /** USD per second, on demand. */
822
+ per_second_usd: number;
823
+ /** USD per hour, on demand. */
824
+ per_hour_usd: number;
825
+ /** USD per second for a run started `preemptible`. */
826
+ preemptible_per_second_usd: number;
827
+ /** Within the account's largest sandbox shape (`limits.max_sandbox_vcpus`). */
828
+ available: boolean;
829
+ }
830
+ /**
831
+ * `GET /pricing` — the price list, read live on every call from what billing charges. Without a
832
+ * key: what a new account gets (`scope: "new_account"`). With one: what that key's organization
833
+ * pays (`scope: "organization"`). A run is billed at the rates in force when it was created.
834
+ */
835
+ export interface PricingView {
836
+ /** `promigence.pricing/v1`. */
837
+ schema: string;
838
+ /** When these values were read (RFC 3339): read them again rather than keeping a copy. */
839
+ as_of: string;
840
+ /** `usd`. */
841
+ currency: string;
842
+ scope: "new_account" | "organization" | string;
843
+ /** `null` for `new_account`. */
844
+ org_id: string | null;
845
+ plan: {
846
+ plan_id: string;
847
+ display_name: string;
848
+ /** `null` when no plan is assigned and the standard values apply. */
849
+ version: number | null;
850
+ /** Work is paid from free credit, within the trial's limits, until a card is added. */
851
+ on_trial: boolean;
852
+ };
853
+ /** `per_second`. */
854
+ billing: string;
855
+ granularity_seconds: number;
856
+ min_billable_seconds: number;
857
+ rates: {
858
+ vcpu_second_usd: number;
859
+ gib_second_usd: number;
860
+ preemptible_vcpu_second_usd: number;
861
+ preemptible_gib_second_usd: number;
862
+ /**
863
+ * The most one snapshot build of the default tier can cost (`limits.build_max_s` seconds at that
864
+ * tier's rate). A build is billed per second at its own tier's rate for the time it runs.
865
+ */
866
+ build_usd: number;
867
+ /** Per GiB of data out above the month's allowance; `null` when not priced. */
868
+ egress_gb_usd: number | null;
869
+ /** Per GiB-month of stored snapshot data; `null` while not priced. */
870
+ storage_gb_month_usd: number | null;
871
+ };
872
+ /** Every shape on sale, smallest first. */
873
+ tiers: PricingTier[];
874
+ limits: {
875
+ /** `null` = no limit. */
876
+ max_concurrency: number | null;
877
+ /** The largest sandbox shape, in vCPU; `null` = no limit. */
878
+ max_sandbox_vcpus: number | null;
879
+ /** The longest a sandbox may live, extensions included. */
880
+ max_session_s: number;
881
+ /** A sandbox's timeout when its run names none. */
882
+ default_timeout_s: number;
883
+ max_count_per_run: number;
884
+ /** Data out included each month, GiB; `null` = none included. */
885
+ egress_gb_month: number | null;
886
+ /** Where data out stops each month, GiB; `null` = it does not stop. */
887
+ max_egress_gb_month: number | null;
888
+ /** Outbound mail ports open. */
889
+ smtp: boolean;
890
+ /** The longest a snapshot build may run, in seconds: the most one build is billed for. */
891
+ build_max_s?: number;
892
+ };
893
+ /** The free credit a new account gets (an account's own: `GET /credits/offer`). */
894
+ free_credit: {
895
+ /** Claimed once, in the browser; 0 once the offer has ended. */
896
+ signup_credit_usd: number;
897
+ /** More when a card is first added; 0 once the offer has ended. */
898
+ card_bonus_usd: number;
899
+ expiry_days_after_claim: number | null;
900
+ /** No free credit outlives this instant; `null` = no fixed end. */
901
+ expires_by: string | null;
902
+ terms_url: string;
903
+ /** New accounts need an invite code; a code may carry its own credit. */
904
+ invite_required: boolean;
905
+ };
906
+ monthly_fee_usd: number;
907
+ /** Usage the monthly fee includes each month, USD. */
908
+ included_credits_usd: number;
909
+ monthly_minimum_usd: number;
910
+ }
911
+ export interface BillingUsage {
912
+ org_id?: string;
913
+ /**
914
+ * `false` when this deployment takes no card: `billing.checkout()` / `portal()` then answer
915
+ * `billing_unavailable`, so nothing should send a customer to them.
916
+ */
917
+ billing_enabled?: boolean;
918
+ /**
919
+ * The payment state (`free` with no subscription, `payg` with one). The plan the limits come
920
+ * from — the name the refusals use — is `me().plan`.
921
+ */
922
+ plan?: string;
923
+ card_on_file?: boolean;
924
+ card?: {
925
+ brand?: string;
926
+ last4?: string;
927
+ } | null;
928
+ /** Set when the card on file does not unlock paid limits for this organization (it already pays for another one,
929
+ * it is being confirmed with a $1 charge, or that charge did not go through). */
930
+ card_note?: string | null;
931
+ /** The $1 that confirms Apple Pay, Google Pay or Amazon Pay on file: `confirming`, `paid` or `failed`. */
932
+ method_check?: {
933
+ method: string;
934
+ status: string;
935
+ amount_usd: number;
936
+ bonus_usd?: number | null;
937
+ } | null;
938
+ suspended?: boolean;
939
+ suspend_reason?: string | null;
940
+ period?: {
941
+ start?: string;
942
+ end?: string;
943
+ basis?: string;
944
+ };
945
+ usage?: {
946
+ total?: number;
947
+ [extra: string]: unknown;
948
+ };
949
+ trial?: {
950
+ credits_remaining?: number;
951
+ [extra: string]: unknown;
952
+ } | null;
953
+ upcoming_invoice?: {
954
+ amount_due?: number | null;
955
+ currency?: string;
956
+ period_end?: string | null;
957
+ } | null;
958
+ upcoming_invoice_error?: string | null;
959
+ [extra: string]: unknown;
960
+ }
961
+ /** One row of `GET /billing/history`: a credit the organization received, or a payment. */
962
+ export type BillingHistoryEntry = M.BillingHistoryEntry;
963
+ /** What `billing.historyPage()` lists: everything, only credits, or only payments. */
964
+ export type BillingHistoryKind = "all" | "credits" | "payments";
965
+ /** One metered row of `GET /me/ledger`. */
966
+ export interface LedgerEntry {
967
+ entry_id: string;
968
+ kind: string;
969
+ run_id?: string | null;
970
+ subject?: string;
971
+ tier?: string;
972
+ seconds?: number;
973
+ sandbox_id?: string | null;
974
+ interim?: boolean;
975
+ amount?: number;
976
+ detail?: unknown;
977
+ created_at?: string;
978
+ [extra: string]: unknown;
979
+ }
980
+ /** One exec's stored output, resolved by `exec_id` (`promigence exec output`). */
981
+ export interface ExecOutput {
982
+ exec_id: string;
983
+ sandbox_id?: string;
984
+ run_id?: string;
985
+ argv?: string[];
986
+ exit_code?: number | null;
987
+ stdout: string;
988
+ stderr: string;
989
+ truncated?: boolean;
990
+ /** Bytes actually returned after `range`, and the full size before it. */
991
+ bytes: number;
992
+ total_bytes: number;
993
+ range?: {
994
+ from: number;
995
+ to: number;
996
+ };
997
+ }
998
+ /** One line of a quote: N sandboxes at `per_second_usd` for up to `seconds_per_sandbox` each. */
999
+ export interface QuoteLine {
1000
+ sandbox_count: number;
1001
+ per_second_usd: number;
1002
+ seconds_per_sandbox: number;
1003
+ }
1004
+ /** The quote: per-second billing, `max_cost = Σ sandbox_count × seconds_per_sandbox × per_second_usd`. */
1005
+ export interface Quote {
1006
+ max_cost: number;
1007
+ /** `vcpus × vcpu_second_usd + mem_gib × gib_second_usd`. */
1008
+ per_second_usd: number;
1009
+ vcpu_second_usd: number;
1010
+ gib_second_usd: number;
1011
+ vcpus: number;
1012
+ mem_gib: number;
1013
+ /** `ceil(timeout_ms / 1000)`: the most one sandbox can bill. */
1014
+ seconds_per_sandbox: number;
1015
+ breakdown: QuoteLine[];
1016
+ }
1017
+ /** `POST /runs/quote` — the bill before the run starts. */
1018
+ export interface RunQuote {
1019
+ snapshot: string;
1020
+ snapshot_id?: string | null;
1021
+ count: number;
1022
+ timeout_ms: number;
1023
+ tier: string;
1024
+ billing: "per_second";
1025
+ granularity_seconds: number;
1026
+ min_billable_seconds: number;
1027
+ vcpus: number;
1028
+ mem_gib: number;
1029
+ vcpu_second_usd: number;
1030
+ gib_second_usd: number;
1031
+ per_second_usd: number;
1032
+ per_hour_usd: number;
1033
+ seconds_per_sandbox: number;
1034
+ /** Rounded to cents; `quote.max_cost` carries full precision. */
1035
+ max_cost: number;
1036
+ breakdown: QuoteLine[];
1037
+ quote: Quote;
1038
+ spend_cap: number;
1039
+ /** Trial orgs: credits not already reserved by live sandboxes. `null` with a card. */
1040
+ credits_available?: number | null;
1041
+ /** Things that will surprise the invoice if unread: a free bench tier, a quote already over
1042
+ * the cap, a quote the trial credits do not cover. */
1043
+ warnings: string[];
1044
+ }
1045
+ /** `POST /runs/:id/replay` — client-triggered, never automatic. */
1046
+ export interface ReplayResponse {
1047
+ ok: boolean;
1048
+ run_id: string;
1049
+ replayed: number;
1050
+ sandboxes: Array<{
1051
+ sandbox_id: string;
1052
+ replay_of: string;
1053
+ state: string;
1054
+ }>;
1055
+ detail?: string;
1056
+ }
1057
+ export interface Capture {
1058
+ snapshot_id: string;
1059
+ run_id: string;
1060
+ sandbox_id: string;
1061
+ outcome: string;
1062
+ code: string;
1063
+ /** What keeping it costs — and what deleting it gives back. */
1064
+ bytes: number;
1065
+ captured_at: string;
1066
+ /** When it goes at the latest; it can stop being openable sooner (`available`). */
1067
+ expires_at: string;
1068
+ /** Whether `runs.open` can still open it; `false` once it was lost before it expired. */
1069
+ available?: boolean;
1070
+ }
1071
+ /** `GET /runs/:id/captures`. */
1072
+ export interface CapturesResponse {
1073
+ ok: boolean;
1074
+ run_id: string;
1075
+ captures: Capture[];
1076
+ }
1077
+ /**
1078
+ * `POST /runs/:id/open` — a NEW live sandbox forked from a failure, with its own id and its own
1079
+ * billing clock. It is not the dead sandbox resurrected.
1080
+ */
1081
+ export interface OpenResponse {
1082
+ ok: boolean;
1083
+ run_id: string;
1084
+ /** The new sandbox. Exec against this one. */
1085
+ sandbox_id: string;
1086
+ state: string;
1087
+ /** The sandbox whose failure it was forked from. */
1088
+ opened_from: string;
1089
+ snapshot_id: string;
1090
+ /** When that sandbox failed, and how. */
1091
+ failed_at: string;
1092
+ outcome: string;
1093
+ code: string;
1094
+ expires_at: string;
1095
+ committed_seconds: number;
1096
+ timeout_ms: number;
1097
+ max_cost: number;
1098
+ ready_ms?: number;
1099
+ detail?: string;
1100
+ }
1101
+ /**
1102
+ * `PATCH /sandboxes/:id` — extend one live sandbox inside the cap, and/or set its idle rule.
1103
+ *
1104
+ * Everything but `ok` and `sandbox_id` is optional because either field may be sent on its own:
1105
+ * a call that only set `autoPauseS` answers with the idle window and nothing about the cap,
1106
+ * since nothing about the cap changed.
1107
+ */
1108
+ export interface SandboxPatchResponse {
1109
+ ok: boolean;
1110
+ sandbox_id: string;
1111
+ run_id?: string;
1112
+ timeout_ms?: number;
1113
+ committed_seconds?: number;
1114
+ cost?: {
1115
+ added: number;
1116
+ charged: number;
1117
+ live_commitment: number;
1118
+ ceiling: number;
1119
+ remaining: number;
1120
+ };
1121
+ /** Seconds of idleness after which the sandbox pauses itself; `0` when it never does. */
1122
+ auto_pause_s?: number;
1123
+ /** What counts as idle: `"requests"` or `"activity"`. */
1124
+ auto_pause_mode?: AutoPauseMode;
1125
+ }
1126
+ /**
1127
+ * What the idle rule counts as idle. `"requests"` (the default): nobody has called the sandbox
1128
+ * for the window and no command, terminal or SSH session is open. `"activity"`: all of that, and
1129
+ * for the whole window nothing inside used CPU or sent or received a byte, and no network
1130
+ * connection to the outside is open — for an agent that runs inside the sandbox on its own.
1131
+ */
1132
+ export type AutoPauseMode = M.AutoPauseMode;
1133
+ /** `POST /snapshots/:id/verify` — re-run the recorded `verify` in a fresh fork. */
1134
+ export interface SnapshotVerifyResponse {
1135
+ run_id: string;
1136
+ snapshot_id: string;
1137
+ alias?: string | null;
1138
+ verify: string;
1139
+ workspace: string;
1140
+ /** The most the fork can cost (`seconds × per_second_usd`) — printed before waiting, like `build_price`. */
1141
+ price: number;
1142
+ seconds: number;
1143
+ per_second_usd: number;
1144
+ tier: string;
1145
+ count: number;
1146
+ last_verified_at?: string | null;
1147
+ sandboxes: Array<{
1148
+ sandbox_id: string;
1149
+ state: string;
1150
+ }>;
1151
+ }
1152
+ /** `GET /sandboxes/:id/files/stat`. */
1153
+ export interface FileStat {
1154
+ path: string;
1155
+ size: number;
1156
+ /** Octal, as a string — `"644"`. */
1157
+ mode: string;
1158
+ mtime_unix_ns: number;
1159
+ is_dir: boolean;
1160
+ is_symlink: boolean;
1161
+ }
1162
+ export interface DirEntry {
1163
+ name: string;
1164
+ path: string;
1165
+ size: number;
1166
+ /** Octal, as a string — `"100644"`. */
1167
+ mode: string;
1168
+ /**
1169
+ * `null` when the listing could give names only. A missing owner is reported as missing
1170
+ * rather than as root, because `0` is a real uid somebody may act on.
1171
+ */
1172
+ uid?: number | null;
1173
+ gid?: number | null;
1174
+ mtime_unix_ns: number;
1175
+ is_dir: boolean;
1176
+ is_symlink: boolean;
1177
+ }
1178
+ /** An opaque label of how a file request was answered, passed through for support. */
1179
+ export type ServedBy = string;
1180
+ /** `GET /sandboxes/:id/files/list?path=`. */
1181
+ export interface DirListing {
1182
+ sandbox_id: string;
1183
+ path: string;
1184
+ entries: DirEntry[];
1185
+ /** More than 10,000 entries, or a listing too large for one exec: this is a prefix. */
1186
+ truncated: boolean;
1187
+ served_by?: ServedBy;
1188
+ }
1189
+ /** `?recursive=&depth=&limit=&hidden=` on `files/list`. */
1190
+ export interface DirListOpts {
1191
+ recursive?: boolean;
1192
+ /** With `recursive`: how many levels below `path`. Unset = no bound. */
1193
+ depth?: number;
1194
+ limit?: number;
1195
+ /** Dotfiles are listed by default; `false` omits them. */
1196
+ hidden?: boolean;
1197
+ }
1198
+ export interface FileInfo {
1199
+ sandbox_id: string;
1200
+ path: string;
1201
+ size: number;
1202
+ /** Octal, as a string — `"40755"` for a directory. */
1203
+ mode: string;
1204
+ mtime_unix_ns: number;
1205
+ is_dir: boolean;
1206
+ is_symlink: boolean;
1207
+ served_by?: ServedBy;
1208
+ }
1209
+ /** One hit of `GET /sandboxes/:id/files/search`. `line`/`text` only for a `contains` search. */
1210
+ export interface FileMatch {
1211
+ path: string;
1212
+ line?: number | null;
1213
+ text?: string | null;
1214
+ }
1215
+ export interface SearchResult {
1216
+ sandbox_id: string;
1217
+ path: string;
1218
+ matches: FileMatch[];
1219
+ /** `limit` was reached: this is a prefix of the hits, not all of them. */
1220
+ truncated: boolean;
1221
+ served_by?: ServedBy;
1222
+ }
1223
+ /** `pattern` is a name glob (`*.py`); `contains` is literal text looked for inside the files. */
1224
+ export interface SearchOpts {
1225
+ pattern?: string;
1226
+ contains?: string;
1227
+ ignoreCase?: boolean;
1228
+ limit?: number;
1229
+ hidden?: boolean;
1230
+ }
1231
+ /**
1232
+ * `POST /sandboxes/:id/files/archive` — where the tar landed **inside the sandbox** and how big
1233
+ * it is. The bytes stay in the sandbox on purpose; see `files.downloadDir`.
1234
+ */
1235
+ export interface ArchiveResult {
1236
+ sandbox_id: string;
1237
+ /** Where the tar landed inside the sandbox. */
1238
+ archive_path: string;
1239
+ /** Its size in bytes. */
1240
+ size: number;
1241
+ /** The directory that was packed. */
1242
+ source: string;
1243
+ served_by?: ServedBy;
1244
+ }
1245
+ /** `POST /sandboxes/:id/files/extract`. `entries` is `null` on the fallback path, which cannot
1246
+ * count them without a second pass — a number invented there would be worse than none. */
1247
+ export interface ExtractResult {
1248
+ sandbox_id: string;
1249
+ archive: string;
1250
+ into: string;
1251
+ entries: number | null;
1252
+ served_by?: ServedBy;
1253
+ }
1254
+ /**
1255
+ * A background exec (`POST /sandboxes/:id/execs {background: true}`) as the API reports it.
1256
+ * `exec_id` is scoped to its sandbox and only resolves under the sandbox that started it.
1257
+ */
1258
+ export interface BackgroundExecRecord {
1259
+ exec_id: string;
1260
+ sandbox_id: string;
1261
+ state: "running" | "exited" | "failed";
1262
+ /** The command as started (`sh` or `argv` joined). */
1263
+ argv: string;
1264
+ /** Started with `stdin: true`, so `execs.stdin` can write to it. A child's stdin cannot be
1265
+ * opened after it has been spawned, which is why this is a property of the start and not a
1266
+ * thing to try. */
1267
+ stdin_open?: boolean;
1268
+ started_unix_ns?: number;
1269
+ /** Bytes buffered (≤ `max_output_bytes` per stream) and bytes written in total. */
1270
+ stdout_bytes: number;
1271
+ stderr_bytes: number;
1272
+ stdout_total?: number;
1273
+ stderr_total?: number;
1274
+ truncated?: boolean;
1275
+ /** Started with `follow`: the newest output is kept, and the handle goes 10 minutes after it ends. */
1276
+ follow?: boolean;
1277
+ /** A range read of a `follow` command: bytes dropped between the asked offset and the first held. */
1278
+ skipped?: number;
1279
+ exit?: {
1280
+ exit_code: number | null;
1281
+ signal: number | null;
1282
+ wall_ms?: number;
1283
+ cpu_ms?: number;
1284
+ max_rss_kib?: number;
1285
+ truncated?: boolean;
1286
+ oom_killed?: boolean;
1287
+ };
1288
+ error?: {
1289
+ code: string;
1290
+ msg: string;
1291
+ };
1292
+ /** With `waitMs`: how long the API parked the request. */
1293
+ waited_ms?: number;
1294
+ /**
1295
+ * Present when the sandbox pauses itself when idle: after the command ends, the idle rule waits
1296
+ * up to this many seconds for its result to be read. `forget` the handle to release it at once.
1297
+ */
1298
+ idle_hold_s?: number;
1299
+ }
1300
+ /** A published port (`POST /sandboxes/:id/ports`). The `url` is live until revoked or killed. */
1301
+ /** What `POST /sandboxes/:id/ssh` answers. */
1302
+ export interface SshAccess {
1303
+ sandbox_id: string;
1304
+ key_id: string;
1305
+ fingerprint: string;
1306
+ /** The login name. It is the sandbox id: a key is authorised for one name only. */
1307
+ user: string;
1308
+ host: string;
1309
+ /** The access credential. Shown once, expires, reaches this sandbox and nothing else. */
1310
+ token: string;
1311
+ expires_at: string;
1312
+ idle_timeout_s: number;
1313
+ /** A `known_hosts` line body for this sandbox. Pin it. */
1314
+ host_key: string;
1315
+ host_key_fingerprint: string;
1316
+ proxy_command: string;
1317
+ ssh_command: string;
1318
+ }
1319
+ export interface SshKeyRecord {
1320
+ key_id: string;
1321
+ fingerprint: string;
1322
+ user: string;
1323
+ expires_at: string;
1324
+ created_at: string;
1325
+ token_active: boolean;
1326
+ }
1327
+ export interface SshAccessList {
1328
+ sandbox_id: string;
1329
+ keys: SshKeyRecord[];
1330
+ idle_timeout_s: number;
1331
+ host_key?: string;
1332
+ host_key_fingerprint?: string;
1333
+ }
1334
+ export interface SshRevoked {
1335
+ sandbox_id: string;
1336
+ key_id: string;
1337
+ revoked: boolean;
1338
+ }
1339
+ export interface PortRecord {
1340
+ sandbox_id: string;
1341
+ port: number;
1342
+ /** 256-bit capability; anyone with the URL reaches the port. */
1343
+ token: string;
1344
+ url: string;
1345
+ /** The same token as a DNS label, for a wildcard-DNS deployment (`<label>.<suffix>`). */
1346
+ host_url_label?: string;
1347
+ created_unix_ns?: number;
1348
+ }
1349
+ /** A key named without its secret (`/me`'s `key`, the key lists). */
1350
+ export interface KeyRef {
1351
+ key_id: string;
1352
+ name?: string;
1353
+ /** The first few characters, enough to recognise a key without holding it. */
1354
+ prefix?: string;
1355
+ /** A project's service key: it outlives its minter's membership. */
1356
+ service?: boolean;
1357
+ }
1358
+ export interface MeResponse {
1359
+ org_id?: string;
1360
+ /** The organization's name. */
1361
+ org?: string;
1362
+ /** The key this request was made with. */
1363
+ key?: KeyRef | null;
1364
+ user?: {
1365
+ user_id: string;
1366
+ email?: string;
1367
+ provider?: string;
1368
+ } | null;
1369
+ /**
1370
+ * The plan the organisation's limits and prices come from: `hobby` for a trial organisation
1371
+ * with no plan assigned. The name `tier_over_plan` and the other refusals use.
1372
+ */
1373
+ plan?: string;
1374
+ limits?: {
1375
+ max_inflight?: number;
1376
+ max_concurrency?: number;
1377
+ max_session_s?: number | null;
1378
+ max_sandbox_vcpus?: number | null;
1379
+ [extra: string]: number | null | undefined;
1380
+ };
1381
+ /**
1382
+ * Sandboxes holding capacity now (queued, starting, ready or running; paused ones hold none)
1383
+ * against the limit.
1384
+ */
1385
+ concurrency?: {
1386
+ limit?: number;
1387
+ in_use?: number;
1388
+ };
1389
+ /**
1390
+ * What the organisation has spent, in USD. Absent (with `spend_detail`, `trial` and `notice`)
1391
+ * for a key that may not see the organisation's money: only an owner's key of the default
1392
+ * project does.
1393
+ */
1394
+ spend?: number;
1395
+ spend_detail?: {
1396
+ total?: number;
1397
+ currency?: string;
1398
+ billable_seconds?: number;
1399
+ by_kind?: Record<string, number>;
1400
+ };
1401
+ /** The cap a run gets when it names none, in USD; `0` names none (the run is capped at its quote). */
1402
+ default_spend_cap?: number;
1403
+ card_on_file?: boolean;
1404
+ /** The free trial: credits, data out and the limits that apply until a card is on file. */
1405
+ trial?: TrialStatus;
1406
+ /** Something about the account to act on (the card-required switch), or null. */
1407
+ notice?: AccountNotice | null;
1408
+ [extra: string]: unknown;
1409
+ }
1410
+ /**
1411
+ * `Promigence-Notice` on any response, and `notice` on `me()` / `GET /billing`: an announced or
1412
+ * active card requirement for an org on the free trial. `code` is `card_required_soon` before
1413
+ * `effective_at`, `card_required` after it. Credits are never taken away.
1414
+ */
1415
+ export interface AccountNotice {
1416
+ code: "card_required_soon" | "card_required" | (string & {});
1417
+ effective_at?: string;
1418
+ credits_remaining?: number;
1419
+ message?: string;
1420
+ fix?: string;
1421
+ docs?: string;
1422
+ [extra: string]: unknown;
1423
+ }
1424
+ export interface TrialStatus {
1425
+ on_trial: boolean;
1426
+ card_on_file: boolean;
1427
+ credits_granted: number;
1428
+ credits_remaining: number;
1429
+ credits_reserved?: number;
1430
+ credits_available?: number;
1431
+ egress_gb_used: number;
1432
+ egress_gb_cap: number | null;
1433
+ limits?: {
1434
+ max_concurrency?: number | null;
1435
+ max_session_s?: number | null;
1436
+ egress_gb_month?: number | null;
1437
+ smtp?: boolean;
1438
+ };
1439
+ policy?: {
1440
+ mode: string;
1441
+ card_required_from?: string | null;
1442
+ notice_from?: string | null;
1443
+ card_required_now?: boolean;
1444
+ };
1445
+ [extra: string]: unknown;
1446
+ }
1447
+ /** The response header an account notice rides on. */
1448
+ export declare const NOTICE_HEADER = "promigence-notice";
1449
+ export interface PromigenceClientOpts {
1450
+ apiKey?: string;
1451
+ apiUrl?: string;
1452
+ /** Injected for tests; defaults to global `fetch`. An injected `fetch` carries every request. */
1453
+ fetch?: FetchLike;
1454
+ /**
1455
+ * Send API calls over one shared HTTP/2 connection when the API offers it (Node.js only;
1456
+ * default `true`). `false`, or `PROMIGENCE_HTTP2=0` in the environment, sends every request
1457
+ * through `fetch`. Anything HTTP/2 cannot do safely falls back to `fetch` on its own.
1458
+ */
1459
+ http2?: boolean;
1460
+ /**
1461
+ * Per-request timeout (default 30 s). Waits are bounded separately, by `waitTimeoutMs`, and a
1462
+ * wait is not ended by one request hitting this — see `waitLoop`.
1463
+ *
1464
+ * From the CLI: `promigence --request-timeout DUR` or `PROMIGENCE_REQUEST_TIMEOUT`.
1465
+ */
1466
+ requestTimeoutMs?: number;
1467
+ /**
1468
+ * How many times a request is sent again (default 3): after a 429 or a 503, and after a 502 or
1469
+ * 504 that is not the API's own answer when sending twice is harmless (a read, a delete, or a
1470
+ * create with its idempotency key).
1471
+ */
1472
+ maxRetries?: number;
1473
+ /** Backoff base when the server sends no `Retry-After` (default 250 ms). */
1474
+ retryBaseMs?: number;
1475
+ userAgent?: string;
1476
+ /**
1477
+ * The framework, harness or adapter driving this client: `"harbor"`, `"my-harness/2.1.0"`.
1478
+ * Highest precedence; see `setCaller` for the rest of the order, the validation
1479
+ * and why nothing is ever auto-detected.
1480
+ */
1481
+ caller?: string;
1482
+ /**
1483
+ * Called once per distinct account notice (`Promigence-Notice`), e.g. a card requirement that
1484
+ * starts on a date. Default: one `process.emitWarning` per distinct notice. The latest is also
1485
+ * on `client.notice`.
1486
+ */
1487
+ onNotice?: (notice: AccountNotice) => void;
1488
+ /**
1489
+ * Called with every answer's timing ([`Timing`]): the round trip this process saw and how long
1490
+ * the answer took on Promigence's side. A CLI uses it to say how far away the caller is.
1491
+ */
1492
+ onTiming?: (timing: Timing) => void;
1493
+ }
1494
+ /** `GET /sandboxes/:id/network`. */
1495
+ export interface SandboxNetwork {
1496
+ sandbox_id: string;
1497
+ /** What was asked for: `"egress"`, `"none"`, or `{ allow: [...] }`. */
1498
+ network: NetworkMode;
1499
+ state: string;
1500
+ /** Hosts this sandbox looked up and was refused, newest first. */
1501
+ refused_hosts?: {
1502
+ host: string;
1503
+ count?: number;
1504
+ last_unix_ns?: number;
1505
+ }[];
1506
+ /** The hosts this sandbox may reach for its host-bound secrets, whatever `network` says. */
1507
+ via_secret?: {
1508
+ secret: string;
1509
+ hosts: string[];
1510
+ }[];
1511
+ /** Total refused lookups, including hosts past the list above. */
1512
+ refused_lookups?: number;
1513
+ /** Connections refused to an address that was never resolved for an allowed host. */
1514
+ refused_connections?: number;
1515
+ }
1516
+ /** `POST /sandboxes/:id/network {network: "none"}`. */
1517
+ export interface SandboxNetworkCut {
1518
+ sandbox_id: string;
1519
+ network: {
1520
+ mode: "none";
1521
+ };
1522
+ /** Whether anything changed; `false` means egress was already cut. */
1523
+ applied: boolean;
1524
+ state: string;
1525
+ }
1526
+ /**
1527
+ * One stored secret, bound to the environment variable it arrives as.
1528
+ *
1529
+ * A plain string is the common case — the secret's name is also the variable's. The object form
1530
+ * is what makes a per-session credential possible: ten stored secrets, ten sandboxes, **one**
1531
+ * variable name, at the same time.
1532
+ *
1533
+ * ```ts
1534
+ * secrets: ["OPENAI_API_KEY", { name: "work_secret_7", as: "ANTHROPIC_WORK_SECRET" }]
1535
+ * ```
1536
+ *
1537
+ * A secret bound to hosts (`secrets.set(name, value, { hosts })`) does not put its value in the
1538
+ * sandbox; `mode: "raw"` asks for the value itself, and is refused for one stored with `raw: "refused"`.
1539
+ */
1540
+ export type SecretBinding = string | {
1541
+ name: string;
1542
+ as?: string;
1543
+ mode?: "raw";
1544
+ };
1545
+ export interface RunStartOpts {
1546
+ snapshot: string;
1547
+ count: number;
1548
+ timeoutMs?: number;
1549
+ spendCap?: number;
1550
+ allowPartial?: boolean;
1551
+ verifyEach?: boolean;
1552
+ network?: NetworkMode;
1553
+ optimizeFor?: OptimizeFor;
1554
+ /**
1555
+ * Secrets for every sandbox of this run. An `exec` is no longer required: a run that names
1556
+ * its secrets here gives them to every command run against every one of its sandboxes,
1557
+ * foreground or background, so a long-lived credentialed worker does not have to hold a
1558
+ * streaming exec open for its whole life.
1559
+ */
1560
+ secrets?: SecretBinding[];
1561
+ labels?: Record<string, string>;
1562
+ /** Terminal exec: run in every sandbox after hand-over; `killOnExit` ends the sandbox. */
1563
+ exec?: {
1564
+ argv?: string[];
1565
+ sh?: string;
1566
+ cwd?: string;
1567
+ env?: Record<string, string>;
1568
+ killOnExit?: boolean;
1569
+ timeoutMs?: number;
1570
+ maxOutputBytes?: number;
1571
+ };
1572
+ /**
1573
+ * Score the work after the episode, in a clean copy of the snapshot. Needs an `exec` — there
1574
+ * is no episode to grade without a command. A snapshot may carry a default grader, in which
1575
+ * case this can be left off.
1576
+ */
1577
+ grade?: {
1578
+ cmd: string;
1579
+ holdoutCmd?: string;
1580
+ timeoutMs?: number;
1581
+ score?: ScoreMode;
1582
+ };
1583
+ /**
1584
+ * Keep what the grader prints: its last 16 KiB come back in each graded sandbox's
1585
+ * `grade.output` in the run report. Never a held-out grader's. Unset: the snapshot's default
1586
+ * grader's own setting when the run uses it, otherwise off.
1587
+ */
1588
+ gradeOutput?: boolean;
1589
+ idempotencyKey?: string;
1590
+ /**
1591
+ * Abort the request. Whether the run was started is then unknown: send the same create again
1592
+ * under the same `idempotencyKey` to get its answer (the API never starts it twice), then end it.
1593
+ */
1594
+ signal?: AbortSignal;
1595
+ /**
1596
+ * Hold `POST /runs` until every sandbox has left `queued`, for at most this long
1597
+ * (`?wait_ms=`, capped at 30 s). The answer's `sandboxes[]` then already carries `ready` rows,
1598
+ * so creating a sandbox is one round trip instead of two. A row still `queued` in the answer
1599
+ * (the budget was spent) is the caller's cue to fall back to `runs.waitReady`.
1600
+ */
1601
+ waitMs?: number;
1602
+ }
1603
+ export interface ExecOpts {
1604
+ argv?: string[];
1605
+ sh?: string;
1606
+ cwd?: string;
1607
+ env?: Record<string, string>;
1608
+ user?: string;
1609
+ /**
1610
+ * Stored secrets to deliver as environment variables. Names, never values.
1611
+ *
1612
+ * A string binds a secret to a variable of the same name; `{ name, as }` binds it to a
1613
+ * different one, which is how two sandboxes hold two values for one variable at once.
1614
+ */
1615
+ secrets?: SecretBinding[];
1616
+ /**
1617
+ * Cancel an in-flight exec, streamed or not: the client stops waiting and the request is closed,
1618
+ * which ends the command in the sandbox. A buffered exec then throws `TimeoutError` ("aborted by
1619
+ * the caller's signal"); a streamed one rejects with the signal's `AbortError`.
1620
+ */
1621
+ signal?: AbortSignal;
1622
+ /**
1623
+ * Close the sandbox on this command's verdict, exactly as `POST /runs {exec}` does.
1624
+ *
1625
+ * This is what `promigence run --stream` uses: the streaming composite has to own the exec (you
1626
+ * cannot attach to a command the scheduler is already running), and without it the sandbox
1627
+ * would be closed by the client's kill and reported `aborted_by_client` — a green run turned
1628
+ * red by the act of watching it.
1629
+ */
1630
+ terminal?: boolean;
1631
+ /** With `terminal`, kill the sandbox when the command exits. Defaults to `terminal`. */
1632
+ killOnExit?: boolean;
1633
+ timeoutMs?: number;
1634
+ /**
1635
+ * Output kept per stream, default 64 KiB. Past it the answer keeps the last bytes and says
1636
+ * `truncated`; the rest cannot be fetched afterwards. A background exec keeps all of it.
1637
+ */
1638
+ maxOutputBytes?: number;
1639
+ /**
1640
+ * `"base64"`: the answer carries the output as its exact bytes (`stdout_b64` / `stderr_b64`,
1641
+ * read them with `execOutputBytes`), for output that is not text. `exec` only: `execStream`
1642
+ * already hands over bytes.
1643
+ */
1644
+ encoding?: "text" | "base64";
1645
+ /**
1646
+ * Sent as `Idempotency-Key` only when you pass one; the SDK never makes one up for a command.
1647
+ * The API runs a buffered or background command once per key (24 h, per org) and answers a
1648
+ * repeat with the first answer (at most 1 MiB of output kept for it; a longer one comes back cut,
1649
+ * `replay_truncated: true`). A streamed command (`execStream`) cannot be replayed and refuses a
1650
+ * key. The SDK itself never sends an exec that may have started a second time.
1651
+ */
1652
+ idempotencyKey?: string;
1653
+ }
1654
+ /** `verify: "bun typecheck"` is shorthand for `{ cmd: "bun typecheck" }`. */
1655
+ export type VerifySpec = string | {
1656
+ cmd: string;
1657
+ allowFast?: boolean;
1658
+ };
1659
+ /** `verify` is optional for the image forms: without it the snapshot is stored `verified: false`. */
1660
+ /**
1661
+ * One **additional** pinned repository a world is built from, cloned before `setup` runs.
1662
+ *
1663
+ * `path` is optional and defaults to the repository's own name beside the workspace
1664
+ * (`https://github.com/org/lib` → `/work/lib`). A relative path is relative to the workspace
1665
+ * (`vendor/lib` → `/work/repo/vendor/lib`), which is what an app-plus-library layout wants.
1666
+ */
1667
+ export interface ExtraSource {
1668
+ repo: string;
1669
+ /** 40 hex. A branch is not a pin. */
1670
+ commit: string;
1671
+ path?: string;
1672
+ }
1673
+ /** The most extra sources one snapshot may name. */
1674
+ export declare const MAX_SOURCES = 8;
1675
+ export interface SnapshotCreateOpts {
1676
+ /**
1677
+ * A public git URL (with `commit`), or `github:owner/name`: a repository the organization's
1678
+ * GitHub connection can read (`github.repos()`), built at `ref` (default: its default branch).
1679
+ */
1680
+ repo?: string;
1681
+ commit?: string;
1682
+ /** `github:` repositories: a branch, a tag or a sha, resolved to a commit when the build is accepted. */
1683
+ ref?: string;
1684
+ /** `github:` repositories: the snapshot the build starts from (id or alias; default `promigence/base`). */
1685
+ from?: string;
1686
+ /**
1687
+ * `github:` repositories with a branch and an `alias`: every push to the branch builds the
1688
+ * snapshot again and moves the alias to it. Each automatic build is a normal build, charged.
1689
+ */
1690
+ track?: boolean;
1691
+ /**
1692
+ * Extra repositories for the `repo` form, cloned in this order into their paths before
1693
+ * `setup` runs: a harness repo beside a task repo, an app beside the library being changed
1694
+ * with it. Each lands in the manifest's `provenance.sources[]` with its commit, which a repo
1695
+ * cloned inside a `setup` step never did — so a world built this way is rebuildable, and one
1696
+ * built the old way only looked it.
1697
+ */
1698
+ sources?: ExtraSource[];
1699
+ image?: string;
1700
+ dockerfile?: {
1701
+ context: string;
1702
+ path?: string;
1703
+ };
1704
+ setup?: string[];
1705
+ verify?: VerifySpec;
1706
+ /** Image forms: a background process started before the snapshot, so forks inherit it running. */
1707
+ start?: string | string[];
1708
+ /**
1709
+ * Image and Dockerfile forms: environment variables for every command, terminal and SSH
1710
+ * session in this snapshot's sandboxes (and its own setup, verify and start), set over the
1711
+ * image's `ENV` as `docker run -e` sets them. A value may build on what the image sets with
1712
+ * `${containerEnv:NAME}` (or `${containerEnv:NAME:default}`):
1713
+ * `{ PATH: "${containerEnv:PATH}:/opt/tools/bin" }`.
1714
+ */
1715
+ env?: Record<string, string>;
1716
+ tier?: Tier;
1717
+ /** Image forms: the name of a stored secret holding the registry credentials. */
1718
+ registrySecret?: string;
1719
+ /**
1720
+ * Image and Dockerfile forms: the user commands, terminals and SSH sessions in its sandboxes
1721
+ * run as when they name none. A user name or uid, with an optional `:group` or `:gid`, as an
1722
+ * image's `USER` takes it; `root` for root. Default: the image's own `USER`, as `docker run`
1723
+ * does (root when it sets none).
1724
+ */
1725
+ user?: string;
1726
+ /**
1727
+ * Image and Dockerfile forms: the user its `start`, `setup` and `verify` run as (for example
1728
+ * the snapshot's own `user`). Default: `root`.
1729
+ */
1730
+ setupUser?: string;
1731
+ alias?: string;
1732
+ /**
1733
+ * A DEFAULT grader for this snapshot: a run on it with no `grade` of its own uses this one.
1734
+ * The same shape as `runs.start({ grade })`.
1735
+ */
1736
+ grade?: {
1737
+ cmd: string;
1738
+ holdoutCmd?: string;
1739
+ timeoutMs?: number;
1740
+ score?: ScoreMode;
1741
+ };
1742
+ /** Runs that use this default grader keep what it prints (`runs.start({ gradeOutput })`). Needs `grade`. */
1743
+ gradeOutput?: boolean;
1744
+ idempotencyKey?: string;
1745
+ }
1746
+ export interface WaitOpts {
1747
+ /** Bound on the wait (default 100 s). */
1748
+ waitTimeoutMs?: number;
1749
+ /**
1750
+ * Force a fixed client-side poll interval. Setting this opts out of the server-side long-poll
1751
+ * (`?wait_ms=`) entirely — use it only to pin a cadence in a test.
1752
+ */
1753
+ pollMs?: number;
1754
+ signal?: AbortSignal;
1755
+ /**
1756
+ * Fires on every read with the run's `capacity` block, so a caller can render a waiting state
1757
+ * that **moves** ("capacity arriving, ~28 s" -> running) instead of a
1758
+ * spinner over a word that never changes.
1759
+ *
1760
+ * Passing it caps each server-side park at `PROGRESS_POLL_MS`, exactly like `onReady`: the park
1761
+ * only ends when *every* sandbox is ready, and the point of this callback is the time before
1762
+ * that.
1763
+ */
1764
+ onCapacity?: (c: Capacity) => void;
1765
+ }
1766
+ /** Per-request knobs the wait helpers pass down to `runs.get` / `runs.sandboxes`. */
1767
+ export interface ReadOpts {
1768
+ /**
1769
+ * Long-poll: ask the API to hold the request until the run reaches the endpoint's condition
1770
+ * (all sandboxes out of `queued` / run finished) or `waitMs` elapse. A server that ignores the
1771
+ * parameter answers immediately — see `waitReady`'s fallback.
1772
+ */
1773
+ waitMs?: number;
1774
+ onHeaders?: (headers: Headers) => void;
1775
+ signal?: AbortSignal;
1776
+ }
1777
+ export interface WaitReadyResult {
1778
+ sandboxes: SandboxRef[];
1779
+ timedOut: boolean;
1780
+ waitedMs: number;
1781
+ }
1782
+ export interface WaitRunResult {
1783
+ run: RunRecord;
1784
+ timedOut: boolean;
1785
+ waitedMs: number;
1786
+ }
1787
+ export type { CredentialStore } from "./credstore.js";
1788
+ export interface Credentials {
1789
+ api_key?: string;
1790
+ api_url?: string;
1791
+ }
1792
+ export declare function configDir(env?: NodeJS.ProcessEnv): string;
1793
+ export declare function credentialsPath(env?: NodeJS.ProcessEnv): string;
1794
+ /**
1795
+ * Why the credentials file (or its directory) is not this user's alone, or `undefined` when it
1796
+ * is. The CLI refuses to WRITE into a directory another user owns; the readers refuse to trust
1797
+ * one, since a planted file names both the key and the URL every request goes to. Owned by
1798
+ * another user, or writable by others, is refused; group-writable only when the group is not
1799
+ * this user's own (a umask 002 system gives every user a private group, and a file the user made
1800
+ * by hand there must keep working). No POSIX owner (Windows): nothing to check.
1801
+ */
1802
+ export declare function credentialsTrustProblem(path: string): string | undefined;
1803
+ /**
1804
+ * Why the credentials FILE can be read by someone else, or `undefined` when only its owner can.
1805
+ * `promigence login` always writes it 0600, so a looser mode is
1806
+ * a file made or copied by hand. It is still used, as it always was — refusing would break a working
1807
+ * setup — but a secret others can read is named once, with the fix. No POSIX mode (Windows): nothing.
1808
+ */
1809
+ export declare function credentialsExposure(path: string): string | undefined;
1810
+ /**
1811
+ * The stored sign-in: JSON `{api_key, api_url}` (a bare key on the first line is accepted too) from
1812
+ * the credentials file, or from the OS keychain item `promigence login` stored when there is no
1813
+ * file. A file another user could have written is ignored, with one warning per process (see
1814
+ * `credentialsTrustProblem`).
1815
+ */
1816
+ export declare function readCredentials(env?: NodeJS.ProcessEnv): Credentials;
1817
+ export declare function apiUrlFromCompatUrl(url: string | undefined): string | undefined;
1818
+ export declare function apiUrlFromCompatDomain(domain: string | undefined): string | undefined;
1819
+ export declare function compatApiKey(key: string | undefined): string | undefined;
1820
+ /** Which variable a key came from, for `promigence doctor`. */
1821
+ export declare function keyOrigin(env?: NodeJS.ProcessEnv): string | undefined;
1822
+ /** `name@sha256:<64 hex>`, optionally with a tag before the `@`: a reference already pinned. */
1823
+ export declare const IMAGE_DIGEST_RE: RegExp;
1824
+ /**
1825
+ * Why `image` is not a reference the API can pin, or `undefined` when it is.
1826
+ *
1827
+ * The same checks, in the same order, as the API makes (and the Python SDK's
1828
+ * `image_ref_problem`), so a reference is refused here exactly when the server would refuse it
1829
+ * — and a tag is accepted, because the server pins it: `POST /snapshots` resolves `python:3.12`
1830
+ * to `python@sha256:…` itself.
1831
+ */
1832
+ export declare function imageRefProblem(image: unknown): string | undefined;
1833
+ /**
1834
+ * Refuse a malformed image reference before a round trip is spent on it. A tag is fine: the
1835
+ * server pins it to a digest when the snapshot is created.
1836
+ */
1837
+ export declare function checkImageRef(image: unknown): asserts image is string;
1838
+ /**
1839
+ * Whether a well-formed image reference names its version: a tag (`python:3.12`) or a digest
1840
+ * (`python@sha256:…`). `python` alone names neither, and is pinned to whatever `python:latest`
1841
+ * is on the day it is used.
1842
+ */
1843
+ export declare function imageRefNamesVersion(image: string): boolean;
1844
+ /**
1845
+ * Refuse an image reference with neither a tag nor a digest, which would silently build
1846
+ * whatever `:latest` is today. `:latest` written out is a choice and is accepted.
1847
+ */
1848
+ export declare function checkImageVersioned(image: string): void;
1849
+ /** A git context, pinned: `https://github.com/org/app@<40 hex>`. A branch is not a pin. */
1850
+ export declare const CONTEXT_GIT_RE: RegExp;
1851
+ /**
1852
+ * What in a repository URL can carry a credential: a user name, password or token (`https://TOKEN@…`,
1853
+ * `https://user:pass@…`, `:token@`, a percent-encoded `@`, any `@` in a scheme URL, the scp form's
1854
+ * `git@host:…`), a query string or a fragment. The server refuses every such URL with
1855
+ * `repo_credentials_not_supported`; this is the same rule, so the URL is refused before it
1856
+ * leaves this process. A public
1857
+ * `https://host/org/repo` is `undefined`.
1858
+ */
1859
+ export declare function repoUrlCredential(url: string): "userinfo" | "query" | "fragment" | undefined;
1860
+ /** `URL@<pin>` → `URL`; a last `@` followed by a path is not a pin, and the whole string is the URL. */
1861
+ export declare function withoutPin(s: string): string;
1862
+ export declare const REPO_CREDENTIALS_FIX: string;
1863
+ /**
1864
+ * Refuse a repository URL that carries a credential, naming `field` and never the URL — the
1865
+ * server's own answer, `repo_credentials_not_supported` (exit 2), raised before anything is sent.
1866
+ */
1867
+ export declare function checkRepoUrl(url: unknown, field: string): void;
1868
+ /** Refuse a context that is neither a pinned git URL nor a packed directory. */
1869
+ export declare function checkDockerfileContext(context: unknown): asserts context is string;
1870
+ /**
1871
+ * The `POST /snapshots` body for one of its three forms; throws `InvalidArgumentError` before
1872
+ * anything is sent. Kept field-for-field with `sdk-py` (`_ops.snapshot_create_body`) so the two
1873
+ * SDKs send the same bytes for the same call.
1874
+ */
1875
+ export declare function snapshotCreateBody(opts: SnapshotCreateOpts): Record<string, unknown>;
1876
+ /** Build the right `PromigenceError` subclass for an API error `{code, message, fix, docs}`. */
1877
+ export declare function errorFor(code: string, message: string, fix: string, extra?: {
1878
+ docs?: string;
1879
+ status?: number;
1880
+ requestId?: string;
1881
+ retryAfterMs?: number;
1882
+ category?: string;
1883
+ retryable?: boolean;
1884
+ }): PromigenceError;
1885
+ /**
1886
+ * Is `method path` a create that takes an `Idempotency-Key`? `path` is relative to `/v1` and may
1887
+ * carry a query string. The list is the API reference's (generated from the operations that
1888
+ * document the header), so a create the API keys is never sent unkeyed by this client.
1889
+ */
1890
+ export declare function isKeyedCreate(method: string, path: string): boolean;
1891
+ /** A sandbox id as a request names it: a non-empty string, or refused before anything is sent. */
1892
+ export declare function checkSandboxId(sandboxId: unknown, call: string): asserts sandboxId is string;
1893
+ /** A spend cap in USD: a finite number, 0 or more. A negative one used to be sent and echoed back as "$-1.00". */
1894
+ export declare function checkSpendCap(spendCap: number): number;
1895
+ /** A key for ONE logical create: made once, then sent on every attempt of that create. */
1896
+ export declare function newIdempotencyKey(): string;
1897
+ /** Budget handed to one long-poll request. Shorter than the 100 s bound so a dead connection,
1898
+ * a redeploy or a proxy idle-timeout costs one round trip, not the whole wait. */
1899
+ export declare const LONG_POLL_MS = 5000;
1900
+ interface RequestOpts {
1901
+ method: "GET" | "POST" | "DELETE" | "PUT" | "PATCH";
1902
+ path: string;
1903
+ body?: unknown;
1904
+ idempotencyKey?: string;
1905
+ timeoutMs?: number;
1906
+ signal?: AbortSignal;
1907
+ /** Skip the Authorization header (unauthenticated routes). */
1908
+ anonymous?: boolean;
1909
+ /** Called with the response headers of the attempt that answered (not of one that was retried). */
1910
+ onHeaders?: (headers: Headers) => void;
1911
+ /** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
1912
+ headers?: Record<string, string>;
1913
+ }
1914
+ /** One answer's timing ([`PromigenceClient.lastTiming`]). */
1915
+ export interface Timing {
1916
+ method: string;
1917
+ path: string;
1918
+ /** The round trip as the client saw it, in ms, until the answer's headers arrived. */
1919
+ totalMs: number;
1920
+ /** How long the answer took on Promigence's side, in ms; `null` when the service did not say. */
1921
+ ourMs: number | null;
1922
+ }
1923
+ /** The `app` span of a `Server-Timing` header (`app;dur=12.35`), in ms; `null` without one. */
1924
+ export declare function serverAppMs(header: string | null | undefined): number | null;
1925
+ export declare class PromigenceClient {
1926
+ #private;
1927
+ readonly apiUrl: string;
1928
+ private readonly fetchImpl;
1929
+ private readonly ownFetch;
1930
+ /** Set when control calls may travel over HTTP/2; each falls back to `fetchImpl` on its own. */
1931
+ private readonly h2;
1932
+ private readonly requestTimeoutMs;
1933
+ private readonly maxRetries;
1934
+ private readonly retryBaseMs;
1935
+ private readonly userAgent;
1936
+ /** See `PromigenceClientOpts.cliErrors`. */
1937
+ private readonly cliErrors;
1938
+ /**
1939
+ * The framework label this client sends (`promigence-caller`), or `undefined` when nobody set one.
1940
+ * Resolved once, at construction; see `setCaller`.
1941
+ */
1942
+ readonly caller: string | undefined;
1943
+ /** Requests attempted, retries performed — used by tests and `promigence doctor`. */
1944
+ readonly stats: {
1945
+ requests: number;
1946
+ retries: number;
1947
+ };
1948
+ /**
1949
+ * The most recent answer's timing: `totalMs` is the whole round trip as this process saw it,
1950
+ * until the answer's headers arrived; `ourMs` is how long the answer took on Promigence's side
1951
+ * (`null` when the service did not say). `totalMs − ourMs` is the network between you and us.
1952
+ */
1953
+ lastTiming: Timing | null;
1954
+ /** The latest account notice any response carried (`Promigence-Notice`), if one did. */
1955
+ notice: AccountNotice | undefined;
1956
+ private readonly onNotice;
1957
+ private readonly onTiming;
1958
+ private readonly noticesSeen;
1959
+ constructor(opts?: PromigenceClientOpts);
1960
+ /** Read `Promigence-Notice` off a response (its raw value); tell the caller once per distinct notice. */
1961
+ private observeNotice;
1962
+ /** The key this client sends, if one was resolved. Read on purpose; never printed. */
1963
+ get apiKey(): string | undefined;
1964
+ /** True when a key was resolved from opts / env / credentials file. */
1965
+ get authenticated(): boolean;
1966
+ /** What `JSON.stringify` shows: where the client points and whether it has a key — not the key. */
1967
+ toJSON(): {
1968
+ apiUrl: string;
1969
+ authenticated: boolean;
1970
+ };
1971
+ /** What `console.log` / `util.inspect` show: the same as `toJSON`. */
1972
+ [inspect.custom](_depth: number, opts: InspectOptions): string;
1973
+ /**
1974
+ * Why no key was used although one was found: it belongs to another API URL than this
1975
+ * client's, or it holds a character a header cannot carry. `undefined` when the client has a
1976
+ * key, or when none was found at all.
1977
+ */
1978
+ get credentialsProblem(): PromigenceError | undefined;
1979
+ /**
1980
+ * Start connecting to the API now, so the first request does not wait for the connection.
1981
+ *
1982
+ * Never throws and never waits for the network: the one-time TLS setup runs here, and the
1983
+ * address lookup and the handshake go on in the background. Nothing is sent: the first request
1984
+ * is still the next call you make, and it uses this connection when it is ready (or waits for it
1985
+ * to finish opening). Calling it again, or from other clients for the same API, opens nothing
1986
+ * more. An unused connection closes after a few seconds and does not keep the process alive;
1987
+ * the next request then connects as usual. Does nothing where requests do not travel over the
1988
+ * SDK's own connection: an injected `fetch`, `http2: false`, a proxy, or a runtime other than
1989
+ * Node.js.
1990
+ */
1991
+ preconnect(): void;
1992
+ private requireKey;
1993
+ private bearer;
1994
+ /**
1995
+ * How long to wait before sending a refused request again, or `undefined` to give up — the
1996
+ * rule `request()` applies, for the calls that cannot go through it (byte transfers, the
1997
+ * opening of an exec stream, the terminal handshake). Retried: a 429, and a 503 that says
1998
+ * nothing started — any 503 for a request safe to send twice, else only one carrying a
1999
+ * `Retry-After` (every refusal at the door sets one) or a refusal code — and, for a request
2000
+ * safe to send twice, a 502 or 504 that is not the API's own answer (it carries no error
2001
+ * code). At most `maxRetries` times, and never a `Retry-After` longer than
2002
+ * MAX_RETRY_SLEEP_MS: that one is the caller's.
2003
+ */
2004
+ private refusalBackoff;
2005
+ /** A long-poll request must outlive its own `wait_ms`; everything else keeps the default. */
2006
+ private readExtras;
2007
+ private guardDispatcher;
2008
+ private dispatcherFor;
2009
+ request<T>(opts: RequestOpts): Promise<T>;
2010
+ /**
2011
+ * A request whose body — in either direction — is **bytes, not JSON** (`files get|put`).
2012
+ *
2013
+ * `request()` does `await res.text()`, which is exactly the buffering-and-decoding an eval
2014
+ * artefact must not go through: it would corrupt binary and cost a second copy of a file that
2015
+ * can be gigabytes. This reads the body as an `ArrayBuffer` and hands it back untouched.
2016
+ * Errors still arrive as JSON, so a non-2xx answer is decoded and mapped the same way.
2017
+ */
2018
+ private bytes;
2019
+ /**
2020
+ * `GET` a byte body **without holding it**: each chunk goes to `onChunk` as it arrives, and
2021
+ * the next is not read until `onChunk` has finished with it, so a 10 GiB artefact costs one
2022
+ * chunk of memory. Errors arrive
2023
+ * as JSON and are mapped exactly as [`bytes`] maps them; a door refusal is sent again.
2024
+ */
2025
+ private stream;
2026
+ /**
2027
+ * `GET /health` — no key required, which is the point: `promigence doctor` must be able to tell
2028
+ * "the API is down" from "your key is wrong", and a route that needs a key cannot.
2029
+ */
2030
+ health(opts?: {
2031
+ signal?: AbortSignal;
2032
+ timeoutMs?: number;
2033
+ }): Promise<HealthResponse>;
2034
+ /** `GET /status` — the public service view: the synthetic check window and one row per service. */
2035
+ status(opts?: {
2036
+ signal?: AbortSignal;
2037
+ timeoutMs?: number;
2038
+ }): Promise<StatusResponse>;
2039
+ /**
2040
+ * `GET /pricing` — your organization's price list, live: the per-second rates, every sandbox
2041
+ * shape on sale and its price (a snapshot build bills at its tier's), the limits, the free credit and the plan they
2042
+ * come from, read on every call from what billing charges. Needs a key: without one this throws
2043
+ * `AuthError` and sends nothing. The published list prices are on the pricing page.
2044
+ *
2045
+ * Prices change: read them when you need them rather than keeping a copy.
2046
+ *
2047
+ * @param opts.newAccount No longer supported: there is no key-less price list. Passing `true`
2048
+ * throws `InvalidArgumentError` and sends nothing.
2049
+ */
2050
+ pricing(opts?: {
2051
+ newAccount?: boolean;
2052
+ signal?: AbortSignal;
2053
+ timeoutMs?: number;
2054
+ }): Promise<PricingView>;
2055
+ /**
2056
+ * The templates: ready-made starting points to fork. Neither call needs a key.
2057
+ * `list()` — every template, each with the snapshot to fork, its version and a count of what it
2058
+ * has installed. `get(name)` — one template with everything it has installed (each package and
2059
+ * tool with its version) and every version with what changed.
2060
+ */
2061
+ readonly templates: {
2062
+ list: (opts?: {
2063
+ signal?: AbortSignal;
2064
+ }) => Promise<M.TemplateSummary[]>;
2065
+ get: (name: string, opts?: {
2066
+ signal?: AbortSignal;
2067
+ }) => Promise<M.TemplateDetail>;
2068
+ };
2069
+ me(opts?: {
2070
+ signal?: AbortSignal;
2071
+ }): Promise<MeResponse>;
2072
+ /**
2073
+ * `GET /me/outcomes?period=month` — what this organization (with a project's key: that project) got
2074
+ * this month, measured from its own runs and sandboxes. A figure with nothing behind it is `null`.
2075
+ */
2076
+ outcomes(opts?: {
2077
+ signal?: AbortSignal;
2078
+ }): Promise<Outcomes>;
2079
+ readonly billing: {
2080
+ /** `GET /billing` — plan, card, period, usage, trial, upcoming invoice. */
2081
+ usage: () => Promise<BillingUsage>;
2082
+ /** `GET /me/ledger?limit=N` — the metered rows the month's total is the sum of (one page). */
2083
+ ledger: (limit?: number) => Promise<LedgerEntry[]>;
2084
+ /**
2085
+ * The same rows with the page kept: feed `nextCursor` back as `cursor` until it is `null`.
2086
+ * A server that issues no cursor answers one page with `nextCursor: null`.
2087
+ */
2088
+ ledgerPage: (opts?: {
2089
+ limit?: number;
2090
+ cursor?: string;
2091
+ }) => Promise<Page<LedgerEntry>>;
2092
+ /**
2093
+ * `GET /billing/history` — the credits the organization received and its payments, newest
2094
+ * first (one page). `kind` narrows it to `credits` or `payments`; feed `nextCursor` back as
2095
+ * `cursor` until it is `null`. An owner's key.
2096
+ */
2097
+ historyPage: (opts?: {
2098
+ kind?: BillingHistoryKind;
2099
+ limit?: number;
2100
+ cursor?: string;
2101
+ }) => Promise<Page<BillingHistoryEntry>>;
2102
+ /** `POST /billing/checkout` — the hosted page that puts a card on file. */
2103
+ checkout: () => Promise<{
2104
+ url: string;
2105
+ session_id?: string;
2106
+ expires_at?: string;
2107
+ }>;
2108
+ /** `POST /billing/portal` — the hosted page for an existing card and past invoices. */
2109
+ portal: () => Promise<{
2110
+ url: string;
2111
+ }>;
2112
+ /** `GET /referrals` — your code and its offer, your referrals by status, what they earned. */
2113
+ referrals: () => Promise<ReferralStatus>;
2114
+ /** `POST /referrals/apply` — apply a referral code (an owner's key, before the first paid invoice). */
2115
+ applyReferral: (code: string) => Promise<ReferralApplied>;
2116
+ };
2117
+ /**
2118
+ * This key's API keys. With an API key the list is the key's own project's; `all` includes
2119
+ * revoked ones.
2120
+ */
2121
+ readonly keys: {
2122
+ /** `GET /keys[?all=1]`. */
2123
+ list: (opts?: {
2124
+ all?: boolean;
2125
+ }) => Promise<KeyRecord[]>;
2126
+ /**
2127
+ * `POST /keys` — a new key in this key's project. `api_key` in the answer is the only time
2128
+ * the secret is shown. A key minted by a key is revoked when that key is; a service key
2129
+ * cannot mint.
2130
+ */
2131
+ create: (opts?: {
2132
+ name?: string;
2133
+ } & KeyExpiry) => Promise<KeyRecord>;
2134
+ /**
2135
+ * `DELETE /keys/:id` — revoke a key and every key it minted (`also_revoked` lists them).
2136
+ * Revoking a key in another project needs that project's own key or a signed-in owner.
2137
+ */
2138
+ revoke: (keyId: string) => Promise<KeyRecord>;
2139
+ };
2140
+ /** The organization this key belongs to, its projects, members and pending invites. */
2141
+ readonly orgs: {
2142
+ /** `GET /orgs` — with an API key, the key's own organization. */
2143
+ list: () => Promise<OrgRecord[]>;
2144
+ /** `POST /orgs/:org/default` — make it the organization a sign-in and the CLI use when none is named. */
2145
+ setDefault: (org: string) => Promise<OrgRecord>;
2146
+ /**
2147
+ * `POST /orgs/:org/keys` — mint a key in this key's organization. With an API key the new key
2148
+ * stays in this key's project (`project` may name only that one); a service key, or a key in
2149
+ * another project, needs a signed-in owner.
2150
+ */
2151
+ createKey: (org: string, opts?: {
2152
+ name?: string;
2153
+ project?: string;
2154
+ } & KeyExpiry) => Promise<OrgKeyRecord>;
2155
+ /** `GET /orgs/:org/members` — who belongs and their roles. */
2156
+ members: (org: string) => Promise<MemberRecord[]>;
2157
+ /** `GET /orgs/:org/invites` — invites nobody has accepted yet (gone after 14 days). */
2158
+ invites: (org: string) => Promise<InviteRecord[]>;
2159
+ /**
2160
+ * `DELETE /orgs/:org/members/me` — leave the organization. The keys you minted for yourself
2161
+ * there are revoked; the last owner cannot leave, and nobody can leave their only organization.
2162
+ */
2163
+ leave: (org: string) => Promise<{
2164
+ org_id: string;
2165
+ user_id: string;
2166
+ removed: boolean;
2167
+ keys_revoked: number;
2168
+ }>;
2169
+ projects: {
2170
+ /** `GET /orgs/:org/projects`. */
2171
+ list: (org: string) => Promise<ProjectRecord[]>;
2172
+ /** `POST /orgs/:org/projects` — `name`: 1-40 lowercase letters, digits and dashes; not `default`. */
2173
+ create: (org: string, name: string) => Promise<ProjectRecord>;
2174
+ /** `PATCH /orgs/:org/projects/:project {name}` — rename it (the `default` project keeps its name). */
2175
+ rename: (org: string, project: string, name: string) => Promise<ProjectRecord>;
2176
+ };
2177
+ };
2178
+ readonly credits: {
2179
+ /** `GET /credits/offer` — claimable (how much, until when), claimed, being checked, or none. */
2180
+ offer: () => Promise<CreditOffer>;
2181
+ };
2182
+ /**
2183
+ * The GitHub connection: install the platform's GitHub App once, then build snapshots from the
2184
+ * repositories it can read (`snapshots.create({ repo: "github:owner/name", ref })`).
2185
+ */
2186
+ readonly github: {
2187
+ /** `GET /integrations/github` — connections, installs not confirmed yet, and tracked branches. */
2188
+ status: () => Promise<GithubStatus>;
2189
+ /** `POST /integrations/github/connect` — the install link (15 minutes); open it in a browser. */
2190
+ connect: () => Promise<GithubConnect>;
2191
+ /**
2192
+ * `POST /integrations/github/requests` — a connection started for you in the console: open
2193
+ * `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
2194
+ * it here: use `connect()`).
2195
+ */
2196
+ ticket: () => Promise<ConnectTicket>;
2197
+ /** `GET /integrations/github/requests/:id` — one of your connection requests: `pending`, or how it ended. */
2198
+ request: (requestId: string) => Promise<ConnectRequest>;
2199
+ /** `DELETE /integrations/github/installations/:id` — nothing is built from that account any more. */
2200
+ disconnect: (installationId: number | string) => Promise<GithubDisconnected>;
2201
+ /** `GET /integrations/github/repos` — one page (at most 100) across every connection. */
2202
+ repos: (opts?: {
2203
+ page?: number;
2204
+ perPage?: number;
2205
+ }) => Promise<GithubRepos>;
2206
+ /** `DELETE /integrations/github/tracks/:id` — the snapshot stays; pushes stop rebuilding it. */
2207
+ untrack: (trackId: string) => Promise<GithubUntracked>;
2208
+ };
2209
+ /**
2210
+ * The GitLab connection: authorize the platform's GitLab application once (read-only), have an
2211
+ * owner confirm it, then build snapshots from the projects it can read
2212
+ * (`snapshots.create({ repo: "gitlab:group/project", ref })`).
2213
+ */
2214
+ readonly gitlab: {
2215
+ /** `GET /integrations/gitlab` — connections, authorizations waiting to be confirmed, tracked branches. */
2216
+ status: () => Promise<GitlabStatus>;
2217
+ /** `POST /integrations/gitlab/connect` — the authorize link (15 minutes); open it in a browser. */
2218
+ connect: () => Promise<GitlabConnect>;
2219
+ /**
2220
+ * `POST /integrations/gitlab/requests` — a connection started for you in the console: open
2221
+ * `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
2222
+ * it here: use `connect()`).
2223
+ */
2224
+ ticket: () => Promise<ConnectTicket>;
2225
+ /** `GET /integrations/gitlab/requests/:id` — one of your connection requests: `pending`, or how it ended. */
2226
+ request: (requestId: string) => Promise<ConnectRequest>;
2227
+ /** `DELETE /integrations/gitlab/connections/:id` — revoked on GitLab; nothing is built through it any more. */
2228
+ disconnect: (connectionId: string) => Promise<GitlabDisconnected>;
2229
+ /** `GET /integrations/gitlab/repos` — one page (at most 100) of one connection's projects. */
2230
+ repos: (opts?: {
2231
+ page?: number;
2232
+ perPage?: number;
2233
+ connectionId?: string;
2234
+ }) => Promise<GitlabRepos>;
2235
+ /** `DELETE /integrations/gitlab/tracks/:id` — the snapshot stays; new commits stop rebuilding it. */
2236
+ untrack: (trackId: string) => Promise<GitlabUntracked>;
2237
+ };
2238
+ readonly runs: {
2239
+ /** `POST /runs` — returns immediately; sandboxes start `queued`. */
2240
+ start: (opts: RunStartOpts) => Promise<RunStartResponse>;
2241
+ /** `GET /runs/:id`, optionally long-polled with `{ waitMs }`. */
2242
+ get: (runId: string, opts?: ReadOpts) => Promise<RunRecord>;
2243
+ /** `GET /runs?limit=N` — this org's runs, newest first (one page). */
2244
+ list: (limit?: number) => Promise<RunRecord[]>;
2245
+ /** The same route with `nextCursor` kept: feed it back as `cursor` until it is `null`. */
2246
+ listPage: (opts?: {
2247
+ limit?: number;
2248
+ cursor?: string;
2249
+ }) => Promise<Page<RunRecord>>;
2250
+ /** Alias of `get`. */
2251
+ status: (runId: string, opts?: ReadOpts) => Promise<RunRecord>;
2252
+ /** `GET /runs/:id/sandboxes` — array or `{sandboxes:[…]}`, normalised. */
2253
+ sandboxes: (runId: string, opts?: ReadOpts) => Promise<SandboxRef[]>;
2254
+ /**
2255
+ * The same read, keeping the `capacity` block the API attaches.
2256
+ *
2257
+ * A server that predates the block answers without it, and this returns `capacity:
2258
+ * undefined` — a client must degrade to the old "queued" rendering rather than showing an
2259
+ * empty progress line.
2260
+ */
2261
+ sandboxesWithCapacity: (runId: string, opts?: ReadOpts) => Promise<{
2262
+ sandboxes: SandboxRef[];
2263
+ capacity?: Capacity;
2264
+ }>;
2265
+ /** `GET /runs/:id/report` — the validity report. */
2266
+ report: (runId: string) => Promise<RunReport>;
2267
+ /** `POST /runs/:id/cancel` — stop queued starts. */
2268
+ cancel: (runId: string) => Promise<unknown>;
2269
+ /** `DELETE /runs/:id` — kill every sandbox of the run. */
2270
+ kill: (runId: string) => Promise<unknown>;
2271
+ /**
2272
+ * `POST /runs/quote` — what the run will cost, before it starts.
2273
+ *
2274
+ * The only pricing promise in the pitch, and it has no side effects: no build, no fork, no
2275
+ * row, no charge. Prices what `runs.start` would charge, at the tier the snapshot was
2276
+ * verified at. `warnings` is the part a caller must not skip — a timeout over 15 minutes is
2277
+ * a multiplier, not an error.
2278
+ */
2279
+ quote: (opts: Pick<RunStartOpts, "snapshot" | "count" | "timeoutMs"> & {
2280
+ tier?: string;
2281
+ spendCap?: number;
2282
+ }) => Promise<RunQuote>;
2283
+ /**
2284
+ * `POST /runs/:id/replay` — re-run the environment failures of a run.
2285
+ *
2286
+ * Only `env_failed` sandboxes are replayable (they are never auto-retried after hand-over;
2287
+ * a replay is a new sandbox, billed like any other), the new rows carry `replay_of`, and they are held out of
2288
+ * `first_attempt_success_pct` / `final_success_pct` and reported under `fork.replay`. A
2289
+ * replay never spends past what is left of `min(quote.max_cost, spend_cap)`. A command that
2290
+ * was started with environment variables needs a value for each again (`opts.env`).
2291
+ */
2292
+ replay: (runId: string, sandboxIds?: string[], opts?: {
2293
+ env?: Record<string, string>;
2294
+ }) => Promise<ReplayResponse>;
2295
+ captures: (runId: string) => Promise<CapturesResponse>;
2296
+ /**
2297
+ * `POST /runs/:id/open` — reopen a failure as a LIVE sandbox, at the moment it broke.
2298
+ *
2299
+ * **Not `replay`.** A replay re-runs the work from the snapshot the run started from; this
2300
+ * forks the state the sandbox was in when it failed — the half-written file, the dead
2301
+ * process, the database mid-migration — and hands it back running. What comes back is a new
2302
+ * sandbox with its own id and its own billing clock, so it costs what a fork costs, and a
2303
+ * finished run whose budget is spent needs a `spendCap` for the reopening.
2304
+ */
2305
+ open: (runId: string, opts?: {
2306
+ sandboxId?: string;
2307
+ spendCap?: number;
2308
+ timeoutMs?: number;
2309
+ }) => Promise<OpenResponse>;
2310
+ /**
2311
+ * Wait until every sandbox of the run has left `queued`.
2312
+ *
2313
+ * Served by the API's `?wait_ms=` long-poll, so a run that is ready is observed when it is
2314
+ * ready rather than on the client's next poll tick. `onReady` fires once per sandbox, in the
2315
+ * order they become ready; because the server parks until *all*
2316
+ * of them are ready, a caller that wants per-sandbox progress caps each park at
2317
+ * `PROGRESS_POLL_MS` so partial readiness still streams at (at least) the old cadence.
2318
+ */
2319
+ waitReady: (runId: string, opts?: WaitOpts & {
2320
+ onReady?: (s: SandboxRef) => void;
2321
+ }) => Promise<WaitReadyResult>;
2322
+ /**
2323
+ * Wait until the run's work is over: the run reached a terminal state, or every sandbox has
2324
+ * ended or outlived its command (`killOnExit: false`, `command_outcome` set). Long-polled.
2325
+ *
2326
+ * A kept sandbox does not end when its command does — it stays live until it is killed or
2327
+ * expires — so waiting for it to END waited out its whole timeout.
2328
+ */
2329
+ waitDone: (runId: string, opts?: WaitOpts) => Promise<WaitRunResult>;
2330
+ };
2331
+ /**
2332
+ * The loop behind `waitReady` / `waitDone`.
2333
+ *
2334
+ * Each iteration hands the API a `wait_ms` budget and lets *it* hold the request until the
2335
+ * state we are blocked on arrives, so the client adds no poll interval of its own to the wait.
2336
+ * A server that ignores `wait_ms` answers immediately without the `promigence-waited-ms` marker;
2337
+ * after two such answers we stop asking and fall back to the adaptive ladder below, which is
2338
+ * also what an explicit `pollMs` selects up front.
2339
+ *
2340
+ * Bounded by `waitTimeoutMs` (default 100 s) exactly as before: on expiry the caller still gets
2341
+ * `timedOut: true` with the last body it saw, and nothing is killed.
2342
+ */
2343
+ private waitLoop;
2344
+ readonly sandboxes: {
2345
+ /** `POST /sandboxes/:id/execs` — foreground exec, inline output up to `maxOutputBytes`. */
2346
+ exec: (sandboxId: string, opts: ExecOpts) => Promise<ExecResponse>;
2347
+ /**
2348
+ * `POST /sandboxes/:id/execs` with `Accept: text/event-stream`.
2349
+ *
2350
+ * Calls `on` for every frame as it arrives and resolves with the same `ExecResponse` shape
2351
+ * the inline form returns, assembled from the stream. Output payloads are base64 because a
2352
+ * chunk boundary lands mid-UTF-8 as often as not; `on.stdout`/`on.stderr` are handed the
2353
+ * decoded bytes so the caller never has to know that.
2354
+ *
2355
+ * Uses `fetch`'s streaming body rather than `res.text()`, which is what `request()` does and
2356
+ * why it cannot be reused here: `await res.text()` is precisely the buffering this removes.
2357
+ */
2358
+ execStream: (sandboxId: string, opts: ExecOpts, on?: {
2359
+ stdout?: (chunk: Uint8Array) => void;
2360
+ stderr?: (chunk: Uint8Array) => void;
2361
+ heartbeat?: () => void;
2362
+ }) => Promise<ExecResponse>;
2363
+ /**
2364
+ * `POST /sandboxes/:id/execs {background: true}` — start a command and return its handle
2365
+ * record immediately. Output is buffered server-side (up to `maxOutputBytes` per stream,
2366
+ * default 1 MiB) and read by range with `execs.read`; `execs.get(…, {waitMs})` parks until
2367
+ * it ends.
2368
+ *
2369
+ * `secrets` are allowed here. Background output is served by byte range, and that range is
2370
+ * masked before it leaves the service, so a credentialed worker can run in the
2371
+ * background instead of holding a foreground stream open for its whole life.
2372
+ */
2373
+ execBackground: (sandboxId: string, opts: Omit<ExecOpts, "terminal" | "killOnExit" | "signal"> & {
2374
+ stdin?: boolean;
2375
+ follow?: boolean;
2376
+ }) => Promise<BackgroundExecRecord>;
2377
+ /** Background execs of one sandbox (`GET|POST|DELETE /sandboxes/:id/execs/:exec_id…`). */
2378
+ execs: {
2379
+ /** Every background exec this sandbox has started and not forgotten, newest first. */
2380
+ list: (sandboxId: string) => Promise<BackgroundExecRecord[]>;
2381
+ /** Status; with `waitMs` the API parks until the exec ends or the wait (≤ 30 s) is spent. */
2382
+ get: (sandboxId: string, execId: string, opts?: {
2383
+ waitMs?: number;
2384
+ signal?: AbortSignal;
2385
+ }) => Promise<BackgroundExecRecord>;
2386
+ /**
2387
+ * One range of one stream. `eof` is true only once the exec has ended **and** the range
2388
+ * reached the end — a reader that has caught up with a running process is not at EOF.
2389
+ */
2390
+ read: (sandboxId: string, execId: string, stream: "stdout" | "stderr", opts?: {
2391
+ offset?: number;
2392
+ len?: number;
2393
+ waitMs?: number;
2394
+ untilOutput?: boolean;
2395
+ signal?: AbortSignal;
2396
+ }) => Promise<{
2397
+ data: Uint8Array;
2398
+ offset: number;
2399
+ eof: boolean;
2400
+ skipped: number;
2401
+ record: BackgroundExecRecord;
2402
+ }>;
2403
+ /**
2404
+ * Write to a running background exec's standard input, and/or close it.
2405
+ *
2406
+ * `data` travels base64 because the point of stdin is usually that it is **not** text: a
2407
+ * JSON string cannot carry an arbitrary byte, and a client that pretended otherwise would
2408
+ * corrupt the first binary payload anyone sent. `{ data, eof: true }` in one call is the
2409
+ * common "write this and close", which is what a process reading to EOF is waiting for.
2410
+ *
2411
+ * Refusals worth branching on: `no_stdin` (started without `stdin: true` — its input is
2412
+ * /dev/null and cannot be opened now) and the usage error for an exec that has already
2413
+ * ended. Neither wrote anything.
2414
+ */
2415
+ stdin: (sandboxId: string, execId: string, opts?: {
2416
+ data?: string | Uint8Array;
2417
+ eof?: boolean;
2418
+ }) => Promise<{
2419
+ ok: boolean;
2420
+ sandbox_id: string;
2421
+ exec_id: string;
2422
+ written: number;
2423
+ eof: boolean;
2424
+ }>;
2425
+ /**
2426
+ * Send a signal (number or name; default SIGTERM). Resolves `delivered: false,
2427
+ * already_ended: true` for an exec that had already ended — the process is gone either way.
2428
+ */
2429
+ signal: (sandboxId: string, execId: string, signal?: number | string) => Promise<{
2430
+ ok: boolean;
2431
+ exec_id: string;
2432
+ signal: number;
2433
+ delivered: boolean;
2434
+ already_ended?: boolean;
2435
+ }>;
2436
+ /** SIGKILL if still running, then drop the handle and its buffered output. */
2437
+ forget: (sandboxId: string, execId: string) => Promise<{
2438
+ ok: boolean;
2439
+ exec_id: string;
2440
+ }>;
2441
+ };
2442
+ /**
2443
+ * Preview URLs for ports inside the sandbox. A URL is a capability — anyone holding it reaches
2444
+ * that port — and dies with `revoke`, with a pause, or with the sandbox.
2445
+ */
2446
+ ports: {
2447
+ expose: (sandboxId: string, port: number) => Promise<PortRecord>;
2448
+ list: (sandboxId: string) => Promise<PortRecord[]>;
2449
+ /** A token revokes that URL; a port number revokes every URL for that port. */
2450
+ revoke: (sandboxId: string, tokenOrPort: string | number) => Promise<{
2451
+ ok: boolean;
2452
+ sandbox_id: string;
2453
+ revoked: string[];
2454
+ }>;
2455
+ };
2456
+ ssh: {
2457
+ /**
2458
+ * `POST /sandboxes/:id/ssh` — authorise an `ssh-ed25519` public key and mint the
2459
+ * short-lived credential its tunnel authenticates with.
2460
+ *
2461
+ * Two credentials come back. `token` is what the tunnel accepts: it reaches this sandbox
2462
+ * only and expires, so it is safe in a `~/.ssh/config`. `host_key` is the sandbox's own,
2463
+ * minted for it and gone with it — pin it rather than turning host verification off.
2464
+ */
2465
+ open: (sandboxId: string, opts: {
2466
+ publicKey: string;
2467
+ ttlS?: number;
2468
+ idleTimeoutS?: number;
2469
+ }) => Promise<SshAccess>;
2470
+ /** `GET /sandboxes/:id/ssh` — the keys this sandbox will accept, and for how long. */
2471
+ list: (sandboxId: string) => Promise<SshAccessList>;
2472
+ /** `DELETE /sandboxes/:id/ssh/:key_id` — by key id or fingerprint. Idempotent. */
2473
+ revoke: (sandboxId: string, keyIdOrFingerprint: string) => Promise<SshRevoked>;
2474
+ /**
2475
+ * `GET /sandboxes/:id/ssh/connect` — the raw tunnel, for a `ProxyCommand`.
2476
+ *
2477
+ * Almost nobody calls this directly: `promigence sandbox ssh <id> --proxy` is what an
2478
+ * `ssh` config points at. It is here because the SDK should be able to do everything the
2479
+ * CLI can.
2480
+ */
2481
+ connect: (sandboxId: string, opts: {
2482
+ token?: string;
2483
+ onData: (data: Uint8Array) => void;
2484
+ onClose: () => void;
2485
+ timeoutMs?: number;
2486
+ }) => Promise<SshTunnel>;
2487
+ };
2488
+ /**
2489
+ * `GET /sandboxes/:id/pty` — an interactive terminal over a WebSocket. Resolves once the
2490
+ * terminal exists. `cmd` defaults to a login shell (`/bin/sh -l`, `TERM=xterm-256color`).
2491
+ */
2492
+ pty: (sandboxId: string, opts?: {
2493
+ cols?: number;
2494
+ rows?: number;
2495
+ cmd?: string;
2496
+ /** Stored secret names, or `{name, as}` bindings, put into the terminal's environment
2497
+ * by the server. Names travel in a header; values never leave the service. */
2498
+ secrets?: SecretBinding[];
2499
+ onData?: (data: Uint8Array) => void;
2500
+ onExit?: (exit: PtyExit) => void;
2501
+ timeoutMs?: number;
2502
+ /** Pick the same terminal up again when its connection drops or the service is updated;
2503
+ * see `PtyConnectOpts.resume`. Default: on. */
2504
+ resume?: boolean;
2505
+ /** Reconnect in a new shell when the service is updated (a WebSocket `Close` 1012) and the
2506
+ * terminal cannot be picked up again; see `PtyConnectOpts.reconnect`. Default: on without
2507
+ * `cmd`, off with one. */
2508
+ reconnect?: boolean;
2509
+ /** Called after each reconnect: `resumed` says whether it is the same terminal or a new
2510
+ * shell. */
2511
+ onReconnected?: (how: {
2512
+ resumed: boolean;
2513
+ }) => void;
2514
+ }) => Promise<PtySession>;
2515
+ /** `GET /sandboxes/:id`. */
2516
+ get: (sandboxId: string) => Promise<SandboxRecord>;
2517
+ /**
2518
+ * `POST /sandboxes/:id/pause` — park the sandbox and stop its billing clock.
2519
+ *
2520
+ * Billing: a paused sandbox bills no seconds. The paused interval is subtracted from the
2521
+ * billed span, and both the spend cap's boundary and the sandbox's own `timeoutMs` move with
2522
+ * it, so a sandbox paused overnight is not killed two seconds after it resumes.
2523
+ *
2524
+ * A pause can take seconds on a large, busy sandbox (under a second on a small, quiet one);
2525
+ * this call's timeout allows for it. `took_ms` in the answer is how long it took.
2526
+ *
2527
+ * Files and memory are kept. Background execs end at the pause (`background_execs_killed`
2528
+ * counts them; their ids answer not found after resume); anything else you started keeps
2529
+ * running when the sandbox resumes.
2530
+ */
2531
+ pause: (sandboxId: string) => Promise<PauseResult>;
2532
+ /**
2533
+ * `POST /sandboxes/:id/resume` — bring it back as it was and restart the clock.
2534
+ * `optimizeFor` left out keeps the sandbox's own setting. `spendCap`: for a sandbox paused at
2535
+ * its run's spend cap (`pause_reason: "spend_cap"`), the run's new cap in US dollars, above
2536
+ * the one it reached; the sandbox goes on with what the new cap leaves.
2537
+ */
2538
+ resume: (sandboxId: string, opts?: {
2539
+ optimizeFor?: OptimizeFor;
2540
+ spendCap?: number;
2541
+ }) => Promise<ResumeResult>;
2542
+ /**
2543
+ * `POST /sandboxes/:id/snapshot` — photograph a running sandbox and keep it running.
2544
+ *
2545
+ * Unlike `pause`, this does not end the sandbox: you get back an ordinary snapshot id you
2546
+ * can fork, and the sandbox you called it on carries on. Background processes are ended to
2547
+ * take it, and the count comes back in `background_execs_killed` rather than silently.
2548
+ *
2549
+ * The same ten-minute budget as `pause`: on a large sandbox it can take seconds.
2550
+ *
2551
+ * A sandbox that has been given the value of a secret is refused with `secrets_in_snapshot`
2552
+ * (409): the snapshot, and every sandbox started from it, could contain that value. Pass
2553
+ * `force: true` to take it anyway; the answer then carries a `secrets_in_saved_state`
2554
+ * warning.
2555
+ *
2556
+ * An `alias` another snapshot already has is refused (`usage`, 400) unless `moveAlias: true`
2557
+ * moves it to the new snapshot; the other one stays, unnamed, reachable by id.
2558
+ */
2559
+ snapshot: (sandboxId: string, opts?: {
2560
+ alias?: string;
2561
+ force?: boolean;
2562
+ moveAlias?: boolean;
2563
+ }) => Promise<SandboxSnapshotResult>;
2564
+ /**
2565
+ * `GET /sandboxes?limit=[&labels=][&state=][&cursor=]` — this org's sandboxes, newest run
2566
+ * first, as a plain array.
2567
+ *
2568
+ * `list({ labels: { session: "x" } })` narrows it **on the server**. That is the shape you
2569
+ * want for "find the sandbox for this session, or make one": filtering the array yourself
2570
+ * cannot see past `limit`. `state` is the same deal.
2571
+ *
2572
+ * One page, and the cursor that follows it is dropped. Use [`listPage`] to walk past the
2573
+ * first page; this form stays an array because every existing caller iterates it.
2574
+ *
2575
+ * The number form (`list(100)`) is the original signature and still works.
2576
+ */
2577
+ list: (opts?: number | SandboxListOptions) => Promise<SandboxRecord[]>;
2578
+ /**
2579
+ * The same route with the page kept whole: `{ items, nextCursor, limit }`.
2580
+ *
2581
+ * This used to be impossible to offer — the route issued no cursor, and a paginator over a
2582
+ * single page is a promise that has to be broken later. It issues one now, so the promise is
2583
+ * real: feed `nextCursor` back as `cursor` until it is `null`.
2584
+ */
2585
+ listPage: (opts?: number | SandboxListOptions) => Promise<Page<SandboxRecord>>;
2586
+ /**
2587
+ * `POST /sandboxes/:id/probe` — is this environment still the one that was verified?
2588
+ *
2589
+ * Compares the sandbox's files under the paths its snapshot's verification recorded with what
2590
+ * was recorded. It is the same check a failed run gets, which decides whether the failure was
2591
+ * the environment's (`integrity_mismatch`, not billed) or a path the snapshot's own `verify`
2592
+ * never covered (`verify_did_not_cover`, billed). Asking for it up front is cheaper than
2593
+ * spending a run to find out.
2594
+ *
2595
+ * `first_bad_path` names the **first differing path**, which is the only part of a failed
2596
+ * probe anyone can act on.
2597
+ */
2598
+ probe: (sandboxId: string, opts?: {
2599
+ paths?: string[];
2600
+ }) => Promise<{
2601
+ sandbox_id: string;
2602
+ ok: boolean;
2603
+ ms: number;
2604
+ checked: string | string[];
2605
+ first_bad_path?: string | null;
2606
+ }>;
2607
+ /** `DELETE /sandboxes/:id`. */
2608
+ kill: (sandboxId: string) => Promise<unknown>;
2609
+ /**
2610
+ * `GET /sandboxes/:id/network` — what this sandbox may reach, and every host it asked for
2611
+ * and was refused.
2612
+ *
2613
+ * `refused_hosts` is the field worth reading. A sandbox started with `network: {allow: [...]}`
2614
+ * whose build will not install anything has usually asked for one host nobody thought of;
2615
+ * this names it, with a count, instead of leaving a call to hang.
2616
+ */
2617
+ network: (sandboxId: string) => Promise<SandboxNetwork>;
2618
+ /**
2619
+ * `POST /sandboxes/:id/network {network: "none"}` — cut a running sandbox's egress.
2620
+ *
2621
+ * The pattern this exists for: fork with the network on, install the dependencies, cut egress,
2622
+ * *then* let the agent run. Before this the policy was fixed at fork, so that sequence needed
2623
+ * two sandboxes or a network the agent could still reach.
2624
+ *
2625
+ * One direction. There is no call that turns egress back on — a sandbox whose network can
2626
+ * re-open on request is not one whose egress you can reason about.
2627
+ */
2628
+ setNetworkNone: (sandboxId: string) => Promise<SandboxNetworkCut>;
2629
+ /**
2630
+ * `PATCH /sandboxes/:id` — extend one live sandbox, set its idle rule, or both.
2631
+ *
2632
+ * `timeoutMs` is the **total** lifetime, not an offset from now. It throws
2633
+ * `SpendCapError` (409, exit 5) when the added seconds (× the per-second rate) do not fit in
2634
+ * the cap you set (the run's `spendCap`, or the org's default cap) − charged −
2635
+ * live_commitment, and nothing changes. The launch quote is not a bound.
2636
+ *
2637
+ * `autoPauseS` is how long the sandbox may sit idle before it pauses itself; `0` turns that
2638
+ * off, and the server keeps a floor under the rest (a sandbox that spends more time pausing
2639
+ * than it saves is worse than no rule). A sandbox with a command still running is not idle,
2640
+ * so a long build is never cut short by it.
2641
+ *
2642
+ * `autoPauseMode` says what idle means (`AutoPauseMode`); unset leaves it as it is.
2643
+ *
2644
+ * The two are separate promises and either may be sent alone: the timeout is the bound the
2645
+ * customer committed to spend, the idle rule is what stops them spending it on nothing.
2646
+ */
2647
+ patch: (sandboxId: string, opts: {
2648
+ timeoutMs?: number;
2649
+ autoPauseS?: number;
2650
+ autoPauseMode?: AutoPauseMode;
2651
+ }) => Promise<SandboxPatchResponse>;
2652
+ /**
2653
+ * `get` streams: the body is handed back as bytes and never parsed, because an eval
2654
+ * artefact, a trace or a run log is exactly what this is for. `put` sends the file as the
2655
+ * body rather than base64 inside JSON.
2656
+ */
2657
+ files: {
2658
+ /**
2659
+ * A directory, each entry as `lstat` reports it, in one request. `recursive` walks below it,
2660
+ * `depth` bounds that walk, `hidden: false` drops dotfiles; `truncated` past `limit`
2661
+ * (10,000 by default).
2662
+ */
2663
+ list: (sandboxId: string, path?: string, opts?: DirListOpts) => Promise<DirListing>;
2664
+ /**
2665
+ * Find files by name glob (`pattern`), by literal content (`contains`), or both.
2666
+ *
2667
+ * At least one of the two is required, and that is checked here rather than after a round
2668
+ * trip: a search with neither is a full walk of the tree that answers nothing.
2669
+ */
2670
+ search: (sandboxId: string, path: string, opts?: SearchOpts) => Promise<SearchResult>;
2671
+ /**
2672
+ * `mkdir`. `parents` is `mkdir -p`: the intermediate directories, and no error if it is
2673
+ * already there.
2674
+ */
2675
+ mkdir: (sandboxId: string, path: string, opts?: {
2676
+ mode?: number | string;
2677
+ parents?: boolean;
2678
+ }) => Promise<FileInfo>;
2679
+ /** Rename or move, inside the sandbox. `overwrite` replaces an existing `to`. */
2680
+ move: (sandboxId: string, from: string, to: string, opts?: {
2681
+ overwrite?: boolean;
2682
+ }) => Promise<FileInfo>;
2683
+ /** Copy, inside the sandbox. `recursive` for a directory. */
2684
+ copy: (sandboxId: string, from: string, to: string, opts?: {
2685
+ recursive?: boolean;
2686
+ overwrite?: boolean;
2687
+ }) => Promise<FileInfo>;
2688
+ /**
2689
+ * Mode and/or ownership. At least one of `mode`, `uid`, `gid` is required — a chmod that
2690
+ * changes nothing is a round trip that answers nothing, and the API refuses it.
2691
+ */
2692
+ chmod: (sandboxId: string, path: string, opts: {
2693
+ mode?: number | string;
2694
+ uid?: number;
2695
+ gid?: number;
2696
+ recursive?: boolean;
2697
+ }) => Promise<FileInfo>;
2698
+ /**
2699
+ * Tar a directory **into the sandbox** and answer with the tar's path and size.
2700
+ *
2701
+ * The archive deliberately does not cross the wire here: the transfer is the ordinary
2702
+ * `get`/`put` byte path, which has no size ceiling, and a route that returned the bytes
2703
+ * would have one. `exclude` is a list of name globs (`[".git", "node_modules"]`). The tar
2704
+ * carries the source directory under its own name, so an `extract` of it wants `strip: 1`.
2705
+ */
2706
+ archive: (sandboxId: string, path: string, opts?: {
2707
+ out?: string;
2708
+ exclude?: string[];
2709
+ }) => Promise<ArchiveResult>;
2710
+ /** Unpack a tar that is **already inside** the sandbox. `strip` drops leading path components. */
2711
+ extract: (sandboxId: string, archive: string, into: string, opts?: {
2712
+ strip?: number;
2713
+ }) => Promise<ExtractResult>;
2714
+ stat: (sandboxId: string, path: string) => Promise<{
2715
+ sandbox_id: string;
2716
+ path: string;
2717
+ stat: FileStat;
2718
+ }>;
2719
+ get: (sandboxId: string, path: string, opts?: {
2720
+ signal?: AbortSignal;
2721
+ timeoutMs?: number;
2722
+ }) => Promise<Uint8Array>;
2723
+ /**
2724
+ * `get` without holding the file: every chunk goes to `onChunk` as it arrives (write it to
2725
+ * disk, hash it, pipe it), and the next is read only once `onChunk` resolves. Memory is one
2726
+ * chunk whatever the size. Resolves with the byte count.
2727
+ */
2728
+ download: (sandboxId: string, path: string, onChunk: (chunk: Uint8Array) => void | Promise<void>, opts?: {
2729
+ signal?: AbortSignal;
2730
+ timeoutMs?: number;
2731
+ }) => Promise<{
2732
+ bytes: number;
2733
+ }>;
2734
+ /** `text` is a convenience over `get` for logs and JSON traces. */
2735
+ text: (sandboxId: string, path: string, opts?: {
2736
+ signal?: AbortSignal;
2737
+ timeoutMs?: number;
2738
+ }) => Promise<string>;
2739
+ /**
2740
+ * Write a file of **any size**, in [`FILE_PUT_CHUNK_BYTES`] requests.
2741
+ *
2742
+ * Chunk 0 goes without `?offset=`, which means "this is the file" (create, truncate);
2743
+ * every later chunk carries one, which appends, so a file has no size limit here.
2744
+ *
2745
+ * A failure mid-loop leaves a **prefix** in the sandbox, not a corrupt file. Re-running the
2746
+ * same `put` is safe: chunk 0 truncates.
2747
+ *
2748
+ * `mode` (`0o755`) sets the file's permission bits. Without it a file that already exists
2749
+ * keeps its mode, as a shell `>` does, and a new one is `0o644`.
2750
+ */
2751
+ put: (sandboxId: string, path: string, data: string | Uint8Array, opts?: {
2752
+ mode?: number;
2753
+ signal?: AbortSignal;
2754
+ timeoutMs?: number;
2755
+ }) => Promise<{
2756
+ ok: boolean;
2757
+ sandbox_id: string;
2758
+ path: string;
2759
+ bytes: number;
2760
+ mode: string;
2761
+ chunks: number;
2762
+ }>;
2763
+ /**
2764
+ * `put` from a source read one chunk at a time — a file on disk of any size, without
2765
+ * holding it (a 2 GiB put was refused and a 1.5 GB one peaked at 3.3 GB RSS).
2766
+ * `read(offset, len)` returns up to `len` bytes at `offset`; `size` is the total.
2767
+ */
2768
+ putFrom: (sandboxId: string, path: string, source: {
2769
+ size: number;
2770
+ read: (offset: number, len: number) => Uint8Array | Promise<Uint8Array>;
2771
+ }, opts?: {
2772
+ mode?: number;
2773
+ signal?: AbortSignal;
2774
+ timeoutMs?: number;
2775
+ }) => Promise<{
2776
+ ok: boolean;
2777
+ sandbox_id: string;
2778
+ path: string;
2779
+ bytes: number;
2780
+ mode: string;
2781
+ chunks: number;
2782
+ }>;
2783
+ delete: (sandboxId: string, path: string, opts?: {
2784
+ recursive?: boolean;
2785
+ }) => Promise<{
2786
+ ok: boolean;
2787
+ path: string;
2788
+ deleted: boolean;
2789
+ existed: boolean;
2790
+ }>;
2791
+ };
2792
+ /**
2793
+ * Output for one `exec_id`, the payload behind `truncated` and `stdout_ref`.
2794
+ *
2795
+ * Resolution: one `GET /execs/:id`, checked against an explicit `sandboxId` or `runId`. A
2796
+ * `runId` used to scan that run's sandboxes one request each: ~28 s for a 100-wide run, and
2797
+ * an exec of an ended sandbox could still be missed.
2798
+ */
2799
+ output: (execId: string, opts?: {
2800
+ sandboxId?: string;
2801
+ runId?: string;
2802
+ range?: {
2803
+ from?: number;
2804
+ to?: number;
2805
+ };
2806
+ }) => Promise<ExecOutput>;
2807
+ };
2808
+ /** Bound helpers for one sandbox (kept from the 0.0.0 shape). */
2809
+ sandbox(id: string): {
2810
+ id: string;
2811
+ exec: (opts: ExecOpts) => Promise<CommandResult>;
2812
+ get: () => Promise<SandboxRecord>;
2813
+ kill: () => Promise<unknown>;
2814
+ pause: () => Promise<PauseResult>;
2815
+ resume: (opts?: {
2816
+ optimizeFor?: OptimizeFor;
2817
+ spendCap?: number;
2818
+ }) => Promise<ResumeResult>;
2819
+ patch: (opts: {
2820
+ timeoutMs?: number;
2821
+ autoPauseS?: number;
2822
+ autoPauseMode?: AutoPauseMode;
2823
+ }) => Promise<SandboxPatchResponse>;
2824
+ files: {
2825
+ get: (path: string) => Promise<Uint8Array<ArrayBufferLike>>;
2826
+ text: (path: string) => Promise<string>;
2827
+ put: (path: string, data: string | Uint8Array, opts?: {
2828
+ mode?: number;
2829
+ }) => Promise<{
2830
+ ok: boolean;
2831
+ sandbox_id: string;
2832
+ path: string;
2833
+ bytes: number;
2834
+ mode: string;
2835
+ chunks: number;
2836
+ }>;
2837
+ stat: (path: string) => Promise<{
2838
+ sandbox_id: string;
2839
+ path: string;
2840
+ stat: FileStat;
2841
+ }>;
2842
+ delete: (path: string) => Promise<{
2843
+ ok: boolean;
2844
+ path: string;
2845
+ deleted: boolean;
2846
+ existed: boolean;
2847
+ }>;
2848
+ };
2849
+ probe: (opts?: {
2850
+ paths?: string[];
2851
+ }) => Promise<{
2852
+ sandbox_id: string;
2853
+ ok: boolean;
2854
+ ms: number;
2855
+ checked: string | string[];
2856
+ first_bad_path?: string | null;
2857
+ }>;
2858
+ };
2859
+ /**
2860
+ * Org-scoped secrets. The value is **write-only**: nothing in this namespace —
2861
+ * or in the API behind it — hands one back. `list` returns names and byte counts so a caller
2862
+ * can tell "is this the key I meant" without being able to read it.
2863
+ *
2864
+ * Names are validated here rather than at the server: an environment variable that cannot
2865
+ * exist is a client-side mistake, and finding out after a round trip helps nobody.
2866
+ */
2867
+ readonly secrets: {
2868
+ set: (name: string, value: string, opts?: SecretSetOpts) => Promise<SecretRecord>;
2869
+ list: () => Promise<{
2870
+ secrets: SecretRecord[];
2871
+ }>;
2872
+ delete: (name: string) => Promise<{
2873
+ name: string;
2874
+ deleted: boolean;
2875
+ }>;
2876
+ };
2877
+ /**
2878
+ * Container images, as far as we need to know about them: what digest does this tag name?
2879
+ */
2880
+ readonly images: {
2881
+ /**
2882
+ * `POST /images/resolve` — `python:3.12` → `python@sha256:…`, resolved **by the server**.
2883
+ *
2884
+ * You need no container runtime on this machine to call it. That is the entire point:
2885
+ * `snapshots.create({ image })` takes a digest, and the only way to get one used to be
2886
+ * `docker buildx imagetools inspect` locally — a Docker install demanded of somebody who is
2887
+ * renting sandboxes so as not to need one.
2888
+ *
2889
+ * `image` in the answer is the pinned reference to pass straight to `snapshots.create`. For
2890
+ * a multi-arch tag it carries the **index** digest, which is what a pull of that tag
2891
+ * fetches; `platform_digest` names the `linux/amd64` child beside it, and a tag with no
2892
+ * `linux/amd64` build is refused here rather than at build time.
2893
+ *
2894
+ * An already-pinned reference is returned unchanged without a registry call, so this is
2895
+ * safe to call unconditionally.
2896
+ *
2897
+ * `registrySecret` names a stored secret (`secrets.set`) holding `user:password` for a
2898
+ * private registry.
2899
+ */
2900
+ resolve: (image: string, opts?: {
2901
+ registrySecret?: string;
2902
+ }) => Promise<ResolvedImage>;
2903
+ };
2904
+ readonly captures: {
2905
+ /**
2906
+ * `DELETE /captures/:snapshot_id` — destroy a preserved failure now, before it expires.
2907
+ *
2908
+ * The bytes go; `bytes` is what was freed. A customer must be able to destroy
2909
+ * their own forensics on demand and be told what was destroyed.
2910
+ */
2911
+ delete: (snapshotId: string) => Promise<{
2912
+ ok: boolean;
2913
+ snapshot_id: string;
2914
+ deleted: boolean;
2915
+ bytes: number;
2916
+ deleted_at: string;
2917
+ }>;
2918
+ };
2919
+ readonly snapshots: {
2920
+ /**
2921
+ * `POST /snapshots` — 202 with a `build_id`, or 200 with the existing snapshot when an
2922
+ * identical build already ran. Nothing is built synchronously: a build is
2923
+ * minutes, so the caller polls `status` (or `waitBuild`).
2924
+ */
2925
+ create: (opts: SnapshotCreateOpts) => Promise<SnapshotBuild>;
2926
+ /** `GET /snapshots/builds/:build_id`, optionally long-polled with `{ waitMs }`. */
2927
+ status: (buildId: string, opts?: ReadOpts) => Promise<SnapshotBuild>;
2928
+ /** `GET /snapshots/builds/:build_id/build-log` — the build log, tail first. */
2929
+ buildLog: (buildId: string, tail?: number) => Promise<string[]>;
2930
+ /**
2931
+ * `GET /snapshots/builds?limit=N` — the build history, newest first.
2932
+ *
2933
+ * A build that was rejected keeps its row and its reason, so this is the list you go to after a build
2934
+ * that produced nothing.
2935
+ */
2936
+ builds: (limit?: number) => Promise<SnapshotBuild[]>;
2937
+ /** The same route with `nextCursor` kept: feed it back as `cursor` until it is `null`. */
2938
+ buildsPage: (opts?: {
2939
+ limit?: number;
2940
+ cursor?: string;
2941
+ }) => Promise<Page<SnapshotBuild>>;
2942
+ /** `GET /snapshots/:id` — `snap_<id>` | `<alias>` | `<alias>@<commit7>`. */
2943
+ get: (idOrAlias: string) => Promise<SnapshotRecord>;
2944
+ /** `GET /snapshots` — this org's snapshots plus every public one (public needs no key). */
2945
+ list: (opts?: {
2946
+ limit?: number;
2947
+ cursor?: string;
2948
+ }) => Promise<SnapshotRecord[]>;
2949
+ /** The same route with `nextCursor` kept: feed it back as `cursor` until it is `null`. */
2950
+ listPage: (opts?: {
2951
+ limit?: number;
2952
+ cursor?: string;
2953
+ }) => Promise<Page<SnapshotRecord>>;
2954
+ /**
2955
+ * `GET /snapshots` with **no** Authorization header — the list an agent with no key sees,
2956
+ * which is exactly what `promigence examples` must show. Filtered to `visibility: "public"`
2957
+ * because a server that answers the anonymous call with more than that is misconfigured and
2958
+ * the CLI should not repeat its mistake.
2959
+ */
2960
+ public: (limit?: number) => Promise<SnapshotRecord[]>;
2961
+ /**
2962
+ * `DELETE /snapshots/:id` — take one of your own snapshots out of the catalogue.
2963
+ *
2964
+ * The snapshot leaves your catalogue at once; the space it used is reclaimed later.
2965
+ * Finished runs keep their reports. Refused (`usage`) while a sandbox forked from it is
2966
+ * still alive — kill those first.
2967
+ */
2968
+ delete: (idOrAlias: string) => Promise<{
2969
+ snapshot_id: string;
2970
+ alias?: string | null;
2971
+ deleted: boolean;
2972
+ }>;
2973
+ /** `PUT /snapshots/:id/alias` — the sidecar's one mutable field. */
2974
+ alias: (idOrAlias: string, alias: string | null) => Promise<SnapshotRecord>;
2975
+ /**
2976
+ * Wait for a build to leave `running`, bounded by `waitTimeoutMs` (default 100 s).
2977
+ *
2978
+ * `timedOut: true` means exactly that and nothing else: the build is untouched and still
2979
+ * running — come back with `status(buildId)`.
2980
+ */
2981
+ waitBuild: (buildId: string, opts?: WaitOpts) => Promise<{
2982
+ build: SnapshotBuild;
2983
+ timedOut: boolean;
2984
+ waitedMs: number;
2985
+ }>;
2986
+ /**
2987
+ * `POST /snapshots/:id/verify` — re-run the snapshot's recorded `verify` in a fresh fork.
2988
+ *
2989
+ * stores a snapshot only if its `verify` passed on the machine that built it; this asks
2990
+ * whether that is still true — after a move, after a month, before
2991
+ * trusting a number that came out of it. It is an ordinary one-sandbox run, so it goes
2992
+ * through the same report and the same ledger, and bills per second at the snapshot's tier, at most
2993
+ * `price` (in the reply, to print before waiting like `build_price`).
2994
+ */
2995
+ verify: (id: string, opts?: {
2996
+ timeoutMs?: number;
2997
+ spendCap?: number;
2998
+ }) => Promise<SnapshotVerifyResponse>;
2999
+ /** `GET /snapshots/:id/manifest` — the snapshot's sealed manifest and what can be verified of it. */
3000
+ manifest: (idOrAlias: string) => Promise<SnapshotManifestResponse>;
3001
+ /**
3002
+ * The `.promigence` bundle for a snapshot: the sealed form, which names exactly one
3003
+ * environment and verifies offline (`verifyBundle`). This client composes it from the
3004
+ * snapshot's sealed manifest (`served_by: "client"`); the API serves no bundle of its own.
3005
+ */
3006
+ bundle: (idOrAlias: string, opts?: {
3007
+ alias?: string;
3008
+ }) => Promise<{
3009
+ bundle: Bundle;
3010
+ served_by: "client";
3011
+ record?: SnapshotRecord;
3012
+ }>;
3013
+ };
3014
+ }
3015
+ export declare function toCommandResult(r: ExecResponse): CommandResult;