@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/dist/client.d.ts CHANGED
@@ -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>;
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@ 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";
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
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";
@@ -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 compat = ours === undefined ? compatApiKey(env["E2B_API_KEY"]) : undefined;
279
- const compatUrl = compat === undefined
280
- ? undefined
281
- : apiUrlFromCompatUrl(env["E2B_API_URL"]) ?? apiUrlFromCompatDomain(env["E2B_DOMAIN"]);
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 ?? compat;
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) : "E2B_API_KEY";
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 };
@@ -652,6 +652,7 @@ export function errorFor(code, message, fix, extra = {}) {
652
652
  case "invalid_argument":
653
653
  return attach(new InvalidArgumentError(message, extra.cli ? fix : withoutCliHelp(fix), code));
654
654
  case "not_released": return attach(new NotReleasedError(message));
655
+ case "service_restart": return attach(new ServiceRestartError(message, fix));
655
656
  default:
656
657
  if (ENV_CODES.has(code) || extra.category === "env")
657
658
  return attach(new EnvFailedError(code, message, fix));
@@ -836,7 +837,8 @@ export class PromigenceClient {
836
837
  refusalBackoff(status, retryAfterMs, code, attempt, replayable, signal) {
837
838
  if (attempt >= this.maxRetries || signal?.aborted)
838
839
  return undefined;
839
- if (status !== 429 && status !== 503)
840
+ const gateway = (status === 502 || status === 504) && replayable && code === undefined;
841
+ if (status !== 429 && status !== 503 && !gateway)
840
842
  return undefined;
841
843
  if ((retryAfterMs ?? 0) > MAX_RETRY_SLEEP_MS)
842
844
  return undefined;
@@ -959,12 +961,17 @@ export class PromigenceClient {
959
961
  throw new UnreachableError(`cannot reach ${url}: ${transportDetail(e)}`);
960
962
  }
961
963
  const retryAfterMs = parseRetryAfter(headerOf(res, "retry-after"));
962
- let retryable = (res.status === 429 || res.status === 503) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
964
+ const gateway = (res.status === 502 || res.status === 504) && replayable;
965
+ let retryable = (res.status === 429 || res.status === 503 || gateway) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
963
966
  let body;
964
967
  if (retryable && res.status === 503 && !replayable && retryAfterMs === undefined) {
965
968
  body = await res.text().catch(() => "");
966
969
  retryable = REFUSED_503_CODES.has(errorCodeOf(body) ?? "");
967
970
  }
971
+ if (retryable && gateway) {
972
+ body = await res.text().catch(() => "");
973
+ retryable = errorCodeOf(body) === undefined;
974
+ }
968
975
  if (retryable && attempt < this.maxRetries) {
969
976
  const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
970
977
  const jitter = Math.random();
@@ -972,6 +979,8 @@ export class PromigenceClient {
972
979
  ? retryAfterMs + jitter * Math.min(250, this.retryBaseMs)
973
980
  : backoff * (0.5 + jitter * 0.5);
974
981
  this.stats.retries++;
982
+ if (gateway && opts.method === "DELETE")
983
+ resentDelete = true;
975
984
  if (body === undefined)
976
985
  try {
977
986
  await res.text();
@@ -1258,6 +1267,14 @@ export class PromigenceClient {
1258
1267
  const r = await this.request({ method: "GET", path: `/me/ledger?${pageQuery(limit, 1000, opts.cursor)}` });
1259
1268
  return pageOf(r, "entries", limit);
1260
1269
  },
1270
+ historyPage: async (opts = {}) => {
1271
+ const limit = opts.limit ?? 50;
1272
+ const q = new URLSearchParams(pageQuery(limit, 100, opts.cursor));
1273
+ if (opts.kind !== undefined && opts.kind !== "all")
1274
+ q.set("kind", opts.kind);
1275
+ const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
1276
+ return pageOf(r, "entries", limit);
1277
+ },
1261
1278
  checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
1262
1279
  portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
1263
1280
  referrals: () => this.request({ method: "GET", path: "/referrals" }),
@@ -1752,6 +1769,10 @@ export class PromigenceClient {
1752
1769
  on.heartbeat?.();
1753
1770
  continue;
1754
1771
  }
1772
+ if (event === "reconnect") {
1773
+ reader.cancel().catch(() => { });
1774
+ 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.");
1775
+ }
1755
1776
  let parsed = {};
1756
1777
  try {
1757
1778
  parsed = JSON.parse(data);
@@ -1987,6 +2008,9 @@ export class PromigenceClient {
1987
2008
  ...(opts.onData ? { onData: opts.onData } : {}),
1988
2009
  ...(opts.onExit ? { onExit: opts.onExit } : {}),
1989
2010
  ...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
2011
+ ...(opts.resume === undefined ? {} : { resume: opts.resume }),
2012
+ ...(opts.reconnect === undefined ? {} : { reconnect: opts.reconnect }),
2013
+ ...(opts.onReconnected ? { onReconnected: opts.onReconnected } : {}),
1990
2014
  refuse: (status, text, retryAfter) => {
1991
2015
  let payload = {};
1992
2016
  try {
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.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/core.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { hintArg } from "./hint.js";
2
- export const VERSION = "0.1.0-beta.7";
2
+ export const VERSION = "0.1.0-beta.9";
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";
6
6
  export const KEY_CONTACT = "support@promigence.ai";
7
7
  export const WAIT_TIMEOUT_MS = 100_000;
8
8
  export const IMAGE_BUILD_TIMEOUT_MS = 15 * 60_000;
9
- export const IMAGE_BUILD_LIST_PRICE = 1.0;
9
+ export const IMAGE_BUILD_LIST_PRICE = 0.1656;
10
10
  export const MAX_OUTPUT_BYTES_DEFAULT = 64 * 1024;
11
11
  export const FILE_PUT_CHUNK_BYTES = 16 * 1024 * 1024;
12
12
  const PREVIEWS_OFF = /^previews are not enabled on this deployment\.?$/i;
@@ -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,
@@ -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: 10,
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,
@@ -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
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 home = env["HOME"];
50
- return home && existsSync(join(home, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
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
  }
package/dist/github.d.ts CHANGED
@@ -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;
package/dist/gitlab.d.ts CHANGED
@@ -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;