howtofixcli 0.7.8 → 0.7.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.
Files changed (152) hide show
  1. package/dist/chunk-27TRKD65.js +2 -0
  2. package/dist/chunk-3FYCFLNK.js +1 -0
  3. package/dist/chunk-56SEAGAQ.js +1 -0
  4. package/dist/chunk-63ZCQDXH.js +6 -0
  5. package/dist/chunk-6FUWMLWO.js +1 -0
  6. package/dist/chunk-7CHDNTH6.js +1 -0
  7. package/dist/chunk-ACOMNDUL.js +185 -0
  8. package/dist/chunk-AEVPET7X.js +2 -0
  9. package/dist/chunk-AGGIYXN7.js +2 -0
  10. package/dist/chunk-BHVBO4SC.js +1 -0
  11. package/dist/chunk-FPXVDP32.js +8 -0
  12. package/dist/chunk-GIUYIKH3.js +109 -0
  13. package/dist/chunk-GQCAJ56I.js +5 -0
  14. package/dist/chunk-GXB7RZHH.js +1 -0
  15. package/dist/chunk-IGEY3AWF.js +1 -0
  16. package/dist/chunk-ILDDDLRC.js +1 -0
  17. package/dist/chunk-JFYVRWJI.js +2 -0
  18. package/dist/chunk-MVLCGU3G.js +1 -0
  19. package/dist/chunk-N345KWWK.js +1 -0
  20. package/dist/chunk-NBQFPCRT.js +1 -0
  21. package/dist/chunk-OIPSLIZE.js +5 -0
  22. package/dist/chunk-ORJ2QZBG.js +3 -0
  23. package/dist/chunk-PCALBZOO.js +2 -0
  24. package/dist/chunk-PDNBGUYP.js +2 -0
  25. package/dist/chunk-S656RE4A.js +1 -0
  26. package/dist/chunk-TSEKPGK4.js +1 -0
  27. package/dist/chunk-U6CI6KXN.js +34 -0
  28. package/dist/chunk-UTYFLLSM.js +1 -0
  29. package/dist/chunk-V3R4FYTJ.js +1 -0
  30. package/dist/chunk-V656XVXV.js +8 -0
  31. package/dist/chunk-WKUJ7CFO.js +1 -0
  32. package/dist/chunk-WOT6VMZA.js +1 -0
  33. package/dist/chunk-WUPU6LTD.js +4 -0
  34. package/dist/chunk-WX3V5P7U.js +5 -0
  35. package/dist/chunk-ZKZ6WCI3.js +2 -0
  36. package/dist/main.js +94 -536
  37. package/package.json +15 -62
  38. package/dist/account-credential.js +0 -136
  39. package/dist/account-credential.test.js +0 -38
  40. package/dist/activity.js +0 -124
  41. package/dist/agent-defs.js +0 -237
  42. package/dist/agent-limiter.js +0 -68
  43. package/dist/agents.js +0 -554
  44. package/dist/anim.js +0 -361
  45. package/dist/args.js +0 -228
  46. package/dist/at-file.js +0 -28
  47. package/dist/brand-extension.js +0 -87
  48. package/dist/brand.js +0 -76
  49. package/dist/browser.js +0 -58
  50. package/dist/cache-report.js +0 -139
  51. package/dist/checkpoint.js +0 -437
  52. package/dist/claims.js +0 -167
  53. package/dist/cli-auth.js +0 -142
  54. package/dist/cli-checkin.js +0 -120
  55. package/dist/cli-credentials.js +0 -254
  56. package/dist/clipboard.js +0 -149
  57. package/dist/collapse.js +0 -129
  58. package/dist/commands.js +0 -301
  59. package/dist/compaction-guard.js +0 -81
  60. package/dist/context-extension.js +0 -17
  61. package/dist/dcp/commands.js +0 -172
  62. package/dist/dcp/compress-state.js +0 -139
  63. package/dist/dcp/compress.js +0 -329
  64. package/dist/dcp/dcp-extension.js +0 -288
  65. package/dist/dcp/dedup.js +0 -93
  66. package/dist/dcp/ids.js +0 -189
  67. package/dist/dcp/index.js +0 -25
  68. package/dist/dcp/prompts.js +0 -68
  69. package/dist/dcp/protected-content.js +0 -221
  70. package/dist/dcp/protected.js +0 -161
  71. package/dist/dcp/prune.js +0 -418
  72. package/dist/dcp/state.js +0 -176
  73. package/dist/device-id.js +0 -90
  74. package/dist/device-id.test.js +0 -23
  75. package/dist/errors.js +0 -63
  76. package/dist/extensions.js +0 -48
  77. package/dist/fatal.js +0 -35
  78. package/dist/feed.js +0 -498
  79. package/dist/fs-atomic.js +0 -54
  80. package/dist/gateway-errors.js +0 -90
  81. package/dist/git.js +0 -230
  82. package/dist/goal-extension.js +0 -88
  83. package/dist/goal.js +0 -222
  84. package/dist/hooks-extension.js +0 -176
  85. package/dist/hooks.js +0 -443
  86. package/dist/howtofix-account.js +0 -715
  87. package/dist/hud-line.js +0 -153
  88. package/dist/hwid.js +0 -130
  89. package/dist/interrupt.js +0 -60
  90. package/dist/json-events.js +0 -50
  91. package/dist/level-style.js +0 -112
  92. package/dist/loop-guard.js +0 -95
  93. package/dist/lsp-tools.js +0 -32
  94. package/dist/lsp.js +0 -347
  95. package/dist/mcp-config.js +0 -85
  96. package/dist/mcp-lifecycle.js +0 -147
  97. package/dist/model-catalog.js +0 -217
  98. package/dist/model-resolve.js +0 -65
  99. package/dist/notes.js +0 -78
  100. package/dist/oauth-login.js +0 -568
  101. package/dist/paths.js +0 -61
  102. package/dist/permissions.js +0 -298
  103. package/dist/plan-mode.js +0 -363
  104. package/dist/preset-command.js +0 -227
  105. package/dist/preset-extension.js +0 -35
  106. package/dist/preset-mcp.js +0 -292
  107. package/dist/presets.js +0 -727
  108. package/dist/prompt-files.js +0 -103
  109. package/dist/prompt-text.js +0 -41
  110. package/dist/proxy.js +0 -137
  111. package/dist/quality/ast.js +0 -196
  112. package/dist/quality/cognitive.js +0 -83
  113. package/dist/quality/cyclomatic.js +0 -30
  114. package/dist/quality/halstead.js +0 -83
  115. package/dist/quality/index.js +0 -18
  116. package/dist/quality/length.js +0 -3
  117. package/dist/quality/maintainability.js +0 -15
  118. package/dist/quality/nesting.js +0 -57
  119. package/dist/quality/report.js +0 -803
  120. package/dist/quality/smells.js +0 -67
  121. package/dist/quality-tool.js +0 -63
  122. package/dist/read-only-commands.js +0 -597
  123. package/dist/repo-map.js +0 -292
  124. package/dist/rtk.js +0 -193
  125. package/dist/sandbox.js +0 -105
  126. package/dist/schema.js +0 -214
  127. package/dist/session-lifecycle.js +0 -19
  128. package/dist/session-retention.js +0 -111
  129. package/dist/session.js +0 -446
  130. package/dist/sessions.js +0 -100
  131. package/dist/settings.js +0 -148
  132. package/dist/skills.js +0 -35
  133. package/dist/slop-tool.js +0 -96
  134. package/dist/spend-cap.js +0 -43
  135. package/dist/subagent.js +0 -171
  136. package/dist/tar.js +0 -146
  137. package/dist/theme.js +0 -79
  138. package/dist/thinking.js +0 -45
  139. package/dist/todo.js +0 -114
  140. package/dist/tool-loadout.js +0 -97
  141. package/dist/tool-render.js +0 -769
  142. package/dist/tools.js +0 -176
  143. package/dist/ui.js +0 -4757
  144. package/dist/update.js +0 -533
  145. package/dist/usage-segment.js +0 -69
  146. package/dist/web-tools.js +0 -46
  147. package/dist/web.js +0 -265
  148. package/dist/welcome.js +0 -179
  149. package/dist/workflow-engine.js +0 -246
  150. package/dist/workflow-progress.js +0 -446
  151. package/dist/workflow-view.js +0 -195
  152. package/dist/workflow.js +0 -349
package/dist/main.js CHANGED
@@ -1,537 +1,95 @@
1
1
  #!/usr/bin/env node
2
- import { helpText, parseArgs } from "./args.js";
3
- import { APP_TITLE, APP_VERSION } from "./brand.js";
4
- import { installCrashHandlers } from "./fatal.js";
5
- import { reportError } from "./errors.js";
6
- import { AGENT_DIR, pinAgentDir } from "./paths.js";
7
- import { jsonLine } from "./json-events.js";
8
- import { spendCapMessage, unpricedBudgetMessage } from "./spend-cap.js";
9
- /**
10
- * The working directory to run in, or a reason the process cannot start.
11
- *
12
- * `process.cwd()` throws `ENOENT` when the directory it was started in has been
13
- * deleted — a real case (the scratch dir is cleaned up mid-session) and an
14
- * uncaught crash with a stack trace, since the throw happens while building the
15
- * session options. Report it as a sentence instead.
16
- */
17
- function workingDirectory() {
18
- try {
19
- return { cwd: process.cwd() };
20
- }
21
- catch {
22
- return {
23
- error: "the current directory no longer exists (it was deleted while the shell stayed in it). " +
24
- "cd to a directory that exists, or pass one on the command line.",
25
- };
26
- }
27
- }
28
- /**
29
- * A closed stdout (`howtofix --version | head -1`) makes every later write throw
30
- * `EPIPE`, which the top-level handler used to report as a crash with a stack
31
- * trace. A pipe that went away is not an error the user needs to see.
32
- */
33
- function exitQuietlyOnEpipe() {
34
- process.stdout.on("error", (error) => {
35
- if (error.code === "EPIPE")
36
- process.exit(0);
37
- throw error;
38
- });
39
- }
40
- async function main() {
41
- exitQuietlyOnEpipe();
42
- pinAgentDir();
43
- const startedAt = performance.now();
44
- const here = workingDirectory();
45
- if ("error" in here) {
46
- console.error(`${APP_TITLE}: ${here.error}`);
47
- process.exitCode = 1;
48
- return;
49
- }
50
- const cwd = here.cwd;
51
- // A Node too old to be supported fails deep inside a dependency instead of here.
52
- // `undici` 8 reaches `zlib.createZstdDecompress` at module load, and that function
53
- // only exists from Node 22.19 — so on Node 20 the user gets
54
- // `TypeError: Cannot read properties of undefined (reading 'bind')` with a stack
55
- // trace pointing at `undici/lib/interceptor/decompress.js`, which explains nothing
56
- // and names no fix. `engines` in `package.json` is not enough on its own: npm's
57
- // `engine-strict` is false by default, so the install succeeds with a warning the
58
- // user never sees.
59
- //
60
- // `--help` and `--version` are deliberately still served. They exit before `undici`
61
- // is ever loaded, so they cannot hit the bug, and the user who most needs to read the
62
- // help is the one on the runtime the CLI cannot run — refusing to answer leaves them
63
- // with a command that looks broken and no way to learn why. Everything else is
64
- // refused, because a session that cannot start the agent is not a usable CLI and
65
- // failing here is what turns a stack trace inside a dependency into a sentence.
66
- //
67
- // Gated behind an env var so the check itself is what breaks under test, not the
68
- // runtime: `HXF_FAKE_NODE` lets a check drive this branch on a modern Node. The
69
- // same value feeds the message, so a test sees the version it asked about rather
70
- // than the one it happens to be running on.
71
- //
72
- // The exit code says whether the ENVIRONMENT is supported, not whether output was
73
- // produced: an old Node exits non-zero even for `--help`, so a script reading the
74
- // code is not told the runtime is fine.
75
- const reportedVersion = process.env.HXF_FAKE_NODE ?? process.versions.node;
76
- const [major, minor] = reportedVersion.split(".").map((part) => Number.parseInt(part, 10));
77
- if (!Number.isFinite(major) || major < 22 || (major === 22 && Number.isFinite(minor) && minor < 19)) {
78
- // Same sentence on both streams, and stderr carries it in the informational case
79
- // too: `--version` is often parsed by a script, and a warning on stdout would
80
- // corrupt the value it is reading.
81
- console.error(`${APP_TITLE}: Node ${reportedVersion} is too old. Node 22.19 or newer is required ` +
82
- `(run "node --version" to check yours), because undici needs zstd, which Node added in 22.19. ` +
83
- `Upgrade Node and run this again.`);
84
- process.exitCode = 1;
85
- const argv = process.argv.slice(2);
86
- // The two flags that work on any runtime, served from here so the account repair
87
- // below — which makes an HTTP call, and therefore loads undici — is never reached
88
- // on a runtime that cannot load it.
89
- if (argv.some((token) => token === "-h" || token === "--help")) {
90
- console.log(helpText());
91
- return;
92
- }
93
- if (argv.some((token) => token === "-v" || token === "--version")) {
94
- console.log(`${APP_TITLE} ${APP_VERSION}`);
95
- return;
96
- }
97
- return;
98
- }
99
- // Questions about the installation, answered without a session, a model, or
100
- // any of the repairs below. They run first on purpose: `howtofix models` used
101
- // to rewrite settings.json and print two notices about it before listing a
102
- // single model, which reads like the command did something it did not.
103
- const subArgv = process.argv.slice(2);
104
- if (subArgv[0] === "doctor" || subArgv[0] === "models" || subArgv[0] === "sessions") {
105
- const context = subArgv[0];
106
- try {
107
- const commands = await import("./commands.js");
108
- if (context === "doctor")
109
- process.exitCode = await commands.runDoctor();
110
- else if (context === "models")
111
- process.exitCode = commands.runModels();
112
- else
113
- process.exitCode = commands.runSessions(subArgv.slice(1));
114
- }
115
- catch (error) {
116
- reportError(APP_TITLE, context, error);
117
- process.exitCode = 1;
118
- }
119
- return;
120
- }
121
- // Repair a howtofix gateway entry that predates `compat.supportsDeveloperRole`.
122
- // The gateway refuses the `developer` role that the SDK selects for every
123
- // reasoning model, so such an entry fails every prompt silently — the spinner
124
- // turns and nothing is ever reported. `/connect` writes the flag, but only for
125
- // models it adds, so an account connected earlier stays broken until something
126
- // repairs the file. The repair is silent: it runs on every start and only says
127
- // anything when it actually changed something. Imported lazily to keep
128
- // `--version` and `--help` free of the model stack.
129
- const { repairGatewayModels, ensureGatewaySettings, repairModelFacts, syncGatewayModelsOnStart, checkAccount } = await import("./howtofix-account.js");
130
- const { readCliCredential, renewCliCredentialIfNeeded } = await import("./cli-credentials.js");
131
- const { AGENT_DIR: stateDir } = await import("./paths.js");
132
- const repaired = repairGatewayModels(stateDir);
133
- if (repaired > 0) {
134
- console.error(`${APP_TITLE}: repaired ${repaired} howtofix model(s) that the gateway would have rejected`);
135
- }
136
- // Repair the numbers a synced model never got: a real context window and the
137
- // reasoning flag that enables thinking. Both used to be guesses — 128k for
138
- // every model, and no capability at all — so a model with a 1M window read as
139
- // almost full and a reasoning model showed no thinking levels. The catalogue is
140
- // cached next to the agent state, so this is a file read on a normal start and
141
- // a fetch only when the cache is missing or stale.
142
- const facts = await repairModelFacts(stateDir);
143
- if (facts.changed > 0) {
144
- console.error(`${APP_TITLE}: filled in the context window and reasoning flag for ${facts.changed} model(s) from the model catalogue`);
145
- }
146
- else if (facts.message) {
147
- // A catalogue that could not be read is not a failure — the models keep the
148
- // conservative defaults — but it is worth one line, because otherwise the
149
- // wrong numbers look like the model's own.
150
- console.error(`${APP_TITLE}: model catalogue unavailable (${facts.message}); context windows stay at their conservative defaults`);
151
- }
152
- // The SDK's HTTP idle timeout defaults to 5 minutes, so a provider that goes
153
- // quiet mid-request leaves a spinner and nothing else for that long. 90s lets
154
- // the SDK's own retry recover while the user is still watching. Written only
155
- // when the key is missing, so a value the user set is kept.
156
- if (ensureGatewaySettings(stateDir)) {
157
- console.error(`${APP_TITLE}: set httpIdleTimeoutMs in settings.json so a stalled provider recovers instead of hanging`);
158
- }
159
- // The account's model list follows its tier, and `models.json` is a snapshot:
160
- // after a tier change the file still offered what the account could reach on
161
- // the day it was written, and the only refresh was a manual `/sync`. Pull the
162
- // list once per start when a sign-in exists and add whatever is new — the
163
- // gateway filters by the account's own group, so a free account can never
164
- // collect a VIP model this way. Silent unless something was added, and never
165
- // fatal: an unreachable gateway must not stop the CLI from starting.
166
- try {
167
- const stored = readCliCredential(stateDir);
168
- if (stored) {
169
- const renewed = await renewCliCredentialIfNeeded(stateDir, stored);
170
- const account = await checkAccount(renewed.credential.accessToken);
171
- if (account.ok) {
172
- const synced = await syncGatewayModelsOnStart(stateDir, account.models);
173
- if (synced.added > 0) {
174
- console.error(`${APP_TITLE}: added ${synced.added} model(s) this account can now reach`);
175
- }
176
- }
177
- }
178
- }
179
- catch {
180
- // A start-time re-sync that fails costs the previous list, which still
181
- // works; `/sync` remains the manual way to try again.
182
- }
183
- let args;
184
- try {
185
- args = parseArgs(process.argv.slice(2));
186
- }
187
- catch (error) {
188
- const message = error instanceof Error ? error.message : String(error);
189
- console.error(`${APP_TITLE}: ${message}\n${helpText()}`);
190
- process.exitCode = 2;
191
- return;
192
- }
193
- // These two exit before a session exists, so they must not load the SDK.
194
- if (args.help) {
195
- console.log(helpText());
196
- return;
197
- }
198
- if (args.version) {
199
- console.log(`${APP_TITLE} ${APP_VERSION}`);
200
- // A build whose signing key does not match the panel's would fail every
201
- // sign-in with a 401 that reads like a server fault. Say it here, where it
202
- // costs nothing, rather than leaving it to be discovered in the login flow.
203
- const { cliSigningKeyMatchesPanel, cliSigningKeyFingerprint } = await import("./cli-auth.js");
204
- if (!cliSigningKeyMatchesPanel()) {
205
- console.error(`${APP_TITLE}: this build's CLI signing key (${cliSigningKeyFingerprint()}) does not match ` +
206
- `the key the panel verifies against, so sign-in will be refused.`);
207
- process.exitCode = 1;
208
- }
209
- return;
210
- }
211
- // The sandbox re-executes this process once, before any session exists. The
212
- // guard in the environment is what stops the child from wrapping itself again.
213
- if (args.sandbox && !process.env.HOWTOFIX_SANDBOXED) {
214
- const { detectSandbox, enterSandbox, describeSandbox } = await import("./sandbox.js");
215
- const kind = detectSandbox();
216
- if (!kind) {
217
- console.error(`${APP_TITLE}: --sandbox needs a working bubblewrap (Linux) or sandbox-exec (macOS). ` +
218
- `Neither could be used here — on Linux this usually means unprivileged user namespaces are disabled.`);
219
- process.exitCode = 1;
220
- return;
221
- }
222
- console.error(describeSandbox(kind));
223
- process.exitCode = enterSandbox(kind, { cwd, agentDir: AGENT_DIR, argv: [process.execPath, ...process.argv.slice(1)] });
224
- return;
225
- }
226
- // Route through a proxy before the first request goes out. Here rather than at
227
- // the top of `main` because this is the first line that cannot exit early:
228
- // `--version` and `--help` have already returned above, and loading the proxy
229
- // agent for a command that makes no request is a cost with no purpose. The
230
- // update check and the sign-in exchange both go through it, and they run
231
- // before any session exists — a proxy installed after them would be a setting
232
- // that works only sometimes.
233
- const { installProxy } = await import("./proxy.js");
234
- installProxy(AGENT_DIR);
235
- // `--update` installs the newest release; `--check-update` only looks. Both
236
- // exit without a session, so neither loads the model stack.
237
- if (args.update || args.checkUpdate) {
238
- const { PACKAGE_NAME, checkForUpdate, installUpdate, isInstalledCopy } = await import("./update.js");
239
- const check = await checkForUpdate(APP_VERSION);
240
- if (!check.latest) {
241
- // Not reaching the registry is a failure only when an install was asked
242
- // for; a plain check that could not run has nothing to report.
243
- console.error(`${APP_TITLE}: ${check.message}`);
244
- process.exitCode = args.update ? 1 : 0;
245
- return;
246
- }
247
- console.log(check.message);
248
- if (args.checkUpdate || !check.available)
249
- return;
250
- if (!isInstalledCopy()) {
251
- console.error(`${APP_TITLE}: this copy was not installed by npm, so npm cannot replace it. ` +
252
- `install it with: npm install -g ${PACKAGE_NAME}`);
253
- process.exitCode = 1;
254
- return;
255
- }
256
- const result = await installUpdate({ version: check.latest });
257
- console.log(result.message);
258
- if (!result.ok)
259
- process.exitCode = 1;
260
- return;
261
- }
262
- // `howtofix preset <new|check|pack>` authors a preset and exits. It runs before
263
- // anything loads the SDK, because authoring has nothing to do with a model and
264
- // a preset author on a machine without credentials should still be able to
265
- // validate what they wrote.
266
- const presetArgv = process.argv.slice(2);
267
- if (presetArgv[0] === "preset") {
268
- const { runPresetCommand } = await import("./preset-command.js");
269
- process.exitCode = runPresetCommand(presetArgv.slice(1));
270
- return;
271
- }
272
- if (args.print && !args.prompt) {
273
- console.error(`${APP_TITLE}: --print needs a prompt`);
274
- process.exitCode = 2;
275
- return;
276
- }
277
- const { expandFileReferences } = await import("./at-file.js");
278
- const { isApprovalMode, setApprovalMode, setDangerousBypass, isDangerousBypass } = await import("./permissions.js");
279
- const { openSession } = await import("./session.js");
280
- // Print mode has no dialogs, so anything that would ask must not block. The
281
- // interactive default is `full-auto` too: an approval prompt that fires on
282
- // every write is friction the user has to configure away, and a critical
283
- // command is denied in every mode regardless.
284
- setApprovalMode(args.approval && isApprovalMode(args.approval) ? args.approval : "full-auto");
285
- // `--dangerously-skip-permissions` lifts the last two refusals print mode can
286
- // still hit: a critical command and the loop guard. A stored preference does
287
- // the same for someone who never passes flags — the flag wins when it is
288
- // given, so a one-off run cannot be overridden by a file.
289
- const { readWorkbenchSettings } = await import("./settings.js");
290
- if (args.dangerouslySkipPermissions || readWorkbenchSettings(stateDir).skipPermissions)
291
- setDangerousBypass(true);
292
- // A bypass nobody can see is the dangerous kind: it removes the refusals a
293
- // user relies on and leaves no trace of why. Say it is on, every run.
294
- if (isDangerousBypass()) {
295
- console.error(`${APP_TITLE}: permission checks are OFF — critical commands (filesystem wipes, ` +
296
- `fork bombs, host shutdown) and the repeated-call guard are NOT enforced for this run. ` +
297
- `Turn it off with /settings, or by removing skipPermissions from settings.json.`);
298
- }
299
- const open = (options) => openSession({
300
- cwd,
301
- model: args.model,
302
- thinking: args.thinking,
303
- continueSession: options.continueSession ?? false,
304
- noSession: options.noSession ?? false,
305
- sessionPath: options.sessionPath,
306
- // The caller chooses the session, not the prompt. `--print` is the only
307
- // route that runs without a terminal, and the prompt must say so or a
308
- // skill that asks a question becomes a dead end that still exits 0.
309
- interactive: !args.print,
310
- });
311
- const opened = await open({
312
- continueSession: args.continueSession,
313
- sessionPath: args.sessionPath,
314
- noSession: args.noSession || args.print,
315
- });
316
- // The note reports how the session was opened, so a refusal below must not
317
- // swallow it: print writes it here, ahead of any guard; the TUI carries it
318
- // into the feed as `options.note`.
319
- if (args.print && opened.note)
320
- console.error(opened.note);
321
- // A budget on an unpriced model can never fire, so refuse instead of
322
- // pretending to guard — in *both* modes, now that the workbench enforces the
323
- // cap too: a TUI drawing a cap over a model that reports $0 a turn would be
324
- // the exact lie this refusal exists to prevent. See `priced` on
325
- // `OpenedSession`.
326
- if (args.maxBudgetUsd !== undefined && !opened.priced) {
327
- console.error(`${APP_TITLE}: ${unpricedBudgetMessage(opened.modelLabel, stateDir)}`);
328
- await opened.dispose();
329
- process.exitCode = 2;
330
- return;
331
- }
332
- if (args.print) {
333
- // `--print` has no slash router, but the TUI routes every "/" prompt to one
334
- // (submit → runSlash) and never lets those reach the model. Without this
335
- // guard the same prompt is forwarded verbatim — `/connect sk-...` pasted
336
- // into a script sends the credential to the provider and records it in the
337
- // transcript, and the command silently does not run. Skills are exempt:
338
- // the TUI forwards those as prompts too (runSlash → skillInvocation).
339
- const trimmedPrompt = args.prompt.trimStart();
340
- if (trimmedPrompt.startsWith("/")) {
341
- const name = trimmedPrompt.slice(1).split(/\s+/)[0] ?? "";
342
- const { skillInvocation } = await import("./skills.js");
343
- const skillNames = opened.session.resourceLoader.getSkills().skills.map((skill) => skill.name);
344
- const isSkill = skillInvocation(trimmedPrompt, skillNames) !== undefined;
345
- if (!isSkill) {
346
- const { slashCommands } = await import("./ui.js");
347
- if (slashCommands(opened.session).some((command) => command.name === name)) {
348
- console.error(`${APP_TITLE}: /${name} runs in the TUI only — --print sends prompts straight to the model. ` +
349
- `Run \`howtofix\` without --print for interactive commands.`);
350
- await opened.dispose();
351
- process.exitCode = 2;
352
- return;
353
- }
354
- }
355
- }
356
- const stream = args.outputFormat === "stream-json";
357
- const json = args.outputFormat === "json";
358
- // Both machine formats end with a result record; only `stream-json` writes
359
- // the events along the way.
360
- const machine = stream || json;
361
- // A `system`-shaped header first, so a consumer learns the model and the
362
- // session id before any event that refers to them — the same courtesy the
363
- // SDK's own JSON mode pays. Only the streaming format has events to head.
364
- if (stream) {
365
- const header = {
366
- type: "system",
367
- subtype: "init",
368
- cwd,
369
- session_id: opened.session.sessionId,
370
- model: opened.modelLabel,
371
- output_format: args.outputFormat,
372
- max_budget_usd: args.maxBudgetUsd ?? null,
373
- dangerously_skip_permissions: args.dangerouslySkipPermissions,
374
- cli_version: APP_VERSION,
375
- };
376
- process.stdout.write(`${JSON.stringify(header)}\n`);
377
- }
378
- let unsubscribe;
379
- let budgetHit;
380
- const checkBudget = () => {
381
- if (args.maxBudgetUsd === undefined || budgetHit !== undefined)
382
- return;
383
- const spent = opened.session.getSessionStats().cost;
384
- if (spent > args.maxBudgetUsd) {
385
- budgetHit = { spent };
386
- void opened.session.abort();
387
- }
388
- };
389
- try {
390
- // Both machine formats and text need the same budget signal, and the only
391
- // reliable one is the event stream: usage is updated as events arrive, so
392
- // a timer that samples the total can miss the update entirely and let the
393
- // run overspend. Subscribe in every mode; only `stream-json` writes the
394
- // lines out.
395
- unsubscribe = opened.session.subscribe((event) => {
396
- if (stream) {
397
- const line = jsonLine(event);
398
- if (line !== undefined)
399
- process.stdout.write(line);
400
- }
401
- checkBudget();
402
- });
403
- await opened.session.prompt(expandFileReferences(args.prompt, cwd));
404
- const text = opened.session.getLastAssistantText() ?? "";
405
- if (budgetHit) {
406
- console.error(`${APP_TITLE}: ${spendCapMessage(budgetHit.spent, args.maxBudgetUsd)}`);
407
- // A consumer reading the stream needs a terminal record on this path
408
- // too: without it the run just stops mid-stream and looks truncated.
409
- if (machine) {
410
- const stats = opened.session.getSessionStats();
411
- process.stdout.write(`${JSON.stringify({
412
- type: "result",
413
- subtype: "error_max_budget_usd",
414
- is_error: true,
415
- result: text,
416
- session_id: opened.session.sessionId,
417
- duration_ms: Math.round(performance.now() - startedAt),
418
- num_turns: stats.assistantMessages,
419
- total_cost_usd: stats.cost,
420
- usage: {
421
- input_tokens: stats.tokens.input,
422
- output_tokens: stats.tokens.output,
423
- cache_read_input_tokens: stats.tokens.cacheRead,
424
- cache_creation_input_tokens: stats.tokens.cacheWrite,
425
- },
426
- })}\n`);
427
- }
428
- process.exitCode = 1;
429
- return;
430
- }
431
- if (machine) {
432
- const stats = opened.session.getSessionStats();
433
- const last = opened.session.state.messages.at(-1);
434
- process.stdout.write(`${JSON.stringify({
435
- type: "result",
436
- subtype: "success",
437
- is_error: false,
438
- result: text,
439
- session_id: opened.session.sessionId,
440
- duration_ms: Math.round(performance.now() - startedAt),
441
- num_turns: stats.assistantMessages,
442
- total_cost_usd: stats.cost,
443
- usage: {
444
- input_tokens: stats.tokens.input,
445
- output_tokens: stats.tokens.output,
446
- cache_read_input_tokens: stats.tokens.cacheRead,
447
- cache_creation_input_tokens: stats.tokens.cacheWrite,
448
- },
449
- stop_reason: last?.role === "assistant" ? last.stopReason : undefined,
450
- })}\n`);
451
- }
452
- else {
453
- process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
454
- }
455
- // The SDK contract: a final assistant response that ended in `error` or
456
- // `aborted` produces a nonzero exit status, so scripts can branch on it.
457
- const last = opened.session.state.messages.at(-1);
458
- if (last?.role === "assistant") {
459
- if (last.stopReason === "error" || last.stopReason === "aborted")
460
- process.exitCode = 1;
461
- else if (last.errorMessage)
462
- process.exitCode = 1;
463
- // A failed turn used to exit 1 with no output at all, which is
464
- // indistinguishable from a crash. Say what the provider said.
465
- if (process.exitCode === 1 && last.errorMessage) {
466
- console.error(`${APP_TITLE}: ${last.errorMessage}`);
467
- // Same gap as the TUI: the gateway's own sentence, and the fix for it.
468
- // Imported here rather than at the top so a run that never fails does
469
- // not pay to parse it.
470
- const { diagnoseGatewayFailure } = await import("./gateway-errors.js");
471
- const fix = diagnoseGatewayFailure(last.errorMessage);
472
- if (fix !== undefined)
473
- console.error(`${APP_TITLE}: ${fix}`);
474
- }
475
- // Same reasoning as the budget path: a JSON consumer needs the terminal
476
- // record even when the run failed.
477
- if (machine && process.exitCode === 1) {
478
- const stats = opened.session.getSessionStats();
479
- process.stdout.write(`${JSON.stringify({
480
- type: "result",
481
- subtype: last.stopReason === "aborted" ? "error_aborted" : "error_during_execution",
482
- is_error: true,
483
- result: text,
484
- session_id: opened.session.sessionId,
485
- duration_ms: Math.round(performance.now() - startedAt),
486
- num_turns: stats.assistantMessages,
487
- total_cost_usd: stats.cost,
488
- usage: {
489
- input_tokens: stats.tokens.input,
490
- output_tokens: stats.tokens.output,
491
- cache_read_input_tokens: stats.tokens.cacheRead,
492
- cache_creation_input_tokens: stats.tokens.cacheWrite,
493
- },
494
- stop_reason: last.stopReason,
495
- })}\n`);
496
- }
497
- }
498
- }
499
- finally {
500
- unsubscribe?.();
501
- await opened.dispose();
502
- }
503
- return;
504
- }
505
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
506
- console.error(`${APP_TITLE}: interactive mode needs a terminal. use --print for pipes.`);
507
- await opened.dispose();
508
- process.exitCode = 2;
509
- return;
510
- }
511
- const { runWorkbench } = await import("./ui.js");
512
- // Only a copy npm owns can be replaced by npm, and only when the user has not
513
- // turned it off. The check itself is handed to the workbench so it runs after
514
- // the UI is up rather than before it.
515
- const { autoUpdateEnabled, isInstalledCopy } = await import("./update.js");
516
- const canSelfUpdate = isInstalledCopy() && autoUpdateEnabled(`${stateDir}/settings.json`);
517
- await runWorkbench({
518
- cwd,
519
- modelLabel: opened.modelLabel,
520
- session: opened.session,
521
- // The `/rtk` command toggles the flag the tool-result extension reads; the setter
522
- // lives beside that flag so the two cannot drift apart.
523
- setCompressToolOutput: opened.setCompressToolOutput,
524
- initialPrompt: args.prompt || undefined,
525
- note: opened.note,
526
- // The cap the print path enforces over one run, handed to a UI that has to
527
- // enforce it over many: same flag, same dollars, different lifetime.
528
- maxBudgetUsd: args.maxBudgetUsd,
529
- update: canSelfUpdate ? { current: APP_VERSION, install: true } : undefined,
530
- reopen: open,
531
- });
532
- }
533
- installCrashHandlers(APP_TITLE);
534
- main().catch((error) => {
535
- reportError(APP_TITLE, undefined, error);
536
- process.exitCode = 1;
537
- });
2
+ import{b as N,c as U}from"./chunk-6FUWMLWO.js";import{a as M,b as P}from"./chunk-AGGIYXN7.js";import{D as A,K as T}from"./chunk-V656XVXV.js";import"./chunk-UTYFLLSM.js";import{a as i,b as x}from"./chunk-OIPSLIZE.js";import"./chunk-WOT6VMZA.js";var F={off:!0,minimal:!0,low:!0,medium:!0,high:!0,xhigh:!0,max:!0},I={suggest:!0,"auto-edit":!0,"full-auto":!0},R={text:!0,json:!0,"stream-json":!0};function ce(t){let e=Number(t);if(!Number.isFinite(e)||e<=0)throw new Error(`--max-budget-usd needs a positive number of dollars, got '${t}'`);return e}function _(t,e,l){let d=t[e];if(d?.includes("="))return d.slice(d.indexOf("=")+1);let p=t[e+1];if(!p||p.startsWith("-"))throw new Error(`${l} needs a value`);return t.splice(e+1,1),p}function W(t){let e={print:!1,continueSession:!1,noSession:!1,help:!1,version:!1,update:!1,checkUpdate:!1,outputFormat:"text",dangerouslySkipPermissions:!1,sandbox:!1,prompt:""},l=[],d=[...t];for(let p=0;p<d.length;p++){let s=d[p]??"";if(s==="--"){l.push(...d.slice(p+1));break}if(s==="-h"||s==="--help")e.help=!0;else if(s==="-v"||s==="--version")e.version=!0;else if(s==="-p"||s==="--print")e.print=!0;else if(s==="-c"||s==="--continue")e.continueSession=!0;else if(s==="--no-session")e.noSession=!0;else if(s==="--sandbox")e.sandbox=!0;else if(s==="--update")e.update=!0;else if(s==="--check-update")e.checkUpdate=!0;else if(s==="-r"||s==="--resume")e.continueSession=!0;else if(s==="--session"||s.startsWith("--session="))e.sessionPath=_(d,p,"--session");else if(s==="-m"||s==="--model"||s.startsWith("--model="))e.model=_(d,p,"--model");else if(s==="--thinking"||s.startsWith("--thinking="))e.thinking=_(d,p,"--thinking");else if(s==="--approval"||s.startsWith("--approval="))e.approval=_(d,p,"--approval");else if(s==="--output-format"||s.startsWith("--output-format="))e.outputFormat=_(d,p,"--output-format");else if(s==="--max-budget-usd"||s.startsWith("--max-budget-usd="))e.maxBudgetUsd=ce(_(d,p,"--max-budget-usd"));else if(s==="--dangerously-skip-permissions")e.dangerouslySkipPermissions=!0;else{if(s.startsWith("-"))throw new Error(`unknown option ${s}`);l.push(s)}}if(e.thinking&&!F[e.thinking])throw new Error(`thinking must be one of: ${Object.keys(F).join(", ")}`);if(e.approval&&!I[e.approval])throw new Error(`approval must be one of: ${Object.keys(I).join(", ")}`);if(!R[e.outputFormat])throw new Error(`output-format must be one of: ${Object.keys(R).join(", ")}`);if(e.outputFormat!=="text"&&!e.print)throw new Error(`--output-format ${e.outputFormat} is a machine contract; it needs --print`);if(e.dangerouslySkipPermissions&&!e.print)throw new Error("--dangerously-skip-permissions is for unattended runs; it needs --print. interactively, pick an approval mode instead (/approval, or --approval)");if(e.prompt=l.join(" ").trim(),e.update&&e.checkUpdate)throw new Error("--update installs; --check-update only looks. pick one");if((e.update||e.checkUpdate)&&(e.print||e.continueSession||e.sessionPath||e.prompt))throw new Error("--update and --check-update act on the installation, not a session; they take no prompt");if(e.continueSession&&e.noSession)throw new Error("--continue and --no-session cannot be combined");if(e.sessionPath&&e.noSession)throw new Error("--session and --no-session cannot be combined");if(e.sessionPath&&e.continueSession)throw new Error("--session and --continue cannot be combined");if(e.print&&(e.continueSession||e.sessionPath))throw new Error("--print runs one prompt in a throwaway session; it cannot be combined with --continue or --session");if(e.sessionPath&&!e.sessionPath.endsWith(".jsonl"))throw new Error(`--session expects a session file, got '${e.sessionPath}'`);return e}function $(){return`${i} ${x}
3
+ coding agent. layout is a workbench, not a chat log.
4
+
5
+ usage:
6
+ howtofix [options] [prompt]
7
+ howtofix -p "explain this repo"
8
+ howtofix preset new|check|pack author a preset
9
+ howtofix doctor|models|sessions inspect the installation
10
+ howtofix sessions --search <text> find a session by what was said in it
11
+ howtofix sessions --export <n> print session n from the list as markdown
12
+
13
+ options:
14
+ -p, --print run one prompt and print the answer
15
+ -c, --continue resume the latest session in this folder
16
+ -r, --resume alias for --continue
17
+ --session <file> run inside a specific saved session file
18
+ --no-session do not write a session file
19
+ -m, --model <id> provider/model, or a fuzzy model id
20
+ --thinking <lv> off | minimal | low | medium | high | xhigh | max
21
+ --approval <m> suggest | auto-edit | full-auto
22
+ --output-format <f> text | json | stream-json
23
+ text for a person; json writes the final result only;
24
+ stream-json writes every event as it happens
25
+ --max-budget-usd <n> stop the run once it has spent n dollars
26
+ --sandbox run the whole session inside a filesystem sandbox:
27
+ the project is writable, the rest is read-only.
28
+ needs bubblewrap (Linux) or sandbox-exec (macOS)
29
+ --dangerously-skip-permissions
30
+ run everything, including commands otherwise refused.
31
+ unattended runs only; pairs with --print
32
+ --update install the newest release and exit
33
+ --check-update say whether a newer release exists
34
+ -h, --help
35
+ -v, --version
36
+
37
+ keys:
38
+ enter send
39
+ shift+enter new line
40
+ ctrl+c stop the run, or quit when idle
41
+ ctrl+d quit
42
+ ctrl+l jump to the latest line
43
+ ctrl+o clear the screen (history is kept)
44
+ ctrl+t raise the thinking level (wraps to off)
45
+ ctrl+shift+t lower the thinking level
46
+ esc clear the draft
47
+ ctrl+v paste from the clipboard
48
+ pgup / pgdn scroll the transcript a page
49
+ home / end jump to the top / back to the latest line
50
+ wheel scroll the transcript
51
+
52
+ commands:
53
+ /init /model /providers /thinking /approval /login /connect /logout /new
54
+ /resume /compact /name /stats /rtk /diff /commit /undo /rewind /review /tools /export
55
+ /copy /agents /goal /plan /workflow /session /help /quit /exit
56
+ /dcp /context /decompress /sweep
57
+ /update /usage /sync
58
+ /skills /preset /settings /proxy /roblox
59
+
60
+ /stats shows session tokens, cost, and the prompt-cache hit rate; a high read
61
+ total alone hides re-billed context, so the rate is what tells you the truth.
62
+ /rtk compresses long tool output (logs, diffs, greps) before the model reads it.
63
+ Off by default: /rtk on to save tokens when skimming, /rtk off to keep every line.
64
+ Nothing is invented by it \u2014 it drops redundant lines and says how many it dropped,
65
+ and never touches a failed command or one already truncated.
66
+
67
+ /preset lists skills bundled with this build and skills from the registry, by
68
+ category; picking one downloads it into the project and loads it as a skill.
69
+ An integration preset installs disabled: its skills and MCP server stay dormant
70
+ until /preset enable <name>, and /preset disable <name> takes the server back
71
+ out of the live session. /roblox reports that state for RoToolbox in one line.
72
+ /skills lists what is loaded now.
73
+
74
+ /proxy <url> routes requests through a proxy, /proxy off stops, /proxy env uses
75
+ HTTPS_PROXY / HTTP_PROXY / ALL_PROXY; /proxy alone shows what is set.
76
+
77
+ /agents <id> <message> messages a parked agent; /agents <id> shows one.
78
+
79
+ a line starting with ! runs in the project shell.
80
+
81
+ an npm install checks for a newer release at startup and can install it itself;
82
+ /update does it on demand. config and sessions live in ~/.howtofix/agent.
83
+ /connect signs in to howtofix.id in your browser, and falls back to a pasted
84
+ token; /login takes another provider's key.
85
+ `}function pe(t,e=!1){let{message:l}=M(t);if(!e)return l;let d=t instanceof Error?t.stack:void 0;return d&&d.includes(`
86
+ `)?`${l}
87
+ ${d}`:l}function L(t,e){let l=e?.debug??process.env.HOWTOFIX_DEBUG==="1",d=e?.exit??(s=>process.exit(s)),p=s=>{console.error(`${t}: ${pe(s,l)}`),d(1)};process.on("uncaughtException",p),process.on("unhandledRejection",p)}function le(t){return t.type==="message_update"}function ue(t){if(t.type==="toolcall_start"){let d=t.contentIndex,s=d===void 0?void 0:t.partial?.content[d];if(s?.type!=="toolCall")return;let{partial:y,...v}=t;return{...v,id:s.id,toolName:s.name}}if(!("partial"in t))return t;let{partial:e,...l}=t;return l}function me(t){if(!le(t))return t;if(t.message.role!=="assistant")return;let e=ue(t.assistantMessageEvent);if(e!==void 0)return{type:"message_update",usage:t.message.usage,assistantMessageEvent:e}}function B(t){let e=me(t);if(e!==void 0)return`${JSON.stringify(e)}
88
+ `}function fe(){try{return{cwd:process.cwd()}}catch{return{error:"the current directory no longer exists (it was deleted while the shell stayed in it). cd to a directory that exists, or pass one on the command line."}}}function he(){process.stdout.on("error",t=>{throw t.code==="EPIPE"&&process.exit(0),t})}async function ge(){he(),T();let t=performance.now(),e=fe();if("error"in e){console.error(`${i}: ${e.error}`),process.exitCode=1;return}let l=e.cwd,d=process.env.HXF_FAKE_NODE??process.versions.node,[p,s]=d.split(".").map(n=>Number.parseInt(n,10));if(!Number.isFinite(p)||p<22||p===22&&Number.isFinite(s)&&s<19){console.error(`${i}: Node ${d} is too old. Node 22.19 or newer is required (run "node --version" to check yours), because undici needs zstd, which Node added in 22.19. Upgrade Node and run this again.`),process.exitCode=1;let n=process.argv.slice(2);if(n.some(c=>c==="-h"||c==="--help")){console.log($());return}if(n.some(c=>c==="-v"||c==="--version")){console.log(`${i} ${x}`);return}return}let y=process.argv.slice(2);if(y[0]==="doctor"||y[0]==="models"||y[0]==="sessions"){let n=y[0];try{let c=await import("./chunk-GQCAJ56I.js");n==="doctor"?process.exitCode=await c.runDoctor():n==="models"?process.exitCode=c.runModels():process.exitCode=c.runSessions(y.slice(1))}catch(c){P(i,n,c),process.exitCode=1}return}let v;try{v=W(process.argv.slice(2))}catch(n){let c=n instanceof Error?n.message:String(n);console.error(`${i}: ${c}
89
+ ${$()}`),process.exitCode=2;return}if(v.print&&!v.prompt){console.error(`${i}: --print needs a prompt`),process.exitCode=2;return}let{repairGatewayModels:D,ensureGatewaySettings:J,repairModelFacts:G,syncGatewayModelsOnStart:H,checkAccount:X}=await import("./chunk-IGEY3AWF.js"),{readCliCredential:q,renewCliCredentialIfNeeded:K}=await import("./chunk-NBQFPCRT.js"),{AGENT_DIR:h}=await import("./chunk-GXB7RZHH.js"),C=D(h);C>0&&console.error(`${i}: repaired ${C} howtofix model(s) that the gateway would have rejected`);let S=await G(h);S.changed>0?console.error(`${i}: filled in the context window and reasoning flag for ${S.changed} model(s) from the model catalogue`):S.message&&console.error(`${i}: model catalogue unavailable (${S.message}); context windows stay at their conservative defaults`),J(h)&&console.error(`${i}: set httpIdleTimeoutMs in settings.json so a stalled provider recovers instead of hanging`);try{let n=q(h);if(n){let c=await K(h,n),w=await X(c.credential.accessToken);if(w.ok){let m=await H(h,w.models);m.added>0&&console.error(`${i}: added ${m.added} model(s) this account can now reach`)}}}catch{}let o=v;if(o.help){console.log($());return}if(o.version){console.log(`${i} ${x}`);let{cliSigningKeyMatchesPanel:n,cliSigningKeyFingerprint:c}=await import("./chunk-S656RE4A.js");n()||(console.error(`${i}: this build's CLI signing key (${c()}) does not match the key the panel verifies against, so sign-in will be refused.`),process.exitCode=1);return}if(o.sandbox&&!process.env.HOWTOFIX_SANDBOXED){let{detectSandbox:n,enterSandbox:c,describeSandbox:w}=await import("./chunk-N345KWWK.js"),m=n();if(!m){console.error(`${i}: --sandbox needs a working bubblewrap (Linux) or sandbox-exec (macOS). Neither could be used here \u2014 on Linux this usually means unprivileged user namespaces are disabled.`),process.exitCode=1;return}console.error(w(m)),process.exitCode=c(m,{cwd:l,agentDir:A,argv:[process.execPath,...process.argv.slice(1)]});return}let{installProxy:V}=await import("./chunk-WKUJ7CFO.js");if(V(A),o.update||o.checkUpdate){let{PACKAGE_NAME:n,checkForUpdate:c,installUpdate:w,isInstalledCopy:m}=await import("./chunk-7CHDNTH6.js"),k=await c(x);if(!k.latest){console.error(`${i}: ${k.message}`),process.exitCode=o.update?1:0;return}if(console.log(k.message),o.checkUpdate||!k.available)return;if(!m()){console.error(`${i}: this copy was not installed by npm, so npm cannot replace it. install it with: npm install -g ${n}`),process.exitCode=1;return}let b=await w({version:k.latest});console.log(b.message),b.ok||(process.exitCode=1);return}let O=process.argv.slice(2);if(O[0]==="preset"){let{runPresetCommand:n}=await import("./chunk-FPXVDP32.js");process.exitCode=n(O.slice(1));return}let{expandFileReferences:Y}=await import("./chunk-TSEKPGK4.js"),{isApprovalMode:z,setApprovalMode:Q,setDangerousBypass:Z,isDangerousBypass:ee}=await import("./chunk-56SEAGAQ.js"),{openSession:se}=await import("./chunk-U6CI6KXN.js");Q(o.approval&&z(o.approval)?o.approval:"full-auto");let{readWorkbenchSettings:te}=await import("./chunk-V3R4FYTJ.js");(o.dangerouslySkipPermissions||te(h).skipPermissions)&&Z(!0),ee()&&console.error(`${i}: permission checks are OFF \u2014 critical commands (filesystem wipes, fork bombs, host shutdown) and the repeated-call guard are NOT enforced for this run. Turn it off with /settings, or by removing skipPermissions from settings.json.`);let j=n=>se({cwd:l,model:o.model,thinking:o.thinking,continueSession:n.continueSession??!1,noSession:n.noSession??!1,sessionPath:n.sessionPath,interactive:!o.print}),r=await j({continueSession:o.continueSession,sessionPath:o.sessionPath,noSession:o.noSession||o.print});if(o.print&&r.note&&console.error(r.note),o.maxBudgetUsd!==void 0&&!r.priced){console.error(`${i}: ${U(r.modelLabel,h)}`),await r.dispose(),process.exitCode=2;return}if(o.print){let n=o.prompt.trimStart();if(n.startsWith("/")){let u=n.slice(1).split(/\s+/)[0]??"",{skillInvocation:f}=await import("./chunk-ILDDDLRC.js"),a=r.session.resourceLoader.getSkills().skills.map(E=>E.name);if(!(f(n,a)!==void 0)){let{slashCommands:E}=await import("./chunk-GIUYIKH3.js");if(E(r.session).some(de=>de.name===u)){console.error(`${i}: /${u} runs in the TUI only \u2014 --print sends prompts straight to the model. Run \`howtofix\` without --print for interactive commands.`),await r.dispose(),process.exitCode=2;return}}}let c=o.outputFormat==="stream-json",w=o.outputFormat==="json",m=c||w;if(c){let u={type:"system",subtype:"init",cwd:l,session_id:r.session.sessionId,model:r.modelLabel,output_format:o.outputFormat,max_budget_usd:o.maxBudgetUsd??null,dangerously_skip_permissions:o.dangerouslySkipPermissions,cli_version:x};process.stdout.write(`${JSON.stringify(u)}
90
+ `)}let k,b,ae=()=>{if(o.maxBudgetUsd===void 0||b!==void 0)return;let u=r.session.getSessionStats().cost;u>o.maxBudgetUsd&&(b={spent:u},r.session.abort())};try{k=r.session.subscribe(a=>{if(c){let g=B(a);g!==void 0&&process.stdout.write(g)}ae()}),await r.session.prompt(Y(o.prompt,l));let u=r.session.getLastAssistantText()??"";if(b){if(console.error(`${i}: ${N(b.spent,o.maxBudgetUsd)}`),m){let a=r.session.getSessionStats();process.stdout.write(`${JSON.stringify({type:"result",subtype:"error_max_budget_usd",is_error:!0,result:u,session_id:r.session.sessionId,duration_ms:Math.round(performance.now()-t),num_turns:a.assistantMessages,total_cost_usd:a.cost,usage:{input_tokens:a.tokens.input,output_tokens:a.tokens.output,cache_read_input_tokens:a.tokens.cacheRead,cache_creation_input_tokens:a.tokens.cacheWrite}})}
91
+ `)}process.exitCode=1;return}if(m){let a=r.session.getSessionStats(),g=r.session.state.messages.at(-1);process.stdout.write(`${JSON.stringify({type:"result",subtype:"success",is_error:!1,result:u,session_id:r.session.sessionId,duration_ms:Math.round(performance.now()-t),num_turns:a.assistantMessages,total_cost_usd:a.cost,usage:{input_tokens:a.tokens.input,output_tokens:a.tokens.output,cache_read_input_tokens:a.tokens.cacheRead,cache_creation_input_tokens:a.tokens.cacheWrite},stop_reason:g?.role==="assistant"?g.stopReason:void 0})}
92
+ `)}else process.stdout.write(u.endsWith(`
93
+ `)?u:`${u}
94
+ `);let f=r.session.state.messages.at(-1);if(f?.role==="assistant"){if((f.stopReason==="error"||f.stopReason==="aborted"||f.errorMessage)&&(process.exitCode=1),process.exitCode===1&&f.errorMessage){console.error(`${i}: ${f.errorMessage}`);let{diagnoseGatewayFailure:a}=await import("./chunk-MVLCGU3G.js"),g=a(f.errorMessage);g!==void 0&&console.error(`${i}: ${g}`)}if(m&&process.exitCode===1){let a=r.session.getSessionStats();process.stdout.write(`${JSON.stringify({type:"result",subtype:f.stopReason==="aborted"?"error_aborted":"error_during_execution",is_error:!0,result:u,session_id:r.session.sessionId,duration_ms:Math.round(performance.now()-t),num_turns:a.assistantMessages,total_cost_usd:a.cost,usage:{input_tokens:a.tokens.input,output_tokens:a.tokens.output,cache_read_input_tokens:a.tokens.cacheRead,cache_creation_input_tokens:a.tokens.cacheWrite},stop_reason:f.stopReason})}
95
+ `)}}}finally{k?.(),await r.dispose()}return}if(!process.stdin.isTTY||!process.stdout.isTTY){console.error(`${i}: interactive mode needs a terminal. use --print for pipes.`),await r.dispose(),process.exitCode=2;return}let{runWorkbench:oe}=await import("./chunk-GIUYIKH3.js"),{autoUpdateEnabled:ne,isInstalledCopy:re}=await import("./chunk-7CHDNTH6.js"),ie=re()&&ne(`${h}/settings.json`);await oe({cwd:l,modelLabel:r.modelLabel,session:r.session,setCompressToolOutput:r.setCompressToolOutput,initialPrompt:o.prompt||void 0,note:r.note,maxBudgetUsd:o.maxBudgetUsd,update:ie?{current:x,install:!0}:void 0,reopen:j})}L(i);ge().catch(t=>{P(i,void 0,t),process.exitCode=1});