carthagent 1.1.0-beta.2 → 1.1.0-beta.3

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.
@@ -0,0 +1,79 @@
1
+ # Beta.2 release acceptance
2
+
3
+ November 2, 2026. Immutable tag `v1.1.0-beta.2` points to
4
+ `e14aeb17c213a927a27e9522da65db5928219647`.
5
+
6
+ ## Completed
7
+
8
+ - Native onboarding PR #238 and first-task guidance PR #239 merged. Release PR
9
+ #240 merged with tag ancestry preserved at `fc5382364f41b1563ed137ed0db0c5711e041a83`.
10
+ - Five-platform prebuild [41505735348](https://github.com/B-icy/Carthagent/actions/runs/41505735348)
11
+ passed. Local manifest regeneration verified all archive/executable hashes and
12
+ embedded provenance against the pinned runtime recipe.
13
+ - Release CI [41509102387](https://github.com/B-icy/Carthagent/actions/runs/41509102387)
14
+ passed across Linux, macOS and Windows, including installed-package acceptance.
15
+ - Final draft acceptance [41511681097](https://github.com/B-icy/Carthagent/actions/runs/41511681097)
16
+ passed against the exact tag, assets and main ancestry.
17
+ - GitHub prerelease is public with five runtime archives, the manifest and
18
+ combined checksums. Independent anonymous verification of all five public
19
+ archives passed.
20
+ - Release workflow [41512736590](https://github.com/B-icy/Carthagent/actions/runs/41512736590)
21
+ passed anonymous installed-package acceptance and reports successful npm
22
+ publication with signed provenance (transparency log index 3648230788).
23
+ - Backend onboarding #178 and retention reporting #179 are deployed. Live
24
+ authenticated account identity and read-only plan terms were verified earlier.
25
+
26
+ ## npm publication verified
27
+
28
+ After the workflow reported `+ carthagent@1.1.0-beta.2`, public registry requests
29
+ from the acceptance environment still returned 404 for that exact version.
30
+ Both npm CLI and cache-bypassed package metadata initially reported
31
+ `next=1.1.0-beta.1`, `latest=1.0.7`. Registry visibility subsequently converged;
32
+ no repeat publication was attempted.
33
+
34
+ The public beta.2 tarball's SHA-512 matches registry integrity and the provenance
35
+ subject digest. Sigstore verification passed for the GitHub Actions issuer and
36
+ the repository's `release.yml@refs/heads/main` identity. The statement identifies
37
+ release workflow 41512736590, and package `gitHead` matches the immutable tag.
38
+ Verified tags: `next=1.1.0-beta.2`, `latest=1.0.7`.
39
+ Evidence: `/tmp/opencode/beta2-npm-acceptance.json`.
40
+
41
+ ## Installed update verified
42
+
43
+ The website command `npm install -g carthagent@next` installed beta.2 into an
44
+ isolated Linux x64 home/prefix. Default runtime acquisition resolved the pinned
45
+ CTG-owned executable with SHA-256
46
+ `8a34735274c0b62049f6ae882dc955016fde54458efc18822fbcedb2e8eddeda`.
47
+ The beta update check reported the installed version as current.
48
+ Evidence: `/tmp/opencode/beta2-clean-install-acceptance.json`.
49
+
50
+ An isolated npm installation of beta.1 detected beta.2 with
51
+ `ctg update --channel beta --check`. Running `ctg update --channel beta` upgraded
52
+ it to beta.2; a profile sentinel remained byte-for-byte intact. This used a
53
+ disposable home and global npm prefix, not the user's normal installation.
54
+ Evidence: `/tmp/opencode/beta2-update-acceptance.json`.
55
+
56
+ ## Remaining acceptance
57
+
58
+ Stripe sandbox access subsequently became available. Real purchases, upgrades,
59
+ SCA, renewal, failed renewal and cancellation were exercised separately against
60
+ the isolated backend (see backend `docs/stripe-sandbox-acceptance.md`).
61
+
62
+ An isolated published beta.2 login connected the authorized existing Cloud account.
63
+ A real range-fix task passed four independent tests. Continuing that same session
64
+ hit its persistent elapsed-time budget; beta.2 showed only an unexplained interrupt.
65
+ The follow-up source fix adds actionable budget diagnostics and explicit
66
+ `--reset-budget` support. With that source launcher and the same installed runtime,
67
+ profile and session, continuation added an ascending-across-zero regression and
68
+ passed all five independent tests. This launcher fix is not part of immutable beta.2.
69
+ Execution budgets are not billing credit and never reset on payment or ordinary
70
+ continuation. Evidence: `/tmp/opencode/stripe-acceptance/live-continuation-fixed.log`.
71
+
72
+ Full task continuation across a genuine payment boundary remains incomplete:
73
+ the real model task used existing Cloud credit and sandbox payments used a separate
74
+ backend without a model upstream. Screenshot approval is not claimed. Local terminal timing failures
75
+ were followed by passing full local, CI and draft acceptance; this does not prove
76
+ that every terminal timing issue is resolved.
77
+
78
+ The normal installed profile and Invest-With-Friends files were not changed by
79
+ these release checks. Stable npm `latest` remains 1.0.7.
@@ -1,7 +1,10 @@
1
- # Cloud onboarding — unreleased implementation
1
+ # Cloud onboarding — beta.2 implementation
2
2
 
3
- November 2, 2026. This branch is not published beta acceptance. npm `next` remains
4
- 1.1.0-beta.1 and `latest` remains 1.0.7.
3
+ November 2, 2026. Beta.2 is published on GitHub and npm `next`; `latest` remains
4
+ 1.0.7. See [published acceptance](beta2-published-acceptance.md) for final CI,
5
+ draft/public runtime gates, signed npm provenance, Linux clean installation and
6
+ isolated beta.1 → beta.2 update evidence. Historical preparation notes below do
7
+ not supersede that release record.
5
8
 
6
9
  Native PR #238 merged at `a5ad2164d056fee7f7183579ce6cc97305bae53b` after CI
7
10
  41504674623 passed. First-task guidance is tracked in PR #239. Beta.2 runtime
@@ -0,0 +1,47 @@
1
+ # Carthagent 1.1.0-beta.3 — release candidate
2
+
3
+ This candidate makes continuation failures actionable. Publication is pending;
4
+ these notes do not establish npm availability.
5
+
6
+ ## Continue the existing task
7
+
8
+ - An exhausted persistent execution budget now reports the reason and recovery
9
+ command instead of an unexplained interrupt.
10
+ - `--reset-budget` explicitly authorizes a fresh budget for a resumed session:
11
+
12
+ ```sh
13
+ ctg --print --continue --reset-budget "Continue the task"
14
+ ```
15
+
16
+ - Ordinary continuation and payment preserve the execution budget, including
17
+ elapsed idle time. Billing credit is separate. Interactive users can run
18
+ `/delivery-budget-reset` before continuing the existing task.
19
+ - CLI help consistently names Carthagent Cloud and documents `billing refresh`.
20
+
21
+ ## Acceptance evidence
22
+
23
+ The source fix passed 1,132 tests with five skips, builds and lint. PR #242 CI
24
+ passed Linux, macOS and Windows, browser and installed-package gates. One macOS
25
+ 22 service-stop identity timeout passed on retry; the separate push run passed.
26
+
27
+ The authorized existing Cloud account completed a real range-fix task. The same
28
+ session, profile and installed beta.2 runtime then continued with the source
29
+ launcher and explicit reset, adding a regression and passing all five project
30
+ tests independently. Final beta.3 installed acceptance remains required.
31
+
32
+ Backend PRs #180–181 are deployed: automatic payment-status checks, clearer
33
+ sign-in and refund guidance, direct Builder review, modern Stripe compatibility
34
+ and accurate scheduled-cancellation dates while retaining paid access.
35
+ Sandbox purchases, prorated upgrades, initial and upgrade SCA, declined-upgrade
36
+ recovery, renewal, failed renewal, automatic refunds and disputes were exercised
37
+ separately. These do not establish a single
38
+ useful-task → payment boundary → same-task continuation journey: the model task
39
+ used existing production credit and payments used an isolated sandbox backend.
40
+
41
+ ## Release channel
42
+
43
+ Targets npm `next`; stable `latest` remains 1.0.7. Runtime prebuild
44
+ [41537249365](https://github.com/B-icy/Carthagent/actions/runs/41537249365) uses
45
+ reviewed source `c11103f8b57c6b3254473a78d535a404bea9a84a` and the unchanged
46
+ combined-v5 runtime recipe. Final assets and manifest must pass immutable draft
47
+ acceptance before publication.
@@ -100,6 +100,7 @@ function errorText(error) {
100
100
  function headlessFailureMessage(latest, headlessError) {
101
101
  const raw = errorText(latest?.error ?? latest?.session?.error ?? lastAssistantError(latest?.messages) ?? headlessError);
102
102
  const outcome = latest?.outcome ?? latest?.session?.outcome;
103
+ if (['elapsed-time', 'tool-calls', 'repair-rounds'].includes(latest?.budgetReason)) return `Session execution budget exhausted: ${latest.budgetReason}. Billing refresh does not reset execution limits. To authorize a fresh budget, resume this session with --reset-budget (for example: ctg --print --continue --reset-budget "Continue the task"), or run /delivery-budget-reset in the existing terminal session before continuing.`;
103
104
  if (/ModelNotSelectedError|No model is available|no provider|provider.*not (connected|selected|configured)|not authenticated/i.test(raw ?? '')) {
104
105
  return 'No AI provider connected. Run `ctg login` to connect one (Carthagent Cloud is recommended), or set an API key env var (e.g. ANTHROPIC_API_KEY), then retry. Run `ctg native --list-models` to see available models.';
105
106
  }
@@ -559,6 +560,9 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
559
560
  options.session = await latestSessionForWorkspace(client, cwd);
560
561
  if (!options.session) throw new Error('No native Carthagent session exists for this workspace to continue.');
561
562
  }
563
+ if (options.resetBudget && !options.session && !options.continue && !options.resume) {
564
+ throw new Error('--reset-budget requires --session, --continue or --resume.');
565
+ }
562
566
  eventController = new AbortController();
563
567
  const nativeEvents = [];
564
568
  let eventFailure;
@@ -600,6 +604,9 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
600
604
  const definitions = native.rpcDefinitions ?? await nativeRpcDefinitions();
601
605
  await client.rpc(definitions.SessionControls).framing({ sessionID, enabled: options.autoFraming }, { location: { directory: cwd }, signal: signal.controller.signal });
602
606
  }
607
+ if (options.resetBudget && sessionID) {
608
+ await client.session.command({ sessionID, name: 'delivery-budget-reset', text: '' }, { location: { directory: cwd }, signal: signal.controller.signal });
609
+ }
603
610
  if (framedPrompt) {
604
611
  if (!sessionID) {
605
612
  const created = await headlessApi.createSession(service, cwd, { body: {
@@ -644,6 +651,11 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
644
651
  const definitions = native.rpcDefinitions ?? await nativeRpcDefinitions();
645
652
  await client.rpc(definitions.SessionControls).framing({ sessionID: requestedSession, enabled: options.autoFraming }, { location: { directory: cwd }, signal: signal.controller.signal });
646
653
  }
654
+ if (requestedSession && options.resetBudget && !signal.controller.signal.aborted) {
655
+ const resumed = await client.session.get({ sessionID: requestedSession }, { signal: signal.controller.signal });
656
+ assertSessionWorkspace(resumed, cwd);
657
+ await client.session.command({ sessionID: requestedSession, name: 'delivery-budget-reset', text: '' }, { location: { directory: cwd }, signal: signal.controller.signal });
658
+ }
647
659
  try {
648
660
  result = await headlessApi.runHeadless(service, cwd, framedPrompt || prompt || '', {
649
661
  sessionID: options.session,
@@ -658,7 +670,16 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
658
670
  headlessError = error;
659
671
  result = { session: { id: options.session }, messages: [], outcome: signal.controller.signal.aborted ? 'interrupted' : 'failed', terminal: true, error: error.message };
660
672
  }
661
- const latest = headlessError ? result : await reconcileHeadlessResult(result, { client, signal: signal.controller.signal });
673
+ let latest = headlessError ? result : await reconcileHeadlessResult(result, { client, signal: signal.controller.signal });
674
+ if (latest.outcome === 'interrupted' && !latest.error && !latest.session?.error && !lastAssistantError(latest.messages) && latest.session?.id && typeof client.rpc === 'function' && !signal.controller.signal.aborted) {
675
+ // An interrupt may carry no error in the host projection. Read the
676
+ // authoritative persistent budget without restarting the task.
677
+ try {
678
+ const definitions = native.rpcDefinitions ?? await nativeRpcDefinitions();
679
+ const budget = await client.rpc(definitions.Carthagent).budgetStatus({ sessionID: latest.session.id }, { location: { directory: cwd }, signal: AbortSignal.any([signal.controller.signal, AbortSignal.timeout(2000)]) });
680
+ if (budget?.stopped) latest = { ...latest, budgetReason: budget.stopped, error: latest.error ?? headlessFailureMessage({ budgetReason: budget.stopped }) };
681
+ } catch { /* Keep the original failure when budget status is unavailable. */ }
682
+ }
662
683
  eventController.abort();
663
684
  await eventReader;
664
685
  if (eventFailure) throw new Error(`Native Carthagent event stream failed: ${eventFailure.message}`, { cause: eventFailure });
@@ -26,6 +26,7 @@ const BOOL_FLAGS = new Map([
26
26
  ['--protect-existing', ['protectExisting', true]], ['--jev', ['jev', true]], ['--no-jev', ['jev', false]],
27
27
  ['--print', ['print', true]], ['--demo', ['demo', true]], ['-c', ['continue', true]],
28
28
  ['--continue', ['continue', true]], ['-r', ['resume', true]], ['--resume', ['resume', true]],
29
+ ['--reset-budget', ['resetBudget', true]],
29
30
  ]);
30
31
 
31
32
  const aliases = new Map([
@@ -84,9 +85,9 @@ export function cliHelp(version = '') {
84
85
  \x1b[32mhash\x1b[0m, \x1b[32mfingerprint\x1b[0m Calculate workspace SHA-256 fingerprint
85
86
  \x1b[32mvalidate\x1b[0m <file.json> Validate acceptance contract
86
87
  \x1b[32mlogin\x1b[0m, \x1b[32mauth\x1b[0m Connect an AI provider
87
- \x1b[32maccount\x1b[0m Show Ship credit, plan, grants and CLI sessions
88
- \x1b[32mbilling\x1b[0m [checkout|portal] Open Ship billing
89
- \x1b[32mlogout-cloud\x1b[0m [session] Revoke the current or named Ship session
88
+ \x1b[32maccount\x1b[0m Show Cloud credit, plan, grants and CLI sessions
89
+ \x1b[32mbilling\x1b[0m [checkout|portal|refresh] Open or refresh Cloud billing
90
+ \x1b[32mlogout-cloud\x1b[0m [session] Revoke the current or named Cloud session
90
91
  \x1b[32marchive\x1b[0m [list|search|read|export] Read-only legacy session archive
91
92
  \x1b[32mreview\x1b[0m Configure self-review or review a PR
92
93
  \x1b[32mserve\x1b[0m, \x1b[32mweb\x1b[0m, \x1b[32mstart\x1b[0m Start the web dashboard
@@ -110,6 +111,8 @@ export function cliHelp(version = '') {
110
111
  --max-tools <n> Session tool-call budget (0 disables)
111
112
  --max-seconds <n> Session elapsed-time budget (0 disables)
112
113
  --max-repairs <n> Automatic-repair budget (0 disables)
114
+ --reset-budget Explicitly reset a resumed session's execution budget
115
+ Budgets persist across continuation, including idle time.
113
116
  --protect-existing Preserve files present at session start
114
117
  --rewrite-cap <n> Whole-file writes per path (0 disables)
115
118
  --tool-output-cap <n> Bound text output per tool result
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "releaseVersion": "1.1.0-beta.2",
3
+ "releaseVersion": "1.1.0-beta.3",
4
4
  "compatibilityVersion": "2.0.32",
5
5
  "runtimeVersion": "0.0.0-ctg.2.0.32",
6
6
  "sourceRevision": "843a6c514e606b436a7fe713422ace0a93ad3d61",
@@ -9,28 +9,28 @@
9
9
  "assets": {
10
10
  "linux-x64": {
11
11
  "name": "opencode-linux-x64.tar.gz",
12
- "archiveSha256": "0389612a88f9b002b819e4fc12eb5bd9e337b9c5352de60dd3f523e343ac01c2",
13
- "executableSha256": "8a34735274c0b62049f6ae882dc955016fde54458efc18822fbcedb2e8eddeda"
12
+ "archiveSha256": "ab95bd99fc2bf52f867927a817dde1d3882122b80f85ef85cf416733afa62483",
13
+ "executableSha256": "f9ad13d8aad76bf1d16334712f0060b886757f5cdabddacce42f9805d4482c1c"
14
14
  },
15
15
  "linux-arm64": {
16
16
  "name": "opencode-linux-arm64.tar.gz",
17
- "archiveSha256": "7a01012e830a2bee2195402bf0c525bd345754ee4abb72af3ba4a597ceb096e5",
18
- "executableSha256": "aea44aa7d12ee9aef5e6fd21fbfe1d23bd2aa2c731d955d5393c4fe2c4b1dcd7"
17
+ "archiveSha256": "3869dd9398693afffe9f438192fbb12061b66a211993018379fb6870d2012bd2",
18
+ "executableSha256": "0c302d55bba608d83c42f54b769ec3994421d3dcb16ea5d2a9455dcd91f884a1"
19
19
  },
20
20
  "darwin-x64": {
21
21
  "name": "opencode-darwin-x64.tar.gz",
22
- "archiveSha256": "cf88ba7c06a75a9e2021cfbb783a23fb93f09e2fb2dce86067346c8c666a2c5b",
23
- "executableSha256": "17c67ad68d84a9c489a4d3567784aa8a2e3f7b086153693a57c1ef0a8b270a4f"
22
+ "archiveSha256": "b20111d8fd2368ac9bde7f90b12835a465b8c92da3db1f3a35671860a325eb27",
23
+ "executableSha256": "23f7a845e75592efa11dff407e147287584eae7f63fca1a9c4ccbe9558656bc3"
24
24
  },
25
25
  "darwin-arm64": {
26
26
  "name": "opencode-darwin-arm64.tar.gz",
27
- "archiveSha256": "8db89fcec1f4fe502dfbb6f0e9ad6e2ccecdc6191ad14d6afa5338e9e0990341",
28
- "executableSha256": "789dc613d191d339ef4b789d36dd32403db9422f11841659aea5a9ec4d3e6e37"
27
+ "archiveSha256": "eb65c635c8cf19f0830b56b2d8087387148edaf44cf821c881b72388c9c185fd",
28
+ "executableSha256": "d10a7b549748a131c578ed8cd041aeb652f1ae9c79c5f3912a731f2a69218736"
29
29
  },
30
30
  "windows-x64": {
31
31
  "name": "opencode-windows-x64.tar.gz",
32
- "archiveSha256": "9f7139dea76e662109a4c80b109b92525c993a37c69aae5b45623f8d69ab9467",
33
- "executableSha256": "38edbca2f4304f2efa2afa57b654c362c14f422c141c3ebd82e1062221dccd80"
32
+ "archiveSha256": "a0309761781f57b5ea9f329d41f2e977391949addc9baaca6b85025780249d3c",
33
+ "executableSha256": "91eac290633313c7fce4a426ef265f193e995b285746df6a2a6010f4727fe813"
34
34
  }
35
35
  }
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carthagent",
3
- "version": "1.1.0-beta.2",
3
+ "version": "1.1.0-beta.3",
4
4
  "workspaces": [
5
5
  "plugins/carthagent"
6
6
  ],
@@ -102,11 +102,11 @@ test('check fails clearly when no delivery report exists', t => {
102
102
  assert.match(result.stderr, /No delivery report/);
103
103
  });
104
104
 
105
- test('guidance routing is not a user-facing command and Ship account commands are documented', t => {
105
+ test('guidance routing is not a user-facing command and Cloud account commands are documented', t => {
106
106
  const result = run(fixture(t), '--help');
107
107
  assert.equal(result.status, 0, result.stderr);
108
108
  assert.doesNotMatch(result.stdout, /\broute\b|\bprofiles\b/);
109
- assert.match(result.stdout, /account.*Ship credit/);
109
+ assert.match(result.stdout, /account.*Cloud credit/);
110
110
  assert.match(result.stdout, /billing.*checkout\|portal/);
111
111
  assert.match(result.stdout, /logout-cloud/);
112
112
  });
@@ -42,6 +42,40 @@ test('native CLI selects headless execution and preserves output-mode options',
42
42
  assert.equal(invocation.prompt, 'Do work');
43
43
  });
44
44
 
45
+ test('headless continuation explains persistent budget exhaustion and resets only on explicit request', async t => {
46
+ const { mkdtempSync, rmSync } = await import('node:fs');
47
+ const { tmpdir } = await import('node:os');
48
+ const cwd = mkdtempSync(join(tmpdir(), 'ctg-budget-continuation-'));
49
+ t.after(() => rmSync(cwd, { recursive: true, force: true }));
50
+ const { io, output } = ioCapture();
51
+ const calls = [];
52
+ let stopped = 'elapsed-time';
53
+ const runtime = await createOpenCodeCliRuntime({ cwd, io, stdout: { write() {} }, native: {
54
+ runtime: {}, service: { ensureService: async () => ({}) }, rpcDefinitions: { Carthagent: {} },
55
+ clientFactory: async () => ({
56
+ rpc: () => ({ budgetStatus: async () => ({ stopped }) }),
57
+ session: {
58
+ list: async () => ({ data: [{ id: 'same-session', location: { directory: cwd } }] }),
59
+ get: async () => ({ id: 'same-session', location: { directory: cwd } }),
60
+ command: async input => { calls.push(input); stopped = null; },
61
+ },
62
+ }),
63
+ headless: { runHeadless: async (_service, _cwd, _prompt, options) => {
64
+ assert.equal(options.sessionID, 'same-session');
65
+ calls.push('prompt');
66
+ return { session: { id: 'same-session' }, outcome: stopped ? 'interrupted' : 'succeeded', terminal: true,
67
+ messages: stopped ? [] : [{ type: 'assistant', finish: 'stop', content: [{ type: 'text', text: 'continued' }] }] };
68
+ } },
69
+ } });
70
+ assert.equal(parseOpenCodeArgs(['--continue', '--reset-budget', 'Continue']).resetBudget, true);
71
+ assert.equal(await runtime.run({ mode: 'headless', options: { continue: true }, prompt: 'Continue' }), 1);
72
+ assert.match(output.stderr.at(-1), /budget exhausted: elapsed-time.*--reset-budget/);
73
+ assert.deepEqual(calls, ['prompt'], 'no implicit budget reset or prompt retry');
74
+ assert.equal(await runtime.run({ mode: 'headless', options: { continue: true, resetBudget: true }, prompt: 'Continue' }), 0);
75
+ assert.deepEqual(calls.slice(1), [{ sessionID: 'same-session', name: 'delivery-budget-reset', text: '' }, 'prompt']);
76
+ await assert.rejects(runtime.run({ mode: 'headless', options: { resetBudget: true }, prompt: 'New task' }), /requires --session/);
77
+ });
78
+
45
79
  test('headless CLI validates prompts and maps cancellation to conventional exit code', async () => {
46
80
  const { io, output } = ioCapture();
47
81
  assert.equal(await runOpenCodeCli({ argv: [], stdoutIsTTY: false, io, runtime: {} }), 1);
@@ -77,10 +111,10 @@ test('model, auth, cloud and domain entrypoints dispatch to injected native capa
77
111
  ]);
78
112
  });
79
113
 
80
- test('help preserves command aliases, Ship operations and isolation semantics', async () => {
114
+ test('help preserves command aliases, Cloud operations and isolation semantics', async () => {
81
115
  const { output, io } = ioCapture();
82
116
  assert.equal(await runOpenCodeCli({ argv: ['--help'], io, version: '1.0.3' }), 0);
83
- assert.match(output.stdout[0], /account.*Ship credit/);
117
+ assert.match(output.stdout[0], /account.*Cloud credit/);
84
118
  assert.match(output.stdout[0], /billing.*checkout\|portal/);
85
119
  assert.match(output.stdout[0], /--isolate\s+Isolate extension\/skill discovery \(not a worktree\)/);
86
120
  assert.equal(cliHelp('x').includes('--engine-cli, --agent-cli'), true);