promigence 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
@@ -21,15 +21,13 @@ own (`npm config set prefix ~/.npm-global` and add `~/.npm-global/bin` to your `
21
21
 
22
22
  ## Create your account
23
23
 
24
- Promigence is in private beta: you need an invite code.
25
-
26
24
  ```sh
27
25
  promigence signup
28
26
  ```
29
27
 
30
- It asks for your invite code, then opens your browser to create the account. Your API key is saved
31
- in your system keychain, or in `~/.config/promigence/credentials` (mode 0600) where there is none. On
32
- another machine, use `promigence login`: it signs you in with a code we email you, or your password. In CI or on an
28
+ It opens your browser to create the account. Your API key is saved in your system keychain, or in
29
+ `~/.config/promigence/credentials` (mode 0600) where there is none.
30
+ On another machine, use `promigence login`: it signs you in with a code we email you. In CI or on an
33
31
  agent's machine, set `PROMIGENCE_API_KEY` instead. `promigence doctor` checks the setup.
34
32
 
35
33
  ## First commands
@@ -57,13 +55,19 @@ promigence fork --snapshot py --count 5 --cap 2
57
55
 
58
56
  ## For AI agents
59
57
 
60
- Run `promigence agent-guide` (about 1.2k tokens) before driving the CLI: it covers the model, the
58
+ Run `promigence agent-guide` (about 1.4k tokens) before driving the CLI: it covers the model, the
61
59
  JSON output contract, exit codes and errors. `promigence agent-guide --install` writes the same
62
60
  guidance into your repo for coding agents, and `promigence mcp` serves the CLI as an MCP server.
63
61
  Every command takes `--json`.
64
62
 
65
63
  ## From TypeScript
66
64
 
65
+ In your project (the global install gives you the command only):
66
+
67
+ ```sh
68
+ npm i promigence
69
+ ```
70
+
67
71
  ```ts
68
72
  import { Sandbox } from "promigence";
69
73
 
package/dist/account.js CHANGED
@@ -223,6 +223,7 @@ const billingUsage = async (ctx) => {
223
223
  usage,
224
224
  card_on_file: b.card_on_file ?? false,
225
225
  ...(b.card ? { card: b.card } : {}),
226
+ ...(b.card_note ? { card_note: b.card_note } : {}),
226
227
  ...(b.trial ? { trial: b.trial } : {}),
227
228
  ...(b.suspended ? { suspended: true, suspend_reason: b.suspend_reason ?? null } : {}),
228
229
  upcoming_invoice: b.upcoming_invoice ?? null,
@@ -260,6 +261,8 @@ const billingUsage = async (ctx) => {
260
261
  if (trial?.credits_remaining !== undefined)
261
262
  lines.push(` trial credits left: ${usd(trial.credits_remaining)}`);
262
263
  lines.push(` card: ${b.card_on_file ? paymentMethodText(b.card) : `none on file${billingOff ? " (card payments are not open yet)" : ""}`}`);
264
+ if (b.card_note)
265
+ lines.push(` ${b.card_note}`);
263
266
  if (b.upcoming_invoice?.amount_due !== undefined && b.upcoming_invoice?.amount_due !== null) {
264
267
  lines.push(` next invoice: ${usd(b.upcoming_invoice.amount_due)}`);
265
268
  }
@@ -282,6 +285,86 @@ const billingPortal = async (ctx) => {
282
285
  const next = nexts(["promigence billing usage", "this month's spend without leaving the terminal"]);
283
286
  return emit(ctx.mode, okEnvelope(ctx.cmd.name, { url: res.url }, next), ["promigence billing portal: open this page to manage the card and past invoices:", ` ${res.url}`], 0, ctx.pointer);
284
287
  };
288
+ const HISTORY_KINDS = ["all", "credits", "payments"];
289
+ const CREDIT_WHAT = {
290
+ free_credit: "Free credit",
291
+ card_bonus: "Card bonus",
292
+ referral: "Referral reward",
293
+ plan: "Included with your plan",
294
+ from_promigence: "Credit from Promigence",
295
+ service_restart: "Credit for a service restart",
296
+ confirmation_credit: "Credit for the $1 confirmation",
297
+ };
298
+ const CREDIT_ENDED = { used: "used", expired: "expired", taken_back: "taken back" };
299
+ const PAYMENT_WHAT = {
300
+ invoice_paid: "Invoice paid",
301
+ payment_failed: "Payment failed",
302
+ refund: "Refund",
303
+ };
304
+ const PAYMENT_STATUS = {
305
+ paid: "paid",
306
+ retrying: "will be retried",
307
+ failed: "not paid",
308
+ refunded: "refunded",
309
+ confirmed: "confirmed",
310
+ };
311
+ const wordFor = (m, k) => k && Object.prototype.hasOwnProperty.call(m, k) ? m[k] : undefined;
312
+ const unpaid = (status) => status === "retrying" || status === "failed";
313
+ export function historyLines(entries) {
314
+ return entries.map((e) => {
315
+ const day = (e.at ?? "").slice(0, 10);
316
+ const c = e.credit;
317
+ if (c) {
318
+ const state = wordFor(CREDIT_ENDED, c.status) ??
319
+ `${usd(c.remaining_usd)} left, ${c.expires_at ? `expires ${c.expires_at.slice(0, 10)}` : "no expiry"}`;
320
+ return ` ${day} ${wordFor(CREDIT_WHAT, c.kind) ?? "Credit"} ${usd(c.granted_usd)} ${state}`;
321
+ }
322
+ const p = e.payment;
323
+ if (!p)
324
+ return ` ${day} (a row this version cannot show: see --json)`;
325
+ const check = p.kind === "method_check";
326
+ const what = check
327
+ ? `$${Number.isInteger(p.amount_usd) ? String(p.amount_usd) : p.amount_usd.toFixed(2)} to confirm ${p.method || "your payment method"}`
328
+ : (wordFor(PAYMENT_WHAT, p.kind) ?? "Payment");
329
+ const detail = [p.plan ? `${p.plan} plan` : null, p.invoice_number ? `invoice ${p.invoice_number}` : null]
330
+ .filter(Boolean)
331
+ .join(", ");
332
+ const status = check && p.status === "failed" ? "failed" : (wordFor(PAYMENT_STATUS, p.status) ?? p.status);
333
+ const url = typeof p.invoice_url === "string" && p.invoice_url.startsWith("https://") ? p.invoice_url : null;
334
+ const link = url && !check ? ` · ${unpaid(p.status) ? "pay at" : "receipt"} ${url}` : "";
335
+ return ` ${day} ${what}${detail ? ` (${detail})` : ""} ${usd(p.amount_usd)} ${status}${link}`;
336
+ });
337
+ }
338
+ const billingHistory = async (ctx) => {
339
+ const kind = str(ctx, "--kind") ?? "all";
340
+ if (!HISTORY_KINDS.includes(kind)) {
341
+ throw usageError(`--kind: '${kind}' is not a kind`, `Pass one of ${HISTORY_KINDS.join(" | ")}.`);
342
+ }
343
+ const limit = limitOf(ctx, 50, 100);
344
+ const cursor = str(ctx, "--cursor");
345
+ const page = await client(ctx).billing.historyPage({
346
+ kind: kind,
347
+ limit,
348
+ ...(cursor === undefined ? {} : { cursor }),
349
+ });
350
+ const entries = page.items;
351
+ const more = page.nextCursor;
352
+ const owed = entries.some((e) => e.payment && e.payment.kind !== "method_check" && unpaid(e.payment.status));
353
+ const next = nexts(...(more
354
+ ? [[`${sh `promigence billing history --limit ${String(limit)}`}${kind === "all" ? "" : sh ` --kind ${kind}`}${sh ` --cursor ${more}`}`, "the next page"]]
355
+ : []), ...(owed
356
+ ? [["promigence billing portal", "pay the open invoice, or update the card on file"]]
357
+ : [["promigence billing usage", "this month's spend and credit"]]));
358
+ const empty = { all: "no credits or payments yet", credits: "no credit yet", payments: "no payments yet" };
359
+ const noun = kind === "all" ? "" : ` ${entries.length === 1 ? kind.slice(0, -1) : kind}`;
360
+ const lines = [
361
+ `promigence billing history: ${entries.length}${noun}, newest first`,
362
+ ...historyLines(entries),
363
+ ...(entries.length ? [] : [` (${empty[kind]})`]),
364
+ ...(more ? [` more: --cursor ${more}`] : []),
365
+ ];
366
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { kind, entries, count: entries.length, next_cursor: more }, next), lines, 0, ctx.pointer);
367
+ };
285
368
  const referrals = async (ctx) => {
286
369
  const r = await client(ctx).billing.referrals();
287
370
  const next = r.code
@@ -745,6 +828,7 @@ export const ACCOUNT_HANDLERS = {
745
828
  status,
746
829
  "billing usage": billingUsage,
747
830
  "billing portal": billingPortal,
831
+ "billing history": billingHistory,
748
832
  referrals,
749
833
  "referrals apply": referralsApply,
750
834
  "snapshot delete": snapshotDelete,
@@ -1,5 +1,6 @@
1
- import { PromigenceClient, type ExecResponse, type OptimizeFor, type RunReport, type SandboxRef, type ScoreMode, type SecretRecord } from "./sdk/index.js";
1
+ import { PromigenceClient, type ExecResponse, type OptimizeFor, type RunReport, type SandboxRef, type ScoreMode, type SecretRecord, type SnapshotBuild } from "./sdk/index.js";
2
2
  import { type Mode } from "./io.js";
3
+ import { type BuildPrice } from "./pricing.js";
3
4
  import { type Command, type NextHint } from "./surface.js";
4
5
  export interface Ctx {
5
6
  cmd: Command;
@@ -46,6 +47,7 @@ export declare function gradeOpts(ctx: Ctx): {
46
47
  score?: ScoreMode;
47
48
  } | undefined;
48
49
  export declare function secretListLines(s: SecretRecord): string[];
50
+ export declare function buildPriceOf(b: SnapshotBuild): BuildPrice | undefined;
49
51
  export declare function verifyTiming(v: Record<string, unknown> | undefined): string | undefined;
50
52
  export declare function buildLogLines(log: string[] | undefined, state: string): string[];
51
53
  export declare function snapshotSource(s: {
package/dist/commands.js CHANGED
@@ -10,7 +10,7 @@ import { DevcontainerError, detectOne, load, planJson, planLines, planSetup, res
10
10
  import { detect, ecosystemOfCommands, imageCreateCommand, readConfig } from "./init.js";
11
11
  import { CREDENTIAL_REMEDY, confirmOverride, credentialRefusal, emitUploadList, localTarNoMetadataArgs } from "./uploadguard.js";
12
12
  import { emit, errorEnvelope, errorTextLines, exitWith, forkLine, forkSummary, gradeLine, ms, noCommandFix, okEnvelope, parseMaxOutput, recordNotice, sandboxGrade, parseCommandTimeout, parseCount, parseDuration, parseUsd, pickExit, reportSummaryLines, toPromigenceError, totalsLine, usageError, write, writeBytes, } from "./io.js";
13
- import { buildPrice } from "./pricing.js";
13
+ import { buildPrice, buildPriceWords } from "./pricing.js";
14
14
  import { DOCS_URL, WAIT_TIMEOUT_DEFAULT } from "./surface.js";
15
15
  import { escapeLine, fieldForTerminal, forTerminal, jsonLine, sh, shellArg } from "./term.js";
16
16
  import { snapshotArg } from "./bundle.js";
@@ -430,6 +430,10 @@ function buildFields(b) {
430
430
  out["source"] = b.source;
431
431
  if (typeof b.verified === "boolean")
432
432
  out["verified"] = b.verified;
433
+ for (const k of ["build_price", "per_second_usd", "max_seconds", "billable_seconds", "cost_usd"]) {
434
+ if (typeof b[k] === "number")
435
+ out[k] = b[k];
436
+ }
433
437
  const ab = b;
434
438
  if (ab.alias_requested !== undefined)
435
439
  out["alias_requested"] = ab.alias_requested;
@@ -440,6 +444,11 @@ function buildFields(b) {
440
444
  function isBuilding(b) {
441
445
  return b.state === "queued" || b.state === "running";
442
446
  }
447
+ export function buildPriceOf(b) {
448
+ return typeof b.build_price === "number" && typeof b.per_second_usd === "number" && typeof b.max_seconds === "number"
449
+ ? { most_usd: b.build_price, per_second_usd: b.per_second_usd, max_s: b.max_seconds }
450
+ : undefined;
451
+ }
443
452
  function buildExit(b) {
444
453
  if (b.state === "verified" || b.state === "built")
445
454
  return 0;
@@ -474,9 +483,15 @@ function buildLines(b) {
474
483
  const timing = verifyTiming(b.verification);
475
484
  if (timing)
476
485
  lines.push(` verify: ${timing}`);
486
+ const cost = typeof b.cost_usd === "number" && !isBuilding(b) ? b.cost_usd : undefined;
487
+ const ran = typeof b.billable_seconds === "number" ? ` for the ${b.billable_seconds} s it ran` : "";
477
488
  if (b.error) {
478
489
  lines.push(` ${b.error.guard ? `guard ${b.error.guard}: ` : ""}${b.error.message ?? b.error.code ?? "rejected"}`);
479
- lines.push(` nothing was stored${b.error.stored === false ? "" : "?"} and nothing was charged`);
490
+ const stored = `nothing was stored${b.error.stored === false ? "" : "?"}`;
491
+ lines.push(cost !== undefined && cost > 0 ? ` ${stored}; billed ${usd(cost)}${ran}` : ` ${stored} and nothing was charged`);
492
+ }
493
+ else if (cost !== undefined && cost > 0) {
494
+ lines.push(` billed ${usd(cost)}${ran}`);
480
495
  }
481
496
  for (const w of b.warnings ?? [])
482
497
  lines.push(` warning: ${w}`);
@@ -875,14 +890,22 @@ const snapshotCreate = async (ctx) => {
875
890
  const reused = started.deduped === true;
876
891
  const joinedBuild = started.joined === true;
877
892
  const replay = buildReplayNote(started, str(ctx, "--idempotency-key") !== undefined);
878
- const charged = reused || joinedBuild || replay !== undefined ? 0 : (typeof started.build_price === "number" ? started.build_price : await buildPrice(c));
893
+ const free = reused || joinedBuild || replay !== undefined;
894
+ const builtAt = tier ?? (typeof started["tier"] === "string" ? started["tier"] : undefined);
895
+ const perSecond = buildPriceOf(started);
896
+ const flat = !free && perSecond === undefined && typeof started.build_price === "number" ? started.build_price : undefined;
897
+ const said = free || flat !== undefined ? perSecond : (perSecond ?? await buildPrice(c, builtAt));
898
+ const charged = free ? 0 : flat ?? said?.most_usd ?? 0;
879
899
  submitted["build_price"] = charged;
880
- if (reused || joinedBuild || replay !== undefined)
900
+ if (!free && said !== undefined)
901
+ Object.assign(submitted, { per_second_usd: said.per_second_usd, max_seconds: said.max_s });
902
+ if (free)
881
903
  submitted["charged"] = false;
882
904
  Object.assign(submitted, replayFields(started));
883
905
  await write(process.stderr, ctx.mode === "json"
884
906
  ? JSON.stringify({
885
907
  hint: "build_price", build_price: charged, unit: "USD", ...(tier === undefined ? {} : { tier }),
908
+ ...(!free && said !== undefined ? { per_second_usd: said.per_second_usd, max_seconds: said.max_s } : {}),
886
909
  ...(reused ? { deduped: true } : {}), ...(joinedBuild ? { joined: true } : {}),
887
910
  ...(replay === undefined ? {} : { replayed: true }),
888
911
  }) + "\n"
@@ -892,7 +915,9 @@ const snapshotCreate = async (ctx) => {
892
915
  ? `build_price: $0.00 - an identical build already ran (${started.build_id}); reusing it, nothing charged\n`
893
916
  : joinedBuild
894
917
  ? `build_price: $0.00 - joining an identical build already running (${started.build_id}); it is charged once\n`
895
- : `build_price: $${charged.toFixed(2)} (tier ${tier ?? "default"}) - building ${label}\n`);
918
+ : flat !== undefined
919
+ ? `build_price: $${flat.toFixed(2)} (tier ${tier ?? "default"}) - building ${label}\n`
920
+ : `build_price: ${buildPriceWords(said, builtAt ?? "default")} - building ${label}\n`);
896
921
  if (sources.length && ctx.mode !== "json" && !reused) {
897
922
  await write(process.stderr, sources
898
923
  .map((s) => ` also ${s.repo}@${s.commit.slice(0, 7)} -> ${s.path ?? "beside the workspace, under its own name"}`)
@@ -920,7 +945,7 @@ const snapshotCreate = async (ctx) => {
920
945
  const follow = sh `promigence snapshot status ${started.build_id} --wait`;
921
946
  const err = new PromigenceError({
922
947
  code: "aborted_by_client",
923
- message: `interrupted by ${sig}; build ${started.build_id} goes on and nothing was cancelled (it is charged once it verifies)`,
948
+ message: `interrupted by ${sig}; build ${started.build_id} goes on and nothing was cancelled (it is billed per second until it ends)`,
924
949
  fix: `Follow it: ${follow}.`,
925
950
  exitCode: 130,
926
951
  });
@@ -1587,7 +1612,8 @@ async function runImageSnapshot(ctx, c) {
1587
1612
  checkImageRef(image);
1588
1613
  const count = parseCount(required(ctx, "--count", "Pass --count N (number of sandboxes to fork)."), "--count");
1589
1614
  const cap = await spendCap(ctx, c);
1590
- const price = await buildPrice(c);
1615
+ const most = await buildPrice(c);
1616
+ const price = most.most_usd;
1591
1617
  const timeout = str(ctx, "--timeout");
1592
1618
  const quoted = await c.runs.quote({
1593
1619
  snapshot: DEFAULT_SNAPSHOT, count, ...(timeout === undefined ? {} : { timeoutMs: parseDuration(timeout, "--timeout") }),
@@ -1595,29 +1621,43 @@ async function runImageSnapshot(ctx, c) {
1595
1621
  const needed = Math.ceil((price + (quoted ?? 0)) * 100) / 100;
1596
1622
  if (cap < price + (quoted ?? 0)) {
1597
1623
  const existing = (quoted ?? 0) <= cap ? await existingImageSnapshot(c, image) : undefined;
1598
- throw new SpendCapError(`a new image's snapshot build costs ${moneyUsd(price)} on top of the run` +
1624
+ throw new SpendCapError(`a new image's snapshot build can cost up to ${moneyUsd(price)} on top of the run` +
1599
1625
  `${quoted === undefined ? "" : ` (about ${moneyUsd(quoted)})`}, over --cap ${moneyUsd(cap)}; nothing was built or started`, (existing === undefined ? "" : `This image already has snapshot ${existing}: run it under this cap with ${sh `promigence run --snapshot ${existing}`} instead of --image (any start command or settings it was made with come with it). Or `) +
1600
- `${existing === undefined ? "Pass" : "pass"} --cap ${needed.toFixed(2)} or more (a build is charged once, only when this image has no snapshot yet, and reused free after), ` +
1626
+ `${existing === undefined ? "Pass" : "pass"} --cap ${needed.toFixed(2)} or more (a build is billed per second while it runs, only when this image has no snapshot yet, and reused free after), ` +
1601
1627
  `or build it first, ${sh `promigence snapshot create --image ${image} --wait`}, and run its snapshot with --snapshot.`);
1602
1628
  }
1603
1629
  await write(process.stderr, ctx.mode === "json"
1604
1630
  ? jsonLine(process.stderr, { hint: "image snapshot", image })
1605
1631
  : `image ${image}: creating or reusing its snapshot (a first build takes minutes)\n`);
1606
1632
  let charged = 0;
1633
+ let perSecond = false;
1607
1634
  let buildId;
1608
1635
  const buildOpts = { image, allowBuild: true };
1609
1636
  const snapshotId = await imageSnapshot(c, buildOpts, {
1610
1637
  onBuild: async (b) => {
1611
1638
  const free = b.deduped === true || b.joined === true || b.idempotent_replay === true || b.state === "verified" || b.state === "built";
1639
+ const said = buildPriceOf(b);
1640
+ perSecond = !free && said !== undefined;
1612
1641
  charged = free ? 0 : (typeof b.build_price === "number" ? b.build_price : price);
1613
1642
  buildId = b.build_id;
1614
1643
  await write(process.stderr, ctx.mode === "json"
1615
- ? jsonLine(process.stderr, { hint: "build_price", build_price: charged, unit: "USD", image, ...(b.build_id ? { build_id: b.build_id } : {}), ...(free ? { charged: false } : {}) })
1644
+ ? jsonLine(process.stderr, {
1645
+ hint: "build_price", build_price: charged, unit: "USD", image,
1646
+ ...(perSecond && said ? { per_second_usd: said.per_second_usd, max_seconds: said.max_s } : {}),
1647
+ ...(b.build_id ? { build_id: b.build_id } : {}), ...(free ? { charged: false } : {}),
1648
+ })
1616
1649
  : free
1617
1650
  ? `build_price: $0.00 - this image's snapshot already exists; reusing it, nothing charged\n`
1618
- : `build_price: $${charged.toFixed(2)} - building ${image}'s snapshot (charged once it verifies; counted in --cap)\n`);
1651
+ : perSecond && said
1652
+ ? `build_price: ${buildPriceWords(said)} - building ${image}'s snapshot (billed per second while it builds; counted in --cap)\n`
1653
+ : `build_price: $${charged.toFixed(2)} - building ${image}'s snapshot (charged once it verifies; counted in --cap)\n`);
1619
1654
  },
1620
1655
  });
1656
+ if (perSecond && buildId !== undefined) {
1657
+ const done = await c.snapshots.status(buildId).catch(() => undefined);
1658
+ if (done !== undefined && typeof done.cost_usd === "number" && !isBuilding(done))
1659
+ charged = done.cost_usd;
1660
+ }
1621
1661
  await write(process.stderr, ctx.mode === "json"
1622
1662
  ? jsonLine(process.stderr, { hint: "image snapshot ready", image, snapshot_id: snapshotId })
1623
1663
  : fieldForTerminal(process.stderr, `image ${image}: snapshot ${snapshotId}`) + "\n");
package/dist/errfix.js CHANGED
@@ -106,6 +106,7 @@ function notFoundFor(kind, ctx, apiFix) {
106
106
  const BILLING_CMD = /`promigence billing (?:card|portal)`/;
107
107
  export function withoutBilling(text) {
108
108
  return text
109
+ .replace(/Owners: update the card with `promigence billing portal`; the hold lifts once the invoice is paid\.(?: For a disputed payment, write to support@promigence\.ai\.)?/, `To lift it, email ${SUPPORT}.`)
109
110
  .replace(/,? or move to a plan that allows it: `promigence billing (?:card|portal)`\.?/, `, or email ${SUPPORT} for a plan that allows it.`)
110
111
  .replace(/or add a card to lift it: `promigence billing (?:card|portal)`/, `or email ${SUPPORT} to lift it`)
111
112
  .replace(/adding a card raises it \(owners: `promigence billing (?:card|portal)`\)\.?/, `to raise it, email ${SUPPORT}.`)
@@ -20,7 +20,7 @@ export interface TierEntry {
20
20
  per_second_usd: number;
21
21
  }
22
22
  export declare const TIERS: readonly TierEntry[];
23
- export declare const BUILD_PRICE = 1;
23
+ export declare const BUILD_MAX_S = 3600;
24
24
  export declare const PRICING: {
25
25
  readonly vcpu_second_usd: 0.000014;
26
26
  readonly gib_second_usd: 0.0000045;
package/dist/generated.js CHANGED
@@ -106,7 +106,7 @@ export const ERRORS = [
106
106
  fix: "Set PROMIGENCE_API_KEY or run `promigence auth login --from-env VAR`. No key yet: `promigence login` (sign in) or https://promigence.ai." },
107
107
  { code: "forbidden", category: "other", http: 403, exit: 7, billed: "no",
108
108
  message: "{detail}",
109
- fix: "Do what the message names: sign in (`promigence login`), use a key from that org, or ask an owner." },
109
+ fix: "Do what the message names: sign in (`promigence login`), use a full-access key (`promigence keys create`) or one from that org, or ask an owner." },
110
110
  { code: "not_found", category: "other", http: 404, exit: 8, billed: "no",
111
111
  message: "{kind} `{id}` not found",
112
112
  fix: "Check the id; aliases resolve in your org first, then public snapshots (`promigence examples`)." },
@@ -158,12 +158,12 @@ export const ERRORS = [
158
158
  { code: "capacity_limit", category: "env", http: 409, exit: 3, billed: "no",
159
159
  message: "{requested} {tier} sandboxes at once is more than one run can start at once; at most {max_at_once}",
160
160
  fix: "Not billed; nothing started. Run at most {max_at_once} at once (lower `--count`), or use a smaller tier." },
161
- { code: "build_timeout", category: "other", http: 422, exit: 4, billed: "no",
161
+ { code: "build_timeout", category: "other", http: 422, exit: 4, billed: "yes",
162
162
  message: "the build ran past its {limit_min}-minute limit",
163
- fix: "Not billed; nothing stored. Shorten `setup` (at most 64 steps, {limit_min} minutes in all) or bake the slow part into the image." },
164
- { code: "dockerfile_build_failed", category: "other", http: 400, exit: 2, billed: "no",
163
+ fix: "Nothing was stored; the {limit_min} minutes it ran are billed. Shorten `setup` (at most 64 steps, {limit_min} minutes in all) or bake the slow part into the image." },
164
+ { code: "dockerfile_build_failed", category: "other", http: 400, exit: 2, billed: "yes",
165
165
  message: "your image could not be built from the Dockerfile: {detail}",
166
- fix: "Nothing was stored and nothing was billed. Fix the Dockerfile (the lines above are the end of your own build output) and run `promigence snapshot create --dockerfile PATH ...` again." },
166
+ fix: "Nothing was stored; the time the build ran is billed. Fix the Dockerfile (the lines above are the end of your own build output) and run `promigence snapshot create --dockerfile PATH ...` again." },
167
167
  { code: "image_unresolvable", category: "other", http: 422, exit: 2, billed: "no",
168
168
  message: "`{image}` could not be resolved to a digest: {detail}",
169
169
  fix: "Nothing was stored and nothing was billed. Check the name and tag; a private image needs `registry_secret` naming a stored secret that holds `user:password` (`promigence secrets set`). You can also pass the image already pinned as NAME@sha256:<digest>." },
@@ -191,7 +191,7 @@ export const ERRORS = [
191
191
  { code: "concurrency_limit", category: "other", http: 409, exit: 5, billed: "no",
192
192
  message: "{live} live + {requested} requested sandboxes is over your limit of {limit}",
193
193
  fix: "Wait for sandboxes to end or lower `--count` (`--allow-partial` covers the spend cap only); a retry of the same request fails the same way. Trial orgs get {trial_limit}; to raise it, email support@promigence.ai." },
194
- { code: "egress_cap_reached", category: "other", http: 403, exit: 10, billed: "no",
194
+ { code: "egress_cap_reached", category: "other", http: 403, exit: 5, billed: "no",
195
195
  message: "monthly data-out cap reached: {used_gb} of {cap_gb} GB",
196
196
  fix: "Use `--network none` (still allowed) or wait for the 1st (UTC). For more data out a month, email support@promigence.ai." },
197
197
  { code: "project_cap_reached", category: "cap", http: 403, exit: 5, billed: "no",
@@ -199,7 +199,10 @@ export const ERRORS = [
199
199
  fix: "Ask for less (`--count`, `--cap`, a shorter extension), wait for the 1st (UTC), or have an owner raise it: `promigence project cap {project} <usd>`." },
200
200
  { code: "spend_limit_reached", category: "cap", http: 402, exit: 5, billed: "no",
201
201
  message: "today's spend limit {limit} has {remaining} left; this needs {needed}",
202
- fix: "Running sandboxes finish. The limit resets at 00:00 UTC and grows with each paid invoice; ask for less, or email support@promigence.ai. After a failed payment, new work waits until it is paid (owners: `promigence billing portal`)." },
202
+ fix: "Running sandboxes finish. The limit resets at 00:00 UTC and grows with each paid invoice; ask for less, or email support@promigence.ai." },
203
+ { code: "billing_hold", category: "other", http: 402, exit: 5, billed: "no",
204
+ message: "new work is on hold: {reason}",
205
+ fix: "Running sandboxes finish. Owners: update the card with `promigence billing portal`; the hold lifts once the invoice is paid. For a disputed payment, write to support@promigence.ai." },
203
206
  { code: "tier_over_plan", category: "other", http: 403, exit: 2, billed: "no",
204
207
  message: "the {tier} tier ({vcpus} vCPU) is over the {limit} vCPU per-sandbox limit of your {plan} plan",
205
208
  fix: "Use a tier of at most {limit} vCPU (`promigence snapshot create … --tier {max_tier}` is the largest; a fork runs on its snapshot's), or move to a plan that allows it: `promigence billing portal`." },
@@ -260,12 +263,15 @@ export const CLIENT_ERRORS = [
260
263
  { code: "env_failed", category: "env", http: 0, exit: 3, billed: "no",
261
264
  message: "the sandbox ended before hand-over",
262
265
  fix: "Ours, not billed. The run's report names the failure's own code (`promigence run report RUN`); start the sandbox again." },
263
- { code: "snapshot_rejected", category: "other", http: 0, exit: 6, billed: "no",
266
+ { code: "snapshot_rejected", category: "other", http: 0, exit: 6, billed: "unless_ours",
264
267
  message: "a build guard refused it; nothing was stored",
265
- fix: "Not charged. Read the build log (`promigence snapshot status BUILD --log 80`): the guard named in the error says what to change (verify exited non-zero, a setup step failed, a time or size bound), then build again." },
268
+ fix: "The time the build ran is billed, unless it ended on our side. Read the build log (`promigence snapshot status BUILD --log 80`): the guard named in the error says what to change (verify exited non-zero, a setup step failed, a time or size bound), then build again." },
266
269
  { code: "build_failed", category: "env", http: 0, exit: 3, billed: "no",
267
270
  message: "the build could not finish; nothing was stored",
268
271
  fix: "Not charged. Build again; if it repeats, email support@promigence.ai with the build id." },
272
+ { code: "service_restart", category: "env", http: 0, exit: 3, billed: "no",
273
+ message: "a service restart stopped the command while its output was streaming",
274
+ fix: "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." },
269
275
  { code: "not_connected", category: "other", http: 0, exit: 7, billed: "no",
270
276
  message: "the connection request ended without connecting anything",
271
277
  fix: "Nothing was connected. Do what the message names, then run the connect command again; `promigence github status` (or `gitlab status`) lists what is connected." },
@@ -285,7 +291,7 @@ export const TIERS = [
285
291
  { name: "bench-8x16", vcpus: 8, mem_gib: 16, disk_gib: 20, sellable: false, per_second_usd: 0 },
286
292
  { name: "bench-4x8", vcpus: 4, mem_gib: 8, disk_gib: 40, sellable: false, per_second_usd: 0 },
287
293
  ];
288
- export const BUILD_PRICE = 1;
294
+ export const BUILD_MAX_S = 3600;
289
295
  export const PRICING = {
290
296
  vcpu_second_usd: 0.000014,
291
297
  gib_second_usd: 0.0000045,
package/dist/github.js CHANGED
@@ -119,7 +119,7 @@ const githubStatus = async (ctx) => {
119
119
  lines.push("pending:", ...s.pending.map((p) => ` installation ${p.installation_id}${p.account ? ` (${p.account})` : ""}: ${p.message}`));
120
120
  }
121
121
  if (s.tracks.length)
122
- lines.push("tracked branches (each push rebuilds, at the build price):", ...s.tracks.map(trackLine));
122
+ lines.push("tracked branches (each push rebuilds, billed per second):", ...s.tracks.map(trackLine));
123
123
  const next = s.connections.length
124
124
  ? nexts(["promigence repos", "the repositories the connection can read"])
125
125
  : nexts(["promigence github connect", "install the App on your GitHub account"]);
@@ -289,12 +289,12 @@ export async function githubCreate(ctx) {
289
289
  };
290
290
  if (track) {
291
291
  const branch = ref ?? "the default branch";
292
- const each = await buildPrice(client(ctx));
292
+ const each = (await buildPrice(client(ctx), tier)).most_usd;
293
293
  await write(process.stderr, ctx.mode === "json"
294
294
  ? jsonLine(process.stderr, { hint: "track", repo, branch, alias, build_price_each: each, unit: "USD" })
295
295
  : glPath
296
- ? `tracking: every new commit on ${branch} (checked every minute) rebuilds this snapshot and moves ${alias} to it, at the build price ($${each.toFixed(2)} per build), charged to your org; stop with \`${sh `promigence gitlab untrack ${alias}`}\`\n`
297
- : `tracking: every push to ${branch} rebuilds this snapshot and moves ${alias} to it, at the build price ($${each.toFixed(2)} per build), charged to your org; stop with \`${sh `promigence github untrack ${alias}`}\`\n`);
296
+ ? `tracking: every new commit on ${branch} (checked every minute) rebuilds this snapshot and moves ${alias} to it, each build billed per second while it runs (up to $${each.toFixed(2)}), charged to your org; stop with \`${sh `promigence gitlab untrack ${alias}`}\`\n`
297
+ : `tracking: every push to ${branch} rebuilds this snapshot and moves ${alias} to it, each build billed per second while it runs (up to $${each.toFixed(2)}), charged to your org; stop with \`${sh `promigence github untrack ${alias}`}\`\n`);
298
298
  }
299
299
  return { opts, label, submitted, tier };
300
300
  }
package/dist/gitlab.js CHANGED
@@ -120,7 +120,7 @@ const gitlabStatus = async (ctx) => {
120
120
  lines.push("waiting for an owner's confirmation:", ...s.pending.map((p) => ` ${p.username} (${p.connection_id}): ${p.message}`));
121
121
  }
122
122
  if (s.tracks.length) {
123
- lines.push("tracked branches (each new commit rebuilds, at the build price; checked every minute):", ...s.tracks.map(trackLine));
123
+ lines.push("tracked branches (each new commit rebuilds, billed per second; checked every minute):", ...s.tracks.map(trackLine));
124
124
  }
125
125
  if (s.connections.length) {
126
126
  lines.push("optional, for instant rebuilds: a project webhook (push events) with each connection's URL and token in --json (webhook_url, webhook_token)");
package/dist/guide.js CHANGED
@@ -1,15 +1,15 @@
1
- import { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_PRICE, PRICING, } from "./generated.js";
1
+ import { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_MAX_S, PRICING, } from "./generated.js";
2
2
  export const BILLED_TEXT = {
3
3
  no: "no: nothing is billed for this",
4
4
  yes: "yes: the time used is billed",
5
5
  credited: "no: ours, credited back",
6
6
  unless_ours: "the time used is billed, unless the cause is shown to be ours: then it is credited back",
7
7
  };
8
- import { DOCS_URL, EXIT_CODES, EXIT_PRECEDENCE_TEXT, GOLDEN_PATH, GROUPS, GUIDE_COMMON_ERRORS, LISTED_COMMANDS, LIVE_SUMMARY, NOT_LIVE_SUMMARY, SCHEMA, VERSION, WAIT_TIMEOUT_DEFAULT, } from "./surface.js";
8
+ import { DOCS_URL, EXIT_CODES, EXIT_PRECEDENCE_TEXT, GOLDEN_PATH, GROUPS, GUIDE_COMMON_ERRORS, LISTED_COMMANDS, NOT_LIVE_SUMMARY, SCHEMA, VERSION, WAIT_TIMEOUT_DEFAULT, compactNames, } from "./surface.js";
9
9
  export const SECTIONS = ["core", "commands", "errors", "cost"];
10
10
  export const BUDGETS = {
11
- core: 1200,
12
- commands: 4475,
11
+ core: 1500,
12
+ commands: 4500,
13
13
  errors: 2325,
14
14
  cost: 600,
15
15
  };
@@ -20,7 +20,25 @@ export function lookupError(code) {
20
20
  export const ALL_ERROR_CODES = [...ERRORS, ...CLIENT_ERRORS].map((e) => e.code);
21
21
  export const estimateTokens = (s) => Math.ceil(s.length / 4);
22
22
  const exitLine = EXIT_CODES.filter((e) => e.code !== 125 && e.code !== 13).map((e) => `${e.code} ${e.name}`).join(" · ");
23
+ const codesOn = (n) => ERRORS.filter((e) => e.exit === n).map((e) => e.code).join(", ");
24
+ const EXIT_NOTE = {
25
+ 7: codesOn(7),
26
+ 10: `${codesOn(10)} only: retry after Retry-After`,
27
+ 12: `some sandboxes ${codesOn(12)}`,
28
+ 13: "agent-guide --check: files would change",
29
+ 125: "exec --sandbox: ours; else the command's code",
30
+ };
31
+ const exitLegend = EXIT_CODES.map((e) => `${e.code} ${e.name}${EXIT_NOTE[e.code] ? ` (${EXIT_NOTE[e.code]})` : ""}`).join(" · ");
23
32
  const liveNames = new Set(LISTED_COMMANDS.map((c) => c.name));
33
+ const HOSTED_LIVE = compactNames(LISTED_COMMANDS.filter((c) => !c.notOnHosted).map((c) => c.name));
34
+ const NOT_ON_HOSTED = LISTED_COMMANDS.filter((c) => c.notOnHosted);
35
+ const COUNT_WORDS = ["no", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
36
+ const exitOf = (code) => {
37
+ const e = ERROR_BY_CODE[code];
38
+ if (!e)
39
+ throw new Error(`the guide names ${code}, which is not a known error code`);
40
+ return e.exit;
41
+ };
24
42
  function renderCore() {
25
43
  const common = GUIDE_COMMON_ERRORS.map((code) => {
26
44
  const e = ERROR_BY_CODE[code];
@@ -37,7 +55,7 @@ function renderCore() {
37
55
  " verify → snapshot → fork(N) → exec → validity report",
38
56
  "",
39
57
  "- **snapshot** — an image (pinned) + ordered `setup` steps + a `verify` command that, if given, must exit 0.",
40
- " If verify fails nothing is stored and nothing is charged. A branch is not a pin.",
58
+ " If verify fails nothing is stored; the build's own time is billed. A branch is not a pin.",
41
59
  "- **fork** — a warm copy of that workspace, with its `start` service ready;",
42
60
  " N at once, each checked before you get it.",
43
61
  "- **validity report** — per sandbox, and this split is the product: **env** failures are ours",
@@ -63,15 +81,22 @@ function renderCore() {
63
81
  "Progress, prices and diagnostics go to **stderr** — never parse them. `next[].cmd` is",
64
82
  "id-based, runnable verbatim (fill any UPPERCASE word); use it, never invent one.",
65
83
  "",
66
- `Exit codes: ${exitLine}.`,
84
+ `Exit codes: ${exitLegend}.`,
67
85
  `Highest-precedence code wins: ${EXIT_PRECEDENCE_TEXT}.`,
68
86
  "",
69
- "## The six you will actually hit",
87
+ `## The ${COUNT_WORDS[common.length] ?? common.length} you will actually hit`,
70
88
  "",
71
89
  "(`{name}` in a fix is filled from the failure's evidence.)",
72
90
  "",
73
91
  ...common,
74
92
  "",
93
+ "## Limits and stops",
94
+ "",
95
+ `- \`setup_required\` (exit ${exitOf("setup_required")}): answer the account's setup questions in the console first.`,
96
+ `- \`egress_cap_reached\` (exit ${exitOf("egress_cap_reached")}): the month's data out is spent; \`--network none\` still starts; it resets on the 1st (UTC).`,
97
+ "- A paused sandbox is kept 30 days (`pause_expires_at`); resuming keeps it, else it is deleted (`expired`).",
98
+ `- \`stopped_by_support\` (exit ${exitOf("stopped_by_support")}): support stopped it; the message says why.`,
99
+ "",
75
100
  "## Do",
76
101
  "",
77
102
  "- Pass `--cap USD` on every `fork`/`run`: a run over it is refused (exit 5) before anything starts.",
@@ -99,7 +124,10 @@ function renderCore() {
99
124
  `· \`promigence <command> --help\` · ${DOCS_URL}`,
100
125
  "Same surface as MCP tools: `promigence mcp` (stdio; `claude mcp add promigence -- promigence mcp`).",
101
126
  "",
102
- `Live in v${VERSION}: ${LIVE_SUMMARY}`,
127
+ `Live in v${VERSION}: ${HOSTED_LIVE}`,
128
+ ...(NOT_ON_HOSTED.length
129
+ ? [`Not on the hosted service yet: ${compactNames(NOT_ON_HOSTED.map((c) => c.name))} (${[...new Set(NOT_ON_HOSTED.map((c) => c.notOnHosted))].join(", ")}).`]
130
+ : []),
103
131
  ...(NOT_LIVE_SUMMARY ? ["Every other command exits 1 `not_released` in the same envelope."] : []),
104
132
  "",
105
133
  ].join("\n");
@@ -217,7 +245,8 @@ function renderCost(prices) {
217
245
  const vcpu = prices?.vcpu_second_usd ?? PRICING.vcpu_second_usd;
218
246
  const gib = prices?.gib_second_usd ?? PRICING.gib_second_usd;
219
247
  const granularity = prices?.granularity_seconds ?? PRICING.granularity_seconds;
220
- const build = prices?.build_usd ?? BUILD_PRICE;
248
+ const buildMax = prices?.build_max_s ?? BUILD_MAX_S;
249
+ const buildHours = buildMax % 3600 === 0 ? `${buildMax / 3600} h` : `${Math.ceil(buildMax / 60)} min`;
221
250
  const view = prices?.view;
222
251
  const source = prices === undefined
223
252
  ? "List prices when this CLI was built. Live, and your plan's: `promigence pricing`."
@@ -241,8 +270,10 @@ function renderCost(prices) {
241
270
  ...tiers.map((t) => `| \`${t.name}\` | ${t.vcpus} | ${t.mem_gib} GiB | ${t.disk_gib} GiB | $${t.per_second_usd} | ${perHour(t.per_second_usd)} |`),
242
271
  ...(typeof limit === "number" ? ["", `Your plan runs sandboxes of up to ${limit} vCPU.`] : []),
243
272
  "",
244
- `Building a snapshot costs $${build.toFixed(2)} once, printed on stderr **before** the build starts.`,
245
- "A build that fails `verify` or a guard stores nothing and is not charged.",
273
+ `Building a snapshot bills per second at its tier's rate while it runs, at most ${buildHours} (the most it can`,
274
+ "cost is printed on stderr **before** the build starts; what it cost, when it ends). A build that",
275
+ "fails `verify` or a guard stores nothing and is billed the time it ran; one that fails on our",
276
+ "side is not billed, and reusing an identical build is free.",
246
277
  "",
247
278
  "`env` failures (`promigence agent-guide --section errors`): one before hand-over is not billed,",
248
279
  "e.g. " + ERRORS.filter((e) => e.category === "env").slice(0, 3).map((e) => `\`${e.code}\``).join(", ") + ". After hand-over, a failure our",
@@ -320,7 +351,7 @@ export function renderLlmsTxt() {
320
351
  "",
321
352
  "## Index",
322
353
  "",
323
- ...LISTED_COMMANDS.map((c) => `- \`promigence ${c.name}\` — ${c.summary}`),
354
+ ...LISTED_COMMANDS.map((c) => `- \`promigence ${c.name}\` — ${c.summary}${c.notOnHosted ? ` (not on the hosted service yet)` : ""}`),
324
355
  "",
325
356
  "- `promigence agent-guide` — this document's first section",
326
357
  "- `promigence agent-guide --section commands` — every command with its flags",
package/dist/handlers.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, statSync, writeFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- import { BUNDLE_EXT, DEFAULT_SNAPSHOT, PromigenceError, VERSION as SDK_VERSION, configDir, credentialsPath, compatApiKey, platformOf, readCredentialsWithStore, renderBundle, verifyBundle, } from "./sdk/index.js";
3
+ import { BUNDLE_EXT, DEFAULT_SNAPSHOT, PromigenceError, VERSION as SDK_VERSION, configDir, credentialsPath, platformOf, readCredentialsWithStore, renderBundle, verifyBundle, } from "./sdk/index.js";
4
4
  import { storeLabel } from "./sdk/credstore.js";
5
5
  import { bool, client, list, nexts, positional, required, str, } from "./commands.js";
6
6
  import { emit, exitWith, forkSummary, markGuideSeen, ms, okEnvelope, parseCount, parseDuration, parseUsd, pickExit, usageError, write, } from "./io.js";
@@ -30,7 +30,7 @@ const agentGuide = async (ctx) => {
30
30
  const skipped = res.artifacts.find((a) => a.action === "skipped");
31
31
  const next = nexts(...(skipped
32
32
  ? [["promigence agent-guide --install --create", "create AGENTS.md and write the fence into it"]]
33
- : []), ["promigence agent-guide", "the guide itself (~1.2k tokens)"], ["promigence examples", "what you can fork with no build (the built-in base)"]);
33
+ : []), ["promigence agent-guide", "the guide itself (~1.4k tokens)"], ["promigence examples", "what you can fork with no build (the built-in base)"]);
34
34
  const env = okEnvelope(ctx.cmd.name, {
35
35
  mode: checking ? "check" : "install",
36
36
  dir: res.dir,
@@ -259,9 +259,6 @@ function keySource(ctx) {
259
259
  if (creds.api_key) {
260
260
  return { source: store === "file" || store === undefined ? credentialsPath() : `${storeLabel(store)} (item ${credentialsPath()})`, present: true };
261
261
  }
262
- if (compatApiKey(process.env["E2B_API_KEY"])) {
263
- return { source: "E2B_API_KEY (compatibility)", present: true };
264
- }
265
262
  return { source: "none", present: false };
266
263
  }
267
264
  async function clientChecks(ctx) {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { SCHEMA, GUIDE_POINTER, GOLDEN_PATH, COMMANDS, LISTED_COMMANDS, GROUPS,
3
3
  export { ALL_ERROR_CODES, BILLED_TEXT, BUDGETS, SECTIONS, estimateTokens, guideJson, lookupError, renderGuide, renderLlmsTxt,
4
4
  renderGuide as renderAgentGuide, } from "./guide.js";
5
5
  export type { Section } from "./guide.js";
6
- export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_PRICE, PRICING } from "./generated.js";
6
+ export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_MAX_S, PRICING } from "./generated.js";
7
7
  export type { ErrorEntry, TierEntry } from "./generated.js";
8
8
  export { FENCE_BEGIN, FENCE_END, FENCE_MAX_LINES, fenceBody, install, spliceFence } from "./install.js";
9
9
  export type { Artifact, InstallResult } from "./install.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from "./sdk/index.js";
2
2
  export { SCHEMA, GUIDE_POINTER, GOLDEN_PATH, COMMANDS, LISTED_COMMANDS, GROUPS, GLOBAL_FLAGS, EXIT_CODES, LIVE_COMMANDS, LIVE_SUMMARY, NOT_LIVE_SUMMARY, STATUS_LINE, DESCRIPTION, compactNames, EXIT_PRECEDENCE, EXIT_PRECEDENCE_TEXT, OUTPUT_MODE_PRECEDENCE, WAIT_TIMEOUT_DEFAULT, REQUEST_TIMEOUT_DEFAULT, GUIDE_COMMON_ERRORS, findCommand, knownFlags, nearestFlag, unknownFlags, renderHelp, renderCommandHelp, signInLine, notReleasedEnvelope, notReleasedLines, } from "./surface.js";
3
3
  export { ALL_ERROR_CODES, BILLED_TEXT, BUDGETS, SECTIONS, estimateTokens, guideJson, lookupError, renderGuide, renderLlmsTxt, renderGuide as renderAgentGuide, } from "./guide.js";
4
- export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_PRICE, PRICING } from "./generated.js";
4
+ export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_MAX_S, PRICING } from "./generated.js";
5
5
  export { FENCE_BEGIN, FENCE_END, FENCE_MAX_LINES, fenceBody, install, spliceFence } from "./install.js";
6
6
  export { detect, gitFacts, normalizeRemote, readConfig, renderConfig, stripRemoteCredentials } from "./init.js";
7
7
  export { DEVCONTAINER_NAMES, DEVCONTAINER_SPEC, DevcontainerError, detectOne, discover, load, parseJsonc, planJson, planLines, planSetup, resolveGiven, shellQuote, snapshotEnv, snapshotUsers, substituteString, translate, workspaceRootFor, wrapCommand, } from "./devcontainer.js";