hx-cli 0.2.11__tar.gz → 0.2.13__tar.gz
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.
- {hx_cli-0.2.11 → hx_cli-0.2.13}/CHANGELOG.md +60 -1
- {hx_cli-0.2.11 → hx_cli-0.2.13}/PKG-INFO +69 -10
- {hx_cli-0.2.11 → hx_cli-0.2.13}/README.md +67 -9
- {hx_cli-0.2.11 → hx_cli-0.2.13}/pyproject.toml +3 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/__init__.py +1 -1
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/agents/subagent.py +7 -1
- hx_cli-0.2.13/src/hx/auth/claude_code.py +369 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/resolve.py +45 -1
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/cli.py +48 -26
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/loop.py +31 -17
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/session.py +18 -5
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/title.py +14 -1
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/net.py +62 -6
- hx_cli-0.2.13/src/hx/providers/claude_code.py +903 -0
- hx_cli-0.2.13/src/hx/providers/claude_code_catalogue.py +140 -0
- hx_cli-0.2.13/src/hx/providers/claude_code_cli.py +414 -0
- hx_cli-0.2.13/src/hx/providers/claude_code_transcript.py +433 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/models.py +43 -3
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/registry.py +41 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/glob.py +25 -6
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/app.py +4 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/commands.py +46 -1
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/runtime.py +51 -11
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/status.py +17 -6
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/conftest.py +78 -0
- hx_cli-0.2.13/tests/e2e/fake_claude.py +518 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/stub.py +26 -2
- hx_cli-0.2.13/tests/e2e/test_claude_code.py +849 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/test_commands.py +172 -0
- hx_cli-0.2.13/tests/e2e/test_queue.py +156 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/test_tools.py +27 -0
- hx_cli-0.2.13/tests/test_live_claude_code.py +350 -0
- hx_cli-0.2.13/tests/tls_proxy.py +222 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/uv.lock +191 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/.github/workflows/ci.yml +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/.gitignore +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/examples/hooks/check.sh +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/install.sh +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/agents/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/agents/definitions.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/oauth/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/oauth/browser.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/oauth/callback.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/oauth/codex.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/oauth/devin.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/oauth/pkce.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/auth/store.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/config.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/checkpoints.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/compaction.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/context.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/events.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/images.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/lateinject.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/messages.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/core/usage.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/docs.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/frontmatter.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/git.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/hooks/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/hooks/engine.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/hooks/spec.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/keys.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/mcp/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/mcp/client.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/mcp/manager.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/mcp/oauth.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/paths.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/permissions/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/permissions/engine.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/permissions/parser.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/permissions/sandbox.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/base.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/codex.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/codex_catalogue.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/devin.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/devin_catalogue.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/devin_wire.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/openrouter.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/protowire.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/providers/responses_codec.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/skills/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/skills/loader.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/skills/runtime.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/ansi.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/buffer.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/component.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/demo.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/editor.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/keydecode.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/loop.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/markdown.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/modifiers.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/primitives.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/sanitize.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/screen.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/syntax.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/terminal.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/undo.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/width.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/term/word_nav.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/anchors.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/base.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/bash.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/edit.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/grep.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/output.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/read.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/registry.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/symbols.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/task.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/todo.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/websearch.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tools/write.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/trace.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/clipboard.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/format.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/fuzzy.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/glyphs.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/killring.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/limits.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/paint.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/renderers.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/roles.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/theme.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/theme_json.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/themes/ansi.json +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/themes/dark.json +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/themes/light.json +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/blocks.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/dialog.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/login.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/permission.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/pickers.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/prompt.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/src/hx/tui/views/transcript.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/conftest.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/__init__.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/fixtures/echo_server.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/report.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/terminal.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/test_chat.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/test_cli.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/test_extend.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/test_input.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/e2e/test_print.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/test_live.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/test_live_codex.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/test_live_devin.py +0 -0
- {hx_cli-0.2.11 → hx_cli-0.2.13}/tests/test_live_tavily.py +0 -0
|
@@ -28,6 +28,63 @@ the project follows [semantic versioning](https://semver.org/).
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
+
## [0.2.13] - 2026-10-04
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Claude Pro/Max route: `hx auth login claude-code`, then
|
|
36
|
+
`hx --model claude-code/claude-sonnet-5`. Runs Claude models on your Claude
|
|
37
|
+
subscription through the official Claude Code CLI, with HX's own tools,
|
|
38
|
+
approvals and transcript. `/model` lists the account's models and `/effort`
|
|
39
|
+
their levels.
|
|
40
|
+
- Behind a TLS-inspecting proxy the `claude-code` route takes the same
|
|
41
|
+
settings as every other route: `HX_CA_BUNDLE` reaches Claude Code as
|
|
42
|
+
`NODE_EXTRA_CA_CERTS` and `HX_SSL_NO_VERIFY` as
|
|
43
|
+
`NODE_TLS_REJECT_UNAUTHORIZED=0`, Claude Code's own `CLAUDE_CODE_CERT_STORE`
|
|
44
|
+
and `CLAUDE_CODE_CLIENT_CERT` / `_KEY` / `_KEY_PASSPHRASE` (for a network
|
|
45
|
+
that requires a client certificate) pass through, and a certificate failure
|
|
46
|
+
it reports names the fix.
|
|
47
|
+
- On the `claude-code` route, a turn Anthropic bills as Extra Usage while
|
|
48
|
+
your plan still has room says so, and names HX's part of the system prompt,
|
|
49
|
+
instead of claiming the plan's usage limit is reached. A turn whose prompt
|
|
50
|
+
carries HX's own system prompt text (a copied `system-prompt.md`, an
|
|
51
|
+
AGENTS.md, an agent definition) is refused before it is sent, naming where
|
|
52
|
+
the text is; `hx prompt` shows the sources.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- A subagent that fails - a provider error, a refused prompt, the turn limit -
|
|
57
|
+
reports why to the parent model, instead of "(the subagent returned no
|
|
58
|
+
text)".
|
|
59
|
+
- `Glob` with a pattern of `.` or `./` returns an error saying to use `*` or
|
|
60
|
+
`**/*`, and an absolute pattern one naming the `path` and relative pattern
|
|
61
|
+
to send instead, on every Python version, instead of a Python exception.
|
|
62
|
+
- With `tui.enterWhileBusy` set to `"steer"`, `alt+enter` queues a message
|
|
63
|
+
behind the running turn, as the prompt says. It used to steer like `enter`,
|
|
64
|
+
leaving no way to queue in that mode.
|
|
65
|
+
- The status bar's `⧗N queued` count stays visible in a project with a long
|
|
66
|
+
path or branch name; the path is shortened instead of the count being cut off.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## [0.2.12] - 2026-10-01
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
|
|
74
|
+
- `/exit` and `ctrl+d` return the shell at once. They used to wait up to 10
|
|
75
|
+
seconds on a model call renaming the session; sessions are no longer renamed
|
|
76
|
+
on the way out, in the TUI or with `hx -p`.
|
|
77
|
+
- A name set with `/title` is no longer replaced by a generated one, including
|
|
78
|
+
one that was still being generated when you set it.
|
|
79
|
+
- The session name is generated from the opening and the latest stretch of the
|
|
80
|
+
conversation, not only its first 2000 characters.
|
|
81
|
+
- A session left before its first name came back is named from your first
|
|
82
|
+
message, instead of being listed by `/resume` with no name.
|
|
83
|
+
- A session left with `/resume` into another directory is renamed for what it
|
|
84
|
+
became, by the HX that starts there, in the background.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
31
88
|
## [0.2.11] - 2026-10-01
|
|
32
89
|
|
|
33
90
|
### Fixed
|
|
@@ -776,7 +833,9 @@ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/)
|
|
|
776
833
|
- `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
|
|
777
834
|
self-update, and `install.sh` bootstrapping uv with a pinned Python.
|
|
778
835
|
|
|
779
|
-
[Unreleased]: https://github.com/aletisunil/hx/compare/v0.2.
|
|
836
|
+
[Unreleased]: https://github.com/aletisunil/hx/compare/v0.2.13...HEAD
|
|
837
|
+
[0.2.13]: https://github.com/aletisunil/hx/compare/v0.2.12...v0.2.13
|
|
838
|
+
[0.2.12]: https://github.com/aletisunil/hx/compare/v0.2.11...v0.2.12
|
|
780
839
|
[0.2.11]: https://github.com/aletisunil/hx/compare/v0.2.10...v0.2.11
|
|
781
840
|
[0.2.10]: https://github.com/aletisunil/hx/compare/v0.2.9...v0.2.10
|
|
782
841
|
[0.2.9]: https://github.com/aletisunil/hx/compare/v0.2.8...v0.2.9
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hx-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.13
|
|
4
4
|
Summary: HX - an agent harness for the terminal
|
|
5
5
|
Project-URL: Homepage, https://hx.sunilaleti.dev
|
|
6
6
|
Project-URL: Documentation, https://hx.sunilaleti.dev
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
|
31
31
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
32
32
|
Requires-Dist: pytest>=8.2; extra == 'dev'
|
|
33
33
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
34
|
+
Requires-Dist: trustme>=1.2; extra == 'dev'
|
|
34
35
|
Requires-Dist: types-pygments; extra == 'dev'
|
|
35
36
|
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
36
37
|
Provides-Extra: symbols
|
|
@@ -90,7 +91,7 @@ Requires Python 3.11+. macOS and Linux. Update with `hx upgrade`.
|
|
|
90
91
|
|
|
91
92
|
### Credentials
|
|
92
93
|
|
|
93
|
-
HX reaches models over
|
|
94
|
+
HX reaches models over four routes. Which one serves a turn is decided by the
|
|
94
95
|
model id alone, so "what paid for that" is always answerable by reading it:
|
|
95
96
|
|
|
96
97
|
| Model id | Route | Billing |
|
|
@@ -98,8 +99,10 @@ model id alone, so "what paid for that" is always answerable by reading it:
|
|
|
98
99
|
| `anthropic/claude-sonnet-4.5`, `openai/gpt-5`, … | OpenRouter | per token, API key |
|
|
99
100
|
| `openai-codex/gpt-5.6-terra` | ChatGPT Plus/Pro | your subscription |
|
|
100
101
|
| `devin/swe-1-6`, `devin/claude-opus-5`, … | Devin | your subscription |
|
|
102
|
+
| `claude-code/claude-sonnet-5`, `claude-code/claude-opus-4-8`, … | Claude Pro/Max, via Claude Code | your subscription |
|
|
101
103
|
|
|
102
|
-
Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route
|
|
104
|
+
Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route - except
|
|
105
|
+
Claude Code's, which stays with Claude Code.
|
|
103
106
|
|
|
104
107
|
**OpenRouter.** On first run HX asks for a key. Get one at
|
|
105
108
|
<https://openrouter.ai/keys>. Change it later with `/configure` in the TUI, or:
|
|
@@ -223,6 +226,47 @@ changes what a turn costs or how much fits. Router models such as
|
|
|
223
226
|
`devin/adaptive` are resolved to a concrete model on every turn by Devin itself.
|
|
224
227
|
If the list cannot be fetched, `devin/swe-1-6` and `devin/swe-1-6-fast` stand in.
|
|
225
228
|
|
|
229
|
+
**Claude Pro/Max.** HX runs Claude models on your Claude subscription through
|
|
230
|
+
the official [Claude Code](https://claude.com/claude-code) CLI, the way the
|
|
231
|
+
Claude Agent SDK does. Install Claude Code, then:
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
hx auth login claude-code # Claude Code's own sign-in; or /login inside HX
|
|
235
|
+
hx --model claude-code/claude-sonnet-5
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
HX holds no Claude token. The login is Claude Code's, kept and refreshed where
|
|
239
|
+
Claude Code keeps it, so `hx auth logout claude-code` only tells you to run
|
|
240
|
+
`claude auth logout` - which signs out every Claude Code on the machine. A
|
|
241
|
+
headless machine can use a token from `claude setup-token` in
|
|
242
|
+
`CLAUDE_CODE_OAUTH_TOKEN`. A Console login (`claude auth login --console`) bills
|
|
243
|
+
per token and is refused: this route is the subscription.
|
|
244
|
+
|
|
245
|
+
How a turn works: HX starts `claude -p` with every one of Claude Code's own
|
|
246
|
+
tools switched off and serves HX's tools to it as an MCP server. The model's
|
|
247
|
+
tool calls are run, permission-checked and drawn by HX exactly as on every
|
|
248
|
+
other route; Claude Code holds each call open until HX answers it, however long
|
|
249
|
+
an approval takes. The system prompt is Claude Code's, with your AGENTS.md
|
|
250
|
+
files, skills index and `--append-system-prompt` text added. One Claude Code
|
|
251
|
+
process serves a conversation for as long as it can, so the prompt cache
|
|
252
|
+
carries from turn to turn; after an interrupt, a steer while a tool runs, a
|
|
253
|
+
compaction, `/model`, `/effort` or a resumed session, HX starts a new one on a
|
|
254
|
+
session rebuilt from its own transcript. That costs a second or so and a
|
|
255
|
+
partly cold cache, not the conversation.
|
|
256
|
+
|
|
257
|
+
Claude Code is started isolated from your Claude Code setup: no CLAUDE.md, no
|
|
258
|
+
hooks, no plugins or MCP servers from `~/.claude`, no auto-memory, no
|
|
259
|
+
auto-compaction (HX compacts its own transcript), and nothing saved to Claude
|
|
260
|
+
Code's session list. Variables that would move it off the subscription -
|
|
261
|
+
`ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK` and the
|
|
262
|
+
rest - are removed from its environment; its TLS settings are kept (see
|
|
263
|
+
*Behind a TLS-inspecting proxy* below). `HX_CLAUDE_CODE_PATH` points HX at a
|
|
264
|
+
`claude` other than the one on `PATH`.
|
|
265
|
+
|
|
266
|
+
`/model` offers the models Claude Code lists for the account, each with the
|
|
267
|
+
efforts it accepts for `/effort`; `/models refresh` asks again. When a usage
|
|
268
|
+
limit is reached, HX says which one and when it resets.
|
|
269
|
+
|
|
226
270
|
**Web search.** Optional, and the one credential that is not a model route.
|
|
227
271
|
It powers two tools:
|
|
228
272
|
|
|
@@ -232,7 +276,7 @@ It powers two tools:
|
|
|
232
276
|
| `WebFetch` | read up to 5 URLs, returned as markdown |
|
|
233
277
|
|
|
234
278
|
[Tavily](https://app.tavily.com) bills per search rather than per token, so it
|
|
235
|
-
works identically on
|
|
279
|
+
works identically on every route - including the subscription ones,
|
|
236
280
|
where a provider-native search tool is not on offer.
|
|
237
281
|
|
|
238
282
|
Get a key at <https://app.tavily.com> — the free tier is 1000 searches a month
|
|
@@ -286,9 +330,18 @@ startup every session, because it is not a fix. A TLS failure is reported in
|
|
|
286
330
|
one line - `/model` and the startup notice both name the certificate problem
|
|
287
331
|
rather than reporting an empty catalogue with no cause.
|
|
288
332
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
333
|
+
The same settings cover the `claude-code` route, whose requests Claude Code
|
|
334
|
+
makes with its own TLS stack: HX passes `HX_CA_BUNDLE` on to it as
|
|
335
|
+
`NODE_EXTRA_CA_CERTS` (unless you already set that yourself), and
|
|
336
|
+
`HX_SSL_NO_VERIFY` as `NODE_TLS_REJECT_UNAUTHORIZED=0`. Claude Code reads the
|
|
337
|
+
OS trust store on its own, and its own TLS settings reach it untouched:
|
|
338
|
+
`CLAUDE_CODE_CERT_STORE`, and `CLAUDE_CODE_CLIENT_CERT`,
|
|
339
|
+
`CLAUDE_CODE_CLIENT_KEY` and `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE` for a network
|
|
340
|
+
that requires a client certificate.
|
|
341
|
+
|
|
342
|
+
HX never sends a Claude subscription token to the API itself - Anthropic
|
|
343
|
+
accepts those only from Claude Code, and HX will not impersonate it. The
|
|
344
|
+
`claude-code` route runs the real Claude Code instead.
|
|
292
345
|
|
|
293
346
|
---
|
|
294
347
|
|
|
@@ -314,7 +367,7 @@ hx mcp list|add|remove # manage MCP servers
|
|
|
314
367
|
hx mcp login <server> # sign in to a remote MCP server in the browser
|
|
315
368
|
hx mcp logout <server> # forget a remote MCP server's sign-in
|
|
316
369
|
hx auth [set|clear] # manage the OpenRouter key
|
|
317
|
-
hx auth login [provider] # sign in (openrouter, openai-codex, devin)
|
|
370
|
+
hx auth login [provider] # sign in (openrouter, openai-codex, devin, claude-code)
|
|
318
371
|
hx auth logout <provider> # forget a stored credential
|
|
319
372
|
hx docs [section] # the manual; no argument lists its sections
|
|
320
373
|
hx changelog [version] # what shipped in each version
|
|
@@ -709,8 +762,14 @@ After the first exchange, HX asks the model for a short name for the session and
|
|
|
709
762
|
writes it to `~/.hx/sessions/<id>/meta.json`, so `/resume` lists work rather
|
|
710
763
|
than timestamps. It is one small call — cap 32 output tokens, `models.title_model`
|
|
711
764
|
if you want a cheaper model for it — and it is counted in `/cost` like any other.
|
|
712
|
-
If the call fails the session is still named,
|
|
713
|
-
|
|
765
|
+
If the call fails, or you leave before it is back, the session is still named,
|
|
766
|
+
from your first message.
|
|
767
|
+
|
|
768
|
+
The live session is never renamed while it runs, and nothing is renamed on
|
|
769
|
+
`/exit`, which returns your shell at once. A session you leave with `/clear` or
|
|
770
|
+
`/resume` - including a `/resume` into another directory - is renamed in the
|
|
771
|
+
background for what it became. `/title <text>` renames it yourself, and a name
|
|
772
|
+
you chose is never replaced.
|
|
714
773
|
|
|
715
774
|
`/resume` lists every session except the one you are in: this directory's
|
|
716
775
|
first, then other directories', each row naming where it lives. A session from
|
|
@@ -50,7 +50,7 @@ Requires Python 3.11+. macOS and Linux. Update with `hx upgrade`.
|
|
|
50
50
|
|
|
51
51
|
### Credentials
|
|
52
52
|
|
|
53
|
-
HX reaches models over
|
|
53
|
+
HX reaches models over four routes. Which one serves a turn is decided by the
|
|
54
54
|
model id alone, so "what paid for that" is always answerable by reading it:
|
|
55
55
|
|
|
56
56
|
| Model id | Route | Billing |
|
|
@@ -58,8 +58,10 @@ model id alone, so "what paid for that" is always answerable by reading it:
|
|
|
58
58
|
| `anthropic/claude-sonnet-4.5`, `openai/gpt-5`, … | OpenRouter | per token, API key |
|
|
59
59
|
| `openai-codex/gpt-5.6-terra` | ChatGPT Plus/Pro | your subscription |
|
|
60
60
|
| `devin/swe-1-6`, `devin/claude-opus-5`, … | Devin | your subscription |
|
|
61
|
+
| `claude-code/claude-sonnet-5`, `claude-code/claude-opus-4-8`, … | Claude Pro/Max, via Claude Code | your subscription |
|
|
61
62
|
|
|
62
|
-
Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route
|
|
63
|
+
Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route - except
|
|
64
|
+
Claude Code's, which stays with Claude Code.
|
|
63
65
|
|
|
64
66
|
**OpenRouter.** On first run HX asks for a key. Get one at
|
|
65
67
|
<https://openrouter.ai/keys>. Change it later with `/configure` in the TUI, or:
|
|
@@ -183,6 +185,47 @@ changes what a turn costs or how much fits. Router models such as
|
|
|
183
185
|
`devin/adaptive` are resolved to a concrete model on every turn by Devin itself.
|
|
184
186
|
If the list cannot be fetched, `devin/swe-1-6` and `devin/swe-1-6-fast` stand in.
|
|
185
187
|
|
|
188
|
+
**Claude Pro/Max.** HX runs Claude models on your Claude subscription through
|
|
189
|
+
the official [Claude Code](https://claude.com/claude-code) CLI, the way the
|
|
190
|
+
Claude Agent SDK does. Install Claude Code, then:
|
|
191
|
+
|
|
192
|
+
```sh
|
|
193
|
+
hx auth login claude-code # Claude Code's own sign-in; or /login inside HX
|
|
194
|
+
hx --model claude-code/claude-sonnet-5
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
HX holds no Claude token. The login is Claude Code's, kept and refreshed where
|
|
198
|
+
Claude Code keeps it, so `hx auth logout claude-code` only tells you to run
|
|
199
|
+
`claude auth logout` - which signs out every Claude Code on the machine. A
|
|
200
|
+
headless machine can use a token from `claude setup-token` in
|
|
201
|
+
`CLAUDE_CODE_OAUTH_TOKEN`. A Console login (`claude auth login --console`) bills
|
|
202
|
+
per token and is refused: this route is the subscription.
|
|
203
|
+
|
|
204
|
+
How a turn works: HX starts `claude -p` with every one of Claude Code's own
|
|
205
|
+
tools switched off and serves HX's tools to it as an MCP server. The model's
|
|
206
|
+
tool calls are run, permission-checked and drawn by HX exactly as on every
|
|
207
|
+
other route; Claude Code holds each call open until HX answers it, however long
|
|
208
|
+
an approval takes. The system prompt is Claude Code's, with your AGENTS.md
|
|
209
|
+
files, skills index and `--append-system-prompt` text added. One Claude Code
|
|
210
|
+
process serves a conversation for as long as it can, so the prompt cache
|
|
211
|
+
carries from turn to turn; after an interrupt, a steer while a tool runs, a
|
|
212
|
+
compaction, `/model`, `/effort` or a resumed session, HX starts a new one on a
|
|
213
|
+
session rebuilt from its own transcript. That costs a second or so and a
|
|
214
|
+
partly cold cache, not the conversation.
|
|
215
|
+
|
|
216
|
+
Claude Code is started isolated from your Claude Code setup: no CLAUDE.md, no
|
|
217
|
+
hooks, no plugins or MCP servers from `~/.claude`, no auto-memory, no
|
|
218
|
+
auto-compaction (HX compacts its own transcript), and nothing saved to Claude
|
|
219
|
+
Code's session list. Variables that would move it off the subscription -
|
|
220
|
+
`ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK` and the
|
|
221
|
+
rest - are removed from its environment; its TLS settings are kept (see
|
|
222
|
+
*Behind a TLS-inspecting proxy* below). `HX_CLAUDE_CODE_PATH` points HX at a
|
|
223
|
+
`claude` other than the one on `PATH`.
|
|
224
|
+
|
|
225
|
+
`/model` offers the models Claude Code lists for the account, each with the
|
|
226
|
+
efforts it accepts for `/effort`; `/models refresh` asks again. When a usage
|
|
227
|
+
limit is reached, HX says which one and when it resets.
|
|
228
|
+
|
|
186
229
|
**Web search.** Optional, and the one credential that is not a model route.
|
|
187
230
|
It powers two tools:
|
|
188
231
|
|
|
@@ -192,7 +235,7 @@ It powers two tools:
|
|
|
192
235
|
| `WebFetch` | read up to 5 URLs, returned as markdown |
|
|
193
236
|
|
|
194
237
|
[Tavily](https://app.tavily.com) bills per search rather than per token, so it
|
|
195
|
-
works identically on
|
|
238
|
+
works identically on every route - including the subscription ones,
|
|
196
239
|
where a provider-native search tool is not on offer.
|
|
197
240
|
|
|
198
241
|
Get a key at <https://app.tavily.com> — the free tier is 1000 searches a month
|
|
@@ -246,9 +289,18 @@ startup every session, because it is not a fix. A TLS failure is reported in
|
|
|
246
289
|
one line - `/model` and the startup notice both name the certificate problem
|
|
247
290
|
rather than reporting an empty catalogue with no cause.
|
|
248
291
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
292
|
+
The same settings cover the `claude-code` route, whose requests Claude Code
|
|
293
|
+
makes with its own TLS stack: HX passes `HX_CA_BUNDLE` on to it as
|
|
294
|
+
`NODE_EXTRA_CA_CERTS` (unless you already set that yourself), and
|
|
295
|
+
`HX_SSL_NO_VERIFY` as `NODE_TLS_REJECT_UNAUTHORIZED=0`. Claude Code reads the
|
|
296
|
+
OS trust store on its own, and its own TLS settings reach it untouched:
|
|
297
|
+
`CLAUDE_CODE_CERT_STORE`, and `CLAUDE_CODE_CLIENT_CERT`,
|
|
298
|
+
`CLAUDE_CODE_CLIENT_KEY` and `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE` for a network
|
|
299
|
+
that requires a client certificate.
|
|
300
|
+
|
|
301
|
+
HX never sends a Claude subscription token to the API itself - Anthropic
|
|
302
|
+
accepts those only from Claude Code, and HX will not impersonate it. The
|
|
303
|
+
`claude-code` route runs the real Claude Code instead.
|
|
252
304
|
|
|
253
305
|
---
|
|
254
306
|
|
|
@@ -274,7 +326,7 @@ hx mcp list|add|remove # manage MCP servers
|
|
|
274
326
|
hx mcp login <server> # sign in to a remote MCP server in the browser
|
|
275
327
|
hx mcp logout <server> # forget a remote MCP server's sign-in
|
|
276
328
|
hx auth [set|clear] # manage the OpenRouter key
|
|
277
|
-
hx auth login [provider] # sign in (openrouter, openai-codex, devin)
|
|
329
|
+
hx auth login [provider] # sign in (openrouter, openai-codex, devin, claude-code)
|
|
278
330
|
hx auth logout <provider> # forget a stored credential
|
|
279
331
|
hx docs [section] # the manual; no argument lists its sections
|
|
280
332
|
hx changelog [version] # what shipped in each version
|
|
@@ -669,8 +721,14 @@ After the first exchange, HX asks the model for a short name for the session and
|
|
|
669
721
|
writes it to `~/.hx/sessions/<id>/meta.json`, so `/resume` lists work rather
|
|
670
722
|
than timestamps. It is one small call — cap 32 output tokens, `models.title_model`
|
|
671
723
|
if you want a cheaper model for it — and it is counted in `/cost` like any other.
|
|
672
|
-
If the call fails the session is still named,
|
|
673
|
-
|
|
724
|
+
If the call fails, or you leave before it is back, the session is still named,
|
|
725
|
+
from your first message.
|
|
726
|
+
|
|
727
|
+
The live session is never renamed while it runs, and nothing is renamed on
|
|
728
|
+
`/exit`, which returns your shell at once. A session you leave with `/clear` or
|
|
729
|
+
`/resume` - including a `/resume` into another directory - is renamed in the
|
|
730
|
+
background for what it became. `/title <text>` renames it yourself, and a name
|
|
731
|
+
you chose is never replaced.
|
|
674
732
|
|
|
675
733
|
`/resume` lists every session except the one you are in: this directory's
|
|
676
734
|
first, then other directories', each row naming where it lives. A session from
|
|
@@ -55,6 +55,9 @@ dev = [
|
|
|
55
55
|
# A VT emulator, so tests can assert what the differ actually paints rather
|
|
56
56
|
# than what it claims to have written.
|
|
57
57
|
"pyte>=0.8",
|
|
58
|
+
# A throwaway CA, so the TLS tests can stand up the intercepting proxy a
|
|
59
|
+
# corporate network puts between HX and the API.
|
|
60
|
+
"trustme>=1.2",
|
|
58
61
|
]
|
|
59
62
|
|
|
60
63
|
[project.scripts]
|
|
@@ -107,7 +107,13 @@ class SubagentRunner:
|
|
|
107
107
|
try:
|
|
108
108
|
loop = self._build_loop(definition, subagent_id)
|
|
109
109
|
result = await loop.run(prompt)
|
|
110
|
-
report = _final_text(result)
|
|
110
|
+
report = _final_text(result)
|
|
111
|
+
if result.error is not None:
|
|
112
|
+
# The parent model only sees the report: without the reason it
|
|
113
|
+
# cannot tell a refused or broken run from one with nothing to say.
|
|
114
|
+
failure = f"The subagent failed: {result.error}"
|
|
115
|
+
report = f"{report}\n\n{failure}" if report else failure
|
|
116
|
+
report = report or "(the subagent returned no text)"
|
|
111
117
|
cost = loop.session.usage.total_cost_usd
|
|
112
118
|
self._roll_up_cost(loop)
|
|
113
119
|
return SubagentResult(
|