@promigence/sdk 0.1.0-beta.7 → 0.1.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,8 +13,9 @@ npm i @promigence/sdk
13
13
  ```
14
14
 
15
15
  You need a Promigence account (private beta, by invite). The quickest way to create one is the CLI:
16
- `npm i -g promigence`, then `promigence signup`. The SDK reads the key from `PROMIGENCE_API_KEY` or
17
- from where the CLI stored it: the system keychain, or `~/.config/promigence/credentials`.
16
+ `npm i -g promigence`, then `promigence signup`. The SDK uses the key you pass (`apiKey`) or
17
+ `PROMIGENCE_API_KEY`; with neither, the key the CLI stored: the system keychain, or
18
+ `~/.config/promigence/credentials`. A key you pass or set is used as is; the stored one is not read.
18
19
 
19
20
  ## Use it
20
21
 
@@ -420,7 +420,19 @@ export interface SnapshotBuild {
420
420
  /** `true` when the snapshot this build made has since been deleted. */
421
421
  snapshot_deleted?: boolean;
422
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
+ */
423
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;
424
436
  repo?: string;
425
437
  commit?: string;
426
438
  verification?: Record<string, unknown>;
@@ -748,6 +760,8 @@ export interface CreditOffer {
748
760
  state: string;
749
761
  credit_usd: number;
750
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;
751
765
  expires_at?: string | null;
752
766
  campaign?: string | null;
753
767
  message: string;
@@ -845,7 +859,10 @@ export interface PricingView {
845
859
  gib_second_usd: number;
846
860
  preemptible_vcpu_second_usd: number;
847
861
  preemptible_gib_second_usd: number;
848
- /** One snapshot build, charged once it verifies. */
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
+ */
849
866
  build_usd: number;
850
867
  /** Per GiB of data out above the month's allowance; `null` when not priced. */
851
868
  egress_gb_usd: number | null;
@@ -870,6 +887,8 @@ export interface PricingView {
870
887
  max_egress_gb_month: number | null;
871
888
  /** Outbound mail ports open. */
872
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;
873
892
  };
874
893
  /** The free credit a new account gets (an account's own: `GET /credits/offer`). */
875
894
  free_credit: {
@@ -906,6 +925,16 @@ export interface BillingUsage {
906
925
  brand?: string;
907
926
  last4?: string;
908
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;
909
938
  suspended?: boolean;
910
939
  suspend_reason?: string | null;
911
940
  period?: {
@@ -929,6 +958,10 @@ export interface BillingUsage {
929
958
  upcoming_invoice_error?: string | null;
930
959
  [extra: string]: unknown;
931
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";
932
965
  /** One metered row of `GET /me/ledger`. */
933
966
  export interface LedgerEntry {
934
967
  entry_id: string;
@@ -1431,7 +1464,11 @@ export interface PromigenceClientOpts {
1431
1464
  * From the CLI: `promigence --request-timeout DUR` or `PROMIGENCE_REQUEST_TIMEOUT`.
1432
1465
  */
1433
1466
  requestTimeoutMs?: number;
1434
- /** Retries on 429/503 only (default 3). */
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
+ */
1435
1472
  maxRetries?: number;
1436
1473
  /** Backoff base when the server sends no `Retry-After` (default 250 ms). */
1437
1474
  retryBaseMs?: number;
@@ -1869,7 +1906,7 @@ interface RequestOpts {
1869
1906
  signal?: AbortSignal;
1870
1907
  /** Skip the Authorization header (unauthenticated routes). */
1871
1908
  anonymous?: boolean;
1872
- /** Called with the response headers of the attempt that answered (not of a retried 429/503). */
1909
+ /** Called with the response headers of the attempt that answered (not of one that was retried). */
1873
1910
  onHeaders?: (headers: Headers) => void;
1874
1911
  /** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
1875
1912
  headers?: Record<string, string>;
@@ -1944,8 +1981,10 @@ export declare class PromigenceClient {
1944
1981
  * rule `request()` applies, for the calls that cannot go through it (byte transfers, the
1945
1982
  * opening of an exec stream, the terminal handshake). Retried: a 429, and a 503 that says
1946
1983
  * nothing started — any 503 for a request safe to send twice, else only one carrying a
1947
- * `Retry-After` (every refusal at the door sets one) or a refusal code. At most `maxRetries`
1948
- * times, and never a `Retry-After` longer than MAX_RETRY_SLEEP_MS: that one is the caller's.
1984
+ * `Retry-After` (every refusal at the door sets one) or a refusal code — and, for a request
1985
+ * safe to send twice, a 502 or 504 that is not the API's own answer (it carries no error
1986
+ * code). At most `maxRetries` times, and never a `Retry-After` longer than
1987
+ * MAX_RETRY_SLEEP_MS: that one is the caller's.
1949
1988
  */
1950
1989
  private refusalBackoff;
1951
1990
  /** A long-poll request must outlive its own `wait_ms`; everything else keeps the default. */
@@ -1982,7 +2021,7 @@ export declare class PromigenceClient {
1982
2021
  }): Promise<StatusResponse>;
1983
2022
  /**
1984
2023
  * `GET /pricing` — your organization's price list, live: the per-second rates, every sandbox
1985
- * shape on sale and its price, the build price, the limits, the free credit and the plan they
2024
+ * shape on sale and its price (a snapshot build bills at its tier's), the limits, the free credit and the plan they
1986
2025
  * come from, read on every call from what billing charges. Needs a key: without one this throws
1987
2026
  * `AuthError` and sends nothing. The published list prices are on the pricing page.
1988
2027
  *
@@ -2033,6 +2072,16 @@ export declare class PromigenceClient {
2033
2072
  limit?: number;
2034
2073
  cursor?: string;
2035
2074
  }) => Promise<Page<LedgerEntry>>;
2075
+ /**
2076
+ * `GET /billing/history` — the credits the organization received and its payments, newest
2077
+ * first (one page). `kind` narrows it to `credits` or `payments`; feed `nextCursor` back as
2078
+ * `cursor` until it is `null`. An owner's key.
2079
+ */
2080
+ historyPage: (opts?: {
2081
+ kind?: BillingHistoryKind;
2082
+ limit?: number;
2083
+ cursor?: string;
2084
+ }) => Promise<Page<BillingHistoryEntry>>;
2036
2085
  /** `POST /billing/checkout` — the hosted page that puts a card on file. */
2037
2086
  checkout: () => Promise<{
2038
2087
  url: string;
@@ -2433,6 +2482,18 @@ export declare class PromigenceClient {
2433
2482
  onData?: (data: Uint8Array) => void;
2434
2483
  onExit?: (exit: PtyExit) => void;
2435
2484
  timeoutMs?: number;
2485
+ /** Pick the same terminal up again when its connection drops or the service is updated;
2486
+ * see `PtyConnectOpts.resume`. Default: on. */
2487
+ resume?: boolean;
2488
+ /** Reconnect in a new shell when the service is updated (a WebSocket `Close` 1012) and the
2489
+ * terminal cannot be picked up again; see `PtyConnectOpts.reconnect`. Default: on without
2490
+ * `cmd`, off with one. */
2491
+ reconnect?: boolean;
2492
+ /** Called after each reconnect: `resumed` says whether it is the same terminal or a new
2493
+ * shell. */
2494
+ onReconnected?: (how: {
2495
+ resumed: boolean;
2496
+ }) => void;
2436
2497
  }) => Promise<PtySession>;
2437
2498
  /** `GET /sandboxes/:id`. */
2438
2499
  get: (sandboxId: string) => Promise<SandboxRecord>;
@@ -168,7 +168,7 @@ function decodeCredentials(buf) {
168
168
  return buf.subarray(3).toString("utf8");
169
169
  return buf.toString("utf8");
170
170
  }
171
- function storedCredentials(env = process.env) {
171
+ function storedCredentials(env = process.env, platform = process.platform) {
172
172
  const path = credentialsPath(env);
173
173
  let present = true;
174
174
  try {
@@ -178,7 +178,7 @@ function storedCredentials(env = process.env) {
178
178
  present = e.code !== "ENOENT";
179
179
  }
180
180
  if (!present) {
181
- const store = (0, credstore_js_1.keychainStore)(env);
181
+ const store = (0, credstore_js_1.keychainStore)(env, platform);
182
182
  if (store === undefined)
183
183
  return undefined;
184
184
  const r = (0, credstore_js_1.keychainReadCached)(store, path, env);
@@ -292,8 +292,6 @@ function keyOrigin(env = process.env) {
292
292
  const stored = readCredentialsWithStore(env);
293
293
  if (stored.creds.api_key)
294
294
  return stored.store === "macos-keychain" ? "macOS Keychain" : stored.store === "secret-service" ? "Secret Service keyring" : "credentials file";
295
- if (compatApiKey(env["E2B_API_KEY"]))
296
- return "E2B_API_KEY (compat)";
297
295
  return undefined;
298
296
  }
299
297
  function resolveCredentials(opts, env = process.env) {
@@ -302,17 +300,19 @@ function resolveCredentials(opts, env = process.env) {
302
300
  const t = u.replace(/\/+$/, "");
303
301
  return /^https?:\/\/[^/?#]+$/i.test(t) ? `${t}/v1` : t;
304
302
  };
305
- const { creds, store } = readCredentialsWithStore(env);
306
303
  const envKey = set(env["PROMIGENCE_API_KEY"]?.trim());
307
304
  const envUrl = set(env["PROMIGENCE_API_URL"]);
308
305
  const optKey = opts.apiKey?.trim();
306
+ const given = optKey !== undefined || envKey !== undefined;
307
+ const { creds, store } = given
308
+ ? { creds: {}, store: undefined }
309
+ : readCredentialsWithStore(env);
309
310
  const source = optKey !== undefined ? "opts" : envKey !== undefined ? "env" : creds.api_key !== undefined ? "file" : undefined;
310
311
  const ours = optKey ?? envKey ?? creds.api_key;
311
- const compat = ours === undefined ? compatApiKey(env["E2B_API_KEY"]) : undefined;
312
- const compatUrl = compat === undefined
313
- ? undefined
314
- : apiUrlFromCompatUrl(env["E2B_API_URL"]) ?? apiUrlFromCompatDomain(env["E2B_DOMAIN"]);
315
- const fromFile = trim(creds.api_url ?? compatUrl ?? core_js_1.DEFAULT_API_URL);
312
+ const fallback = ours === undefined ? opts.fallbackKey : undefined;
313
+ const fallbackKey = set(fallback?.apiKey.trim());
314
+ const fallbackUrl = fallbackKey === undefined ? undefined : set(fallback?.apiUrl);
315
+ const fromFile = trim(creds.api_url ?? fallbackUrl ?? core_js_1.DEFAULT_API_URL);
316
316
  const fromEnv = envUrl !== undefined ? trim(envUrl) : fromFile;
317
317
  const apiUrl = opts.apiUrl !== undefined ? trim(opts.apiUrl) : fromEnv;
318
318
  const paired = source === "file" ? fromFile : source === "env" ? fromEnv : apiUrl;
@@ -324,10 +324,10 @@ function resolveCredentials(opts, env = process.env) {
324
324
  : "Pass apiKey together with apiUrl, or set PROMIGENCE_API_URL to that URL instead of passing apiUrl.");
325
325
  return { apiKey: undefined, apiUrl, refusal };
326
326
  }
327
- const key = ours ?? compat;
327
+ const key = ours ?? fallbackKey;
328
328
  if (key) {
329
329
  const from = source === "opts" ? "the apiKey option" : source === "env" ? "PROMIGENCE_API_KEY"
330
- : source === "file" ? storedName(store, env) : "E2B_API_KEY";
330
+ : source === "file" ? storedName(store, env) : fallback.from;
331
331
  const refusal = keyRefusal(key, from);
332
332
  if (refusal)
333
333
  return { apiKey: undefined, apiUrl, refusal };
@@ -685,6 +685,7 @@ function errorFor(code, message, fix, extra = {}) {
685
685
  case "invalid_argument":
686
686
  return attach(new core_js_1.InvalidArgumentError(message, extra.cli ? fix : withoutCliHelp(fix), code));
687
687
  case "not_released": return attach(new core_js_1.NotReleasedError(message));
688
+ case "service_restart": return attach(new core_js_1.ServiceRestartError(message, fix));
688
689
  default:
689
690
  if (ENV_CODES.has(code) || extra.category === "env")
690
691
  return attach(new core_js_1.EnvFailedError(code, message, fix));
@@ -869,7 +870,8 @@ class PromigenceClient {
869
870
  refusalBackoff(status, retryAfterMs, code, attempt, replayable, signal) {
870
871
  if (attempt >= this.maxRetries || signal?.aborted)
871
872
  return undefined;
872
- if (status !== 429 && status !== 503)
873
+ const gateway = (status === 502 || status === 504) && replayable && code === undefined;
874
+ if (status !== 429 && status !== 503 && !gateway)
873
875
  return undefined;
874
876
  if ((retryAfterMs ?? 0) > MAX_RETRY_SLEEP_MS)
875
877
  return undefined;
@@ -992,12 +994,17 @@ class PromigenceClient {
992
994
  throw new core_js_1.UnreachableError(`cannot reach ${url}: ${transportDetail(e)}`);
993
995
  }
994
996
  const retryAfterMs = parseRetryAfter(headerOf(res, "retry-after"));
995
- let retryable = (res.status === 429 || res.status === 503) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
997
+ const gateway = (res.status === 502 || res.status === 504) && replayable;
998
+ let retryable = (res.status === 429 || res.status === 503 || gateway) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
996
999
  let body;
997
1000
  if (retryable && res.status === 503 && !replayable && retryAfterMs === undefined) {
998
1001
  body = await res.text().catch(() => "");
999
1002
  retryable = REFUSED_503_CODES.has(errorCodeOf(body) ?? "");
1000
1003
  }
1004
+ if (retryable && gateway) {
1005
+ body = await res.text().catch(() => "");
1006
+ retryable = errorCodeOf(body) === undefined;
1007
+ }
1001
1008
  if (retryable && attempt < this.maxRetries) {
1002
1009
  const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
1003
1010
  const jitter = Math.random();
@@ -1005,6 +1012,8 @@ class PromigenceClient {
1005
1012
  ? retryAfterMs + jitter * Math.min(250, this.retryBaseMs)
1006
1013
  : backoff * (0.5 + jitter * 0.5);
1007
1014
  this.stats.retries++;
1015
+ if (gateway && opts.method === "DELETE")
1016
+ resentDelete = true;
1008
1017
  if (body === undefined)
1009
1018
  try {
1010
1019
  await res.text();
@@ -1291,6 +1300,14 @@ class PromigenceClient {
1291
1300
  const r = await this.request({ method: "GET", path: `/me/ledger?${pageQuery(limit, 1000, opts.cursor)}` });
1292
1301
  return pageOf(r, "entries", limit);
1293
1302
  },
1303
+ historyPage: async (opts = {}) => {
1304
+ const limit = opts.limit ?? 50;
1305
+ const q = new URLSearchParams(pageQuery(limit, 100, opts.cursor));
1306
+ if (opts.kind !== undefined && opts.kind !== "all")
1307
+ q.set("kind", opts.kind);
1308
+ const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
1309
+ return pageOf(r, "entries", limit);
1310
+ },
1294
1311
  checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
1295
1312
  portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
1296
1313
  referrals: () => this.request({ method: "GET", path: "/referrals" }),
@@ -1785,6 +1802,10 @@ class PromigenceClient {
1785
1802
  on.heartbeat?.();
1786
1803
  continue;
1787
1804
  }
1805
+ if (event === "reconnect") {
1806
+ reader.cancel().catch(() => { });
1807
+ throw new core_js_1.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.");
1808
+ }
1788
1809
  let parsed = {};
1789
1810
  try {
1790
1811
  parsed = JSON.parse(data);
@@ -2020,6 +2041,9 @@ class PromigenceClient {
2020
2041
  ...(opts.onData ? { onData: opts.onData } : {}),
2021
2042
  ...(opts.onExit ? { onExit: opts.onExit } : {}),
2022
2043
  ...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
2044
+ ...(opts.resume === undefined ? {} : { resume: opts.resume }),
2045
+ ...(opts.reconnect === undefined ? {} : { reconnect: opts.reconnect }),
2046
+ ...(opts.onReconnected ? { onReconnected: opts.onReconnected } : {}),
2023
2047
  refuse: (status, text, retryAfter) => {
2024
2048
  let payload = {};
2025
2049
  try {
@@ -1,5 +1,5 @@
1
1
  /** Promigence SDK — constants, error hierarchy and shared types. */
2
- export declare const VERSION = "0.1.0-beta.7";
2
+ export declare const VERSION = "0.1.0-beta.9";
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";
@@ -14,12 +14,14 @@ export declare const WAIT_TIMEOUT_MS = 100000;
14
14
  */
15
15
  export declare const IMAGE_BUILD_TIMEOUT_MS: number;
16
16
  /**
17
- * The list price of one new snapshot build this SDK was built with, in USD (charged once it
18
- * verifies; a dedupe or a join is free). Prices change: the live one is `client.pricing()`'s
19
- * `rates.build_usd` (this key's organization's), and a build's own arrives as `build_price` in
20
- * the `POST /snapshots` answer. This is only the fallback when the API cannot be asked.
17
+ * The most one new snapshot build of the default tier can cost at the list prices this SDK was
18
+ * built with, in USD: its time limit (an hour) at that tier's per-second rate. A build is billed per
19
+ * second for the time it runs; reusing or joining an identical build is free. Prices change: the
20
+ * live figure is `client.pricing()`'s `rates.build_usd` (this key's organization's), and a build's
21
+ * own arrives as `build_price` in the `POST /snapshots` answer. This is only the fallback when the
22
+ * API cannot be asked.
21
23
  */
22
- export declare const IMAGE_BUILD_LIST_PRICE = 1;
24
+ export declare const IMAGE_BUILD_LIST_PRICE = 0.1656;
23
25
  /** Inline output budget for an exec before `truncated` + `_ref`. */
24
26
  export declare const MAX_OUTPUT_BYTES_DEFAULT: number;
25
27
  /** One request of a `files.put`. **Not a file-size limit** — `put` loops. */
@@ -190,6 +192,15 @@ export declare class TimeoutError extends PromigenceError {
190
192
  runId?: string;
191
193
  });
192
194
  }
195
+ /**
196
+ * A service update may stop a command that was running while its output was streaming to you
197
+ * (`execStream`, `commands.run` with output callbacks); the time it ran is credited back, and the
198
+ * sandbox keeps running. `retryable` is `false`: the command may have done part of its work before
199
+ * it stopped, so run it again only if that is safe. exit 3
200
+ */
201
+ export declare class ServiceRestartError extends PromigenceError {
202
+ constructor(message: string, fix: string);
203
+ }
193
204
  /** An API error with no more specific class (5xx, unknown code). exit from the table. */
194
205
  export declare class ApiError extends PromigenceError {
195
206
  }
package/dist/cjs/core.js CHANGED
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EXIT_BY_CODE = exports.NotReleasedError = exports.ApiError = exports.TimeoutError = exports.RateLimitError = exports.UnreachableError = exports.NotFoundError = exports.AuthError = exports.NotVerifiedError = exports.SpendCapError = exports.CommandExitError = exports.EnvFailedError = exports.PreviewsUnavailableError = exports.UnsupportedError = exports.InvalidArgumentError = exports.PromigenceError = exports.PREVIEWS_UNAVAILABLE = exports.FILE_PUT_CHUNK_BYTES = exports.MAX_OUTPUT_BYTES_DEFAULT = exports.IMAGE_BUILD_LIST_PRICE = exports.IMAGE_BUILD_TIMEOUT_MS = exports.WAIT_TIMEOUT_MS = exports.KEY_CONTACT = exports.DEFAULT_API_URL = exports.ERRORS_DOCS_URL = exports.DOCS_URL = exports.VERSION = void 0;
3
+ exports.EXIT_BY_CODE = exports.NotReleasedError = exports.ApiError = exports.ServiceRestartError = exports.TimeoutError = exports.RateLimitError = exports.UnreachableError = exports.NotFoundError = exports.AuthError = exports.NotVerifiedError = exports.SpendCapError = exports.CommandExitError = exports.EnvFailedError = exports.PreviewsUnavailableError = exports.UnsupportedError = exports.InvalidArgumentError = exports.PromigenceError = exports.PREVIEWS_UNAVAILABLE = exports.FILE_PUT_CHUNK_BYTES = exports.MAX_OUTPUT_BYTES_DEFAULT = exports.IMAGE_BUILD_LIST_PRICE = exports.IMAGE_BUILD_TIMEOUT_MS = exports.WAIT_TIMEOUT_MS = exports.KEY_CONTACT = exports.DEFAULT_API_URL = exports.ERRORS_DOCS_URL = exports.DOCS_URL = exports.VERSION = void 0;
4
4
  exports.previewsFix = previewsFix;
5
5
  exports.withPreviewsFix = withPreviewsFix;
6
6
  exports.fixBeyondMessage = fixBeyondMessage;
7
7
  exports.previewsOff = previewsOff;
8
8
  exports.exitForCode = exitForCode;
9
9
  const hint_js_1 = require("./hint.js");
10
- exports.VERSION = "0.1.0-beta.7";
10
+ exports.VERSION = "0.1.0-beta.9";
11
11
  exports.DOCS_URL = "https://promigence.ai";
12
12
  exports.ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
13
13
  exports.DEFAULT_API_URL = "https://api.promigence.ai/v1";
14
14
  exports.KEY_CONTACT = "support@promigence.ai";
15
15
  exports.WAIT_TIMEOUT_MS = 100_000;
16
16
  exports.IMAGE_BUILD_TIMEOUT_MS = 15 * 60_000;
17
- exports.IMAGE_BUILD_LIST_PRICE = 1.0;
17
+ exports.IMAGE_BUILD_LIST_PRICE = 0.1656;
18
18
  exports.MAX_OUTPUT_BYTES_DEFAULT = 64 * 1024;
19
19
  exports.FILE_PUT_CHUNK_BYTES = 16 * 1024 * 1024;
20
20
  const PREVIEWS_OFF = /^previews are not enabled on this deployment\.?$/i;
@@ -215,6 +215,12 @@ class TimeoutError extends PromigenceError {
215
215
  }
216
216
  }
217
217
  exports.TimeoutError = TimeoutError;
218
+ class ServiceRestartError extends PromigenceError {
219
+ constructor(message, fix) {
220
+ super({ code: "service_restart", message, fix, exitCode: 3, category: "env", retryable: false });
221
+ }
222
+ }
223
+ exports.ServiceRestartError = ServiceRestartError;
218
224
  class ApiError extends PromigenceError {
219
225
  }
220
226
  exports.ApiError = ApiError;
@@ -233,6 +239,7 @@ exports.EXIT_BY_CODE = {
233
239
  invalid_argument: 2, unsupported: 2, not_released: 1, task_failed: 4,
234
240
  env_failed: 3, snapshot_rejected: 6, build_failed: 3, snapshot_failed: 4,
235
241
  not_connected: 7,
242
+ service_restart: 3,
236
243
  fork_failed: 3, start_timeout: 3, readiness_failed: 3, verify_too_slow: 3,
237
244
  verify_each_failed: 3, toolchain_mismatch: 3, sandbox_unresponsive: 3, oom_within_tier: 3,
238
245
  disk_full_within_tier: 3, sandbox_interrupted: 3, integrity_mismatch: 3, egress_unavailable: 3,
@@ -259,10 +266,11 @@ exports.EXIT_BY_CODE = {
259
266
  build_timeout: 4,
260
267
  image_unresolvable: 2,
261
268
  registry_unavailable: 3,
262
- trial_credits_exhausted: 5, card_required: 5, credit_unclaimed: 5, concurrency_limit: 5, egress_cap_reached: 10,
269
+ trial_credits_exhausted: 5, card_required: 5, credit_unclaimed: 5, concurrency_limit: 5, egress_cap_reached: 5,
263
270
  billing_unavailable: 5,
264
271
  project_cap_reached: 5,
265
272
  spend_limit_reached: 5,
273
+ billing_hold: 5,
266
274
  tier_over_plan: 2,
267
275
  network_host_refused: 9, network_policy_unsupported: 3,
268
276
  secrets_in_snapshot: 2, secrets_unavailable: 3,
@@ -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 HOME holds one)
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
@@ -13,6 +13,7 @@ exports.keychainCacheSet = keychainCacheSet;
13
13
  exports.forgetKeychainReads = forgetKeychainReads;
14
14
  const node_child_process_1 = require("node:child_process");
15
15
  const node_fs_1 = require("node:fs");
16
+ const node_os_1 = require("node:os");
16
17
  const node_path_1 = require("node:path");
17
18
  exports.KEYCHAIN_SERVICE = "promigence-cli";
18
19
  const KEYCHAIN_LABEL = "Promigence CLI credentials";
@@ -43,8 +44,19 @@ function sessionBus(env) {
43
44
  const run = env["XDG_RUNTIME_DIR"];
44
45
  return Boolean(run) && (0, node_fs_1.existsSync)((0, node_path_1.join)(run, "bus"));
45
46
  }
47
+ function userHome(env, home = node_os_1.homedir) {
48
+ const h = env["HOME"];
49
+ if (h)
50
+ return h;
51
+ try {
52
+ return home() || undefined;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
46
58
  const warnedSetting = new Set();
47
- function keychainStore(env = process.env, platform = process.platform) {
59
+ function keychainStore(env = process.env, platform = process.platform, home = node_os_1.homedir) {
48
60
  const raw = (env["PROMIGENCE_CREDENTIAL_STORE"] ?? "").trim().toLowerCase();
49
61
  switch (raw) {
50
62
  case "file": return undefined;
@@ -59,8 +71,8 @@ function keychainStore(env = process.env, platform = process.platform) {
59
71
  }
60
72
  }
61
73
  if (platform === "darwin") {
62
- const home = env["HOME"];
63
- return home && (0, node_fs_1.existsSync)((0, node_path_1.join)(home, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
74
+ const h = userHome(env, home);
75
+ return h && (0, node_fs_1.existsSync)((0, node_path_1.join)(h, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
64
76
  }
65
77
  if (platform === "win32")
66
78
  return undefined;
@@ -107,8 +119,9 @@ function runTool(store, args, env, input, timeout) {
107
119
  const bin = onPath(toolName(store), env);
108
120
  if (bin === undefined)
109
121
  throw Object.assign(new Error(`${toolName(store)} not found`), { code: "ENOENT" });
122
+ const h = store === "macos-keychain" && !env["HOME"] ? userHome(env) : undefined;
110
123
  return (0, node_child_process_1.execFileSync)(bin, args, {
111
- encoding: "utf8", timeout, env,
124
+ encoding: "utf8", timeout, env: h ? { ...env, HOME: h } : env,
112
125
  ...(input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input, stdio: ["pipe", "pipe", "pipe"] }),
113
126
  });
114
127
  }
@@ -34,7 +34,7 @@ export interface GithubPending {
34
34
  state: string;
35
35
  message: string;
36
36
  }
37
- /** A snapshot that follows a branch: every push builds it again (at the build price). */
37
+ /** A snapshot that follows a branch: every push builds it again (each build billed per second). */
38
38
  export interface GithubTrack {
39
39
  track_id: string;
40
40
  repo: string;
@@ -37,7 +37,7 @@ export interface GitlabPending {
37
37
  expires_at: string;
38
38
  message: string;
39
39
  }
40
- /** A snapshot that follows a GitLab branch: each new commit builds it again (at the build price). */
40
+ /** A snapshot that follows a GitLab branch: each new commit builds it again (each build billed per second). */
41
41
  export interface GitlabTrack {
42
42
  track_id: string;
43
43
  repo: string;