@promigence/sdk 0.1.0-beta.6 → 0.1.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/cjs/client.d.ts +100 -24
- package/dist/cjs/client.js +44 -31
- package/dist/cjs/core.d.ts +10 -1
- package/dist/cjs/core.js +14 -4
- package/dist/cjs/credstore.d.ts +3 -2
- package/dist/cjs/credstore.js +17 -4
- package/dist/cjs/models.gen.d.ts +218 -22
- package/dist/cjs/proxy.js +2 -1
- package/dist/cjs/pty.d.ts +65 -3
- package/dist/cjs/pty.js +270 -34
- package/dist/cjs/sandbox.d.ts +34 -4
- package/dist/cjs/sandbox.js +3 -2
- package/dist/cjs/ssh.js +1 -0
- package/dist/client.d.ts +100 -24
- package/dist/client.js +46 -33
- package/dist/core.d.ts +10 -1
- package/dist/core.js +12 -3
- package/dist/credstore.d.ts +3 -2
- package/dist/credstore.js +17 -4
- package/dist/models.gen.d.ts +218 -22
- package/dist/proxy.js +2 -1
- package/dist/pty.d.ts +65 -3
- package/dist/pty.js +269 -33
- package/dist/sandbox.d.ts +34 -4
- package/dist/sandbox.js +3 -2
- package/dist/ssh.js +1 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -253,6 +253,11 @@ export interface Totals {
|
|
|
253
253
|
grader_failed?: number;
|
|
254
254
|
snapshot_failed: number;
|
|
255
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;
|
|
256
261
|
expired: number;
|
|
257
262
|
cap_killed: number;
|
|
258
263
|
not_started: number;
|
|
@@ -349,7 +354,9 @@ export interface SecretRecord {
|
|
|
349
354
|
name: string;
|
|
350
355
|
/** Length of the stored value, so a caller can sanity-check without reading it. */
|
|
351
356
|
bytes: number;
|
|
357
|
+
/** On `secrets.set` only: `false` when an existing secret of that name was overwritten. */
|
|
352
358
|
created?: boolean;
|
|
359
|
+
/** On `secrets.list` only. */
|
|
353
360
|
created_at?: string;
|
|
354
361
|
updated_at?: string;
|
|
355
362
|
/**
|
|
@@ -370,7 +377,7 @@ export interface SecretRecord {
|
|
|
370
377
|
* `api.anthropic.com`. Absent when there is none. A variable the caller sets itself wins.
|
|
371
378
|
*/
|
|
372
379
|
env?: Record<string, string>;
|
|
373
|
-
/** The request headers the secret authenticates
|
|
380
|
+
/** The request headers the secret authenticates: on `secrets.list`, when `hosts` is present. */
|
|
374
381
|
headers?: string[];
|
|
375
382
|
/** What the variable in a sandbox that names it holds instead of the value (on `set` of a bound secret). */
|
|
376
383
|
placeholder?: string;
|
|
@@ -467,13 +474,17 @@ export interface SnapshotRecord {
|
|
|
467
474
|
source?: SnapshotSource;
|
|
468
475
|
/** `false` for an image snapshot built without `verify` — stored, but nothing was verified. */
|
|
469
476
|
verified?: boolean;
|
|
470
|
-
start?: string | string[];
|
|
471
477
|
created_at?: string;
|
|
472
478
|
/**
|
|
473
479
|
* Set when the snapshot can no longer be started, saying why (it was lost before it could be
|
|
474
480
|
* kept); a fork of it is refused `snapshot_unavailable`. Absent while it can be started.
|
|
475
481
|
*/
|
|
476
482
|
unavailable?: string;
|
|
483
|
+
/**
|
|
484
|
+
* The user commands, terminals and SSH sessions in its sandboxes run as when they name none:
|
|
485
|
+
* its image's own `USER`, or the `user` it was created with. Absent: root.
|
|
486
|
+
*/
|
|
487
|
+
user?: string | null;
|
|
477
488
|
[extra: string]: unknown;
|
|
478
489
|
}
|
|
479
490
|
/** `GET /snapshots/:id/manifest`: the snapshot's manifest, sealed (`buildBundle` turns it into a `.promigence` bundle). */
|
|
@@ -510,8 +521,6 @@ export interface ExecResponse {
|
|
|
510
521
|
stdout: string;
|
|
511
522
|
stderr: string;
|
|
512
523
|
truncated?: boolean;
|
|
513
|
-
stdout_ref?: string;
|
|
514
|
-
stderr_ref?: string;
|
|
515
524
|
/** True when the command was killed at its own timeout. */
|
|
516
525
|
timed_out?: boolean;
|
|
517
526
|
/** True when the command was killed for running out of memory. */
|
|
@@ -539,7 +548,13 @@ export interface SandboxRecord extends QueuedInfo {
|
|
|
539
548
|
*/
|
|
540
549
|
pause_reason?: "spend_cap" | "credits_exhausted";
|
|
541
550
|
run_id?: string;
|
|
551
|
+
/**
|
|
552
|
+
* The snapshot it was started from, as its run names it (`runs.get(runId).snapshot_id`): the
|
|
553
|
+
* snapshot's id, or `promigence/base` for the built-in base.
|
|
554
|
+
*/
|
|
542
555
|
snapshot_id?: string;
|
|
556
|
+
/** When the sandbox was created, RFC 3339. */
|
|
557
|
+
created_at?: string;
|
|
543
558
|
outcome?: Outcome;
|
|
544
559
|
/**
|
|
545
560
|
* The labels this sandbox inherited from its run — the same map `runs.start({ labels })` took.
|
|
@@ -627,7 +642,7 @@ export interface Page<T> {
|
|
|
627
642
|
/** What the server actually used, which is not always what was asked for (it caps at 500). */
|
|
628
643
|
limit: number;
|
|
629
644
|
}
|
|
630
|
-
/** `GET /health` — unauthenticated: `ok`, `version`, `
|
|
645
|
+
/** `GET /health` — unauthenticated: `ok`, `version`, `serving` and `location`; `build_sha` to a caller with a key. */
|
|
631
646
|
export interface HealthResponse {
|
|
632
647
|
ok?: boolean;
|
|
633
648
|
version?: string;
|
|
@@ -733,6 +748,8 @@ export interface CreditOffer {
|
|
|
733
748
|
state: string;
|
|
734
749
|
credit_usd: number;
|
|
735
750
|
card_bonus_usd: number;
|
|
751
|
+
/** USD charged to confirm Apple Pay, Google Pay or Amazon Pay when one is added (added to the credit). */
|
|
752
|
+
method_check_usd?: number;
|
|
736
753
|
expires_at?: string | null;
|
|
737
754
|
campaign?: string | null;
|
|
738
755
|
message: string;
|
|
@@ -891,6 +908,16 @@ export interface BillingUsage {
|
|
|
891
908
|
brand?: string;
|
|
892
909
|
last4?: string;
|
|
893
910
|
} | null;
|
|
911
|
+
/** Set when the card on file does not unlock paid limits for this organization (it already pays for another one,
|
|
912
|
+
* it is being confirmed with a $1 charge, or that charge did not go through). */
|
|
913
|
+
card_note?: string | null;
|
|
914
|
+
/** The $1 that confirms Apple Pay, Google Pay or Amazon Pay on file: `confirming`, `paid` or `failed`. */
|
|
915
|
+
method_check?: {
|
|
916
|
+
method: string;
|
|
917
|
+
status: string;
|
|
918
|
+
amount_usd: number;
|
|
919
|
+
bonus_usd?: number | null;
|
|
920
|
+
} | null;
|
|
894
921
|
suspended?: boolean;
|
|
895
922
|
suspend_reason?: string | null;
|
|
896
923
|
period?: {
|
|
@@ -914,6 +941,10 @@ export interface BillingUsage {
|
|
|
914
941
|
upcoming_invoice_error?: string | null;
|
|
915
942
|
[extra: string]: unknown;
|
|
916
943
|
}
|
|
944
|
+
/** One row of `GET /billing/history`: a credit the organization received, or a payment. */
|
|
945
|
+
export type BillingHistoryEntry = M.BillingHistoryEntry;
|
|
946
|
+
/** What `billing.historyPage()` lists: everything, only credits, or only payments. */
|
|
947
|
+
export type BillingHistoryKind = "all" | "credits" | "payments";
|
|
917
948
|
/** One metered row of `GET /me/ledger`. */
|
|
918
949
|
export interface LedgerEntry {
|
|
919
950
|
entry_id: string;
|
|
@@ -1184,9 +1215,15 @@ export interface SearchOpts {
|
|
|
1184
1215
|
* `POST /sandboxes/:id/files/archive` — where the tar landed **inside the sandbox** and how big
|
|
1185
1216
|
* it is. The bytes stay in the sandbox on purpose; see `files.downloadDir`.
|
|
1186
1217
|
*/
|
|
1187
|
-
export interface ArchiveResult
|
|
1218
|
+
export interface ArchiveResult {
|
|
1219
|
+
sandbox_id: string;
|
|
1220
|
+
/** Where the tar landed inside the sandbox. */
|
|
1188
1221
|
archive_path: string;
|
|
1222
|
+
/** Its size in bytes. */
|
|
1223
|
+
size: number;
|
|
1224
|
+
/** The directory that was packed. */
|
|
1189
1225
|
source: string;
|
|
1226
|
+
served_by?: ServedBy;
|
|
1190
1227
|
}
|
|
1191
1228
|
/** `POST /sandboxes/:id/files/extract`. `entries` is `null` on the fallback path, which cannot
|
|
1192
1229
|
* count them without a second pass — a number invented there would be worse than none. */
|
|
@@ -1312,8 +1349,6 @@ export interface MeResponse {
|
|
|
1312
1349
|
email?: string;
|
|
1313
1350
|
provider?: string;
|
|
1314
1351
|
} | null;
|
|
1315
|
-
/** @deprecated Never sent: the key is `key.key_id`. */
|
|
1316
|
-
key_id?: string;
|
|
1317
1352
|
/**
|
|
1318
1353
|
* The plan the organisation's limits and prices come from: `hobby` for a trial organisation
|
|
1319
1354
|
* with no plan assigned. The name `tier_over_plan` and the other refusals use.
|
|
@@ -1349,7 +1384,6 @@ export interface MeResponse {
|
|
|
1349
1384
|
/** The cap a run gets when it names none, in USD; `0` names none (the run is capped at its quote). */
|
|
1350
1385
|
default_spend_cap?: number;
|
|
1351
1386
|
card_on_file?: boolean;
|
|
1352
|
-
default_class?: string;
|
|
1353
1387
|
/** The free trial: credits, data out and the limits that apply until a card is on file. */
|
|
1354
1388
|
trial?: TrialStatus;
|
|
1355
1389
|
/** Something about the account to act on (the card-required switch), or null. */
|
|
@@ -1661,6 +1695,18 @@ export interface SnapshotCreateOpts {
|
|
|
1661
1695
|
tier?: Tier;
|
|
1662
1696
|
/** Image forms: the name of a stored secret holding the registry credentials. */
|
|
1663
1697
|
registrySecret?: string;
|
|
1698
|
+
/**
|
|
1699
|
+
* Image and Dockerfile forms: the user commands, terminals and SSH sessions in its sandboxes
|
|
1700
|
+
* run as when they name none. A user name or uid, with an optional `:group` or `:gid`, as an
|
|
1701
|
+
* image's `USER` takes it; `root` for root. Default: the image's own `USER`, as `docker run`
|
|
1702
|
+
* does (root when it sets none).
|
|
1703
|
+
*/
|
|
1704
|
+
user?: string;
|
|
1705
|
+
/**
|
|
1706
|
+
* Image and Dockerfile forms: the user its `start`, `setup` and `verify` run as (for example
|
|
1707
|
+
* the snapshot's own `user`). Default: `root`.
|
|
1708
|
+
*/
|
|
1709
|
+
setupUser?: string;
|
|
1664
1710
|
alias?: string;
|
|
1665
1711
|
/**
|
|
1666
1712
|
* A DEFAULT grader for this snapshot: a run on it with no `grade` of its own uses this one.
|
|
@@ -1951,12 +1997,15 @@ export declare class PromigenceClient {
|
|
|
1951
1997
|
timeoutMs?: number;
|
|
1952
1998
|
}): Promise<StatusResponse>;
|
|
1953
1999
|
/**
|
|
1954
|
-
* `GET /pricing` —
|
|
1955
|
-
* its price, the build price, the limits, the free credit and the plan they
|
|
1956
|
-
* every call from what billing charges.
|
|
1957
|
-
*
|
|
2000
|
+
* `GET /pricing` — your organization's price list, live: the per-second rates, every sandbox
|
|
2001
|
+
* shape on sale and its price, the build price, the limits, the free credit and the plan they
|
|
2002
|
+
* come from, read on every call from what billing charges. Needs a key: without one this throws
|
|
2003
|
+
* `AuthError` and sends nothing. The published list prices are on the pricing page.
|
|
1958
2004
|
*
|
|
1959
2005
|
* Prices change: read them when you need them rather than keeping a copy.
|
|
2006
|
+
*
|
|
2007
|
+
* @param opts.newAccount No longer supported: there is no key-less price list. Passing `true`
|
|
2008
|
+
* throws `InvalidArgumentError` and sends nothing.
|
|
1960
2009
|
*/
|
|
1961
2010
|
pricing(opts?: {
|
|
1962
2011
|
newAccount?: boolean;
|
|
@@ -2000,6 +2049,16 @@ export declare class PromigenceClient {
|
|
|
2000
2049
|
limit?: number;
|
|
2001
2050
|
cursor?: string;
|
|
2002
2051
|
}) => Promise<Page<LedgerEntry>>;
|
|
2052
|
+
/**
|
|
2053
|
+
* `GET /billing/history` — the credits the organization received and its payments, newest
|
|
2054
|
+
* first (one page). `kind` narrows it to `credits` or `payments`; feed `nextCursor` back as
|
|
2055
|
+
* `cursor` until it is `null`. An owner's key.
|
|
2056
|
+
*/
|
|
2057
|
+
historyPage: (opts?: {
|
|
2058
|
+
kind?: BillingHistoryKind;
|
|
2059
|
+
limit?: number;
|
|
2060
|
+
cursor?: string;
|
|
2061
|
+
}) => Promise<Page<BillingHistoryEntry>>;
|
|
2003
2062
|
/** `POST /billing/checkout` — the hosted page that puts a card on file. */
|
|
2004
2063
|
checkout: () => Promise<{
|
|
2005
2064
|
url: string;
|
|
@@ -2400,6 +2459,18 @@ export declare class PromigenceClient {
|
|
|
2400
2459
|
onData?: (data: Uint8Array) => void;
|
|
2401
2460
|
onExit?: (exit: PtyExit) => void;
|
|
2402
2461
|
timeoutMs?: number;
|
|
2462
|
+
/** Pick the same terminal up again when its connection drops or the service is updated;
|
|
2463
|
+
* see `PtyConnectOpts.resume`. Default: on. */
|
|
2464
|
+
resume?: boolean;
|
|
2465
|
+
/** Reconnect in a new shell when the service is updated (a WebSocket `Close` 1012) and the
|
|
2466
|
+
* terminal cannot be picked up again; see `PtyConnectOpts.reconnect`. Default: on without
|
|
2467
|
+
* `cmd`, off with one. */
|
|
2468
|
+
reconnect?: boolean;
|
|
2469
|
+
/** Called after each reconnect: `resumed` says whether it is the same terminal or a new
|
|
2470
|
+
* shell. */
|
|
2471
|
+
onReconnected?: (how: {
|
|
2472
|
+
resumed: boolean;
|
|
2473
|
+
}) => void;
|
|
2403
2474
|
}) => Promise<PtySession>;
|
|
2404
2475
|
/** `GET /sandboxes/:id`. */
|
|
2405
2476
|
get: (sandboxId: string) => Promise<SandboxRecord>;
|
|
@@ -2475,6 +2546,12 @@ export declare class PromigenceClient {
|
|
|
2475
2546
|
/**
|
|
2476
2547
|
* `POST /sandboxes/:id/probe` — is this environment still the one that was verified?
|
|
2477
2548
|
*
|
|
2549
|
+
* Compares the sandbox's files under the paths its snapshot's verification recorded with what
|
|
2550
|
+
* was recorded. It is the same check a failed run gets, which decides whether the failure was
|
|
2551
|
+
* the environment's (`integrity_mismatch`, not billed) or a path the snapshot's own `verify`
|
|
2552
|
+
* never covered (`verify_did_not_cover`, billed). Asking for it up front is cheaper than
|
|
2553
|
+
* spending a run to find out.
|
|
2554
|
+
*
|
|
2478
2555
|
* `first_bad_path` names the **first differing path**, which is the only part of a failed
|
|
2479
2556
|
* probe anyone can act on.
|
|
2480
2557
|
*/
|
|
@@ -2485,8 +2562,7 @@ export declare class PromigenceClient {
|
|
|
2485
2562
|
ok: boolean;
|
|
2486
2563
|
ms: number;
|
|
2487
2564
|
checked: string | string[];
|
|
2488
|
-
first_bad_path
|
|
2489
|
-
probe: Record<string, unknown>;
|
|
2565
|
+
first_bad_path?: string | null;
|
|
2490
2566
|
}>;
|
|
2491
2567
|
/** `DELETE /sandboxes/:id`. */
|
|
2492
2568
|
kill: (sandboxId: string) => Promise<unknown>;
|
|
@@ -2623,9 +2699,11 @@ export declare class PromigenceClient {
|
|
|
2623
2699
|
/**
|
|
2624
2700
|
* Write a file of **any size**, in [`FILE_PUT_CHUNK_BYTES`] requests.
|
|
2625
2701
|
*
|
|
2626
|
-
*
|
|
2627
|
-
*
|
|
2628
|
-
*
|
|
2702
|
+
* Chunk 0 goes without `?offset=`, which means "this is the file" (create, truncate);
|
|
2703
|
+
* every later chunk carries one, which appends, so a file has no size limit here.
|
|
2704
|
+
*
|
|
2705
|
+
* A failure mid-loop leaves a **prefix** in the sandbox, not a corrupt file. Re-running the
|
|
2706
|
+
* same `put` is safe: chunk 0 truncates.
|
|
2629
2707
|
*
|
|
2630
2708
|
* `mode` (`0o755`) sets the file's permission bits. Without it a file that already exists
|
|
2631
2709
|
* keeps its mode, as a shell `>` does, and a new one is `0o644`.
|
|
@@ -2735,8 +2813,7 @@ export declare class PromigenceClient {
|
|
|
2735
2813
|
ok: boolean;
|
|
2736
2814
|
ms: number;
|
|
2737
2815
|
checked: string | string[];
|
|
2738
|
-
first_bad_path
|
|
2739
|
-
probe: Record<string, unknown>;
|
|
2816
|
+
first_bad_path?: string | null;
|
|
2740
2817
|
}>;
|
|
2741
2818
|
};
|
|
2742
2819
|
/**
|
|
@@ -2883,15 +2960,14 @@ export declare class PromigenceClient {
|
|
|
2883
2960
|
manifest: (idOrAlias: string) => Promise<SnapshotManifestResponse>;
|
|
2884
2961
|
/**
|
|
2885
2962
|
* The `.promigence` bundle for a snapshot: the sealed form, which names exactly one
|
|
2886
|
-
* environment and verifies offline (`verifyBundle`).
|
|
2887
|
-
*
|
|
2888
|
-
* same document.
|
|
2963
|
+
* environment and verifies offline (`verifyBundle`). This client composes it from the
|
|
2964
|
+
* snapshot's sealed manifest (`served_by: "client"`); the API serves no bundle of its own.
|
|
2889
2965
|
*/
|
|
2890
2966
|
bundle: (idOrAlias: string, opts?: {
|
|
2891
2967
|
alias?: string;
|
|
2892
2968
|
}) => Promise<{
|
|
2893
2969
|
bundle: Bundle;
|
|
2894
|
-
served_by: "
|
|
2970
|
+
served_by: "client";
|
|
2895
2971
|
record?: SnapshotRecord;
|
|
2896
2972
|
}>;
|
|
2897
2973
|
};
|
package/dist/client.js
CHANGED
|
@@ -2,8 +2,8 @@ import { readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { inspect } from "node:util";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { ApiError, AuthError, EnvFailedError, PromigenceError, InvalidArgumentError, NotFoundError, NotReleasedError, NotVerifiedError, RateLimitError, SpendCapError, TimeoutError, UnreachableError, DEFAULT_API_URL, ERRORS_DOCS_URL, FILE_PUT_CHUNK_BYTES, MAX_OUTPUT_BYTES_DEFAULT, VERSION, WAIT_TIMEOUT_MS, PreviewsUnavailableError, exitForCode, previewsOff, withPreviewsFix, } from "./core.js";
|
|
6
|
-
import {
|
|
5
|
+
import { ApiError, AuthError, EnvFailedError, PromigenceError, InvalidArgumentError, NotFoundError, NotReleasedError, NotVerifiedError, RateLimitError, ServiceRestartError, SpendCapError, TimeoutError, UnreachableError, DEFAULT_API_URL, ERRORS_DOCS_URL, FILE_PUT_CHUNK_BYTES, MAX_OUTPUT_BYTES_DEFAULT, VERSION, WAIT_TIMEOUT_MS, PreviewsUnavailableError, exitForCode, previewsOff, withPreviewsFix, } from "./core.js";
|
|
6
|
+
import { buildBundle } from "./bundle.js";
|
|
7
7
|
import { keychainReadCached, keychainStore, storeLabel } from "./credstore.js";
|
|
8
8
|
import { CALLER_HEADER, resolveCaller, userAgentWithCaller } from "./caller.js";
|
|
9
9
|
import { connectPty } from "./pty.js";
|
|
@@ -135,7 +135,7 @@ function decodeCredentials(buf) {
|
|
|
135
135
|
return buf.subarray(3).toString("utf8");
|
|
136
136
|
return buf.toString("utf8");
|
|
137
137
|
}
|
|
138
|
-
export function storedCredentials(env = process.env) {
|
|
138
|
+
export function storedCredentials(env = process.env, platform = process.platform) {
|
|
139
139
|
const path = credentialsPath(env);
|
|
140
140
|
let present = true;
|
|
141
141
|
try {
|
|
@@ -145,7 +145,7 @@ export function storedCredentials(env = process.env) {
|
|
|
145
145
|
present = e.code !== "ENOENT";
|
|
146
146
|
}
|
|
147
147
|
if (!present) {
|
|
148
|
-
const store = keychainStore(env);
|
|
148
|
+
const store = keychainStore(env, platform);
|
|
149
149
|
if (store === undefined)
|
|
150
150
|
return undefined;
|
|
151
151
|
const r = keychainReadCached(store, path, env);
|
|
@@ -259,8 +259,6 @@ export function keyOrigin(env = process.env) {
|
|
|
259
259
|
const stored = readCredentialsWithStore(env);
|
|
260
260
|
if (stored.creds.api_key)
|
|
261
261
|
return stored.store === "macos-keychain" ? "macOS Keychain" : stored.store === "secret-service" ? "Secret Service keyring" : "credentials file";
|
|
262
|
-
if (compatApiKey(env["E2B_API_KEY"]))
|
|
263
|
-
return "E2B_API_KEY (compat)";
|
|
264
262
|
return undefined;
|
|
265
263
|
}
|
|
266
264
|
function resolveCredentials(opts, env = process.env) {
|
|
@@ -269,17 +267,19 @@ function resolveCredentials(opts, env = process.env) {
|
|
|
269
267
|
const t = u.replace(/\/+$/, "");
|
|
270
268
|
return /^https?:\/\/[^/?#]+$/i.test(t) ? `${t}/v1` : t;
|
|
271
269
|
};
|
|
272
|
-
const { creds, store } = readCredentialsWithStore(env);
|
|
273
270
|
const envKey = set(env["PROMIGENCE_API_KEY"]?.trim());
|
|
274
271
|
const envUrl = set(env["PROMIGENCE_API_URL"]);
|
|
275
272
|
const optKey = opts.apiKey?.trim();
|
|
273
|
+
const given = optKey !== undefined || envKey !== undefined;
|
|
274
|
+
const { creds, store } = given
|
|
275
|
+
? { creds: {}, store: undefined }
|
|
276
|
+
: readCredentialsWithStore(env);
|
|
276
277
|
const source = optKey !== undefined ? "opts" : envKey !== undefined ? "env" : creds.api_key !== undefined ? "file" : undefined;
|
|
277
278
|
const ours = optKey ?? envKey ?? creds.api_key;
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const fromFile = trim(creds.api_url ?? compatUrl ?? DEFAULT_API_URL);
|
|
279
|
+
const fallback = ours === undefined ? opts.fallbackKey : undefined;
|
|
280
|
+
const fallbackKey = set(fallback?.apiKey.trim());
|
|
281
|
+
const fallbackUrl = fallbackKey === undefined ? undefined : set(fallback?.apiUrl);
|
|
282
|
+
const fromFile = trim(creds.api_url ?? fallbackUrl ?? DEFAULT_API_URL);
|
|
283
283
|
const fromEnv = envUrl !== undefined ? trim(envUrl) : fromFile;
|
|
284
284
|
const apiUrl = opts.apiUrl !== undefined ? trim(opts.apiUrl) : fromEnv;
|
|
285
285
|
const paired = source === "file" ? fromFile : source === "env" ? fromEnv : apiUrl;
|
|
@@ -291,10 +291,10 @@ function resolveCredentials(opts, env = process.env) {
|
|
|
291
291
|
: "Pass apiKey together with apiUrl, or set PROMIGENCE_API_URL to that URL instead of passing apiUrl.");
|
|
292
292
|
return { apiKey: undefined, apiUrl, refusal };
|
|
293
293
|
}
|
|
294
|
-
const key = ours ??
|
|
294
|
+
const key = ours ?? fallbackKey;
|
|
295
295
|
if (key) {
|
|
296
296
|
const from = source === "opts" ? "the apiKey option" : source === "env" ? "PROMIGENCE_API_KEY"
|
|
297
|
-
: source === "file" ? storedName(store, env) :
|
|
297
|
+
: source === "file" ? storedName(store, env) : fallback.from;
|
|
298
298
|
const refusal = keyRefusal(key, from);
|
|
299
299
|
if (refusal)
|
|
300
300
|
return { apiKey: undefined, apiUrl, refusal };
|
|
@@ -507,6 +507,12 @@ export function snapshotCreateBody(opts) {
|
|
|
507
507
|
if (o.env !== undefined && Object.keys(o.env).length && forms[0] === "repo") {
|
|
508
508
|
throw new InvalidArgumentError("snapshots.create env is for an image or a Dockerfile", "A repository snapshot sets its environment in its setup steps, or pass env on each command.");
|
|
509
509
|
}
|
|
510
|
+
if ((o.user !== undefined || o.setupUser !== undefined) && forms[0] === "repo") {
|
|
511
|
+
throw new InvalidArgumentError("snapshots.create user and setupUser are for an image or a Dockerfile", "A repository snapshot runs as root; name a user on each command instead ({ user: 'app' }).");
|
|
512
|
+
}
|
|
513
|
+
if (o.setupUser !== undefined && o.user === undefined) {
|
|
514
|
+
throw new InvalidArgumentError("snapshots.create setupUser needs user", "Pass { user: 'app', setupUser: 'app' }: start, setup and verify as app too (without setupUser they run as root), every later command as app.");
|
|
515
|
+
}
|
|
510
516
|
if (!github && (o.ref !== undefined || o.from !== undefined || o.track)) {
|
|
511
517
|
throw new InvalidArgumentError("snapshots.create ref, from and track are for a connected repository (repo: 'github:owner/name' or 'gitlab:group/project')", "Pass { repo: 'github:your-org/app', ref: 'main' }, or drop them for this form.");
|
|
512
518
|
}
|
|
@@ -516,7 +522,7 @@ export function snapshotCreateBody(opts) {
|
|
|
516
522
|
setup: o.setup, verify, tier: o.tier, alias: o.alias,
|
|
517
523
|
others: [
|
|
518
524
|
["sources", Boolean(o.sources?.length)], ["start", o.start !== undefined],
|
|
519
|
-
["registrySecret", Boolean(o.registrySecret)],
|
|
525
|
+
["registrySecret", Boolean(o.registrySecret)],
|
|
520
526
|
],
|
|
521
527
|
});
|
|
522
528
|
}
|
|
@@ -564,11 +570,13 @@ export function snapshotCreateBody(opts) {
|
|
|
564
570
|
body["registry_secret"] = o.registrySecret;
|
|
565
571
|
if (o.env !== undefined && Object.keys(o.env).length)
|
|
566
572
|
body["env"] = { ...o.env };
|
|
573
|
+
if (o.user !== undefined)
|
|
574
|
+
body["user"] = o.user;
|
|
575
|
+
if (o.setupUser !== undefined)
|
|
576
|
+
body["setup_user"] = o.setupUser;
|
|
567
577
|
}
|
|
568
578
|
if (o.alias)
|
|
569
579
|
body["alias"] = o.alias;
|
|
570
|
-
if (o.cpuClass)
|
|
571
|
-
body["cpu_class"] = o.cpuClass;
|
|
572
580
|
if (o.grade) {
|
|
573
581
|
if (!o.grade.cmd)
|
|
574
582
|
throw new InvalidArgumentError("snapshots.create grade needs a cmd", "Pass { grade: { cmd: 'pytest -q tests/' } }.");
|
|
@@ -644,6 +652,7 @@ export function errorFor(code, message, fix, extra = {}) {
|
|
|
644
652
|
case "invalid_argument":
|
|
645
653
|
return attach(new InvalidArgumentError(message, extra.cli ? fix : withoutCliHelp(fix), code));
|
|
646
654
|
case "not_released": return attach(new NotReleasedError(message));
|
|
655
|
+
case "service_restart": return attach(new ServiceRestartError(message, fix));
|
|
647
656
|
default:
|
|
648
657
|
if (ENV_CODES.has(code) || extra.category === "env")
|
|
649
658
|
return attach(new EnvFailedError(code, message, fix));
|
|
@@ -1207,12 +1216,14 @@ export class PromigenceClient {
|
|
|
1207
1216
|
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
1208
1217
|
});
|
|
1209
1218
|
}
|
|
1210
|
-
pricing(opts = {}) {
|
|
1211
|
-
|
|
1219
|
+
async pricing(opts = {}) {
|
|
1220
|
+
if (opts.newAccount === true) {
|
|
1221
|
+
throw new InvalidArgumentError("pricing({ newAccount: true }) is no longer supported: the price list needs a key", "Call pricing() with your API key for your organization's prices. The published list prices are on the pricing page, and `promigence pricing` prints them without a key.");
|
|
1222
|
+
}
|
|
1223
|
+
this.requireKey();
|
|
1212
1224
|
return this.request({
|
|
1213
1225
|
method: "GET",
|
|
1214
1226
|
path: "/pricing",
|
|
1215
|
-
...(anonymous ? { anonymous: true } : {}),
|
|
1216
1227
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
1217
1228
|
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
1218
1229
|
});
|
|
@@ -1248,6 +1259,14 @@ export class PromigenceClient {
|
|
|
1248
1259
|
const r = await this.request({ method: "GET", path: `/me/ledger?${pageQuery(limit, 1000, opts.cursor)}` });
|
|
1249
1260
|
return pageOf(r, "entries", limit);
|
|
1250
1261
|
},
|
|
1262
|
+
historyPage: async (opts = {}) => {
|
|
1263
|
+
const limit = opts.limit ?? 50;
|
|
1264
|
+
const q = new URLSearchParams(pageQuery(limit, 100, opts.cursor));
|
|
1265
|
+
if (opts.kind !== undefined && opts.kind !== "all")
|
|
1266
|
+
q.set("kind", opts.kind);
|
|
1267
|
+
const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
|
|
1268
|
+
return pageOf(r, "entries", limit);
|
|
1269
|
+
},
|
|
1251
1270
|
checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
|
|
1252
1271
|
portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
|
|
1253
1272
|
referrals: () => this.request({ method: "GET", path: "/referrals" }),
|
|
@@ -1742,6 +1761,10 @@ export class PromigenceClient {
|
|
|
1742
1761
|
on.heartbeat?.();
|
|
1743
1762
|
continue;
|
|
1744
1763
|
}
|
|
1764
|
+
if (event === "reconnect") {
|
|
1765
|
+
reader.cancel().catch(() => { });
|
|
1766
|
+
throw new ServiceRestartError(`a service restart stopped the command on sandbox ${sandboxId} while its output was streaming`, "Not billed: the time the command ran is credited back. The sandbox kept running; the command may have done part of its work before it stopped: run it again if that is safe.");
|
|
1767
|
+
}
|
|
1745
1768
|
let parsed = {};
|
|
1746
1769
|
try {
|
|
1747
1770
|
parsed = JSON.parse(data);
|
|
@@ -1977,6 +2000,9 @@ export class PromigenceClient {
|
|
|
1977
2000
|
...(opts.onData ? { onData: opts.onData } : {}),
|
|
1978
2001
|
...(opts.onExit ? { onExit: opts.onExit } : {}),
|
|
1979
2002
|
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
2003
|
+
...(opts.resume === undefined ? {} : { resume: opts.resume }),
|
|
2004
|
+
...(opts.reconnect === undefined ? {} : { reconnect: opts.reconnect }),
|
|
2005
|
+
...(opts.onReconnected ? { onReconnected: opts.onReconnected } : {}),
|
|
1980
2006
|
refuse: (status, text, retryAfter) => {
|
|
1981
2007
|
let payload = {};
|
|
1982
2008
|
try {
|
|
@@ -2414,19 +2440,6 @@ export class PromigenceClient {
|
|
|
2414
2440
|
},
|
|
2415
2441
|
manifest: (idOrAlias) => this.request({ method: "GET", path: `/snapshots/${seg(idOrAlias)}/manifest` }),
|
|
2416
2442
|
bundle: async (idOrAlias, opts = {}) => {
|
|
2417
|
-
try {
|
|
2418
|
-
const r = await this.request({
|
|
2419
|
-
method: "GET", path: `/snapshots/${seg(idOrAlias)}/bundle`,
|
|
2420
|
-
});
|
|
2421
|
-
if (r?.bundle?.schema === BUNDLE_SCHEMA) {
|
|
2422
|
-
return { bundle: r.bundle, served_by: "control" };
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
catch (e) {
|
|
2426
|
-
const code = e?.code;
|
|
2427
|
-
if (code !== "not_found" && code !== "enoent" && e?.status !== 405)
|
|
2428
|
-
throw e;
|
|
2429
|
-
}
|
|
2430
2443
|
const [record, m] = await Promise.all([
|
|
2431
2444
|
this.snapshots.get(idOrAlias).catch(() => undefined),
|
|
2432
2445
|
this.snapshots.manifest(idOrAlias),
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Promigence SDK — constants, error hierarchy and shared types. */
|
|
2
|
-
export declare const VERSION = "0.1.0-beta.
|
|
2
|
+
export declare const VERSION = "0.1.0-beta.8";
|
|
3
3
|
export declare const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
/** The error reference; each code is an anchor on it (the API's `docs` link has the same form). */
|
|
5
5
|
export declare const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
|
@@ -190,6 +190,15 @@ export declare class TimeoutError extends PromigenceError {
|
|
|
190
190
|
runId?: string;
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* A service update may stop a command that was running while its output was streaming to you
|
|
195
|
+
* (`execStream`, `commands.run` with output callbacks); the time it ran is credited back, and the
|
|
196
|
+
* sandbox keeps running. `retryable` is `false`: the command may have done part of its work before
|
|
197
|
+
* it stopped, so run it again only if that is safe. exit 3
|
|
198
|
+
*/
|
|
199
|
+
export declare class ServiceRestartError extends PromigenceError {
|
|
200
|
+
constructor(message: string, fix: string);
|
|
201
|
+
}
|
|
193
202
|
/** An API error with no more specific class (5xx, unknown code). exit from the table. */
|
|
194
203
|
export declare class ApiError extends PromigenceError {
|
|
195
204
|
}
|
package/dist/core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hintArg } from "./hint.js";
|
|
2
|
-
export const VERSION = "0.1.0-beta.
|
|
2
|
+
export const VERSION = "0.1.0-beta.8";
|
|
3
3
|
export const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
export const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
|
5
5
|
export const DEFAULT_API_URL = "https://api.promigence.ai/v1";
|
|
@@ -194,6 +194,11 @@ export class TimeoutError extends PromigenceError {
|
|
|
194
194
|
this.runId = ids.runId;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
+
export class ServiceRestartError extends PromigenceError {
|
|
198
|
+
constructor(message, fix) {
|
|
199
|
+
super({ code: "service_restart", message, fix, exitCode: 3, category: "env", retryable: false });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
197
202
|
export class ApiError extends PromigenceError {
|
|
198
203
|
}
|
|
199
204
|
export class NotReleasedError extends PromigenceError {
|
|
@@ -210,6 +215,7 @@ export const EXIT_BY_CODE = {
|
|
|
210
215
|
invalid_argument: 2, unsupported: 2, not_released: 1, task_failed: 4,
|
|
211
216
|
env_failed: 3, snapshot_rejected: 6, build_failed: 3, snapshot_failed: 4,
|
|
212
217
|
not_connected: 7,
|
|
218
|
+
service_restart: 3,
|
|
213
219
|
fork_failed: 3, start_timeout: 3, readiness_failed: 3, verify_too_slow: 3,
|
|
214
220
|
verify_each_failed: 3, toolchain_mismatch: 3, sandbox_unresponsive: 3, oom_within_tier: 3,
|
|
215
221
|
disk_full_within_tier: 3, sandbox_interrupted: 3, integrity_mismatch: 3, egress_unavailable: 3,
|
|
@@ -226,7 +232,7 @@ export const EXIT_BY_CODE = {
|
|
|
226
232
|
nonzero_exit: 4, grade_not_passed: 4, task_timeout: 4, oom_over_tier: 4, disk_over_tier: 4,
|
|
227
233
|
shutdown_by_task: 4, modified_by_task: 4,
|
|
228
234
|
cap_exceeded: 5, cap_killed: 12, cap_paused: 5, credits_paused: 5,
|
|
229
|
-
aborted_by_client: 130, expired: 12, not_started: 12, superseded: 0,
|
|
235
|
+
aborted_by_client: 130, stopped_by_support: 12, expired: 12, not_started: 12, superseded: 0,
|
|
230
236
|
usage: 2, internal: 1, auth: 7, not_found: 8, unreachable: 9, rate_limited: 10,
|
|
231
237
|
timeout: 11, not_verified: 6, enoent: 8,
|
|
232
238
|
snapshot_incompatible: 6, storage_limit_reached: 5,
|
|
@@ -236,10 +242,11 @@ export const EXIT_BY_CODE = {
|
|
|
236
242
|
build_timeout: 4,
|
|
237
243
|
image_unresolvable: 2,
|
|
238
244
|
registry_unavailable: 3,
|
|
239
|
-
trial_credits_exhausted: 5, card_required: 5, credit_unclaimed: 5, concurrency_limit: 5, egress_cap_reached:
|
|
245
|
+
trial_credits_exhausted: 5, card_required: 5, credit_unclaimed: 5, concurrency_limit: 5, egress_cap_reached: 5,
|
|
240
246
|
billing_unavailable: 5,
|
|
241
247
|
project_cap_reached: 5,
|
|
242
248
|
spend_limit_reached: 5,
|
|
249
|
+
billing_hold: 5,
|
|
243
250
|
tier_over_plan: 2,
|
|
244
251
|
network_host_refused: 9, network_policy_unsupported: 3,
|
|
245
252
|
secrets_in_snapshot: 2, secrets_unavailable: 3,
|
|
@@ -251,6 +258,8 @@ export const EXIT_BY_CODE = {
|
|
|
251
258
|
invite_invalid: 7,
|
|
252
259
|
invite_required: 7,
|
|
253
260
|
key_expired: 7,
|
|
261
|
+
account_blocked: 7,
|
|
262
|
+
setup_required: 7,
|
|
254
263
|
};
|
|
255
264
|
export function exitForCode(code) {
|
|
256
265
|
return Object.prototype.hasOwnProperty.call(EXIT_BY_CODE, code) ? EXIT_BY_CODE[code] : 1;
|
package/dist/credstore.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* the credentials file (`~/.config/promigence/credentials`, 0600). The CLI writes it
|
|
4
4
|
* (`promigence login`, `promigence auth login`, `promigence logout`); the CLI and both SDKs read it.
|
|
5
5
|
*
|
|
6
|
-
* macOS the login Keychain, through `security` (when
|
|
6
|
+
* macOS the login Keychain, through `security` (when the user's home holds one: HOME,
|
|
7
|
+
* or the account's own home when the environment has no HOME)
|
|
7
8
|
* Linux and BSD the Secret Service (GNOME Keyring, KWallet), through `secret-tool`, when a
|
|
8
9
|
* desktop session bus is there (a CI job or an SSH shell has none: the file)
|
|
9
10
|
* Windows the file: no tool that ships with Windows can read a stored secret back
|
|
@@ -34,7 +35,7 @@ export declare function storeLabel(store: CredentialStore, path?: string): strin
|
|
|
34
35
|
* The keychain this process stores a sign-in in, or undefined for the file. `auto` is decided by
|
|
35
36
|
* the platform and by whether its tool (and, for the Secret Service, a session bus) is there.
|
|
36
37
|
*/
|
|
37
|
-
export declare function keychainStore(env?: NodeJS.ProcessEnv, platform?: string): KeychainStore | undefined;
|
|
38
|
+
export declare function keychainStore(env?: NodeJS.ProcessEnv, platform?: string, home?: () => string): KeychainStore | undefined;
|
|
38
39
|
/**
|
|
39
40
|
* The keychains a sign-out (or a sign-in that lands in the file) must clear for this path: the one
|
|
40
41
|
* in use, and the platform's own even when `PROMIGENCE_CREDENTIAL_STORE=file` (an item a previous
|
package/dist/credstore.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { accessSync, constants, existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
3
4
|
import { delimiter, join } from "node:path";
|
|
4
5
|
export const KEYCHAIN_SERVICE = "promigence-cli";
|
|
5
6
|
const KEYCHAIN_LABEL = "Promigence CLI credentials";
|
|
@@ -30,8 +31,19 @@ function sessionBus(env) {
|
|
|
30
31
|
const run = env["XDG_RUNTIME_DIR"];
|
|
31
32
|
return Boolean(run) && existsSync(join(run, "bus"));
|
|
32
33
|
}
|
|
34
|
+
function userHome(env, home = homedir) {
|
|
35
|
+
const h = env["HOME"];
|
|
36
|
+
if (h)
|
|
37
|
+
return h;
|
|
38
|
+
try {
|
|
39
|
+
return home() || undefined;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
33
45
|
const warnedSetting = new Set();
|
|
34
|
-
export function keychainStore(env = process.env, platform = process.platform) {
|
|
46
|
+
export function keychainStore(env = process.env, platform = process.platform, home = homedir) {
|
|
35
47
|
const raw = (env["PROMIGENCE_CREDENTIAL_STORE"] ?? "").trim().toLowerCase();
|
|
36
48
|
switch (raw) {
|
|
37
49
|
case "file": return undefined;
|
|
@@ -46,8 +58,8 @@ export function keychainStore(env = process.env, platform = process.platform) {
|
|
|
46
58
|
}
|
|
47
59
|
}
|
|
48
60
|
if (platform === "darwin") {
|
|
49
|
-
const
|
|
50
|
-
return
|
|
61
|
+
const h = userHome(env, home);
|
|
62
|
+
return h && existsSync(join(h, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
|
|
51
63
|
}
|
|
52
64
|
if (platform === "win32")
|
|
53
65
|
return undefined;
|
|
@@ -94,8 +106,9 @@ function runTool(store, args, env, input, timeout) {
|
|
|
94
106
|
const bin = onPath(toolName(store), env);
|
|
95
107
|
if (bin === undefined)
|
|
96
108
|
throw Object.assign(new Error(`${toolName(store)} not found`), { code: "ENOENT" });
|
|
109
|
+
const h = store === "macos-keychain" && !env["HOME"] ? userHome(env) : undefined;
|
|
97
110
|
return execFileSync(bin, args, {
|
|
98
|
-
encoding: "utf8", timeout, env,
|
|
111
|
+
encoding: "utf8", timeout, env: h ? { ...env, HOME: h } : env,
|
|
99
112
|
...(input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input, stdio: ["pipe", "pipe", "pipe"] }),
|
|
100
113
|
});
|
|
101
114
|
}
|