@sunilaleti/hx 0.0.0-stage → 1.0.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,1138 @@
1
+ # Changelog
2
+
3
+ What shipped in each released version of HX, newest first.
4
+
5
+ This file is the release record and part of the product: it ships inside the
6
+ package, `hx changelog` prints it, and a session can answer "what's new?" or
7
+ "which version added web search?" from it rather than from a guess. `hx docs`
8
+ prints the manual next to it.
9
+
10
+ The rules, enforced by `tests/e2e/test_cli.py` so a release cannot forget them:
11
+
12
+ - Every released version has a `## [x.y.z] - YYYY-MM-DD` section here, and the
13
+ version in `package.json` always has one. Bumping the version without
14
+ writing the section fails the suite before the tag exists.
15
+ - Work that has landed but is not tagged lives under `## [Unreleased]`. At
16
+ release time that heading is renamed to the new version and dated, and a
17
+ fresh empty `## [Unreleased]` takes its place.
18
+ - Entries say what a user can now do, in one line each, naming the command,
19
+ setting or key that reaches the feature. A changelog nobody can act on is a
20
+ git log with extra steps.
21
+
22
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
23
+ the project follows [semantic versioning](https://semver.org/).
24
+
25
+ ---
26
+
27
+ ## [Unreleased]
28
+
29
+ ---
30
+
31
+ ## [1.0.0] - 2026-10-31
32
+
33
+ ### Changed
34
+
35
+ - The TypeScript build is the release: install it with `npm install --global @sunilaleti/hx`,
36
+ and run the same command again to update.
37
+ - With nothing signed in, `hx` opens straight into the TUI, under the HX logo, and says to type
38
+ `/login`, instead of asking for an OpenRouter key in the shell before anything is on screen.
39
+ A model whose route is not signed in says to `/login` or pick another with `/model`, and the
40
+ first message after signing in goes through.
41
+
42
+ ### Removed
43
+
44
+ - `hx upgrade`. Update by running the install command again.
45
+ - The beta channel (`@sunilaleti/hx@next`). Every version is a stable release on `latest`.
46
+ - The Python build. `hx-cli` on PyPI stops at 0.2.20 and gets no more releases. To move over,
47
+ run `uv tool uninstall hx-cli` and install from npm: `~/.hx` carries over as it is.
48
+ `install.sh` now checks for Node.js 22.15 or newer and installs from npm.
49
+ - The `live` tests, which drove the Python build's internals against real APIs.
50
+
51
+ ### Security
52
+
53
+ - A server in a project's `.hx/mcp.json` no longer starts by itself: a cloned repository could
54
+ run any command the moment `hx` opened in it. HX names each waiting server and what it runs;
55
+ `hx mcp approve <server>` starts it, and `hx mcp add` approves what it adds. An approval is for
56
+ that command, so a server whose command changes waits again. Existing project servers need
57
+ approving once.
58
+ - Read, Grep, Glob, Symbols, Write and Edit ask before touching `~/.ssh`, `~/.aws`, `~/.gnupg`,
59
+ `~/.kube`, `~/.config/gh`, `~/.docker/config.json`, `~/.netrc`, `~/.npmrc`, `~/.pypirc` and
60
+ HX's `auth.json`, in every mode and through symlinks. The sandbox kept the shell out of them,
61
+ but these tools run outside it. An allow rule naming the file opens it.
62
+ - A command hidden inside another no longer passes as read-only: `cat <(rm -rf src)`, `>(...)`,
63
+ zsh's `=(...)` and glob qualifiers, an `awk` program that pipes to or reads from a command,
64
+ `@include`, and `find -fprint0` all ask now.
65
+ - A deny rule holds however the command is spelled: behind `NAME=value` assignments, by full
66
+ path, or behind a wrapper such as `nohup`, `timeout`, `sudo` or `xargs`. An allow rule no
67
+ longer covers a command with assignments it does not name.
68
+ - `acceptEdits` covers only the project: a write outside it, through a symlink that leads out
69
+ of it, or into its `.git` or `.hx` folder asks.
70
+ - `$HX_HOME` (`~/.hx`), which holds every transcript, is made private to its owner (`0700`).
71
+
72
+ ### Fixed
73
+
74
+ - Reads that come before a write in one turn run concurrently, as reads elsewhere do.
75
+ - A login shell that does not speak POSIX sh - csh, tcsh, fish - no longer makes every command
76
+ wait out its timeout: commands run in bash.
77
+
78
+ ---
79
+
80
+ ## [0.3.0-beta.1] - 2026-10-31
81
+
82
+ ### Changed
83
+
84
+ - HX is rewritten in TypeScript and runs on Node.js 22.15 or newer. Install it with
85
+ `npm install --global @sunilaleti/hx@next`, and `hx upgrade` updates it through npm. The
86
+ commands, keys, screens, settings and `~/.hx` are the same, so a session started on the Python
87
+ build resumes here and the other way round.
88
+ - The Symbols tool takes its grammars from `npm install --global @vscode/tree-sitter-wasm`.
89
+
90
+ ### Fixed
91
+
92
+ - An OpenRouter reply that broke off after its first words is no longer retried from the start,
93
+ which showed those words twice.
94
+
95
+ ---
96
+
97
+ ## [0.2.20] - 2026-10-26
98
+
99
+ ### Changed
100
+
101
+ - A tool call is one row with no background: a coloured marker shows how it ended, and what came
102
+ back hangs under it from a `⎿`, for example `● grep TODO` over `⎿ 3 results`. A call that
103
+ gave nothing back takes half the rows it did, one with a summary a row less, and a failed call
104
+ is still the red `✗`. Output too long for the row wraps on that edge rather than being cut at
105
+ the side of the screen.
106
+ - Tool calls are a step quieter than the answer, and what they gave back a step quieter again,
107
+ so the answer is what you read first. A shell command is no longer bold. An approval still
108
+ shows the command at full strength. A theme sets the new levels with `toolTitle`,
109
+ `toolArgs` and `toolOutput`; a theme without `toolArgs` keeps its `accent` for them. The
110
+ `ansi` theme has only the terminal's own two greys, so there a tool header stays at the
111
+ answer's strength and only its arguments and output step down.
112
+ - `toolPendingBg` no longer colours anything, and a theme no longer needs it. A pi theme that
113
+ sets it still loads.
114
+
115
+ ### Fixed
116
+
117
+ - `"theme"` in settings and `HX_THEME` choose the palette at startup again. Every session
118
+ opened in `dark` until `/theme` was run.
119
+
120
+ ---
121
+
122
+ ## [0.2.19] - 2026-10-22
123
+
124
+ ### Added
125
+
126
+ - On a ChatGPT or Claude subscription, the status bar has a usage line: how much of each usage
127
+ window is spent and when it renews, for example
128
+ `5h 16% (renews 06:30 am) | 7d 14% (renews Mon Oct 26 04:00 am)`. A window turns yellow at 75%
129
+ and red at 90%, and drops to 0% by itself when it renews. `/cost` lists the same windows.
130
+ - With an older Claude Code, which gives no percentage below its warning level, a window shows
131
+ only when it renews, for example `5h (renews 06:30 am)`.
132
+
133
+ ### Fixed
134
+
135
+ - A ChatGPT plan that is out of usage is no longer retried three times. HX says which window
136
+ is spent and when it resets.
137
+ - On a narrow terminal, the status bar drops the token counts and the cache field before the
138
+ context gauge. It no longer cuts a field in the middle of a number.
139
+ - A Claude usage limit message gives its reset time in the same form as the usage line.
140
+ - A resumed session can edit the files it read before. `hx resume` and `/resume` used to
141
+ forget every read, so the model saw a file in its history and still had its edit refused
142
+ with "has not been read in this session". A file changed since that read is still refused
143
+ until the model reads it again.
144
+ - After `/clear`, the new session can no longer edit a file that only the previous session read.
145
+ - `/rewind` keeps the reads from before the prompt it goes back to. An edit to a file that was
146
+ read earlier and then restored is no longer refused.
147
+ - A subagent's reads count for the subagent only. The main session must read a file itself
148
+ before it edits it.
149
+ - The refusal of an edit to an unread file tells the model to use the Read tool, because a
150
+ file shown by a shell command does not count.
151
+
152
+ ---
153
+
154
+ ## [0.2.18] - 2026-10-18
155
+
156
+ ### Security
157
+
158
+ - Edit and Write no longer leave a private file's new content readable by other users while
159
+ they write it. The temporary file now gets the file's own mode before any content goes in.
160
+
161
+ ### Fixed
162
+
163
+ - An edit through a symlink says which file it changes. The approval reads "config.yml is a
164
+ link to ~/shared/config.yml", and so does the answered approval and the result the model
165
+ gets. A link into a missing directory is refused in words, not with a raw error.
166
+ - The transcript names a symlink by its own name, not by the name of its target.
167
+ - A failed tool's error wraps in the transcript instead of being cut at the window's edge, so
168
+ a reason that comes after a long path stays readable.
169
+ - An edit to a CRLF file that differs only in line endings is refused as identical. It used to
170
+ be reported as applied, with nothing changed.
171
+ - Counts of one read in the singular when a limit leaves something out: "1 more line",
172
+ "1 more match", "1 more result". So do the other counts that still had "1 lines" forms.
173
+
174
+ ---
175
+
176
+ ## [0.2.17] - 2026-10-18
177
+
178
+ ### Security
179
+
180
+ - The API keys HX itself signs in with (`HX_OPENROUTER_API_KEY`, `OPENROUTER_API_KEY`, and the
181
+ Tavily and Devin keys) are no longer passed to the commands it runs. An auto-approved `env`
182
+ used to put them straight into the model's context.
183
+ - `xxd -r`, `xxd in out`, `uniq in out` and `sort --compress-program` now ask before they run.
184
+ They write files or run a program, but they were auto-approved as read-only commands.
185
+ - A long option cut short now counts as the full option, as getopt_long and git read it.
186
+ `sort --compress=sh`, `sort --out=f`, `git grep --open-files-in=sh` and `git branch --del`
187
+ were auto-approved as read-only. So were `xxd in -p` and macOS `uniq in -c`, which write a
188
+ file named after the flag.
189
+
190
+ ### Fixed
191
+
192
+ - A command that reads stdin (`cat`, `read`, `ssh host ls`) no longer breaks the shell. It used
193
+ to swallow every later command, which then reported success without running. Commands now
194
+ get end-of-file on stdin, and a command with an unclosed quote fails on its own.
195
+ - Background jobs no longer read from the terminal, so a dev server cannot take keystrokes
196
+ from the prompt.
197
+ - Background jobs get `HX=1` and `TERM=dumb`, the same as other commands. A tool that colours
198
+ its output for a real terminal used to write escape codes into the job's log.
199
+ - Non-ASCII command output no longer shows `�` where a read split a character.
200
+ - An edit to a CRLF file keeps CRLF on every line. Each edit used to rewrite the whole file
201
+ with LF, and the diff did not show it. `old_string` with plain newlines now matches there.
202
+ - A hashline replacement that ends with a newline no longer adds a blank line after it.
203
+ - Edit and Write keep a file's mode, so a script stays executable, and write through a
204
+ symlink instead of replacing it with a regular file.
205
+ - With `--cwd`, or `hx resume` from another directory, the approval shows the diff of the
206
+ file the edit will change. It used to read a relative path from the shell's directory.
207
+ - An edit's `+N -M` and its diff now include changed lines that start with `--` or `++`,
208
+ such as SQL comments.
209
+ - "Always allow" on a command HX cannot split into parts, such as a heredoc with an
210
+ apostrophe, is now honoured in later sessions instead of asking again.
211
+ - Counts of one read in the singular: "read 1 line", "1 result", "1 match", "1 tool".
212
+ - Write reports the number of lines the way Read numbers them. A file ending in a newline
213
+ was reported one line longer.
214
+ - The status bar shortens a long project path from its start, so the project's name and
215
+ branch stay visible. Before the first request, the cache field reads "no cache".
216
+
217
+ ---
218
+
219
+ ## [0.2.16] - 2026-10-13
220
+
221
+ ### Fixed
222
+
223
+ - The "Working…" status and the tool spinners now keep moving while a tool runs. They
224
+ used to vanish or freeze from the moment the model asked for a tool until its next reply,
225
+ so a long command looked like an idle session. The clock now counts the whole turn, not
226
+ only the time since the last model reply.
227
+ - A tool call waiting on an approval shows `?` and does not spin, and its "Took" counts
228
+ only the run, not the time you spent deciding. A denied call never ran, so it shows no
229
+ "Took" at all.
230
+
231
+ ---
232
+
233
+ ## [0.2.15] - 2026-10-04
234
+
235
+ ### Changed
236
+
237
+ - `/login` and `hx auth login` list the accounts as OpenAI, Claude, OpenRouter, Devin. At the
238
+ `hx auth login` menu, Enter still picks OpenRouter.
239
+
240
+ ### Fixed
241
+
242
+ - `/login claude-code` behind a network whose TLS inspection signs with a certificate not
243
+ valid for servers ("unsupported certificate purpose") now gives Claude Code's reason and
244
+ the fix: have the network fixed or exempted, or set `HX_SSL_NO_VERIFY=1` meanwhile. It
245
+ used to give the whole sign-in URL and no fix. Turns and HX's own requests that fail the
246
+ same way give the same advice, not "add the CA", which is trusted already.
247
+ - A `HX_CA_BUNDLE`, `SSL_CERT_FILE` or `REQUESTS_CA_BUNDLE` that names a file holding no
248
+ certificates no longer crashes HX at startup. HX says so and verifies against the system
249
+ trust store, and does not hand the file to Claude Code.
250
+ - A wrapped error notice no longer shows a bright green background under every line after
251
+ the first.
252
+
253
+ ---
254
+
255
+ ## [0.2.14] - 2026-10-04
256
+
257
+ ### Added
258
+
259
+ - When the installed Claude Code is behind its current release - and so lists older Claude
260
+ models than your subscription can run - `/model` says so under the list, with the command
261
+ that updates it (`brew upgrade --cask claude-code` for a Homebrew install, otherwise
262
+ `claude update`). After updating, the next `/model` lists the new models; no
263
+ refresh needed.
264
+
265
+ ### Changed
266
+
267
+ - The model catalogue is refreshed from inside the `/model` picker: press `ctrl+r`. The
268
+ list updates in place and keeps your filter and selection; the outcome, or why the refresh
269
+ failed, shows under the list. With no models at all, `/model` now opens anyway and says
270
+ so, so `ctrl+r` is there to fill it.
271
+
272
+ ### Removed
273
+
274
+ - `/models refresh`. It sat one letter away from `/model` and did nothing without its one
275
+ argument; use `ctrl+r` in the `/model` picker.
276
+
277
+ ### Fixed
278
+
279
+ - On the `claude-code` route, the context window is the one Claude Code reports, not a
280
+ 200k guess: Opus 5.5 runs with 1M. Claude Code lists its models without their sizes, and
281
+ what it serves depends on the plan, so a model starts at the window it was measured
282
+ serving on every plan - 1M for Opus 5.5, Sonnet 5.5, Sonnet 5 and Opus 4.7, 200k
283
+ otherwise - until its first turn reports the real window and maximum output for your
284
+ account. From then on the context gauge, the compaction threshold, the `/model` picker
285
+ and later sessions all use it, and a catalogue refresh keeps it.
286
+ - A model Claude Code lists at its long context - Fable 5.1 as `claude-fable-5-1[1m]` -
287
+ runs at it: `/model` offers `claude-code/claude-fable-5-1[1m]` at 1M, where HX used to
288
+ drop the `[1m]` and run the model at its standard context.
289
+ - A turn refused because the model, or its 1M context, needs usage credits your Claude plan
290
+ has off no longer reads "usage limit is reached. It resets at ...": HX says what needs the
291
+ credits, where to turn them on, and which model runs without them. Once they are on, the
292
+ next turn runs.
293
+ - After a catalogue refresh (`ctrl+r` in `/model`, or signing in), the session in progress
294
+ measures its context against the refreshed window, not the one it started with.
295
+ - `/model anthropic` (or typing `anthropic` in the picker) lists the Claude models your Claude
296
+ Code subscription runs, first, ahead of the per-token OpenRouter ones; `openai` does the same
297
+ for Codex. Several words match in any order: `/model anthropic sonnet`.
298
+
299
+ ---
300
+
301
+ ## [0.2.13] - 2026-10-04
302
+
303
+ ### Added
304
+
305
+ - Claude Pro/Max route: `hx auth login claude-code`, then
306
+ `hx --model claude-code/claude-sonnet-5`. Runs Claude models on your Claude
307
+ subscription through the official Claude Code CLI, with HX's own tools,
308
+ approvals and transcript. `/model` lists the account's models and `/effort`
309
+ their levels.
310
+ - Behind a TLS-inspecting proxy the `claude-code` route takes the same
311
+ settings as every other route: `HX_CA_BUNDLE` reaches Claude Code as
312
+ `NODE_EXTRA_CA_CERTS` and `HX_SSL_NO_VERIFY` as
313
+ `NODE_TLS_REJECT_UNAUTHORIZED=0`, Claude Code's own `CLAUDE_CODE_CERT_STORE`
314
+ and `CLAUDE_CODE_CLIENT_CERT` / `_KEY` / `_KEY_PASSPHRASE` (for a network
315
+ that requires a client certificate) pass through, and a certificate failure
316
+ it reports names the fix.
317
+ - On the `claude-code` route, a turn Anthropic bills as Extra Usage while
318
+ your plan still has room says so, and names HX's part of the system prompt,
319
+ instead of claiming the plan's usage limit is reached. A turn whose prompt
320
+ carries HX's own system prompt text (a copied `system-prompt.md`, an
321
+ AGENTS.md, an agent definition) is refused before it is sent, naming where
322
+ the text is; `hx prompt` shows the sources.
323
+
324
+ ### Fixed
325
+
326
+ - A subagent that fails - a provider error, a refused prompt, the turn limit -
327
+ reports why to the parent model, instead of "(the subagent returned no
328
+ text)".
329
+ - `Glob` with a pattern of `.` or `./` returns an error saying to use `*` or
330
+ `**/*`, and an absolute pattern one naming the `path` and relative pattern
331
+ to send instead, on every Python version, instead of a Python exception.
332
+ - With `tui.enterWhileBusy` set to `"steer"`, `alt+enter` queues a message
333
+ behind the running turn, as the prompt says. It used to steer like `enter`,
334
+ leaving no way to queue in that mode.
335
+ - The status bar's `⧗N queued` count stays visible in a project with a long
336
+ path or branch name; the path is shortened instead of the count being cut off.
337
+
338
+ ---
339
+
340
+ ## [0.2.12] - 2026-10-01
341
+
342
+ ### Fixed
343
+
344
+ - `/exit` and `ctrl+d` return the shell at once. They used to wait up to 10
345
+ seconds on a model call renaming the session; sessions are no longer renamed
346
+ on the way out, in the TUI or with `hx -p`.
347
+ - A name set with `/title` is no longer replaced by a generated one, including
348
+ one that was still being generated when you set it.
349
+ - The session name is generated from the opening and the latest stretch of the
350
+ conversation, not only its first 2000 characters.
351
+ - A session left before its first name came back is named from your first
352
+ message, instead of being listed by `/resume` with no name.
353
+ - A session left with `/resume` into another directory is renamed for what it
354
+ became, by the HX that starts there, in the background.
355
+
356
+ ---
357
+
358
+ ## [0.2.11] - 2026-10-01
359
+
360
+ ### Fixed
361
+
362
+ - `esc` while a tool runs no longer breaks the session: the next question is
363
+ answered instead of every later turn failing. A session that was interrupted
364
+ or killed mid-tool before this release also carries on after `hx resume`.
365
+ - `esc` during a `Bash` command stops the command. The next `Bash` call gets its
366
+ own output and exit code, not what was left of the one stopped.
367
+ - An interrupted call keeps what it printed, on screen and for the model, and
368
+ reads "Interrupted after <time>" instead of looking like a failure.
369
+ - A tool that finished keeps its result when `esc` lands while its
370
+ `PostToolUse` hook runs.
371
+ - `Bash` shows a short line such as "Starting..." as soon as it is printed,
372
+ rather than only when more output follows.
373
+
374
+ ---
375
+
376
+ ## [0.2.10] - 2026-09-29
377
+
378
+ ### Fixed
379
+
380
+ - `AGENTS.md` at the repository root now applies when HX is started in a
381
+ subdirectory, followed by each `AGENTS.md` down to that directory.
382
+ - Subagents started with `Task` now follow `~/.hx/AGENTS.md` and the project's
383
+ `AGENTS.md`, as the main session does.
384
+ - A saved `/effort` level shows on the status bar from the first screen of every
385
+ new session, not only after `/effort` is run again.
386
+ - `ctrl+l` switches the model and saves it for later sessions, as `/model` does;
387
+ it used to relabel the status bar only.
388
+ - `/model`, `ctrl+l` and `/effort` open with the cursor on the choice in force.
389
+
390
+ ---
391
+
392
+ ## [0.2.9] - 2026-09-27
393
+
394
+ ### Added
395
+
396
+ - `~/.hx/AGENTS.md` holds your own instructions and is loaded into every
397
+ session in every project, ahead of the project's `AGENTS.md`. The first
398
+ session creates it empty, ready to fill in. `hx prompt` and `/prompt` now
399
+ name each `AGENTS.md` in force, and `/prompt` says when one has changed on
400
+ disk since the session started.
401
+
402
+ ### Fixed
403
+
404
+ - An `AGENTS.md` or system-prompt override that is not valid UTF-8 is skipped
405
+ like an unreadable one, instead of stopping HX from starting.
406
+
407
+ ---
408
+
409
+ ## [0.2.8] - 2026-09-27
410
+
411
+ ### Changed
412
+
413
+ - `ctrl+z` undoes in the prompt and `ctrl+shift+z` redoes; `cmd+z` and
414
+ `cmd+shift+z` do the same in terminals that pass cmd through (kitty,
415
+ WezTerm). Apple's Terminal sends `ctrl+shift+z` as `ctrl+z`, so there it
416
+ undoes. Suspend no longer has a default key: bind `app.suspend` in
417
+ `keybindings.json`, which now also accepts `cmd+` and `option+`.
418
+
419
+ ### Fixed
420
+
421
+ - A markdown table wider than the terminal - pasted into the prompt or in a
422
+ reply - wraps its cells inside their columns instead of cutting them off,
423
+ so every word shows; in a terminal too narrow for its columns it is drawn as
424
+ `header: value` records. A bold phrase or link that wraps keeps its style
425
+ on the next line.
426
+ - A long draft in the prompt wraps between words instead of splitting them
427
+ across rows; only a word longer than the row still breaks inside it. The
428
+ cursor after the last character of a full row no longer hides that
429
+ character.
430
+ - The welcome lines wrap on a narrow terminal instead of being cut off
431
+ mid-word.
432
+
433
+ ---
434
+
435
+ ## [0.2.7] - 2026-09-27
436
+
437
+ ### Added
438
+
439
+ - `HX_OPENROUTER_BASE_URL` points the OpenRouter route at any OpenAI-compatible
440
+ endpoint - a gateway, a recording proxy.
441
+ - Pressing `esc` on a running turn leaves an "Interrupted" line in the transcript,
442
+ so a reply cut off mid-sentence no longer reads as a finished one.
443
+ - A paste over 10 lines or 1000 characters shows in the prompt as
444
+ `[Pasted text #1 +40 lines]` and is sent in full; the token moves and deletes
445
+ as one character, and survives history recall and `ctrl+k`/`ctrl+y`.
446
+ - `\` then `enter` inserts a newline in any terminal, including Apple's Terminal
447
+ and tmux without extended keys, where `shift+enter` cannot be told from `enter`. `\\` then
448
+ `enter` sends a message that ends in a backslash.
449
+
450
+ ### Fixed
451
+
452
+ - `shift+enter` inserts a newline instead of sending the message in kitty, Ghostty,
453
+ WezTerm, iTerm2 and tmux (with `extended-keys on`): HX now asks the terminal for
454
+ the kitty keyboard protocol, falling back to xterm's modifyOtherKeys, and hands
455
+ it back on exit, `ctrl+z` and `/fullscreen`. `ctrl+shift+z` (redo) works there too.
456
+ - `shift+enter` also inserts a newline on macOS in terminals that send it as a
457
+ plain `enter` (VS Code's, Apple's Terminal) or as `option+enter` (the Claude
458
+ desktop app's terminal panel): HX asks macOS which modifiers are held when the
459
+ key arrives. A real `option+enter` still steers, an `enter` hard on the heels of
460
+ a shifted character (`?` then `enter`) still sends, and `HX_MODIFIER_PROBE=0`
461
+ turns the check off.
462
+ - A multi-line paste keeps its line breaks. Terminals send them as carriage
463
+ returns, which HX was deleting, so a pasted table arrived as one long line.
464
+ - Lines typed with `shift+enter` stay separate lines in the transcript instead of
465
+ being joined into one paragraph.
466
+ - On macOS the sandbox now refuses *writes* to credential paths (`~/.ssh`,
467
+ `~/.aws`, `~/.hx/auth.json`, ...) as well as reads, so a session opened in your
468
+ home directory cannot plant an `authorized_keys` entry.
469
+ - On Linux the same holds for a credential path that does not exist yet: a
470
+ session opened in your home directory with no `~/.ssh` could create one and
471
+ plant an `authorized_keys` on the host. HX now creates such a path, empty and
472
+ private (`0700` for a directory, `0600` for a file), and mounts a read-only
473
+ shadow over it. Existing credential directories are shadowed read-only too, so
474
+ a write fails with "Read-only file system" instead of vanishing.
475
+ - Shell commands the model runs show their output in the transcript again - the
476
+ last lines, with `ctrl+o` for the rest - instead of only "Took 12ms".
477
+ - `/context` no longer fails with "'UsageLedger' object has no attribute
478
+ 'total_tokens'"; it lists what fills the window.
479
+ - `/copy` copies the last reply and says how it was copied, instead of failing.
480
+ - `/theme light` reports "Theme: light" rather than "Theme: None".
481
+ - `/compact` on a short session says only that there is nothing to compact, no
482
+ longer followed by a "Compacting…" that never finishes; a compaction that
483
+ saves nothing still reports that it finished, and the context gauge keeps
484
+ counting the system prompt afterwards.
485
+ - `/help` lines its key descriptions up however long a binding is, and a long
486
+ row in `/agents`, `/cost` or any other command output wraps under its own
487
+ column instead of at the left edge.
488
+ - The "always allow" option on an approval names where the rule is really
489
+ saved (`~/.hx`), and the answered approval shows a file relative to the
490
+ project rather than as an absolute path.
491
+ - `/permissions` no longer says the same thing twice.
492
+
493
+ ---
494
+
495
+ ## [0.2.6] - 2026-09-23
496
+
497
+ ### Added
498
+
499
+ - Sign in to remote MCP servers over OAuth. A server that asks for it -
500
+ Atlassian's `https://mcp.atlassian.com/v2/mcp`, for one - shows as needing
501
+ sign-in; `/mcp login <server>` or `hx mcp login <server>` opens the browser
502
+ and the tools arrive without a restart. Tokens refresh on their own;
503
+ `/mcp logout <server>` forgets them. `"oauth": {"clientId": ...}` in
504
+ `mcp.json` covers servers without client registration.
505
+ - MCP servers that change their tool list mid-session have the new list
506
+ swapped in, whether they announce it in a reply or on their own stream.
507
+ - `hx mcp add NAME --url URL --header "Name: value"` saves headers, such as an
508
+ API token, with the server.
509
+ - `/mcp reconnect <server>` restarts one server without restarting HX.
510
+ - `/resume` always offers a list to switch to: this directory's sessions
511
+ first, then every other directory's, each row naming where it lives.
512
+ Picking one from elsewhere restarts HX in that directory on it, with the
513
+ same `--mode` and `--model`. The session already open is no longer listed,
514
+ and the list is no longer cut at 20.
515
+
516
+ ### Fixed
517
+
518
+ - `/resume` said "No previous sessions in this directory" in a fresh
519
+ directory, or inside a session resumed from another one, instead of
520
+ listing anything.
521
+ - `hx resume <id>` ran the session in whatever directory you typed it in,
522
+ not the one it was recorded in, so its paths and tool calls pointed at the
523
+ wrong project.
524
+ - `hx mcp add NAME --url URL`, `--user`, and server arguments like `npx -y`
525
+ were refused as unknown options, so none of them could be run.
526
+ - `hx mcp add` dropped headers from the entry it wrote.
527
+ - A remote MCP server that expires its session is initialized again instead
528
+ of failing every call until HX restarts.
529
+ - A multi-line shell command no longer breaks the screen. Its approval shows
530
+ one row per line, the tool call shows the first line and `+N lines`
531
+ (`ctrl+o` shows the rest), and the approval options and prompt are no longer
532
+ drawn several times over.
533
+ - `/resume` from inside a resumed session showed the resumed conversation
534
+ pushed off the top of the screen, leaving only the "Resumed" notice.
535
+ - A session title with a line break in it no longer splits its row in the
536
+ `/resume` picker.
537
+ - Started in your home directory, the status bar named it `~/.`; it now reads
538
+ `~`.
539
+
540
+ ---
541
+
542
+ ## [0.2.5] - 2026-09-23
543
+
544
+ ### Added
545
+
546
+ - Images as input. `ctrl+v` attaches the image on the clipboard, dragging an
547
+ image file into the terminal attaches it, and `hx -p "..." --image PATH`
548
+ attaches one to a headless prompt. Each shows in the prompt as
549
+ `[Image #N]`.
550
+ - `Read` returns image files as images, so the model can look at a screenshot
551
+ or mockup it is pointed at with `@path`.
552
+ - Images returned by MCP tools reach the model instead of being dropped.
553
+ - Every route carries images: OpenRouter, the Codex subscription and the Devin
554
+ subscription. Large images are scaled and re-encoded to fit each route's
555
+ limits.
556
+ - A model that does not accept images is warned about when one is attached,
557
+ and is told the image was left out rather than sent a request that fails.
558
+ The `/model` picker marks the models that can see images.
559
+
560
+ ### Fixed
561
+
562
+ - Background jobs are waited for, not only signalled, when a session closes.
563
+ An unwaited subprocess transport was collected after the event loop had
564
+ gone, which raised `Event loop is closed` as an unraisable warning on CI
565
+ runs that otherwise passed.
566
+
567
+ ---
568
+
569
+ ## [0.2.4] - 2026-09-19
570
+
571
+ ### Added
572
+
573
+ - `/trace [path]`: write the whole session to one self-contained HTML page and
574
+ open it. The system prompt, the project context, the skills index, every tool
575
+ schema, every prompt and reply, every thinking block, every tool call beside
576
+ the result it produced, the per-call token ledger and the files HX changed.
577
+ Late-injected reminders and compacted turns are included behind a toggle.
578
+ A session still running traces as it stands, including the turn that just
579
+ landed. The page follows your system's dark or light setting and a button
580
+ overrides it; nothing is fetched and nothing is uploaded.
581
+ - `hx trace [SESSION_ID] [PATH]`: the same page for any transcript on the
582
+ machine, without starting a session. Defaults to the last session in this
583
+ directory and prints only the path, so `open "$(hx trace)"` works. Either
584
+ argument may be given alone - a lone one that looks like a path is one.
585
+ - The system prompt and the tool schemas are now written to the transcript on a
586
+ session's first provider call, so a trace of a finished session can say what
587
+ the model was told and not only what it said.
588
+
589
+ ### Security
590
+
591
+ - Permission rules are matched against the path a specifier resolves to, not
592
+ the text it was written as. `deny: Read(~/.ssh/**)` was got past by asking
593
+ for `../../.ssh/id_rsa`, or through a symlink planted in the project. The
594
+ same resolution stops an allow rule leaking: `allow: Edit(src/**)` used to
595
+ cover `src/../../etc/passwd`.
596
+ - `WebFetch` is matched against every URL it was given rather than the first,
597
+ so `deny: WebFetch(https://host/**)` is no longer a question of list order.
598
+ The approval prompt spells the URLs out too; it said `urls=[2 items]`, which
599
+ answered the only question that mattered with a number.
600
+ - The generated Seatbelt profile no longer grants write access to all of
601
+ `/private/var/folders` - every user's and every process's temp and cache.
602
+ `$TMPDIR` was resolved precisely to avoid that, and the profile handed it
603
+ back on the next line. This user's own container stays writable.
604
+ - `~/.hx/auth.json` is created with mode 0600 rather than narrowed to it after
605
+ the tokens are written, closing a window at a predictable path where the
606
+ credential was readable by any local account.
607
+ - The OAuth callback page escapes what the provider sent. An `error` parameter
608
+ carrying markup ran as HTML on a loopback origin that is otherwise the
609
+ user's own.
610
+
611
+ ### Fixed
612
+
613
+ - Slash commands read the session that is in force rather than the one HX
614
+ opened with. After `/resume`, `/trace` wrote a page of zeros into the empty
615
+ startup session's directory and `/cost` reported nobody's usage: the command
616
+ context had captured the session object at startup, and `/resume` rebinds it.
617
+ - A session whose title was the trace template's own data placeholder had the
618
+ whole payload spliced into the page's `<title>`.
619
+ - A trace labelled an encrypted reasoning block "thinking (signed)", which read
620
+ as though the page had dropped the rest of it. It now says the text is the
621
+ provider's summary and the reasoning came back encrypted, and a block that
622
+ arrived with no summary at all says that rather than rendering an empty box.
623
+ - MCP servers survive a large response. One JSON-RPC message is one line and
624
+ asyncio's stream limit is 64 KiB, so a tool returning a file did not truncate
625
+ - it raised out of the read loop and killed the connection for the rest of
626
+ the session. The limit is now 16 MiB, and overshooting it says so.
627
+ - A tool call is no longer dropped when a stream ends without a `finish_reason`.
628
+ The arguments were buffered until that field arrived, so a complete call was
629
+ discarded and the turn reported `end_turn`: the model appeared to stop for no
630
+ reason, and the tokens were already paid for.
631
+ - The Linux sandbox starts again for anyone with credential *files*. Denied
632
+ paths were all shadowed with `--tmpfs`, which mounts a directory and aborts
633
+ bwrap when aimed at a file - and six of them are files, `~/.hx/auth.json`
634
+ among them. Files are shadowed with `/dev/null` instead. bwrap also starts in
635
+ the policy's directory rather than whatever `$PWD` happened to hold.
636
+ - `PersistentShell.cwd` follows the shell. It claimed to track it and was in
637
+ fact the launch directory forever, which meant a background job always
638
+ started where the session opened rather than where `cd` had left it.
639
+ - A background job no longer leaks its log file descriptor for the life of the
640
+ session.
641
+ - A record appended while the transcript was being written is no longer
642
+ dropped. The queue is taken in one step, and a failed write puts its records
643
+ back rather than losing them.
644
+ - An automatic compaction with nothing to summarise is no longer announced and
645
+ rerun every turn for the rest of the session. `/compact` still answers either
646
+ way.
647
+ - `/trace` takes `--no-open`, and never launches a *terminal* browser: with no
648
+ display, `webbrowser` falls through to lynx or w3m, which opens in the
649
+ terminal HX is drawing on and takes the session display with it.
650
+
651
+ ---
652
+
653
+ ## [0.2.3] - 2026-09-18
654
+
655
+ ### Added
656
+
657
+ - Hooks: shell commands HX runs at `PreToolUse`, `PostToolUse`,
658
+ `UserPromptSubmit` and `Stop`. A hook can refuse an action (exit 2, or
659
+ `{"decision": "block"}`), rewrite a tool's arguments before the permission
660
+ engine sees them (`updatedInput`), or add a note for the model
661
+ (`additionalContext`). Configure them under `"hooks"` in
662
+ `~/.hx/settings.json`; `/hooks` lists what is loaded. Hooks declared in a
663
+ project's checked-in `.hx/settings.json` are ignored and reported, because
664
+ cloning a repository must not be enough to run commands on your machine.
665
+ - `Symbols`: outline a file, find where a name is defined, or find where it is
666
+ used, over a tree-sitter parse tree rather than a regex - so a match inside a
667
+ comment or a string never counts. Python, TypeScript, JavaScript, Go and
668
+ Rust. Needs the grammars: `uv tool install "hx-cli[symbols]"`. Without them
669
+ the tool is not registered at all.
670
+ - An example hook, `examples/hooks/check.sh`: after every `Edit`/`Write` it
671
+ runs the project's own checker (`ruff`, `tsc`, `go vet`, `cargo check`)
672
+ against just that file and hands the output back to the model, so a typo is
673
+ caught on the next turn instead of at test time.
674
+ - Line anchors. `Read` now labels each line with a short content hash, and
675
+ `Edit` accepts those anchors in a `hashline` argument to replace a span
676
+ without retyping it. An anchor that no longer resolves means the file moved,
677
+ so the patch is rejected instead of landing at the wrong lines. Set
678
+ `"tools": {"hashline": false}` to go back to plain `cat -n` reads.
679
+
680
+ ### Removed
681
+
682
+ - The `telemetry` setting, which was parsed and never read by anything.
683
+
684
+ ### Fixed
685
+
686
+ - The README's Status section said there were two model routes; Devin made it
687
+ three in 0.2.2.
688
+ - `MultiEdit` and `NotebookEdit` were listed as mutating tools and as edit
689
+ tools in the permission engine, but no such tools exist. A rule written
690
+ against either name silently matched nothing.
691
+
692
+ ---
693
+
694
+ ## [0.2.2] - 2026-09-17
695
+
696
+ ### Added
697
+
698
+ - Devin subscription as a third route: `hx auth login devin` (or `/login`)
699
+ signs in through the browser, and `devin/<model>` ids run against the plan -
700
+ every model the account includes, listed by `/model` and refreshed with
701
+ `/models refresh`. Reasoning depths Devin ships as separate models are folded
702
+ into one, so `/effort` picks the depth, and router models such as
703
+ `devin/adaptive` are assigned per turn. A session token in `DEVIN_API_KEY`
704
+ works where there is no browser. Devin Enterprise accounts sign in through
705
+ the same command by choosing "Log in with Devin for Enterprise".
706
+
707
+ ### Fixed
708
+
709
+ - The `/login` dialog no longer sits on "Starting…" for the whole sign-in: it
710
+ now updates as the flow moves on, and keeps the instructions on screen
711
+ alongside the link, progress and paste field instead of overwriting them.
712
+
713
+ - Resizing the terminal no longer crashes the session. A window manager sends
714
+ a burst of SIGWINCH for one drag of a corner, and a signal landing while the
715
+ previous frame was still writing re-entered stdout: `RuntimeError: reentrant
716
+ call inside <_io.BufferedWriter>`, and HX was gone. The redraw is handed to
717
+ the event loop now, and the burst collapses into one repaint.
718
+
719
+ - `/resume` and `hx resume` bring the whole conversation back, not only its
720
+ text: the reasoning, tool calls, their output and the plan are drawn again.
721
+ A resumed session used to show a blank tinted bar for each tool result and
722
+ nothing at all for the calls themselves.
723
+
724
+ ---
725
+
726
+ ## [0.2.1] - 2026-09-14
727
+
728
+ ### Changed
729
+
730
+ - Code blocks in a reply are drawn as a block - the code on a band of its own
731
+ colour, syntax highlighted, with the language named at the top - instead of
732
+ printing the ``` fences back out as text. Themes set the band with the new
733
+ `mdCodeBlockBg` role, and a theme that leaves colour to the terminal, as
734
+ `ansi` does, gets a rule above and below instead.
735
+
736
+ ### Fixed
737
+
738
+ - The model's reasoning is shown again. It was collapsed to a bare `Thinking…`
739
+ with no key wired to open it, so the text never reached the screen. `ctrl+o`
740
+ now folds and unfolds it, the same key that expands a tool call.
741
+
742
+ ---
743
+
744
+ ## [0.2.0] - 2026-09-14
745
+
746
+ ### Changed
747
+
748
+ - The interface is drawn into your terminal's own scrollback instead of taking
749
+ over the screen. Finished output belongs to the terminal: scroll it, select
750
+ it with the mouse, copy it, pipe it, and it is still there after HX exits.
751
+ `/fullscreen` takes the whole window back for anyone who preferred it that
752
+ way - the prompt pinned to the bottom row, the transcript scrolling above it
753
+ with `pgup`, `pgdn`, `ctrl+home` and `ctrl+end` - and writes the choice to
754
+ `tui.fullscreen` for the next session. On either screen the prompt and the
755
+ status bar sit on the bottom row from the first frame - a conversation that
756
+ does not fill the window is padded above the dock, not below it, and rows a
757
+ closing picker gives back open above it too. The slash-command and `@`-path
758
+ completion lists draw above the prompt for the same reason: under it, every
759
+ keystroke that changed the number of matches moved the prompt and the status
760
+ bar with it. Exiting erases the whole interface - banner, conversation and
761
+ dock - and puts the shell prompt back on the row HX started on, rather than
762
+ leaving a dead prompt box and a status bar on screen above it, from either
763
+ screen: quitting out of `/fullscreen` also takes away the frame that was
764
+ parked underneath it.
765
+ - Approving a tool call is now a framed prompt with one option per line, each
766
+ saying what it does - `always allow` states that it writes a rule to
767
+ `.hx/settings.local.json`. `y`/`s`/`a`/`n` still answer in one keystroke,
768
+ `esc` denies, and the arrow keys work for anyone who wants to read first.
769
+ The command shown is the one that will run, `$` and all. When two subagents
770
+ stop at an approval at once, the older one holds the keyboard and the other
771
+ says it is waiting rather than offering keys that would answer the first.
772
+ - Your theme reaches the whole interface. Links, list bullets, block quotes,
773
+ horizontal rules and inline code in the model's replies now follow the
774
+ palette instead of a hard-coded set of colours, and headings are visibly
775
+ different from each other.
776
+ - Multi-line output from `/help`, `/cost`, `/status` and the rest lines up
777
+ under its own first line instead of at two different indents.
778
+ - The todo list is a block in the conversation rather than a sidebar, and
779
+ `/todos` and `ctrl+t` show it. A scrollback-native interface has no second
780
+ column to put a sidebar in.
781
+ - Mouse selection is the terminal's, across the whole session including what
782
+ has scrolled off, rather than HX's own within the visible screen.
783
+ - Text HX did not write - a file a tool read, a command's output, the model's
784
+ reply, a paste into the prompt - is drawn as plain text. Escape sequences in
785
+ it are removed rather than sent on to your terminal, so a file cannot set
786
+ your clipboard, rename your window, hide a link behind different words, or
787
+ erase what is already on screen by being read.
788
+ - `/clear` now clears the screen and the scrollback above it, instead of
789
+ emptying the transcript and leaving the last session on screen. Terminals
790
+ that do not implement scrollback erasure - macOS Terminal.app - clear the
791
+ screen only.
792
+ - `/model`, the command palette and every other picker are sized to the
793
+ terminal rather than to a fixed eight rows, so a tall window shows a long
794
+ list instead of leaving two thirds of itself empty.
795
+ - `ctrl+z` suspends HX to the background and resuming redraws it; `ctrl+home`
796
+ and `ctrl+end` reach the ends of the conversation; `ctrl+_` undoes in the
797
+ prompt. All three were listed in `/help` and bound to nothing.
798
+ - `ctrl+p` and `ctrl+l` are named in the hints line under the prompt, which is
799
+ where the keys a session offers are supposed to be discoverable.
800
+ - A block drawn against the prompt no longer closes itself with a second rule,
801
+ which left a rule, a blank line and another rule under every picker and under
802
+ every approval. Whichever block ends up against the dock lets the dock's own
803
+ rule close it, and takes its own back as soon as something lands underneath.
804
+ - The installer now opens with a terminal boot animation that draws and lights
805
+ the HX mark, with a clean static version for CI and redirected output.
806
+
807
+ ---
808
+
809
+ ## [0.1.9] - 2026-09-11
810
+
811
+ ### Changed
812
+
813
+ - Project instructions live in `AGENTS.md`, the cross-agent standard, instead
814
+ of `HX.md`. A repo already carrying an `AGENTS.md` for another coding agent
815
+ is understood by HX with no extra file; `/init` writes one. `HX.md` and
816
+ `.hx/HX.md` are no longer read - rename yours.
817
+ - Python 3.13 and 3.14 are supported in fact, not just by the `>=3.11` floor.
818
+ CI runs the suite on every supported minor, so installing on a current
819
+ interpreter is covered by a test run rather than by hope.
820
+
821
+ ### Fixed
822
+
823
+ - `/model` lists the Codex models your ChatGPT account can actually run. HX
824
+ shipped a hard-coded pair — `gpt-5.3-codex` and its spark — that the backend
825
+ now refuses outright, so a paid subscription offered two models and both
826
+ failed. Signing in fetches the account's own list instead; `/models refresh`
827
+ asks again, and the last shipped list stands in when the fetch cannot happen.
828
+ - A Codex model that the account is refused now says so in terms you can act
829
+ on. The backend answers with a 400 naming the model, which reads as a bad
830
+ model id. The message now says entitlement is per model and per account, and
831
+ points at `/models refresh` to get the list that is true for your account.
832
+ - `hx auth` names the plan behind a ChatGPT login (`signed in (plus)`), and
833
+ `/login` says when it is a free one, since Codex is documented as needing a
834
+ paid plan. Neither blocks a model: entitlement is per model, and the fetched
835
+ catalogue is what decides it, so HX reports the plan and lets the backend
836
+ answer.
837
+ - `/model` says what pays for each model. Subscription models showed
838
+ `$0.00/$0.00`, indistinguishable from a free OpenRouter one; they now read
839
+ `subscription`. The status bar tags them too — `gpt-5.6-terra (sub)`.
840
+ - `/resume` counts prompts, not wire-format records. A session you sent one
841
+ prompt to listed as "31 msgs"; it now reads `1 prompt · 31 msgs`, and `/cost`
842
+ says "17 API requests across 1 prompt" so the two numbers reconcile.
843
+ - The status bar follows the branch. It read `git HEAD` once at startup, so
844
+ checking out in an IDE or another terminal left it showing a branch you had
845
+ left; it now re-reads on a timer, worktrees included.
846
+ - Selecting assistant prose or a tool block with the mouse copies it. Textual
847
+ can only extract text from widgets that render plain text, so dragging over
848
+ those blocks highlighted them and copied nothing.
849
+ - Copying goes through `pbcopy`/`wl-copy`/`xclip` before OSC 52. Textual's own
850
+ copy is OSC 52 only, which macOS Terminal ignores and iTerm2 ships disabled,
851
+ so `ctrl+c` on a selection silently did nothing there.
852
+ - The prompt names the steer key the way your keyboard does. It read
853
+ `alt+enter` everywhere, while `/help` two keystrokes away read `option+enter`
854
+ on macOS - the same key, spelled two ways in one window. It now comes from
855
+ the keybinding registry, so it follows the platform and follows a rebind.
856
+ - `ctrl+c` reaches the copy at all. The prompt holds focus for the whole session
857
+ and binds that key itself, so a transcript selection was never what it
858
+ copied. It now copies the selection when there is one, and clears the prompt
859
+ when there is not, exactly as before.
860
+
861
+ ### Added
862
+
863
+ - `models.codex_models` in settings adds Codex model ids to the `/model`
864
+ picker, on top of the ones fetched for your account — for an id the backend
865
+ serves but does not advertise, or a machine that cannot reach it.
866
+ `{"models": {"codex_models": ["gpt-5.6-terra"]}}` adds your own.
867
+ - `/effort` picks how hard the current model thinks, from the levels that model
868
+ actually offers, and the status bar shows the depth in force next to the
869
+ model — `gpt-5.6-terra (sub) · high`. It applies to the next turn and is
870
+ saved for later sessions. Switching models re-resolves it, so a choice the
871
+ new model cannot reach shows as the depth it will really run at.
872
+ - `models.reasoning_effort` sets how hard a reasoning model thinks —
873
+ `none` through `ultra`. Left unset, each Codex model runs at the default its
874
+ own catalogue entry names (`low` on GPT-6 Astra, `medium` on GPT-5.6 Terra)
875
+ instead of one fixed depth for every model. A value is clamped to what the
876
+ chosen model advertises, so `max` runs at `xhigh` on GPT-5.5 rather than
877
+ being refused.
878
+ - `/mouse [on|off]` hands drag-selection back to your terminal, for when you
879
+ want its own selection rather than HX's. Holding alt/option while dragging
880
+ does the same thing without the toggle.
881
+
882
+ ### Changed
883
+
884
+ - "Always allow" grants move out of your repository. They were written to
885
+ `./.hx/settings.local.json`, with a `./.hx/.gitignore` added to hide them —
886
+ two files of HX's in your checkout. They now live in
887
+ `~/.hx/projects/<project>/settings.local.json`, still scoped to the project,
888
+ and an existing local file is migrated on the next run.
889
+ - Grants that much older versions appended to `./.hx/settings.json` — the file
890
+ a project checks in — are lifted into that same local file on the next run,
891
+ and HX says which ones it moved. Only `allow` moves; a project's `deny` and
892
+ `ask` stay shared. It happens once, so a rule you later write there by hand
893
+ is left alone, and an emptied `./.hx` is removed.
894
+ - HX no longer rewrites `./.hx/settings.json` at all. Its one remaining writer
895
+ there, the startup pass that widens stale whole-command rules, now reports
896
+ them and leaves the file to its owner.
897
+
898
+ ---
899
+
900
+ ## [0.1.8] - 2026-09-10
901
+
902
+ ### Fixed
903
+
904
+ - Publishes everything listed under 0.1.7. That tag was cut but never shipped:
905
+ a type annotation on the transcript's pending-approval list failed the lint
906
+ job, so the release build stopped before it reached PyPI. `hx upgrade` moves
907
+ from 0.1.6 straight to here.
908
+
909
+ ---
910
+
911
+ ## [0.1.7] - 2026-09-10
912
+
913
+ ### Added
914
+
915
+ - **Steer a running turn.** `alt+enter` pushes a message into the turn already
916
+ in flight: the model call is cut off mid-stream and the next one starts from
917
+ what you just said. Tools already running are left to finish, so nothing is
918
+ half-written. With an empty prompt it steers the front of the queue, so a
919
+ correction you already queued need not be retyped. `/queue` lists what is
920
+ waiting, `/queue steer <n>` sends one now, `/queue clear` drops them, and the
921
+ status bar shows the depth. Set `tui.enterWhileBusy` to `"steer"` to swap the
922
+ two keys.
923
+ - **Sessions are renamed as they close.** The name written after the first
924
+ exchange described an opening question; `/resume` now lists what the session
925
+ turned into. Also on `/clear` and `/resume`, which leave a session behind.
926
+
927
+ ### Changed
928
+
929
+ - **Approvals happen in the transcript, not in a modal.** The permission prompt
930
+ is now a block at the end of the conversation, directly under the sentence
931
+ where the model said what it intended to do. It takes the keyboard while it is
932
+ unanswered (`y` / `s` / `a` / `n`, `v` for the rest of a clipped diff), and
933
+ answering collapses it into a one-line record of what was granted and how
934
+ widely, so a session shows every rule it accumulated.
935
+ - **More commands are recognised as read-only, and fewer are taken on trust.**
936
+ The allowlist that skips the prompt entirely grew from roughly thirty entries
937
+ to a hundred - digests, `ps`, `readlink`, most text filters, and the read-only
938
+ half of `git` (`rev-parse`, `ls-files`, `cat-file`, `stash list`, and the
939
+ rest). Arguments now decide alongside the name: `sed -i`, `find -exec`,
940
+ `sort -o`, `yq -i`, `git branch -D`, an `awk` program that calls `system()`,
941
+ and `git -c core.pager=…` all still ask. Review it if you run HX on anything
942
+ you would not hand a shell.
943
+ - **"Always allow" writes to `.hx/settings.local.json`.** Grants no longer land
944
+ in `.hx/settings.json`, the file a project checks in - one machine's absolute
945
+ paths were being committed to everyone who cloned the repo. The new layer sits
946
+ above project settings, and HX adds a `.hx/.gitignore` covering it. Existing
947
+ rules in either file keep working.
948
+
949
+ ### Fixed
950
+
951
+ - **`/login` no longer strands you in a dead modal.** The sign-in screen was
952
+ driven before it had finished mounting, so the flow died on its first call and
953
+ left a prompt that took keystrokes and ignored Enter. Opening the browser and
954
+ tearing down the loopback listener also ran on the event loop, freezing the
955
+ TUI - including the Escape that would have cancelled.
956
+ - Text alongside tool results reaches the model on OpenRouter-shaped routes
957
+ instead of being silently dropped.
958
+
959
+ ---
960
+
961
+ ## [0.1.6] - 2026-09-10
962
+
963
+ ### Added
964
+
965
+ - **Web search, over Tavily.** `WebSearch` returns ranked results with a
966
+ snippet each; `WebFetch` reads up to 5 URLs as markdown. Both are registered
967
+ only when a key resolves, so HX without one is unchanged and pays no
968
+ cached-prefix cost for a tool that would always fail. Set the key with
969
+ `hx auth set tavily`, or `TAVILY_API_KEY` / `HX_TAVILY_API_KEY` in the
970
+ environment. Credits spent are reported per call and per session.
971
+ - **Corporate TLS support.** Certificates verify against the operating
972
+ system's trust store, `HX_CA_BUNDLE` (also `SSL_CERT_FILE`,
973
+ `REQUESTS_CA_BUNDLE`) names a CA file, and `HX_SSL_NO_VERIFY=1` turns
974
+ verification off with a warning every session. A TLS failure is now named in
975
+ one line at startup and in `/model` instead of showing up as an empty model
976
+ catalogue with no cause.
977
+ - **Working-tree notices.** The git branch and files that changed on disk
978
+ outside the session are late-injected onto the newest user message, from one
979
+ `git status --porcelain=v2 --branch -z` per provider call, run in a worker
980
+ thread so it never blocks the UI. Off with
981
+ `"context": {"git_notices": false}` or `HX_GIT_NOTICES=0`.
982
+ - **File checkpoints.** Write and Edit park a file's previous bytes in a
983
+ content-addressed store under the session directory, keyed to the position in
984
+ the transcript, so a rewind can undo edits and not just words. A file whose
985
+ bytes moved since HX wrote them is reported and left alone rather than
986
+ overwritten.
987
+ - **`hx docs` and `hx changelog`.** The manual and this file, printed from the
988
+ installed package. `hx docs` with no argument lists the sections;
989
+ `hx docs credentials` prints one. `hx changelog 0.1.5` prints one release.
990
+ The system prompt points at both, so questions about HX itself are answered
991
+ from the shipped documentation.
992
+
993
+ ### Changed
994
+
995
+ - The model catalogue keeps the last good copy when a refresh fails, and
996
+ records why it failed so `/model` can say so. The static routes (the Codex
997
+ models) are registered even when there is no cache file and no network.
998
+
999
+ ---
1000
+
1001
+ ## [0.1.5] - 2026-09-09
1002
+
1003
+ ### Added
1004
+
1005
+ - **A ChatGPT Plus/Pro route.** Sign in with `hx auth login openai-codex` (or
1006
+ `/login` in the TUI) and run `openai-codex/gpt-5.3-codex` against your
1007
+ subscription instead of paying per token. OAuth with PKCE against
1008
+ `auth.openai.com`, a loopback listener on port 1455, a paste-the-URL fallback
1009
+ for SSH and `HX_LOGIN_DEVICE_CODE=1` for the device-code flow; access tokens
1010
+ refresh themselves.
1011
+ - Credentials became one store per route in `~/.hx/auth.json` (mode 0600).
1012
+ `hx auth` reports which routes have a credential and where it came from, and
1013
+ `hx auth logout <provider>` forgets one.
1014
+ - `/model` offers only routes you are actually signed in to, so the picker
1015
+ cannot select a model the session has no way to reach.
1016
+
1017
+ The route serving a turn is decided by the model id alone: `openai-codex/*`
1018
+ goes to the subscription, everything else to OpenRouter.
1019
+
1020
+ ---
1021
+
1022
+ ## [0.1.4] - 2026-09-09
1023
+
1024
+ ### Fixed
1025
+
1026
+ - TUI snapshot tests no longer embed the release version, so bumping the
1027
+ version stopped failing the suite.
1028
+
1029
+ ---
1030
+
1031
+ ## [0.1.3] - 2026-09-09
1032
+
1033
+ ### Fixed
1034
+
1035
+ - An exact slash command runs on the first `enter` instead of only selecting
1036
+ itself in the completion list.
1037
+
1038
+ ---
1039
+
1040
+ ## [0.1.2] - 2026-09-09
1041
+
1042
+ ### Added
1043
+
1044
+ - **Themes as data.** A theme is a JSON file - a `vars` block of colours and a
1045
+ `colors` block mapping semantic roles onto them. Drop one in
1046
+ `~/.hx/themes/mine.json` and `/theme mine` picks it up, no restart. Syntax
1047
+ highlighting is theme-driven, and pi's theme files load unchanged.
1048
+ - **One keybinding registry.** Every key is defined once in `src/hx/keys.py`;
1049
+ `/help`, the hints line and the README table all read from it, and any action
1050
+ id can be rebound in `~/.hx/keybindings.json`. Conflicts and unknown ids are
1051
+ reported at startup rather than silently resolved.
1052
+ - **A completion popup** above the prompt for `/` commands and `@` paths, with
1053
+ fuzzy matching; `tab` cycles, `enter` accepts, `esc` dismisses.
1054
+ - **A readline-style prompt** with a kill ring (`ctrl+w`, `ctrl+u`, `ctrl+k`,
1055
+ `ctrl+y`, `alt+y`), undo and redo.
1056
+ - **System clipboard support.** `ctrl+x` copies the selected message and
1057
+ `/copy` the last reply, through `pbcopy`/`wl-copy`/`xclip`/`xsel` with an
1058
+ OSC 52 fallback that also works over SSH.
1059
+ - The prompt is framed and the working status lives in its border.
1060
+ - `/exit` as an alias for `/quit`.
1061
+
1062
+ ### Fixed
1063
+
1064
+ - Permission grants stay inside what the user actually approved.
1065
+ - A `/model` choice persists to user settings.
1066
+ - Slash commands run in a worker, so a slow one no longer blocks the UI.
1067
+
1068
+ ---
1069
+
1070
+ ## [0.1.0] - 2026-09-08
1071
+
1072
+ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/).
1073
+
1074
+ ### Added
1075
+
1076
+ - **The agent loop**, streaming from OpenRouter with prefix caching, accurate
1077
+ per-turn cost accounting and a stable request prefix.
1078
+ - **The tool suite**: Bash (with a background job runner), Read, Write, Edit,
1079
+ Glob, Grep, TodoWrite and Task, with output capping that keeps the head and
1080
+ tail of a large result and spills the rest to the session directory.
1081
+ - **The permission engine and OS sandbox.** `Tool(specifier)` rules where deny
1082
+ beats ask beats allow, shell commands decomposed into their real segments
1083
+ first, and Seatbelt on macOS / bubblewrap on Linux confining writes and
1084
+ network. Modes cycle with `shift+tab`: `plan`, `default`, `acceptEdits`,
1085
+ `bypass`.
1086
+ - **Context engineering**: late injection for per-turn state, compaction at 80%
1087
+ of the window (or `/compact [focus]`), and cache breakpoints that only
1088
+ advance once enough tokens have accumulated.
1089
+ - **Sessions**: persisted transcripts, `hx resume` / `/resume`, and a model-named
1090
+ session title you can change with `/title`.
1091
+ - **Skills, subagents and MCP.** Skills are `SKILL.md` directories under
1092
+ `.hx/skills/` or `~/.hx/skills/`, loaded on demand so a hundred installed
1093
+ skills cost a hundred lines. Subagents (`explore`, `plan`, `general`, plus
1094
+ your own in `.hx/agents/`) run in their own context and return one report.
1095
+ MCP servers go in `.hx/mcp.json` or `hx mcp add`.
1096
+ - **The TUI**: transcript, status bar with token, cache and cost counters, todo
1097
+ sidebar, model picker, command palette and per-tool renderers.
1098
+ - **Print mode** (`hx -p`), where stdout carries only the assistant's text, so
1099
+ it pipes.
1100
+ - **Configuration** merged from defaults, `~/.hx/settings.json`,
1101
+ `./.hx/settings.json`, `HX_*` environment variables and CLI flags, with
1102
+ permission lists and prompt appends unioned rather than replaced.
1103
+ - `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
1104
+ self-update, and `install.sh` bootstrapping uv with a pinned Python.
1105
+
1106
+ [Unreleased]: https://github.com/aletisunil/hx/compare/v1.0.0...HEAD
1107
+ [1.0.0]: https://github.com/aletisunil/hx/compare/v0.3.0-beta.1...v1.0.0
1108
+ [0.3.0-beta.1]: https://github.com/aletisunil/hx/compare/v0.2.20...v0.3.0-beta.1
1109
+ [0.2.20]: https://github.com/aletisunil/hx/compare/v0.2.19...v0.2.20
1110
+ [0.2.19]: https://github.com/aletisunil/hx/compare/v0.2.18...v0.2.19
1111
+ [0.2.18]: https://github.com/aletisunil/hx/compare/v0.2.17...v0.2.18
1112
+ [0.2.17]: https://github.com/aletisunil/hx/compare/v0.2.16...v0.2.17
1113
+ [0.2.16]: https://github.com/aletisunil/hx/compare/v0.2.15...v0.2.16
1114
+ [0.2.15]: https://github.com/aletisunil/hx/compare/v0.2.14...v0.2.15
1115
+ [0.2.14]: https://github.com/aletisunil/hx/compare/v0.2.13...v0.2.14
1116
+ [0.2.13]: https://github.com/aletisunil/hx/compare/v0.2.12...v0.2.13
1117
+ [0.2.12]: https://github.com/aletisunil/hx/compare/v0.2.11...v0.2.12
1118
+ [0.2.11]: https://github.com/aletisunil/hx/compare/v0.2.10...v0.2.11
1119
+ [0.2.10]: https://github.com/aletisunil/hx/compare/v0.2.9...v0.2.10
1120
+ [0.2.9]: https://github.com/aletisunil/hx/compare/v0.2.8...v0.2.9
1121
+ [0.2.8]: https://github.com/aletisunil/hx/compare/v0.2.7...v0.2.8
1122
+ [0.2.7]: https://github.com/aletisunil/hx/compare/v0.2.6...v0.2.7
1123
+ [0.2.6]: https://github.com/aletisunil/hx/compare/v0.2.5...v0.2.6
1124
+ [0.2.5]: https://github.com/aletisunil/hx/compare/v0.2.4...v0.2.5
1125
+ [0.2.4]: https://github.com/aletisunil/hx/compare/v0.2.3...v0.2.4
1126
+ [0.2.3]: https://github.com/aletisunil/hx/compare/v0.2.2...v0.2.3
1127
+ [0.2.2]: https://github.com/aletisunil/hx/compare/v0.2.1...v0.2.2
1128
+ [0.2.1]: https://github.com/aletisunil/hx/compare/v0.2.0...v0.2.1
1129
+ [0.2.0]: https://github.com/aletisunil/hx/compare/v0.1.9...v0.2.0
1130
+ [0.1.9]: https://github.com/aletisunil/hx/compare/v0.1.8...v0.1.9
1131
+ [0.1.8]: https://github.com/aletisunil/hx/compare/v0.1.7...v0.1.8
1132
+ [0.1.7]: https://github.com/aletisunil/hx/compare/v0.1.6...v0.1.7
1133
+ [0.1.6]: https://github.com/aletisunil/hx/compare/v0.1.5...v0.1.6
1134
+ [0.1.5]: https://github.com/aletisunil/hx/compare/v0.1.4...v0.1.5
1135
+ [0.1.4]: https://github.com/aletisunil/hx/compare/v0.1.3...v0.1.4
1136
+ [0.1.3]: https://github.com/aletisunil/hx/compare/v0.1.2...v0.1.3
1137
+ [0.1.2]: https://github.com/aletisunil/hx/compare/v0.1.0...v0.1.2
1138
+ [0.1.0]: https://github.com/aletisunil/hx/releases/tag/v0.1.0