@sunilaleti/hx 0.3.0-beta.1 → 1.1.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 +72 -1
- package/README.md +112 -81
- package/dist/hx.js +1114 -592
- package/dist/hx.js.map +4 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,75 @@ the project follows [semantic versioning](https://semver.org/).
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
+
## [1.1.0] - 2026-10-31
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- HX updates itself, as Claude Code does. About once a day a session checks npm for a newer
|
|
36
|
+
release, installs it in the background with `npm install --global`, and says to restart.
|
|
37
|
+
`hx update` (or `hx upgrade`) updates now. Turn the background update off with
|
|
38
|
+
`"autoUpdates": false` in settings or `HX_DISABLE_AUTOUPDATER=1`. Only an npm install updates
|
|
39
|
+
itself; `hx -p` never does.
|
|
40
|
+
1.0.0 has no updater: install 1.1.0 once with `npm install --global @sunilaleti/hx@latest`, and
|
|
41
|
+
it keeps itself current from there.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- CI runs the suite twice instead of four times - Linux and macOS, both on Node 24 - and
|
|
46
|
+
publishes with the npm that ships with Node 24. Node 22, the oldest HX supports, is checked
|
|
47
|
+
by installing the package on it and running `hx --version`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## [1.0.0] - 2026-10-31
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- The TypeScript build is the release: install it with `npm install --global @sunilaleti/hx`,
|
|
56
|
+
and run the same command again to update.
|
|
57
|
+
- With nothing signed in, `hx` opens straight into the TUI, under the HX logo, and says to type
|
|
58
|
+
`/login`, instead of asking for an OpenRouter key in the shell before anything is on screen.
|
|
59
|
+
A model whose route is not signed in says to `/login` or pick another with `/model`, and the
|
|
60
|
+
first message after signing in goes through.
|
|
61
|
+
|
|
62
|
+
### Removed
|
|
63
|
+
|
|
64
|
+
- `hx upgrade`. Update by running the install command again.
|
|
65
|
+
- The beta channel (`@sunilaleti/hx@next`). Every version is a stable release on `latest`.
|
|
66
|
+
- The Python build. `hx-cli` on PyPI stops at 0.2.20 and gets no more releases. To move over,
|
|
67
|
+
run `uv tool uninstall hx-cli` and install from npm: `~/.hx` carries over as it is.
|
|
68
|
+
`install.sh` now checks for Node.js 22.15 or newer and installs from npm.
|
|
69
|
+
- The `live` tests, which drove the Python build's internals against real APIs.
|
|
70
|
+
|
|
71
|
+
### Security
|
|
72
|
+
|
|
73
|
+
- A server in a project's `.hx/mcp.json` no longer starts by itself: a cloned repository could
|
|
74
|
+
run any command the moment `hx` opened in it. HX names each waiting server and what it runs;
|
|
75
|
+
`hx mcp approve <server>` starts it, and `hx mcp add` approves what it adds. An approval is for
|
|
76
|
+
that command, so a server whose command changes waits again. Existing project servers need
|
|
77
|
+
approving once.
|
|
78
|
+
- Read, Grep, Glob, Symbols, Write and Edit ask before touching `~/.ssh`, `~/.aws`, `~/.gnupg`,
|
|
79
|
+
`~/.kube`, `~/.config/gh`, `~/.docker/config.json`, `~/.netrc`, `~/.npmrc`, `~/.pypirc` and
|
|
80
|
+
HX's `auth.json`, in every mode and through symlinks. The sandbox kept the shell out of them,
|
|
81
|
+
but these tools run outside it. An allow rule naming the file opens it.
|
|
82
|
+
- A command hidden inside another no longer passes as read-only: `cat <(rm -rf src)`, `>(...)`,
|
|
83
|
+
zsh's `=(...)` and glob qualifiers, an `awk` program that pipes to or reads from a command,
|
|
84
|
+
`@include`, and `find -fprint0` all ask now.
|
|
85
|
+
- A deny rule holds however the command is spelled: behind `NAME=value` assignments, by full
|
|
86
|
+
path, or behind a wrapper such as `nohup`, `timeout`, `sudo` or `xargs`. An allow rule no
|
|
87
|
+
longer covers a command with assignments it does not name.
|
|
88
|
+
- `acceptEdits` covers only the project: a write outside it, through a symlink that leads out
|
|
89
|
+
of it, or into its `.git` or `.hx` folder asks.
|
|
90
|
+
- `$HX_HOME` (`~/.hx`), which holds every transcript, is made private to its owner (`0700`).
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- Reads that come before a write in one turn run concurrently, as reads elsewhere do.
|
|
95
|
+
- A login shell that does not speak POSIX sh - csh, tcsh, fish - no longer makes every command
|
|
96
|
+
wait out its timeout: commands run in bash.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
31
100
|
## [0.3.0-beta.1] - 2026-10-31
|
|
32
101
|
|
|
33
102
|
### Changed
|
|
@@ -1054,7 +1123,9 @@ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/)
|
|
|
1054
1123
|
- `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
|
|
1055
1124
|
self-update, and `install.sh` bootstrapping uv with a pinned Python.
|
|
1056
1125
|
|
|
1057
|
-
[Unreleased]: https://github.com/aletisunil/hx/compare/
|
|
1126
|
+
[Unreleased]: https://github.com/aletisunil/hx/compare/v1.1.0...HEAD
|
|
1127
|
+
[1.1.0]: https://github.com/aletisunil/hx/compare/v1.0.0...v1.1.0
|
|
1128
|
+
[1.0.0]: https://github.com/aletisunil/hx/compare/v0.3.0-beta.1...v1.0.0
|
|
1058
1129
|
[0.3.0-beta.1]: https://github.com/aletisunil/hx/compare/v0.2.20...v0.3.0-beta.1
|
|
1059
1130
|
[0.2.20]: https://github.com/aletisunil/hx/compare/v0.2.19...v0.2.20
|
|
1060
1131
|
[0.2.19]: https://github.com/aletisunil/hx/compare/v0.2.18...v0.2.19
|
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# HX
|
|
2
2
|
|
|
3
|
-
An agent harness for the terminal.
|
|
4
|
-
OpenRouter
|
|
3
|
+
An agent harness for the terminal. TypeScript on Node.js, its own terminal
|
|
4
|
+
renderer, and models through OpenRouter or a ChatGPT, Claude or Devin
|
|
5
|
+
subscription.
|
|
5
6
|
|
|
6
7
|
HX runs in your project directory, reads and edits your code, runs commands in
|
|
7
8
|
a sandboxed shell, and shows you what every turn costs.
|
|
@@ -11,12 +12,23 @@ a sandboxed shell, and shows you what every turn costs.
|
|
|
11
12
|
## Install
|
|
12
13
|
|
|
13
14
|
```sh
|
|
14
|
-
npm install --global @sunilaleti/hx
|
|
15
|
+
npm install --global @sunilaleti/hx
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
HX runs on [Node.js](https://nodejs.org/) 22.15 or newer, on macOS and Linux.
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
Then run `hx`: the first time, it opens with the HX logo and says to type
|
|
20
|
+
`/login`.
|
|
21
|
+
|
|
22
|
+
HX keeps itself up to date, as Claude Code does. About once a day, a session
|
|
23
|
+
checks npm for a newer release, installs it in the background, and says to
|
|
24
|
+
restart. Run `hx update` to update now. To turn the background update off, set
|
|
25
|
+
`"autoUpdates": false` in `~/.hx/settings.json`, or `HX_DISABLE_AUTOUPDATER=1`.
|
|
26
|
+
|
|
27
|
+
Or let the install script check your Node.js version and run that command:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
curl -fsSL https://raw.githubusercontent.com/aletisunil/hx/main/install.sh | sh
|
|
31
|
+
```
|
|
20
32
|
|
|
21
33
|
### `@sunilaleti/hx` on npm, `hx` in your terminal
|
|
22
34
|
|
|
@@ -37,9 +49,14 @@ to your `PATH`:
|
|
|
37
49
|
export PATH="$(npm prefix --global)/bin:$PATH"
|
|
38
50
|
```
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
sessions, sign-ins and settings
|
|
42
|
-
|
|
52
|
+
Coming from `hx-cli`, the Python build on PyPI? It is no longer released.
|
|
53
|
+
Remove it, then install from npm - sessions, sign-ins and settings in `~/.hx`
|
|
54
|
+
carry over:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
uv tool uninstall hx-cli
|
|
58
|
+
npm install --global @sunilaleti/hx
|
|
59
|
+
```
|
|
43
60
|
|
|
44
61
|
### Credentials
|
|
45
62
|
|
|
@@ -329,6 +346,7 @@ hx --no-sandbox # disable OS sandboxing (rules still apply)
|
|
|
329
346
|
hx --system-prompt @p.md # replace the system prompt for one run
|
|
330
347
|
hx --append-system-prompt "Always run the tests" # add to it; repeatable
|
|
331
348
|
hx mcp list|add|remove # manage MCP servers
|
|
349
|
+
hx mcp approve <server> # let a server from the project's .hx/mcp.json start
|
|
332
350
|
hx mcp login <server> # sign in to a remote MCP server in the browser
|
|
333
351
|
hx mcp logout <server> # forget a remote MCP server's sign-in
|
|
334
352
|
hx auth [set|clear] # manage the OpenRouter key
|
|
@@ -336,7 +354,7 @@ hx auth login [provider] # sign in (openrouter, openai-codex, devin, claude-c
|
|
|
336
354
|
hx auth logout <provider> # forget a stored credential
|
|
337
355
|
hx docs [section] # the manual; no argument lists its sections
|
|
338
356
|
hx changelog [version] # what shipped in each version
|
|
339
|
-
hx
|
|
357
|
+
hx update # install the newest release now (also: hx upgrade)
|
|
340
358
|
```
|
|
341
359
|
|
|
342
360
|
Print mode is the scriptable one: stdout carries only the assistant's text, so
|
|
@@ -373,7 +391,7 @@ allowlist it if you would rather not be asked:
|
|
|
373
391
|
|
|
374
392
|
### Keys
|
|
375
393
|
|
|
376
|
-
Every key below is defined once, in `src/
|
|
394
|
+
Every key below is defined once, in `src/keys.ts`. `/help`, the hints line
|
|
377
395
|
under the prompt and this table all read from that registry, and any of them can
|
|
378
396
|
be rebound (see [Keybindings](#keybindings)).
|
|
379
397
|
|
|
@@ -474,7 +492,7 @@ says so; copy the file over and drag or paste its path instead.
|
|
|
474
492
|
#### Keybindings
|
|
475
493
|
|
|
476
494
|
Any key can be rebound in `~/.hx/keybindings.json`, keyed by the action ids in
|
|
477
|
-
`src/
|
|
495
|
+
`src/keys.ts`:
|
|
478
496
|
|
|
479
497
|
```json
|
|
480
498
|
{
|
|
@@ -651,6 +669,7 @@ along with the `.hx/.gitignore` it added to hide it.
|
|
|
651
669
|
{
|
|
652
670
|
"theme": "dark", // dark | light | ansi, or a file in ~/.hx/themes
|
|
653
671
|
"quiet_startup": false, // skip the startup header
|
|
672
|
+
"autoUpdates": true, // install new releases in the background, once a day
|
|
654
673
|
|
|
655
674
|
"models": {
|
|
656
675
|
"model": "anthropic/claude-sonnet-4.5",
|
|
@@ -721,7 +740,7 @@ route at another OpenAI-compatible endpoint - a gateway, or a recording proxy.
|
|
|
721
740
|
stderr so stdout stays pipeable. `/prompt` shows the same thing inside a running
|
|
722
741
|
session.
|
|
723
742
|
|
|
724
|
-
The built-in prompt is `SYSTEM_PROMPT` in `src/
|
|
743
|
+
The built-in prompt is `SYSTEM_PROMPT` in `src/core/context.ts`. It is
|
|
725
744
|
replaced by the first of these that exists:
|
|
726
745
|
|
|
727
746
|
| Source | Scope |
|
|
@@ -785,7 +804,7 @@ mapping semantic roles onto them. Drop one in `~/.hx/themes/mine.json` and
|
|
|
785
804
|
```
|
|
786
805
|
|
|
787
806
|
The role names are pi's, so [pi](https://github.com/earendil-works/pi) theme
|
|
788
|
-
files load here unchanged. `src/
|
|
807
|
+
files load here unchanged. `src/tui/themes/dark.json` is the reference.
|
|
789
808
|
A few roles pi has no concept of may be left out, and borrow another role:
|
|
790
809
|
`toolArgs`, the colour of a tool call's paths and patterns, borrows `accent`;
|
|
791
810
|
`panel` and `mdCodeBlockBg` borrow `surface`; `userText` borrows `text`; and
|
|
@@ -842,7 +861,19 @@ Two independent layers guard every tool call, and both must pass.
|
|
|
842
861
|
holds even in bypass mode. Shell commands are decomposed into their real
|
|
843
862
|
segments first, so an allow rule for `git status` does not carry
|
|
844
863
|
`&& rm -rf /` along with it; a command that cannot be decomposed with
|
|
845
|
-
confidence prompts rather than passing.
|
|
864
|
+
confidence prompts rather than passing. What runs inside a command counts as a
|
|
865
|
+
segment of its own - `$(...)`, `<(...)`, `>(...)` - so `cat <(rm -rf src)` is
|
|
866
|
+
not a read-only `cat`. A deny rule matches the command however it is spelled:
|
|
867
|
+
`deny: Bash(rm:*)` also stops `FOO=1 rm`, `/bin/rm` and `nohup rm`. An allow
|
|
868
|
+
rule covers only what it names, so `allow: Bash(ls)` does not cover
|
|
869
|
+
`LD_PRELOAD=x ls`.
|
|
870
|
+
|
|
871
|
+
**Credential files** - the same `~/.ssh`, `~/.aws` and `auth.json` the sandbox
|
|
872
|
+
guards - always ask before Read, Grep, Glob, Symbols, Write or Edit touch them,
|
|
873
|
+
in every mode, symlinks followed. Those tools run inside HX, outside the
|
|
874
|
+
sandbox. An allow rule that names the file, such as `allow: Read(~/.ssh/config)`,
|
|
875
|
+
opens that one file. A search that starts above them, from `$HOME`, leaves
|
|
876
|
+
them out.
|
|
846
877
|
|
|
847
878
|
**An OS sandbox** wraps command execution: Seatbelt on macOS, bubblewrap on
|
|
848
879
|
Linux. The filesystem is readable, writes are confined to the project and the
|
|
@@ -855,7 +886,13 @@ and the Tavily and Devin keys) are removed from the environment of every
|
|
|
855
886
|
command it runs.
|
|
856
887
|
|
|
857
888
|
Modes cycle with shift+tab: `plan` (read-only — mutating tools are not even
|
|
858
|
-
offered to the model), `default`, `acceptEdits`, `bypass`.
|
|
889
|
+
offered to the model), `default`, `acceptEdits`, `bypass`. `acceptEdits` covers
|
|
890
|
+
the project's own files: a Write or Edit outside the project, through a symlink
|
|
891
|
+
that leads out of it, or into its `.git` or `.hx` folder still asks.
|
|
892
|
+
|
|
893
|
+
**Nothing that arrives with a clone runs by itself.** Hooks in a project's
|
|
894
|
+
shared `.hx/settings.json` are ignored, and a server in its `.hx/mcp.json`
|
|
895
|
+
starts only once you approve it - see [Extending it](#extending-it).
|
|
859
896
|
|
|
860
897
|
---
|
|
861
898
|
|
|
@@ -1083,7 +1120,16 @@ construction.
|
|
|
1083
1120
|
```
|
|
1084
1121
|
|
|
1085
1122
|
Or `hx mcp add local python server.py`, or
|
|
1086
|
-
`hx mcp add remote --url https://example.com/mcp`.
|
|
1123
|
+
`hx mcp add remote --url https://example.com/mcp`.
|
|
1124
|
+
|
|
1125
|
+
A server in the project's `.hx/mcp.json` runs a command on your machine, and
|
|
1126
|
+
the file arrives with a clone - so HX does not start it until you approve it.
|
|
1127
|
+
A session names each one waiting, with what it runs. `hx mcp approve local`
|
|
1128
|
+
starts it from the next session on, and `hx mcp add` approves what it adds.
|
|
1129
|
+
The approval is for that command: if a `git pull` changes it, the server waits
|
|
1130
|
+
again. Servers in your own `~/.hx/mcp.json` need no approval.
|
|
1131
|
+
|
|
1132
|
+
Tools arrive namespaced
|
|
1087
1133
|
`mcp__<server>__<tool>` in a deterministic order. Servers connect concurrently
|
|
1088
1134
|
with a per-server timeout; one that is broken or slow logs a warning and is
|
|
1089
1135
|
dropped rather than taking the session with it. A server that changes its tool
|
|
@@ -1186,23 +1232,26 @@ bypassed by delegating through `Task`.
|
|
|
1186
1232
|
|
|
1187
1233
|
```sh
|
|
1188
1234
|
git clone https://github.com/aletisunil/hx && cd hx
|
|
1189
|
-
|
|
1235
|
+
npm ci
|
|
1190
1236
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1237
|
+
npm run check # tsc, biome, and the source-literal check
|
|
1238
|
+
npm run build # dist/hx.js, the bundle npm ships
|
|
1239
|
+
node dist/hx.js # run from the checkout
|
|
1240
|
+
npm run e2e # build, then the whole end-to-end suite
|
|
1241
|
+
uv run ruff check . && uv run ruff format --check . # the suite's own Python
|
|
1195
1242
|
```
|
|
1196
1243
|
|
|
1197
|
-
The suite is end-to-end
|
|
1198
|
-
|
|
1244
|
+
The suite is end-to-end, written in Python with pytest, and run with
|
|
1245
|
+
[uv](https://docs.astral.sh/uv/). Each test in `tests/e2e/` starts the `hx`
|
|
1246
|
+
bundle - on a real pseudo-terminal for the TUI, or as a plain subprocess for
|
|
1199
1247
|
print mode and the other commands - in a private `$HOME` and `$HX_HOME`, and
|
|
1200
1248
|
points it at a local OpenRouter stand-in with `HX_OPENROUTER_BASE_URL`, and
|
|
1201
1249
|
at a ChatGPT stand-in with `HX_CODEX_BASE_URL`.
|
|
1202
1250
|
Everything between the keyboard and the socket is the shipped code; only the
|
|
1203
1251
|
model's replies are scripted. The screen is read back through a VT emulator,
|
|
1204
1252
|
so assertions are on the cells a terminal shows, not on the bytes HX meant to
|
|
1205
|
-
write.
|
|
1253
|
+
write. The suite never imports HX: it tests `dist/hx.js` by default, and any
|
|
1254
|
+
other `hx` - an installed one, say - named by `HX_E2E_BIN`.
|
|
1206
1255
|
|
|
1207
1256
|
Every run writes `e2e-report/`: `index.html` shows each test's outcome and
|
|
1208
1257
|
every screen it checked, rendered in colour, and one text file per screen lets
|
|
@@ -1212,41 +1261,26 @@ two runs be compared with `diff -r`. CI uploads it from every test job as an
|
|
|
1212
1261
|
Tests that need the OS sandbox are skipped where there is no backend
|
|
1213
1262
|
(`sandbox-exec` on macOS, `bwrap` on Linux).
|
|
1214
1263
|
|
|
1215
|
-
Tests marked `live` hit a real API. The OpenRouter ones cost money:
|
|
1216
|
-
|
|
1217
|
-
```sh
|
|
1218
|
-
OPENROUTER_API_KEY=... uv run pytest -m live tests/test_live.py
|
|
1219
|
-
|
|
1220
|
-
# the Codex route draws on your ChatGPT subscription instead
|
|
1221
|
-
hx auth login openai-codex
|
|
1222
|
-
uv run pytest -m live tests/test_live_codex.py
|
|
1223
|
-
|
|
1224
|
-
# and the Devin route on your Devin subscription
|
|
1225
|
-
hx auth login devin
|
|
1226
|
-
uv run pytest -m live tests/test_live_devin.py
|
|
1227
|
-
```
|
|
1228
|
-
|
|
1229
|
-
They are the only place the wire formats, streaming, tool use and a genuine
|
|
1230
|
-
cache hit are proven against a real service; everything else runs against the
|
|
1231
|
-
stand-in.
|
|
1232
|
-
|
|
1233
1264
|
### Layout
|
|
1234
1265
|
|
|
1235
1266
|
```
|
|
1236
|
-
src/
|
|
1237
|
-
cli.
|
|
1267
|
+
src/
|
|
1268
|
+
cli.ts runtime.ts config.ts paths.ts frontmatter.ts git.ts trace.ts
|
|
1238
1269
|
core/ loop, context assembly, compaction, late injection, sessions, usage
|
|
1239
1270
|
auth/ credential store, OAuth flows, per-route resolution
|
|
1240
|
-
providers/ OpenRouter, Codex, Devin, the model catalogue
|
|
1271
|
+
providers/ OpenRouter, Codex, Claude Code, Devin, the model catalogue
|
|
1241
1272
|
tools/ Bash, Read, Write, Edit, Glob, Grep, Symbols, TodoWrite, Task,
|
|
1242
1273
|
WebSearch, WebFetch, line anchors, output capping
|
|
1243
1274
|
permissions/ rule engine, shell decomposition, Seatbelt/bubblewrap
|
|
1244
1275
|
hooks/ the four events, the trust boundary, subprocess execution
|
|
1245
|
-
skills/ agents/
|
|
1246
|
-
|
|
1276
|
+
skills/ agents/
|
|
1277
|
+
mcp/ client, OAuth, and approval for a project's servers
|
|
1278
|
+
keys.ts keybinding registry: ids, defaults, descriptions, user overrides
|
|
1247
1279
|
term/ the renderer: raw mode, input decoding, the differ, the screen
|
|
1248
1280
|
tui/ views, commands, per-tool renderers, themes
|
|
1249
1281
|
themes/ the shipped palettes, as JSON
|
|
1282
|
+
py.ts pathlib.ts json.ts Python's string, path and JSON behaviour, which
|
|
1283
|
+
HX's output and files were written against
|
|
1250
1284
|
```
|
|
1251
1285
|
|
|
1252
1286
|
The core is headless and emits events; the TUI and print mode are both just
|
|
@@ -1257,44 +1291,43 @@ imports `tui/`.
|
|
|
1257
1291
|
|
|
1258
1292
|
## Releasing
|
|
1259
1293
|
|
|
1260
|
-
For maintainers. HX
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
1. Push the repo to `github.com/<owner>/hx` and update the URLs in
|
|
1265
|
-
`pyproject.toml`.
|
|
1266
|
-
2. Create a GitHub environment named `pypi`.
|
|
1267
|
-
3. On PyPI, add a [trusted publisher](https://docs.pypi.org/trusted-publishers/)
|
|
1268
|
-
for the project: owner, repo `hx`, workflow `ci.yml`, environment `pypi`.
|
|
1269
|
-
No API token is stored anywhere.
|
|
1294
|
+
For maintainers. HX is published to npm as **`@sunilaleti/hx`**, on the
|
|
1295
|
+
`latest` tag, by CI. Every version is a stable release; there is no beta
|
|
1296
|
+
channel.
|
|
1270
1297
|
|
|
1271
|
-
|
|
1298
|
+
A push to `main` whose `package.json` version is not on npm yet is a release:
|
|
1299
|
+
once lint, the test suite and the install script pass, CI's `release` job
|
|
1300
|
+
publishes it to npm, tags it `vX.Y.Z` and makes the GitHub release, with the
|
|
1301
|
+
version's `CHANGELOG.md` section as its notes. A push that leaves the version
|
|
1302
|
+
alone publishes nothing.
|
|
1272
1303
|
|
|
1273
1304
|
```sh
|
|
1274
1305
|
# 1. move CHANGELOG.md's [Unreleased] entries under the new version, dated,
|
|
1275
1306
|
# and leave a fresh empty [Unreleased] behind
|
|
1276
|
-
# 2. bump
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1307
|
+
# 2. bump "version" in package.json
|
|
1308
|
+
npm version X.Y.Z --no-git-tag-version
|
|
1309
|
+
# 3. the whole suite; the version must have a CHANGELOG section
|
|
1310
|
+
npm run e2e
|
|
1311
|
+
git commit -am "chore(release): X.Y.Z"
|
|
1312
|
+
git push origin main
|
|
1280
1313
|
```
|
|
1281
1314
|
|
|
1282
1315
|
Step 1 is not optional and is not a convention: `tests/e2e/test_cli.py` fails the
|
|
1283
|
-
whole suite when
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1316
|
+
whole suite when the version has no `CHANGELOG.md` section, and the `release`
|
|
1317
|
+
job checks again before it publishes. The file ships inside the bundle and
|
|
1318
|
+
`hx changelog` prints it, so a version with no section there would reach users
|
|
1319
|
+
as a blank release.
|
|
1287
1320
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1321
|
+
One-time setup: CI publishes with npm's
|
|
1322
|
+
[trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm
|
|
1323
|
+
token is stored anywhere. On npmjs.com, open `@sunilaleti/hx` → Settings →
|
|
1324
|
+
Trusted Publisher, choose GitHub Actions, and enter owner `aletisunil`,
|
|
1325
|
+
repository `hx` and workflow `ci.yml`, with no environment.
|
|
1293
1326
|
|
|
1294
|
-
To
|
|
1327
|
+
To see what npm would ship before releasing:
|
|
1295
1328
|
|
|
1296
1329
|
```sh
|
|
1297
|
-
|
|
1330
|
+
npm pack --dry-run
|
|
1298
1331
|
```
|
|
1299
1332
|
|
|
1300
1333
|
---
|
|
@@ -1306,13 +1339,11 @@ streaming with prefix caching and accurate cost accounting, session persistence
|
|
|
1306
1339
|
and resume, the tool suite, the permission engine and OS sandbox, late
|
|
1307
1340
|
injection, compaction, output capping, skills, subagents, MCP, and the TUI.
|
|
1308
1341
|
|
|
1309
|
-
|
|
1310
|
-
subscription over OAuth,
|
|
1311
|
-
decides which.
|
|
1342
|
+
Four routes to a model: an OpenRouter API key, a ChatGPT Plus/Pro
|
|
1343
|
+
subscription over OAuth, a Claude Pro/Max subscription through Claude Code, or
|
|
1344
|
+
a Devin subscription over OAuth. The model id decides which.
|
|
1312
1345
|
|
|
1313
|
-
Published to
|
|
1314
|
-
|
|
1346
|
+
Published to npm as [`@sunilaleti/hx`](https://www.npmjs.com/package/@sunilaleti/hx).
|
|
1347
|
+
The Python build that came before it, `hx-cli` on PyPI, stopped at 0.2.20.
|
|
1315
1348
|
|
|
1316
|
-
|
|
1317
|
-
tests exist and cover all three wire formats, tool use, reasoning replay and a
|
|
1318
|
-
genuine cache hit, but they need a credential and are deselected by default.
|
|
1349
|
+
No test reaches a real model: every route is proven against a local stand-in.
|