howtofixcli 0.7.7 → 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.
- package/dist/chunk-27TRKD65.js +2 -0
- package/dist/chunk-3FYCFLNK.js +1 -0
- package/dist/chunk-56SEAGAQ.js +1 -0
- package/dist/chunk-63ZCQDXH.js +6 -0
- package/dist/chunk-6FUWMLWO.js +1 -0
- package/dist/chunk-7CHDNTH6.js +1 -0
- package/dist/chunk-ACOMNDUL.js +185 -0
- package/dist/chunk-AEVPET7X.js +2 -0
- package/dist/chunk-AGGIYXN7.js +2 -0
- package/dist/chunk-BHVBO4SC.js +1 -0
- package/dist/chunk-FPXVDP32.js +8 -0
- package/dist/chunk-GIUYIKH3.js +109 -0
- package/dist/chunk-GQCAJ56I.js +5 -0
- package/dist/chunk-GXB7RZHH.js +1 -0
- package/dist/chunk-IGEY3AWF.js +1 -0
- package/dist/chunk-ILDDDLRC.js +1 -0
- package/dist/chunk-JFYVRWJI.js +2 -0
- package/dist/chunk-MVLCGU3G.js +1 -0
- package/dist/chunk-N345KWWK.js +1 -0
- package/dist/chunk-NBQFPCRT.js +1 -0
- package/dist/chunk-OIPSLIZE.js +5 -0
- package/dist/chunk-ORJ2QZBG.js +3 -0
- package/dist/chunk-PCALBZOO.js +2 -0
- package/dist/chunk-PDNBGUYP.js +2 -0
- package/dist/chunk-S656RE4A.js +1 -0
- package/dist/chunk-TSEKPGK4.js +1 -0
- package/dist/chunk-U6CI6KXN.js +34 -0
- package/dist/chunk-UTYFLLSM.js +1 -0
- package/dist/chunk-V3R4FYTJ.js +1 -0
- package/dist/chunk-V656XVXV.js +8 -0
- package/dist/chunk-WKUJ7CFO.js +1 -0
- package/dist/chunk-WOT6VMZA.js +1 -0
- package/dist/chunk-WUPU6LTD.js +4 -0
- package/dist/chunk-WX3V5P7U.js +5 -0
- package/dist/chunk-ZKZ6WCI3.js +2 -0
- package/dist/main.js +94 -536
- package/package.json +15 -62
- package/dist/account-credential.js +0 -136
- package/dist/account-credential.test.js +0 -38
- package/dist/activity.js +0 -124
- package/dist/agent-defs.js +0 -237
- package/dist/agent-limiter.js +0 -68
- package/dist/agents.js +0 -554
- package/dist/anim.js +0 -361
- package/dist/args.js +0 -228
- package/dist/at-file.js +0 -28
- package/dist/brand-extension.js +0 -87
- package/dist/brand.js +0 -76
- package/dist/browser.js +0 -58
- package/dist/cache-report.js +0 -139
- package/dist/checkpoint.js +0 -437
- package/dist/claims.js +0 -167
- package/dist/cli-auth.js +0 -142
- package/dist/cli-checkin.js +0 -116
- package/dist/cli-credentials.js +0 -254
- package/dist/clipboard.js +0 -149
- package/dist/collapse.js +0 -129
- package/dist/commands.js +0 -301
- package/dist/compaction-guard.js +0 -81
- package/dist/context-extension.js +0 -17
- package/dist/dcp/commands.js +0 -172
- package/dist/dcp/compress-state.js +0 -139
- package/dist/dcp/compress.js +0 -329
- package/dist/dcp/dcp-extension.js +0 -288
- package/dist/dcp/dedup.js +0 -93
- package/dist/dcp/ids.js +0 -189
- package/dist/dcp/index.js +0 -25
- package/dist/dcp/prompts.js +0 -68
- package/dist/dcp/protected-content.js +0 -221
- package/dist/dcp/protected.js +0 -161
- package/dist/dcp/prune.js +0 -418
- package/dist/dcp/state.js +0 -176
- package/dist/device-id.js +0 -90
- package/dist/device-id.test.js +0 -23
- package/dist/errors.js +0 -63
- package/dist/extensions.js +0 -48
- package/dist/fatal.js +0 -35
- package/dist/feed.js +0 -498
- package/dist/fs-atomic.js +0 -54
- package/dist/gateway-errors.js +0 -90
- package/dist/git.js +0 -230
- package/dist/goal-extension.js +0 -88
- package/dist/goal.js +0 -222
- package/dist/hooks-extension.js +0 -176
- package/dist/hooks.js +0 -443
- package/dist/howtofix-account.js +0 -715
- package/dist/hud-line.js +0 -153
- package/dist/hwid.js +0 -130
- package/dist/interrupt.js +0 -60
- package/dist/json-events.js +0 -50
- package/dist/level-style.js +0 -112
- package/dist/loop-guard.js +0 -95
- package/dist/lsp-tools.js +0 -32
- package/dist/lsp.js +0 -347
- package/dist/mcp-config.js +0 -85
- package/dist/mcp-lifecycle.js +0 -147
- package/dist/model-catalog.js +0 -217
- package/dist/model-resolve.js +0 -65
- package/dist/notes.js +0 -78
- package/dist/oauth-login.js +0 -568
- package/dist/paths.js +0 -61
- package/dist/permissions.js +0 -298
- package/dist/plan-mode.js +0 -363
- package/dist/preset-command.js +0 -227
- package/dist/preset-extension.js +0 -35
- package/dist/preset-mcp.js +0 -292
- package/dist/presets.js +0 -727
- package/dist/prompt-files.js +0 -103
- package/dist/prompt-text.js +0 -41
- package/dist/proxy.js +0 -137
- package/dist/quality/ast.js +0 -196
- package/dist/quality/cognitive.js +0 -83
- package/dist/quality/cyclomatic.js +0 -30
- package/dist/quality/halstead.js +0 -83
- package/dist/quality/index.js +0 -18
- package/dist/quality/length.js +0 -3
- package/dist/quality/maintainability.js +0 -15
- package/dist/quality/nesting.js +0 -57
- package/dist/quality/report.js +0 -803
- package/dist/quality/smells.js +0 -67
- package/dist/quality-tool.js +0 -63
- package/dist/read-only-commands.js +0 -597
- package/dist/repo-map.js +0 -292
- package/dist/rtk.js +0 -193
- package/dist/sandbox.js +0 -105
- package/dist/schema.js +0 -214
- package/dist/session-lifecycle.js +0 -19
- package/dist/session-retention.js +0 -111
- package/dist/session.js +0 -446
- package/dist/sessions.js +0 -100
- package/dist/settings.js +0 -148
- package/dist/skills.js +0 -35
- package/dist/slop-tool.js +0 -96
- package/dist/spend-cap.js +0 -43
- package/dist/subagent.js +0 -171
- package/dist/tar.js +0 -146
- package/dist/theme.js +0 -79
- package/dist/thinking.js +0 -45
- package/dist/todo.js +0 -114
- package/dist/tool-loadout.js +0 -97
- package/dist/tool-render.js +0 -769
- package/dist/tools.js +0 -176
- package/dist/ui.js +0 -4751
- package/dist/update.js +0 -533
- package/dist/usage-segment.js +0 -69
- package/dist/web-tools.js +0 -46
- package/dist/web.js +0 -265
- package/dist/welcome.js +0 -179
- package/dist/workflow-engine.js +0 -246
- package/dist/workflow-progress.js +0 -446
- package/dist/workflow-view.js +0 -195
- package/dist/workflow.js +0 -349
package/dist/main.js
CHANGED
|
@@ -1,537 +1,95 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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});
|