@sunilaleti/hx 0.0.0-stage → 0.3.0-beta.1

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