deepline 0.1.103 → 0.1.105

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.
@@ -215,10 +215,13 @@ var SDK_RELEASE = {
215
215
  // preflight (existence, data rows, quotes, duplicate headers), HTML error
216
216
  // scrubbing, and word-boundary watch truncation.
217
217
  // 0.1.103 ships the refined SDK CLI command surface.
218
- version: "0.1.103",
218
+ // 0.1.104 ships postgres_fast suspension/billing parity and runtime worker hardening.
219
+ // 0.1.105 ships the billing catalog surface: billing plans, subscribe,
220
+ // subscription status/cancel, invoices, and the client.billing namespace.
221
+ version: "0.1.105",
219
222
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
220
223
  supportPolicy: {
221
- latest: "0.1.103",
224
+ latest: "0.1.105",
222
225
  minimumSupported: "0.1.53",
223
226
  deprecatedBelow: "0.1.53"
224
227
  }
@@ -349,7 +352,7 @@ var HttpClient = class {
349
352
  signal: controller.signal
350
353
  });
351
354
  clearTimeout(timeoutId);
352
- if (response.status === 401 || response.status === 403) {
355
+ if (response.status === 401 || response.status === 403 && !options?.forbiddenAsApiError) {
353
356
  throw new AuthError();
354
357
  }
355
358
  if (response.status === 429) {
@@ -1441,6 +1444,9 @@ var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
1441
1444
  var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
1442
1445
  var V2_EXECUTE_RESPONSE_CONTRACT = "v2-tool-response";
1443
1446
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
1447
+ var REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY = 3;
1448
+ var REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT = 3;
1449
+ var REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES = 25e5;
1444
1450
  function sleep2(ms) {
1445
1451
  return new Promise((resolve16) => setTimeout(resolve16, ms));
1446
1452
  }
@@ -1453,6 +1459,45 @@ function isTransientCompileManifestError(error) {
1453
1459
  message
1454
1460
  );
1455
1461
  }
1462
+ async function mapWithConcurrency(items, concurrency, mapper) {
1463
+ const results = new Array(items.length);
1464
+ let nextIndex = 0;
1465
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
1466
+ await Promise.all(
1467
+ Array.from({ length: workerCount }, async () => {
1468
+ for (; ; ) {
1469
+ const index = nextIndex;
1470
+ nextIndex += 1;
1471
+ if (index >= items.length) {
1472
+ return;
1473
+ }
1474
+ results[index] = await mapper(items[index], index);
1475
+ }
1476
+ })
1477
+ );
1478
+ return results;
1479
+ }
1480
+ function jsonUtf8Bytes(value) {
1481
+ return new TextEncoder().encode(JSON.stringify(value)).length;
1482
+ }
1483
+ function chunkRegisterPlayArtifacts(artifacts) {
1484
+ const chunks = [];
1485
+ let current = [];
1486
+ for (const artifact of artifacts) {
1487
+ const candidate = [...current, artifact];
1488
+ const candidateTooLarge = candidate.length > REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT || jsonUtf8Bytes({ artifacts: candidate }) > REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES;
1489
+ if (current.length > 0 && candidateTooLarge) {
1490
+ chunks.push(current);
1491
+ current = [artifact];
1492
+ } else {
1493
+ current = candidate;
1494
+ }
1495
+ }
1496
+ if (current.length > 0) {
1497
+ chunks.push(current);
1498
+ }
1499
+ return chunks;
1500
+ }
1456
1501
  var RUN_LOGS_PAGE_LIMIT = 1e3;
1457
1502
  function isRecord2(value) {
1458
1503
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -1620,6 +1665,8 @@ var DeeplineClient = class {
1620
1665
  config;
1621
1666
  /** Canonical run lifecycle namespace backed by `/api/v2/runs`. */
1622
1667
  runs;
1668
+ /** Billing namespace: subscription status/cancel and invoice history. */
1669
+ billing;
1623
1670
  /**
1624
1671
  * Create a low-level SDK client.
1625
1672
  *
@@ -1640,6 +1687,16 @@ var DeeplineClient = class {
1640
1687
  exportDatasetRows: (input2) => this.getPlaySheetRows(input2),
1641
1688
  stop: (runId, options2) => this.stopRun(runId, options2)
1642
1689
  };
1690
+ this.billing = {
1691
+ plans: () => this.getBillingPlans(),
1692
+ subscription: {
1693
+ status: () => this.getBillingSubscriptionStatus(),
1694
+ cancel: (options2) => this.cancelBillingSubscription(options2)
1695
+ },
1696
+ invoices: {
1697
+ list: (options2) => this.listBillingInvoices(options2)
1698
+ }
1699
+ };
1643
1700
  }
1644
1701
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
1645
1702
  get baseUrl() {
@@ -2008,8 +2065,13 @@ var DeeplineClient = class {
2008
2065
  * first when a compiler manifest is not already supplied.
2009
2066
  */
2010
2067
  async registerPlayArtifacts(artifacts) {
2011
- const compiledArtifacts = await Promise.all(
2012
- artifacts.map(async (artifact) => ({
2068
+ if (artifacts.length === 0) {
2069
+ return this.http.post("/api/v2/plays/artifacts", { artifacts });
2070
+ }
2071
+ const compiledArtifacts = await mapWithConcurrency(
2072
+ artifacts,
2073
+ REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY,
2074
+ async (artifact) => ({
2013
2075
  ...artifact,
2014
2076
  compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
2015
2077
  name: artifact.name,
@@ -2017,11 +2079,20 @@ var DeeplineClient = class {
2017
2079
  sourceFiles: artifact.sourceFiles,
2018
2080
  artifact: artifact.artifact
2019
2081
  })
2020
- }))
2082
+ })
2021
2083
  );
2022
- return this.http.post("/api/v2/plays/artifacts", {
2023
- artifacts: compiledArtifacts
2024
- });
2084
+ const responses = [];
2085
+ for (const chunk of chunkRegisterPlayArtifacts(compiledArtifacts)) {
2086
+ responses.push(
2087
+ await this.http.post("/api/v2/plays/artifacts", {
2088
+ artifacts: chunk
2089
+ })
2090
+ );
2091
+ }
2092
+ return {
2093
+ success: responses.every((response) => response.success),
2094
+ artifacts: responses.flatMap((response) => response.artifacts)
2095
+ };
2025
2096
  }
2026
2097
  /**
2027
2098
  * Compile a bundled play artifact into the server-side compiler manifest.
@@ -2994,6 +3065,61 @@ var DeeplineClient = class {
2994
3065
  // ——————————————————————————————————————————————————————————
2995
3066
  // Health
2996
3067
  // ——————————————————————————————————————————————————————————
3068
+ /**
3069
+ * Published plans plus the caller's active plan: prices, monthly grant
3070
+ * credits, rollover policy, and which plans are open for subscription.
3071
+ * Prefer `client.billing.plans()`.
3072
+ *
3073
+ * @returns Snake_case catalog from `GET /api/v2/billing/catalog/current`
3074
+ */
3075
+ async getBillingPlans() {
3076
+ return this.http.get("/api/v2/billing/catalog/current");
3077
+ }
3078
+ /**
3079
+ * Subscription state for the active workspace: active plan, whether a
3080
+ * Stripe subscription backs it, renewal/cancellation facts, and remaining
3081
+ * Deepline credit pools. Prefer `client.billing.subscription.status()`.
3082
+ *
3083
+ * @returns Snake_case subscription status from `GET /api/v2/billing/subscription/status`
3084
+ */
3085
+ async getBillingSubscriptionStatus() {
3086
+ return this.http.get(
3087
+ "/api/v2/billing/subscription/status"
3088
+ );
3089
+ }
3090
+ /**
3091
+ * Schedule subscription cancellation at period end, or reverse a pending
3092
+ * cancellation with `{ undo: true }`. The customer keeps the cycle they
3093
+ * paid for and every remaining credit — cancellation never claws back
3094
+ * credits. Prefer `client.billing.subscription.cancel(...)`.
3095
+ *
3096
+ * @throws {@link DeeplineError} with `statusCode: 409` when the workspace
3097
+ * has no active subscription, and `statusCode: 502` when Stripe rejects
3098
+ * the update (the server message is preserved).
3099
+ */
3100
+ async cancelBillingSubscription(options) {
3101
+ return this.http.post(
3102
+ "/api/v2/billing/subscription/cancel",
3103
+ { action: options?.undo ? "undo_cancel" : "cancel" }
3104
+ );
3105
+ }
3106
+ /**
3107
+ * Customer-facing billing history: subscription invoices plus one-time
3108
+ * credit purchase receipts, newest first, with Stripe-hosted links.
3109
+ * Prefer `client.billing.invoices.list(...)`.
3110
+ *
3111
+ * @param options.limit - Maximum entries to return (server clamps to 1–100, default 24).
3112
+ */
3113
+ async listBillingInvoices(options) {
3114
+ const params = new URLSearchParams();
3115
+ if (options?.limit !== void 0) {
3116
+ params.set("limit", String(options.limit));
3117
+ }
3118
+ const suffix = Array.from(params).length > 0 ? `?${params.toString()}` : "";
3119
+ return this.http.get(
3120
+ `/api/v2/billing/invoices${suffix}`
3121
+ );
3122
+ }
2997
3123
  /**
2998
3124
  * Check API connectivity and server health.
2999
3125
  *
@@ -4130,6 +4256,56 @@ import { Command } from "commander";
4130
4256
  import { appendFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
4131
4257
  import { dirname as dirname4, resolve as resolve3 } from "path";
4132
4258
  import { stringify as stringify2 } from "csv-stringify/sync";
4259
+ var SUBSCRIPTION_STATUS_NEXT_COMMAND = "deepline billing subscription status --json";
4260
+ var SUBSCRIPTION_CANCEL_PATH = "/api/v2/billing/subscription/cancel";
4261
+ function billingFailureFromError(error, options) {
4262
+ if (error instanceof AuthError) {
4263
+ return {
4264
+ exitCode: 3,
4265
+ code: "AUTH_ERROR",
4266
+ message: error.message,
4267
+ next: "deepline auth status --json"
4268
+ };
4269
+ }
4270
+ if (!(error instanceof DeeplineError) || typeof error.statusCode !== "number") {
4271
+ return null;
4272
+ }
4273
+ if (error.statusCode === 404 || error.statusCode === 409) {
4274
+ return {
4275
+ exitCode: 4,
4276
+ code: options.notFoundCode,
4277
+ message: error.message,
4278
+ next: SUBSCRIPTION_STATUS_NEXT_COMMAND
4279
+ };
4280
+ }
4281
+ if (error.statusCode >= 500) {
4282
+ return {
4283
+ exitCode: 5,
4284
+ code: "BILLING_SERVER_ERROR",
4285
+ message: error.message,
4286
+ next: SUBSCRIPTION_STATUS_NEXT_COMMAND
4287
+ };
4288
+ }
4289
+ return null;
4290
+ }
4291
+ function reportBillingFailure(failure, options) {
4292
+ const textLines = [failure.message];
4293
+ if (failure.next) {
4294
+ textLines.push(`Next: ${failure.next}`);
4295
+ }
4296
+ printCommandEnvelope(
4297
+ {
4298
+ ok: false,
4299
+ exitCode: failure.exitCode,
4300
+ code: failure.code,
4301
+ message: failure.message,
4302
+ ...failure.next ? { next: failure.next } : {}
4303
+ },
4304
+ { json: options.json, text: `${textLines.join("\n")}
4305
+ ` }
4306
+ );
4307
+ process.exitCode = failure.exitCode;
4308
+ }
4133
4309
  function humanize(value) {
4134
4310
  return String(value || "").split("_").filter(Boolean).map((token) => token[0]?.toUpperCase() + token.slice(1)).join(" ") || "Unknown";
4135
4311
  }
@@ -4405,6 +4581,273 @@ async function handleLedgerExportAll(options) {
4405
4581
  { json: options.json }
4406
4582
  );
4407
4583
  }
4584
+ function planPriceText(priceUsd, priceInterval) {
4585
+ if (priceUsd === null || priceUsd === void 0) {
4586
+ return "no list price";
4587
+ }
4588
+ return `$${priceUsd}${priceInterval ? `/${priceInterval}` : ""}`;
4589
+ }
4590
+ function planRolloverText(rollover) {
4591
+ const mode = rollover?.mode ?? "none";
4592
+ if (mode === "none") return "no rollover";
4593
+ if (mode === "bounded") {
4594
+ return `rollover up to ${rollover?.max_credits ?? "(unknown)"} credits`;
4595
+ }
4596
+ return `rollover ${mode}`;
4597
+ }
4598
+ async function handlePlans(options) {
4599
+ const { http } = getAuthedHttpClient();
4600
+ const payload = await http.get(
4601
+ "/api/v2/billing/catalog/current"
4602
+ );
4603
+ const activePlan = payload.active_plan ?? {};
4604
+ const plans = Array.isArray(payload.plans) ? payload.plans : [];
4605
+ const metrics = Array.isArray(payload.metrics) ? payload.metrics : [];
4606
+ const lines = [
4607
+ `Catalog: ${payload.catalog_id ?? "(unknown)"} (version ${payload.catalog_version ?? "(unknown)"})`,
4608
+ `Active plan: ${activePlan.public_name ?? "(unknown)"} (${activePlan.plan_version_id ?? "(unknown)"})`,
4609
+ `Assigned by: ${activePlan.assigned_by ?? "default"} | subscription: ${activePlan.has_subscription ? "yes" : "no"}`,
4610
+ ...plans.length === 0 ? ["Plans: none published"] : [
4611
+ "Plans:",
4612
+ ...plans.map(
4613
+ (plan) => `${plan.public_name ?? "(unknown)"} (${plan.plan_version_id ?? "(unknown)"}) | ${planPriceText(plan.price_usd, plan.price_interval)} | ${plan.monthly_grant_credits ?? 0} credits/cycle | ${planRolloverText(plan.rollover)} | ${plan.acquirable ? "acquirable" : "not acquirable"}`
4614
+ )
4615
+ ],
4616
+ ...metrics.length === 0 ? ["Metrics: none"] : [
4617
+ "Metrics:",
4618
+ ...metrics.map(
4619
+ (metric) => `${metric.id ?? "(unknown)"} | ${metric.name ?? "(unknown)"}`
4620
+ )
4621
+ ]
4622
+ ];
4623
+ printCommandEnvelope(
4624
+ {
4625
+ ...payload,
4626
+ render: { sections: [{ title: "billing plans", lines }] }
4627
+ },
4628
+ { json: options.json }
4629
+ );
4630
+ }
4631
+ async function handleSubscribe(planVersionId, options) {
4632
+ const { http } = getAuthedHttpClient();
4633
+ const payload = await http.request(
4634
+ "/api/v2/billing/subscription/checkout",
4635
+ {
4636
+ method: "POST",
4637
+ body: { plan_version_id: planVersionId },
4638
+ forbiddenAsApiError: true
4639
+ }
4640
+ );
4641
+ const url = String(payload.checkout_url || "");
4642
+ if (!options.json && options.open !== false && url) openInBrowser(url);
4643
+ printCommandEnvelope(
4644
+ {
4645
+ ...payload,
4646
+ render: {
4647
+ sections: [
4648
+ {
4649
+ title: "billing subscribe",
4650
+ lines: [url || "Subscription checkout session created."]
4651
+ }
4652
+ ]
4653
+ }
4654
+ },
4655
+ { json: options.json }
4656
+ );
4657
+ }
4658
+ async function handleSubscriptionStatus(options) {
4659
+ const client2 = new DeeplineClient();
4660
+ const payload = await client2.billing.subscription.status();
4661
+ const pools = payload.credit_pools ?? [];
4662
+ const lines = [
4663
+ `Plan: ${payload.plan_name ?? "(unknown)"} (${payload.plan_version_id ?? "(unknown)"})`,
4664
+ `Subscribed: ${payload.subscribed ? "yes" : "no"}`,
4665
+ ...payload.cancel_at_period_end ? [
4666
+ `Cancellation scheduled: ends ${payload.current_period_end ?? "(unknown)"} at period end (credits are kept)`
4667
+ ] : [],
4668
+ `Price: ${planPriceText(payload.price_usd, payload.price_interval)}`,
4669
+ `Monthly grant: ${payload.monthly_grant_credits ?? 0} credits`,
4670
+ `Pooled credits remaining: ${payload.pooled_credits_remaining ?? 0}`,
4671
+ ...pools.length === 0 ? ["Credit pools: none"] : [
4672
+ "Credit pools:",
4673
+ ...pools.map(
4674
+ (pool) => `${pool.pool ?? "(unknown)"} | ${pool.credits_remaining ?? 0}/${pool.credits_granted ?? 0} credits remaining | ${pool.source ?? "(unknown)"}`
4675
+ )
4676
+ ],
4677
+ `Assigned by: ${payload.assigned_by ?? "default"}`
4678
+ ];
4679
+ printCommandEnvelope(
4680
+ {
4681
+ ...payload,
4682
+ render: { sections: [{ title: "billing subscription", lines }] }
4683
+ },
4684
+ { json: options.json }
4685
+ );
4686
+ }
4687
+ async function handleSubscriptionCancel(options) {
4688
+ const client2 = new DeeplineClient();
4689
+ const action = options.undo ? "undo_cancel" : "cancel";
4690
+ if (options.dryRun) {
4691
+ const status = await client2.billing.subscription.status();
4692
+ if (!status.subscribed) {
4693
+ reportBillingFailure(
4694
+ {
4695
+ exitCode: 4,
4696
+ code: "NO_ACTIVE_SUBSCRIPTION",
4697
+ message: "This workspace has no active subscription to cancel.",
4698
+ next: SUBSCRIPTION_STATUS_NEXT_COMMAND
4699
+ },
4700
+ options
4701
+ );
4702
+ return;
4703
+ }
4704
+ const consequence = action === "cancel" ? `Would schedule cancellation at period end${status.current_period_end ? ` (${status.current_period_end})` : ""}. The subscription stays active until then and remaining credits are kept.` : "Would reverse the pending cancellation; the subscription would renew normally.";
4705
+ printCommandEnvelope(
4706
+ {
4707
+ ok: true,
4708
+ dry_run: true,
4709
+ action,
4710
+ plan_version_id: status.plan_version_id,
4711
+ plan_name: status.plan_name,
4712
+ cancel_at_period_end: status.cancel_at_period_end,
4713
+ current_period_end: status.current_period_end,
4714
+ consequence,
4715
+ planned_request: {
4716
+ method: "POST",
4717
+ path: SUBSCRIPTION_CANCEL_PATH,
4718
+ body: { action }
4719
+ },
4720
+ render: {
4721
+ sections: [
4722
+ {
4723
+ title: "billing subscription cancel (dry run)",
4724
+ lines: [
4725
+ `Plan: ${status.plan_name} (${status.plan_version_id})`,
4726
+ `Planned request: POST ${SUBSCRIPTION_CANCEL_PATH} {"action":"${action}"}`,
4727
+ consequence,
4728
+ "No request was sent. Re-run without --dry-run to apply."
4729
+ ]
4730
+ }
4731
+ ]
4732
+ }
4733
+ },
4734
+ { json: options.json }
4735
+ );
4736
+ return;
4737
+ }
4738
+ let result;
4739
+ try {
4740
+ result = await client2.billing.subscription.cancel({
4741
+ undo: Boolean(options.undo)
4742
+ });
4743
+ } catch (error) {
4744
+ const failure = billingFailureFromError(error, {
4745
+ notFoundCode: "NO_ACTIVE_SUBSCRIPTION"
4746
+ });
4747
+ if (!failure) throw error;
4748
+ reportBillingFailure(failure, options);
4749
+ return;
4750
+ }
4751
+ const lines = options.undo ? [
4752
+ "Pending cancellation reversed; the subscription will renew normally.",
4753
+ `Subscription: ${result.subscription_id}`
4754
+ ] : [
4755
+ `Cancellation scheduled for subscription ${result.subscription_id}.`,
4756
+ `The subscription stays active until ${result.current_period_end ?? "the end of the current billing period"}, then cancels at period end.`,
4757
+ "Remaining credits are yours to keep \u2014 cancellation never removes credits.",
4758
+ "Undo with: deepline billing subscription cancel --undo"
4759
+ ];
4760
+ printCommandEnvelope(
4761
+ {
4762
+ ok: true,
4763
+ ...result,
4764
+ render: {
4765
+ sections: [{ title: "billing subscription cancel", lines }]
4766
+ }
4767
+ },
4768
+ { json: options.json }
4769
+ );
4770
+ }
4771
+ function invoiceAmountText(amountCents, currency) {
4772
+ const value = (Number(amountCents ?? 0) / 100).toFixed(2);
4773
+ return String(currency ?? "usd").toLowerCase() === "usd" ? `$${value}` : `${value} ${String(currency).toUpperCase()}`;
4774
+ }
4775
+ function invoiceDateText(createdAt) {
4776
+ return String(createdAt ?? "").slice(0, 10);
4777
+ }
4778
+ function invoiceLine(entry, compact) {
4779
+ const amount = invoiceAmountText(entry.amount_cents, entry.currency);
4780
+ const link = entry.url ?? "(no link)";
4781
+ if (compact) {
4782
+ return [
4783
+ entry.id,
4784
+ invoiceDateText(entry.created_at),
4785
+ amount,
4786
+ entry.status,
4787
+ link
4788
+ ].join(" | ");
4789
+ }
4790
+ return [
4791
+ invoiceDateText(entry.created_at),
4792
+ entry.description,
4793
+ amount,
4794
+ entry.status,
4795
+ link
4796
+ ].join(" | ");
4797
+ }
4798
+ async function handleInvoices(options) {
4799
+ let limit;
4800
+ if (options.limit !== void 0) {
4801
+ limit = Number.parseInt(options.limit, 10);
4802
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
4803
+ reportBillingFailure(
4804
+ {
4805
+ exitCode: 2,
4806
+ code: "INVALID_LIMIT",
4807
+ message: "--limit must be an integer between 1 and 100."
4808
+ },
4809
+ options
4810
+ );
4811
+ return;
4812
+ }
4813
+ }
4814
+ const client2 = new DeeplineClient();
4815
+ let payload;
4816
+ try {
4817
+ payload = await client2.billing.invoices.list(
4818
+ limit === void 0 ? void 0 : { limit }
4819
+ );
4820
+ } catch (error) {
4821
+ const failure = billingFailureFromError(error, {
4822
+ notFoundCode: "NOT_FOUND"
4823
+ });
4824
+ if (!failure) throw error;
4825
+ reportBillingFailure(failure, options);
4826
+ return;
4827
+ }
4828
+ const entries = Array.isArray(payload.entries) ? payload.entries : [];
4829
+ const compact = Boolean(options.compact);
4830
+ const header = compact ? "id | date | amount | status | link" : "date | description | amount | status | link";
4831
+ const lines = entries.length === 0 ? ["No invoices or receipts yet."] : [header, ...entries.map((entry) => invoiceLine(entry, compact))];
4832
+ const envelope = compact ? {
4833
+ org_id: payload.org_id,
4834
+ count: entries.length,
4835
+ entries: entries.map((entry) => ({
4836
+ id: entry.id,
4837
+ date: invoiceDateText(entry.created_at),
4838
+ amount: invoiceAmountText(entry.amount_cents, entry.currency),
4839
+ status: entry.status,
4840
+ url: entry.url
4841
+ }))
4842
+ } : { ...payload, count: entries.length };
4843
+ printCommandEnvelope(
4844
+ {
4845
+ ...envelope,
4846
+ render: { sections: [{ title: "billing invoices", lines }] }
4847
+ },
4848
+ { json: options.json }
4849
+ );
4850
+ }
4408
4851
  async function handleCheckout(options) {
4409
4852
  const { http } = getAuthedHttpClient();
4410
4853
  const payload = await http.post(
@@ -4453,19 +4896,33 @@ async function handleRedeemCode(code, options) {
4453
4896
  );
4454
4897
  }
4455
4898
  function registerBillingCommands(program) {
4456
- const billing = program.command("billing").description("Inspect balance, usage, limits, and checkout flows.").addHelpText(
4899
+ const billing = program.command("billing").description("See your plan and credits, buy more, and manage billing.").addHelpText(
4457
4900
  "after",
4458
4901
  `
4459
4902
  Concepts:
4460
4903
  Billing commands show Deepline credits, not raw provider spend.
4461
- set-limit/off mutate the monthly workspace cap. checkout/redeem-code can open
4462
- a browser unless --no-open is set.
4904
+ checkout/subscribe/redeem-code can open a browser unless --no-open is set.
4463
4905
 
4464
4906
  Examples:
4907
+ # See where you stand
4465
4908
  deepline billing balance --json
4466
4909
  deepline billing usage --limit 20 --json
4467
- deepline billing set-limit 500 --json
4910
+ deepline billing plans --json
4911
+ deepline billing subscription status --json
4912
+
4913
+ # Buy credits or a plan
4468
4914
  deepline billing checkout --credits 1000 --no-open --json
4915
+ deepline billing subscribe runtime-395-2026-07-v1 --no-open --json
4916
+ deepline billing redeem-code --code ABC123 --no-open --json
4917
+
4918
+ # Manage
4919
+ deepline billing subscription cancel --dry-run --json
4920
+ deepline billing set-limit 500 --json
4921
+
4922
+ # Records
4923
+ deepline billing invoices --limit 12 --json
4924
+ deepline billing history --time 1m --json
4925
+ deepline billing ledger export all --json
4469
4926
  `
4470
4927
  );
4471
4928
  billing.command("balance").description("Show current billing balance.").addHelpText(
@@ -4591,6 +5048,110 @@ Examples:
4591
5048
  deepline billing checkout --credits 1000 --discount-code LAUNCH --no-open
4592
5049
  `
4593
5050
  ).option("--tier <tierId>", "Named pricing tier").option("--credits <credits>", "Custom credit amount").option("--discount-code <code>", "Apply a discount code").option("--no-open", "Print the checkout URL without opening a browser").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleCheckout);
5051
+ billing.command("plans").description("Show published plans and the plan you are on.").addHelpText(
5052
+ "after",
5053
+ `
5054
+ Notes:
5055
+ Read-only. Answers "what plans exist and what am I on": each plan's price,
5056
+ monthly grant credits, rollover policy, and whether it is open for
5057
+ subscription, plus the catalog's usage metrics. All amounts are Deepline
5058
+ credits and Deepline-facing USD, never raw provider spend. Subscribe to a
5059
+ listed plan with: deepline billing subscribe <plan_version_id>
5060
+
5061
+ Examples:
5062
+ deepline billing plans
5063
+ deepline billing plans --json
5064
+ `
5065
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handlePlans);
5066
+ billing.command("subscribe").description(
5067
+ "Start a subscription checkout for a plan and optionally open it in your browser."
5068
+ ).addHelpText(
5069
+ "after",
5070
+ `
5071
+ Notes:
5072
+ Creates a Stripe subscription checkout session for the given plan version.
5073
+ Opens the checkout URL in a browser unless --no-open is set. Fails loudly
5074
+ with the server error when subscription checkout is not enabled or the plan
5075
+ is not open for subscription.
5076
+
5077
+ Examples:
5078
+ deepline billing subscribe runtime-395-2026-07-v1
5079
+ deepline billing subscribe runtime-395-2026-07-v1 --no-open --json
5080
+ `
5081
+ ).argument("<plan_version_id>", "Plan version id from `billing plans`").option("--no-open", "Print the checkout URL without opening a browser").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleSubscribe);
5082
+ billing.command("subscription").description("Inspect and manage subscription state for the workspace.").addHelpText(
5083
+ "after",
5084
+ `
5085
+ Examples:
5086
+ deepline billing subscription status
5087
+ deepline billing subscription status --json
5088
+ deepline billing subscription cancel --dry-run
5089
+ deepline billing subscription cancel --json
5090
+ deepline billing subscription cancel --undo --json
5091
+ `
5092
+ ).addCommand(
5093
+ new Command("status").description("Show active plan, subscription state, and credit pools.").addHelpText(
5094
+ "after",
5095
+ `
5096
+ Notes:
5097
+ Read-only. Shows the active plan, whether a Stripe subscription backs it,
5098
+ scheduled cancellation state, and remaining Deepline credits per grant pool.
5099
+
5100
+ Examples:
5101
+ deepline billing subscription status
5102
+ deepline billing subscription status --json
5103
+ `
5104
+ ).option(
5105
+ "--json",
5106
+ "Emit JSON output. Also automatic when stdout is piped"
5107
+ ).action(handleSubscriptionStatus)
5108
+ ).addCommand(
5109
+ new Command("cancel").description("Cancel the subscription at period end. Credits are kept.").addHelpText(
5110
+ "after",
5111
+ `
5112
+ Notes:
5113
+ Mutates subscription state. Cancellation is always AT PERIOD END: the
5114
+ subscription stays active until the end of the paid cycle and remaining
5115
+ Deepline credits are kept \u2014 cancellation never removes credits. --undo
5116
+ reverses a pending cancellation before the period ends. --dry-run reads
5117
+ subscription status and prints the planned mutation without calling the
5118
+ cancel endpoint.
5119
+
5120
+ Exit codes:
5121
+ 0 cancellation scheduled (or reversed with --undo)
5122
+ 3 auth error
5123
+ 4 no active subscription to cancel
5124
+ 5 Stripe/server failure (the server message is preserved)
5125
+
5126
+ Examples:
5127
+ deepline billing subscription cancel --dry-run
5128
+ deepline billing subscription cancel
5129
+ deepline billing subscription cancel --undo
5130
+ deepline billing subscription cancel --json
5131
+ `
5132
+ ).option("--undo", "Reverse a pending cancellation before period end").option(
5133
+ "--dry-run",
5134
+ "Print the planned cancellation without calling the cancel endpoint"
5135
+ ).option(
5136
+ "--json",
5137
+ "Emit JSON output. Also automatic when stdout is piped"
5138
+ ).action(handleSubscriptionCancel)
5139
+ );
5140
+ billing.command("invoices").description("List subscription invoices and credit purchase receipts.").addHelpText(
5141
+ "after",
5142
+ `
5143
+ Notes:
5144
+ Read-only. Shows customer-facing billing history from Stripe, newest first:
5145
+ subscription invoices plus one-time Deepline credit purchase receipts, with
5146
+ Stripe-hosted links. Amounts are what you paid \u2014 never provider spend.
5147
+ --compact keeps id/date/amount/status/url only.
5148
+
5149
+ Examples:
5150
+ deepline billing invoices
5151
+ deepline billing invoices --limit 12 --json
5152
+ deepline billing invoices --compact --json
5153
+ `
5154
+ ).option("--limit <n>", "Maximum entries to return (1-100, default 24)").option("--compact", "Keep only id, date, amount, status, and url").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleInvoices);
4594
5155
  billing.command("redeem-code").description("Redeem a billing code.").addHelpText(
4595
5156
  "after",
4596
5157
  `
@@ -6932,7 +7493,7 @@ var PLAY_RUNTIME_PROVIDERS = {
6932
7493
  runner: PLAY_RUNTIME_BACKENDS.daytona,
6933
7494
  dedup: PLAY_DEDUP_BACKENDS.durableObject,
6934
7495
  artifactKind: PLAY_ARTIFACT_KINDS.cjsNode20,
6935
- label: "Experimental Postgres Scheduler + warm sandbox runner + DO dedup"
7496
+ label: "BETA: Postgres Scheduler + warm sandbox runner + DO dedup"
6936
7497
  },
6937
7498
  postgres_fast_sandbox: {
6938
7499
  id: PLAY_RUNTIME_PROVIDER_IDS.postgresFastSandbox,
@@ -6940,7 +7501,7 @@ var PLAY_RUNTIME_PROVIDERS = {
6940
7501
  runner: PLAY_RUNTIME_BACKENDS.daytona,
6941
7502
  dedup: PLAY_DEDUP_BACKENDS.durableObject,
6942
7503
  artifactKind: PLAY_ARTIFACT_KINDS.cjsNode20,
6943
- label: "Experimental Postgres Scheduler + warm sandbox runner + DO dedup"
7504
+ label: "BETA: Postgres Scheduler + warm sandbox runner + DO dedup"
6944
7505
  },
6945
7506
  postgres_fast_workers: {
6946
7507
  id: PLAY_RUNTIME_PROVIDER_IDS.postgresFastWorkers,
@@ -12177,7 +12738,7 @@ function writeStartedPlayRun(input2) {
12177
12738
  );
12178
12739
  }
12179
12740
  function parsePlayRunOptions(args) {
12180
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
12741
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n --profile defaults to workers_edge; postgres_fast is BETA (opt-in per run, never the default).\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
12181
12742
  let filePath = null;
12182
12743
  let playName = null;
12183
12744
  let input2 = null;