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.
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 -116
  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 -4751
  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/anim.js DELETED
@@ -1,361 +0,0 @@
1
- /**
2
- * Micro-animation primitives.
3
- *
4
- * Every function here is pure in `(text, style, nowMs)`. The workbench already
5
- * repaints on a 90 ms timer while a run is live, so an "animation" is only a
6
- * different pixel for the same state: no timers, no per-frame state, and no
7
- * allocation beyond the strings that get painted anyway.
8
- *
9
- * The one rule that matters for cost is that the sweep is emitted as a handful
10
- * of painted *runs*, not one escape sequence per character — the band is
11
- * quantised into levels so consecutive characters of the same level share a
12
- * single `styleText` call, so the sweep costs one style computation per row.
13
- *
14
- * `HOWTOFIX_NO_MOTION=1` turns every effect into its static form. That is also
15
- * what a non-TTY gets.
16
- */
17
- import { mixColors, parseColor, styleText } from "@netrodev/hxf-tui";
18
- import { MODE } from "./theme.js";
19
- export const MOTION = process.env.HOWTOFIX_NO_MOTION !== "1";
20
- /** Milliseconds per character the sweep travels, unless a caller overrides it. */
21
- const DEFAULT_CHAR_MS = 45;
22
- /** Half-width of the bright band, in characters. */
23
- const BAND = 5;
24
- /** Off-screen padding, so the band enters and leaves instead of snapping. */
25
- const GAP = 8;
26
- /**
27
- * When the wait ramp starts and where it lands: a run that has produced nothing
28
- * for ten seconds is still plausibly thinking, one that has produced nothing for
29
- * a minute is worth interrupting.
30
- */
31
- const WARN_AFTER_MS = 10_000;
32
- const STALLED_AFTER_MS = 60_000;
33
- /** Levels the band is quantised into: runs, not per-character escapes. */
34
- const LEVELS = 3;
35
- /**
36
- * Off-word padding on each side of the glint's travel, in characters.
37
- *
38
- * The band enters from off the word and leaves past the far end, so the light is
39
- * seen crossing rather than appearing on top of the text. The value is a
40
- * compromise and was measured rather than guessed: a padding of ten — the width
41
- * a continuous shimmer loop can afford, because the dead travel is just the gap
42
- * before the next lap — left *nineteen of twenty-seven* frames with nothing lit,
43
- * so a keypress was followed by most of a second of nothing before the light
44
- * arrived. Four keeps the entry readable at roughly 200 ms and leaves the sweep
45
- * itself at 50 ms per character.
46
- */
47
- const GLIMMER_PAD = 4;
48
- export const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);
49
- /** Blend between two palette colours; the terminal has no alpha, so this is "fade". */
50
- export function fade(from, to, amount) {
51
- const t = clamp01(amount);
52
- if (t >= 1)
53
- return to;
54
- if (t <= 0)
55
- return from;
56
- return mixColors(from, to, t);
57
- }
58
- /**
59
- * A meter that moves to its target instead of jumping to it.
60
- *
61
- * The workbench repaints on a timer, so this is stepped on each frame with the
62
- * real elapsed time rather than a fixed frame length: a dropped frame shortens
63
- * nothing and the meter arrives at the same time on a slow terminal. The step is
64
- * clamped, because a resume after a suspend would otherwise integrate seconds of
65
- * simulated time into one frame.
66
- *
67
- * Underneath it is a critically-damped spring: `velocity` accumulates toward the
68
- * remaining distance and is bled off, which gives the small overshoot at the end
69
- * that makes a number feel like it moved rather than teleported.
70
- */
71
- export class Spring {
72
- value;
73
- velocity = 0;
74
- last = Date.now();
75
- target;
76
- constructor(value, target = value) {
77
- this.value = value;
78
- this.target = target;
79
- }
80
- /** Point the spring at a new target; it keeps its speed on the way. */
81
- set(target) {
82
- this.target = target;
83
- }
84
- /** True while it is still moving, so a caller can keep the clock alive. */
85
- get moving() {
86
- return Math.abs(this.target - this.value) > 0.15;
87
- }
88
- /** Jump to a value, discarding any motion. For a new baseline, not a target. */
89
- snap(value) {
90
- this.value = value;
91
- this.target = value;
92
- this.velocity = 0;
93
- this.last = Date.now();
94
- }
95
- /** Advance to `now` and return the current value. */
96
- step(now = Date.now()) {
97
- // The clamp exists for a gap (suspend, a blocked event loop), and it has to
98
- // sit above the real frame length: the workbench tick is 90 ms, so clamping
99
- // at 64 ms would silently throw away a quarter of every frame and make the
100
- // meter settle later here than on a faster machine.
101
- const raw = Math.max(0, now - this.last);
102
- const dt = Math.min(250, raw) / 1000;
103
- this.last = now;
104
- if (!MOTION) {
105
- this.value = this.target;
106
- this.velocity = 0;
107
- return this.value;
108
- }
109
- // A gap longer than the clamp is time nobody saw: there were no frames
110
- // between, so nothing moved on screen and there is no momentum to carry.
111
- // Keeping the old velocity across a suspend would fling the value past the
112
- // target on the first frame back, which reads as a glitch rather than as
113
- // motion.
114
- if (raw > 250)
115
- this.velocity = 0;
116
- // Critically-damped spring, integrated in fixed-duration sub-steps: a
117
- // 90 ms workbench frame and a 16 ms frame integrate the same curve, so a
118
- // meter move takes the same wall-clock time on any machine. The frame
119
- // itself is clamped, so a gap (suspend, a blocked event loop) integrates
120
- // as one frame instead of one enormous step.
121
- const stiffness = 150;
122
- const damping = 2 * Math.sqrt(stiffness);
123
- const substeps = Math.max(1, Math.ceil(dt / 0.008));
124
- const h = dt / substeps;
125
- for (let sub = 0; sub < substeps; sub += 1) {
126
- this.velocity += (this.target - this.value) * stiffness * h - this.velocity * damping * h;
127
- this.value += this.velocity * h;
128
- }
129
- if (!this.moving) {
130
- this.value = this.target;
131
- this.velocity = 0;
132
- }
133
- return this.value;
134
- }
135
- }
136
- /**
137
- * A status colour that says how long the agent has been waiting on the model.
138
- *
139
- * A long silence is not automatically a problem — a big prompt with thinking on
140
- * takes a while — so this is a ramp, not an alarm: it slides toward the warning
141
- * colour and reaches the error colour only after the wait has run long enough to
142
- * be worth interrupting. Claude Code does the same with its spinner gradient;
143
- * the point is that the colour carries information the text does not.
144
- */
145
- export function waitTone(waitedMs, calm, warning, stalled) {
146
- if (!MOTION)
147
- return calm;
148
- if (waitedMs <= WARN_AFTER_MS)
149
- return calm;
150
- if (waitedMs >= STALLED_AFTER_MS)
151
- return stalled;
152
- return fade(warning, stalled, (waitedMs - WARN_AFTER_MS) / (STALLED_AFTER_MS - WARN_AFTER_MS));
153
- }
154
- /**
155
- * A one-shot entrance: 0 → 1 over `durationMs`, eased out, then it stays at 1.
156
- * Callers paint `fade(dim, final, entrance(age, duration))`. The terminal has no
157
- * alpha, so this is a colour move, and the text is never re-flowed by it.
158
- */
159
- export function entrance(ageMs, durationMs) {
160
- // A negative age means the caller does not know when the item appeared (it
161
- // came from the transcript, not from this session): draw it final.
162
- if (!MOTION || durationMs <= 0 || ageMs < 0)
163
- return 1;
164
- return 1 - (1 - clamp01(ageMs / durationMs)) ** 3;
165
- }
166
- /** True while an entrance that began `ageMs` ago still needs repaints. */
167
- export function entering(ageMs, durationMs) {
168
- return MOTION && ageMs >= 0 && ageMs < durationMs;
169
- }
170
- /** How long a newly appended feed item takes to become fully opaque. */
171
- export const ENTRANCE_MS = 260;
172
- /** Delay between consecutive detail lines of one result, so they cascade. */
173
- export const STAGGER_MS = 45;
174
- /**
175
- * How many lines of a block are visible `ageMs` after it appeared.
176
- *
177
- * A line that has not had its turn is not drawn at all — not drawn faint — so
178
- * the block grows into place instead of showing its final height with ghost
179
- * lines in it. `total` is returned once the cascade is over, and the cascade is
180
- * capped so a 200-line diff does not take nine seconds to appear.
181
- */
182
- export function revealedLines(ageMs, total, staggerMs = STAGGER_MS, maxDelayMs = 400) {
183
- if (!MOTION || staggerMs <= 0 || ageMs < 0)
184
- return total;
185
- if (ageMs >= maxDelayMs)
186
- return total;
187
- return Math.min(total, Math.floor(ageMs / staggerMs) + 1);
188
- }
189
- /**
190
- * A slow oscillation between two colours, for a surface that is waiting on the
191
- * user. It is a triangle wave rather than a sine: it never skips a frame at the
192
- * turn, and the difference is invisible at this speed.
193
- */
194
- export function pulse(from, to, now = Date.now(), periodMs = 1400) {
195
- if (!MOTION || periodMs <= 0)
196
- return from;
197
- const phase = (now % periodMs) / periodMs;
198
- return fade(from, to, phase < 0.5 ? phase * 2 : (1 - phase) * 2);
199
- }
200
- /**
201
- * A band of light crossing plain text once, at `progress` through its travel.
202
- *
203
- * This is the level's change effect. It is a *glint*, not a gradient: the band is
204
- * three characters wide with a bright crest and two half-lit shoulders, and it
205
- * advances one character per tick. The first version blurred the light across
206
- * five characters with three blended levels and crossed the word in 320 ms, and
207
- * that is what made it read as a smear rather than as a highlight — a gradient
208
- * spread over a six-character word leaves almost no pixel at full strength, so
209
- * the eye gets a dim wobble instead of a moving point of light. Sharp is both
210
- * more legible and cheaper: fewer distinct runs to paint.
211
- *
212
- * The travel is deliberately unhurried. The band enters from off the word and
213
- * leaves past the far end, so it is seen crossing rather than appearing on top of
214
- * the word, and the padding on each side is wide enough for that entry to be
215
- * read. The caller owns the clock: `progress` runs 0 → 1 over the travel and a
216
- * render never depends on when it happened to be called.
217
- *
218
- * Motion is confined to the three to seven characters of the word itself. A
219
- * ripple that repainted the viewport would have to be painted onto a feed that is
220
- * also scrolling, and under `screen` — where every frame is a diffed repaint — a
221
- * large animated region is the exact case that tears.
222
- */
223
- export function ripple(text, style, progress) {
224
- const rest = styleText(text, { fg: style.rest }, MODE);
225
- if (!MOTION || text.length === 0)
226
- return rest;
227
- const t = clamp01(progress);
228
- if (t >= 1)
229
- return rest;
230
- const band = style.band ?? 1;
231
- // The crest starts and ends off the text, so the band is seen crossing rather
232
- // than appearing on top of the word.
233
- const span = text.length + GLIMMER_PAD * 2;
234
- // Quantised to whole characters, and this is the detail that decides whether
235
- // the effect reads as a glint or as a smear. Stepping the centre continuously
236
- // means that for most frames it sits *between* two characters, so both are half
237
- // lit and neither reaches full strength — the band looks dim and soft at every
238
- // instant, which is exactly the blur this replaced. Advancing one character at a
239
- // time keeps one character at the crest on every frame the band is visible.
240
- const step = Math.min(span - 1, Math.floor(t * span));
241
- const centre = step - GLIMMER_PAD;
242
- const levelAt = (i) => {
243
- const distance = Math.abs(i - centre);
244
- if (distance > band)
245
- return 0;
246
- return 1 - distance / (band + 1);
247
- };
248
- let out = "";
249
- let runStart = 0;
250
- let runLevel = null;
251
- let offset = 0;
252
- let index = 0;
253
- for (const char of text) {
254
- const level = levelAt(index);
255
- if (runLevel === null) {
256
- runLevel = level;
257
- }
258
- else if (level !== runLevel) {
259
- out += styleText(text.slice(runStart, offset), { fg: fade(style.rest, style.lit, runLevel) }, MODE);
260
- runStart = offset;
261
- runLevel = level;
262
- }
263
- offset += char.length;
264
- index += 1;
265
- }
266
- return out + styleText(text.slice(runStart), { fg: fade(style.rest, style.lit, runLevel ?? 0) }, MODE);
267
- }
268
- /**
269
- * Paint text with a travelling band of a colour ramp: a spectrum that flows
270
- * across the word and never sits on a single flat colour.
271
- *
272
- * This is what the top of the effort ladder looks like. The ramp is the one the
273
- * levels themselves are drawn from, so the highest setting carries every colour
274
- * below it — the word is a picture of the ladder rather than a badge for its top
275
- * rung. It runs continuously, which is the one animation here that does: a
276
- * ceiling is a standing fact about the session, and a still word would say
277
- * nothing about being at the limit.
278
- *
279
- * `charMs` is milliseconds per character the spectrum advances, so the flow
280
- * speed is independent of the word's length and of the frame rate.
281
- */
282
- export function spectrum(text, ramp, now = Date.now(), charMs = 90) {
283
- const first = ramp[0] ?? parseColor("#e6e6e6");
284
- if (ramp.length === 0 || text.length === 0)
285
- return styleText(text, { fg: first }, MODE);
286
- if (!MOTION || charMs <= 0)
287
- return styleText(text, { fg: ramp[ramp.length - 1] ?? first }, MODE);
288
- // One lap per `ramp.length * charMs`, each character offset by one step, so the
289
- // band is exactly one ramp long and wraps without a seam.
290
- const lap = ramp.length * charMs;
291
- const step = ((now % lap) + lap) % lap;
292
- let out = "";
293
- let runStart = 0;
294
- let runColor = null;
295
- let offset = 0;
296
- let index = 0;
297
- const colourAt = (i) => {
298
- const pos = (((step + i * charMs) / lap) % 1) * ramp.length;
299
- const from = Math.floor(pos) % ramp.length;
300
- const to = (from + 1) % ramp.length;
301
- return fade(ramp[from] ?? first, ramp[to] ?? first, pos - Math.floor(pos));
302
- };
303
- for (const char of text) {
304
- const colour = colourAt(index);
305
- if (runColor === null) {
306
- runColor = colour;
307
- }
308
- else if (colour !== runColor) {
309
- out += styleText(text.slice(runStart, offset), { fg: runColor }, MODE);
310
- runStart = offset;
311
- runColor = colour;
312
- }
313
- offset += char.length;
314
- index += 1;
315
- }
316
- return out + styleText(text.slice(runStart), { fg: runColor ?? first }, MODE);
317
- }
318
- /** Distance from the band centre, quantised so the sweep emits a few runs. */
319
- function bandLevel(index, centre) {
320
- const distance = Math.abs(index - centre);
321
- if (distance >= BAND)
322
- return 0;
323
- return Math.ceil((1 - distance / BAND) * LEVELS) / LEVELS;
324
- }
325
- /**
326
- * Sweep a brightness band across plain text.
327
- *
328
- * `text` must be plain — no escape sequences — because the band is painted from
329
- * code points. Visible width is preserved exactly: only colour codes are added.
330
- * The band starts off-screen and ends off-screen, so a looping sweep never
331
- * strobes at the boundary.
332
- */
333
- export function shimmer(text, style, now = Date.now()) {
334
- const charMs = style.charMs ?? DEFAULT_CHAR_MS;
335
- if (!MOTION || text.length === 0 || charMs <= 0)
336
- return styleText(text, { fg: style.base }, MODE);
337
- const span = text.length + GAP * 2;
338
- const centre = ((now / charMs) % span) - GAP;
339
- const paint = (run, level) => styleText(run, { fg: fade(style.base, style.bright, level) }, MODE);
340
- let out = "";
341
- let runStart = 0;
342
- let runLevel = null;
343
- let offset = 0;
344
- let index = 0;
345
- // Code-point iteration with code-unit offsets: a surrogate pair crosses the
346
- // band as one character and is never split between two runs.
347
- for (const char of text) {
348
- const level = bandLevel(index, centre);
349
- if (runLevel === null) {
350
- runLevel = level;
351
- }
352
- else if (level !== runLevel) {
353
- out += paint(text.slice(runStart, offset), runLevel);
354
- runStart = offset;
355
- runLevel = level;
356
- }
357
- offset += char.length;
358
- index += 1;
359
- }
360
- return out + paint(text.slice(runStart), runLevel ?? 0);
361
- }
package/dist/args.js DELETED
@@ -1,228 +0,0 @@
1
- import { APP_TITLE, APP_VERSION } from "./brand.js";
2
- const THINKING = { off: true, minimal: true, low: true, medium: true, high: true, xhigh: true, max: true };
3
- const APPROVAL = { suggest: true, "auto-edit": true, "full-auto": true };
4
- /**
5
- * `text` is for a person. `json` writes only the final result record, which is
6
- * what a script that just wants the answer and the cost reaches for.
7
- * `stream-json` writes every event as it happens, which is what a progress UI
8
- * or a CI log needs. The split matches the contract scripts already expect.
9
- */
10
- const OUTPUT_FORMATS = { text: true, json: true, "stream-json": true };
11
- function parseBudget(raw) {
12
- const dollars = Number(raw);
13
- if (!Number.isFinite(dollars) || dollars <= 0) {
14
- throw new Error(`--max-budget-usd needs a positive number of dollars, got '${raw}'`);
15
- }
16
- return dollars;
17
- }
18
- function takeValue(argv, index, flag) {
19
- const inline = argv[index];
20
- if (inline?.includes("="))
21
- return inline.slice(inline.indexOf("=") + 1);
22
- const next = argv[index + 1];
23
- if (!next || next.startsWith("-")) {
24
- throw new Error(`${flag} needs a value`);
25
- }
26
- argv.splice(index + 1, 1);
27
- return next;
28
- }
29
- export function parseArgs(argv) {
30
- const args = {
31
- print: false,
32
- continueSession: false,
33
- noSession: false,
34
- help: false,
35
- version: false,
36
- update: false,
37
- checkUpdate: false,
38
- outputFormat: "text",
39
- dangerouslySkipPermissions: false,
40
- sandbox: false,
41
- prompt: "",
42
- };
43
- const rest = [];
44
- const tokens = [...argv];
45
- for (let i = 0; i < tokens.length; i++) {
46
- const token = tokens[i] ?? "";
47
- if (token === "--") {
48
- rest.push(...tokens.slice(i + 1));
49
- break;
50
- }
51
- if (token === "-h" || token === "--help")
52
- args.help = true;
53
- else if (token === "-v" || token === "--version")
54
- args.version = true;
55
- else if (token === "-p" || token === "--print")
56
- args.print = true;
57
- else if (token === "-c" || token === "--continue")
58
- args.continueSession = true;
59
- else if (token === "--no-session")
60
- args.noSession = true;
61
- else if (token === "--sandbox")
62
- args.sandbox = true;
63
- else if (token === "--update")
64
- args.update = true;
65
- else if (token === "--check-update")
66
- args.checkUpdate = true;
67
- else if (token === "-r" || token === "--resume")
68
- args.continueSession = true;
69
- else if (token === "--session" || token.startsWith("--session=")) {
70
- args.sessionPath = takeValue(tokens, i, "--session");
71
- }
72
- else if (token === "-m" || token === "--model" || token.startsWith("--model=")) {
73
- args.model = takeValue(tokens, i, "--model");
74
- }
75
- else if (token === "--thinking" || token.startsWith("--thinking=")) {
76
- args.thinking = takeValue(tokens, i, "--thinking");
77
- }
78
- else if (token === "--approval" || token.startsWith("--approval=")) {
79
- args.approval = takeValue(tokens, i, "--approval");
80
- }
81
- else if (token === "--output-format" || token.startsWith("--output-format=")) {
82
- args.outputFormat = takeValue(tokens, i, "--output-format");
83
- }
84
- else if (token === "--max-budget-usd" || token.startsWith("--max-budget-usd=")) {
85
- args.maxBudgetUsd = parseBudget(takeValue(tokens, i, "--max-budget-usd"));
86
- }
87
- else if (token === "--dangerously-skip-permissions") {
88
- args.dangerouslySkipPermissions = true;
89
- }
90
- else if (token.startsWith("-")) {
91
- throw new Error(`unknown option ${token}`);
92
- }
93
- else {
94
- rest.push(token);
95
- }
96
- }
97
- if (args.thinking && !THINKING[args.thinking]) {
98
- throw new Error(`thinking must be one of: ${Object.keys(THINKING).join(", ")}`);
99
- }
100
- if (args.approval && !APPROVAL[args.approval]) {
101
- throw new Error(`approval must be one of: ${Object.keys(APPROVAL).join(", ")}`);
102
- }
103
- if (!OUTPUT_FORMATS[args.outputFormat]) {
104
- throw new Error(`output-format must be one of: ${Object.keys(OUTPUT_FORMATS).join(", ")}`);
105
- }
106
- if (args.outputFormat !== "text" && !args.print) {
107
- throw new Error(`--output-format ${args.outputFormat} is a machine contract; it needs --print`);
108
- }
109
- // `--max-budget-usd` used to demand `--print` here, on the theory that only
110
- // a one-shot run has an end to stop at. The workbench has ends too — every
111
- // turn settles — and it reads the same `getSessionStats().cost` that `/stats`
112
- // shows, so the cap is enforced there as well (see spend-cap.ts). No guard.
113
- if (args.dangerouslySkipPermissions && !args.print) {
114
- throw new Error("--dangerously-skip-permissions is for unattended runs; it needs --print. " +
115
- "interactively, pick an approval mode instead (/approval, or --approval)");
116
- }
117
- // Assigned before the guards: a guard that reads `args.prompt` while it is
118
- // still empty never fires, which is how `--update "do something"` sailed past.
119
- args.prompt = rest.join(" ").trim();
120
- if (args.update && args.checkUpdate) {
121
- throw new Error("--update installs; --check-update only looks. pick one");
122
- }
123
- if ((args.update || args.checkUpdate) && (args.print || args.continueSession || args.sessionPath || args.prompt)) {
124
- throw new Error("--update and --check-update act on the installation, not a session; they take no prompt");
125
- }
126
- if (args.continueSession && args.noSession) {
127
- throw new Error("--continue and --no-session cannot be combined");
128
- }
129
- if (args.sessionPath && args.noSession) {
130
- throw new Error("--session and --no-session cannot be combined");
131
- }
132
- if (args.sessionPath && args.continueSession) {
133
- throw new Error("--session and --continue cannot be combined");
134
- }
135
- if (args.print && (args.continueSession || args.sessionPath)) {
136
- throw new Error("--print runs one prompt in a throwaway session; it cannot be combined with --continue or --session");
137
- }
138
- if (args.sessionPath && !args.sessionPath.endsWith(".jsonl")) {
139
- throw new Error(`--session expects a session file, got '${args.sessionPath}'`);
140
- }
141
- return args;
142
- }
143
- export function helpText() {
144
- return `${APP_TITLE} ${APP_VERSION}
145
- coding agent. layout is a workbench, not a chat log.
146
-
147
- usage:
148
- howtofix [options] [prompt]
149
- howtofix -p "explain this repo"
150
- howtofix preset new|check|pack author a preset
151
- howtofix doctor|models|sessions inspect the installation
152
- howtofix sessions --search <text> find a session by what was said in it
153
- howtofix sessions --export <n> print session n from the list as markdown
154
-
155
- options:
156
- -p, --print run one prompt and print the answer
157
- -c, --continue resume the latest session in this folder
158
- -r, --resume alias for --continue
159
- --session <file> run inside a specific saved session file
160
- --no-session do not write a session file
161
- -m, --model <id> provider/model, or a fuzzy model id
162
- --thinking <lv> off | minimal | low | medium | high | xhigh | max
163
- --approval <m> suggest | auto-edit | full-auto
164
- --output-format <f> text | json | stream-json
165
- text for a person; json writes the final result only;
166
- stream-json writes every event as it happens
167
- --max-budget-usd <n> stop the run once it has spent n dollars
168
- --sandbox run the whole session inside a filesystem sandbox:
169
- the project is writable, the rest is read-only.
170
- needs bubblewrap (Linux) or sandbox-exec (macOS)
171
- --dangerously-skip-permissions
172
- run everything, including commands otherwise refused.
173
- unattended runs only; pairs with --print
174
- --update install the newest release and exit
175
- --check-update say whether a newer release exists
176
- -h, --help
177
- -v, --version
178
-
179
- keys:
180
- enter send
181
- shift+enter new line
182
- ctrl+c stop the run, or quit when idle
183
- ctrl+d quit
184
- ctrl+l jump to the latest line
185
- ctrl+o clear the screen (history is kept)
186
- ctrl+t raise the thinking level (wraps to off)
187
- ctrl+shift+t lower the thinking level
188
- esc clear the draft
189
- ctrl+v paste from the clipboard
190
- pgup / pgdn scroll the transcript a page
191
- home / end jump to the top / back to the latest line
192
- wheel scroll the transcript
193
-
194
- commands:
195
- /init /model /providers /thinking /approval /login /connect /logout /new
196
- /resume /compact /name /stats /rtk /diff /commit /undo /rewind /review /tools /export
197
- /copy /agents /goal /plan /workflow /session /help /quit /exit
198
- /dcp /context /decompress /sweep
199
- /update /usage /sync
200
- /skills /preset /settings /proxy /roblox
201
-
202
- /stats shows session tokens, cost, and the prompt-cache hit rate; a high read
203
- total alone hides re-billed context, so the rate is what tells you the truth.
204
- /rtk compresses long tool output (logs, diffs, greps) before the model reads it.
205
- Off by default: /rtk on to save tokens when skimming, /rtk off to keep every line.
206
- Nothing is invented by it — it drops redundant lines and says how many it dropped,
207
- and never touches a failed command or one already truncated.
208
-
209
- /preset lists skills bundled with this build and skills from the registry, by
210
- category; picking one downloads it into the project and loads it as a skill.
211
- An integration preset installs disabled: its skills and MCP server stay dormant
212
- until /preset enable <name>, and /preset disable <name> takes the server back
213
- out of the live session. /roblox reports that state for RoToolbox in one line.
214
- /skills lists what is loaded now.
215
-
216
- /proxy <url> routes requests through a proxy, /proxy off stops, /proxy env uses
217
- HTTPS_PROXY / HTTP_PROXY / ALL_PROXY; /proxy alone shows what is set.
218
-
219
- /agents <id> <message> messages a parked agent; /agents <id> shows one.
220
-
221
- a line starting with ! runs in the project shell.
222
-
223
- an npm install checks for a newer release at startup and can install it itself;
224
- /update does it on demand. config and sessions live in ~/.howtofix/agent.
225
- /connect signs in to howtofix.id in your browser, and falls back to a pasted
226
- token; /login takes another provider's key.
227
- `;
228
- }
package/dist/at-file.js DELETED
@@ -1,28 +0,0 @@
1
- import { readFileSync, statSync } from "node:fs";
2
- import { isAbsolute, resolve } from "node:path";
3
- /** Files larger than this are referenced but not inlined. */
4
- const MAX_INLINE_BYTES = 64 * 1024;
5
- /**
6
- * Expand `@path` tokens into `<file>` blocks, the way aider, gemini-cli, and
7
- * codex do. Unknown or binary-looking paths are left untouched for the model
8
- * to read with its own tools.
9
- */
10
- export function expandFileReferences(text, cwd) {
11
- return text.replace(/(^|\s)@([^\s]+)/g, (match, lead, raw) => {
12
- const token = raw.replace(/[),.;:]+$/, "");
13
- const trailing = raw.slice(token.length);
14
- const target = isAbsolute(token) ? token : resolve(cwd, token);
15
- try {
16
- const stats = statSync(target);
17
- if (!stats.isFile() || stats.size === 0 || stats.size > MAX_INLINE_BYTES)
18
- return match;
19
- const content = readFileSync(target);
20
- if (content.includes(0))
21
- return match;
22
- return `${lead}<file name="${target}">\n${content.toString("utf8").trimEnd()}\n</file>${trailing}`;
23
- }
24
- catch {
25
- return match;
26
- }
27
- });
28
- }