hx-cli 0.2.12__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.
Files changed (158) hide show
  1. {hx_cli-0.2.12 → hx_cli-0.2.13}/CHANGELOG.md +41 -1
  2. {hx_cli-0.2.12 → hx_cli-0.2.13}/PKG-INFO +61 -8
  3. {hx_cli-0.2.12 → hx_cli-0.2.13}/README.md +59 -7
  4. {hx_cli-0.2.12 → hx_cli-0.2.13}/pyproject.toml +3 -0
  5. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/__init__.py +1 -1
  6. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/agents/subagent.py +7 -1
  7. hx_cli-0.2.13/src/hx/auth/claude_code.py +369 -0
  8. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/resolve.py +45 -1
  9. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/cli.py +30 -1
  10. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/net.py +62 -6
  11. hx_cli-0.2.13/src/hx/providers/claude_code.py +903 -0
  12. hx_cli-0.2.13/src/hx/providers/claude_code_catalogue.py +140 -0
  13. hx_cli-0.2.13/src/hx/providers/claude_code_cli.py +414 -0
  14. hx_cli-0.2.13/src/hx/providers/claude_code_transcript.py +433 -0
  15. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/models.py +43 -3
  16. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/registry.py +41 -0
  17. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/glob.py +25 -6
  18. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/commands.py +45 -0
  19. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/runtime.py +22 -4
  20. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/status.py +17 -6
  21. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/conftest.py +78 -0
  22. hx_cli-0.2.13/tests/e2e/fake_claude.py +518 -0
  23. hx_cli-0.2.13/tests/e2e/test_claude_code.py +849 -0
  24. hx_cli-0.2.13/tests/e2e/test_queue.py +156 -0
  25. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/test_tools.py +27 -0
  26. hx_cli-0.2.13/tests/test_live_claude_code.py +350 -0
  27. hx_cli-0.2.13/tests/tls_proxy.py +222 -0
  28. {hx_cli-0.2.12 → hx_cli-0.2.13}/uv.lock +191 -0
  29. {hx_cli-0.2.12 → hx_cli-0.2.13}/.github/workflows/ci.yml +0 -0
  30. {hx_cli-0.2.12 → hx_cli-0.2.13}/.gitignore +0 -0
  31. {hx_cli-0.2.12 → hx_cli-0.2.13}/examples/hooks/check.sh +0 -0
  32. {hx_cli-0.2.12 → hx_cli-0.2.13}/install.sh +0 -0
  33. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/agents/__init__.py +0 -0
  34. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/agents/definitions.py +0 -0
  35. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/__init__.py +0 -0
  36. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/oauth/__init__.py +0 -0
  37. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/oauth/browser.py +0 -0
  38. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/oauth/callback.py +0 -0
  39. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/oauth/codex.py +0 -0
  40. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/oauth/devin.py +0 -0
  41. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/oauth/pkce.py +0 -0
  42. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/auth/store.py +0 -0
  43. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/config.py +0 -0
  44. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/__init__.py +0 -0
  45. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/checkpoints.py +0 -0
  46. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/compaction.py +0 -0
  47. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/context.py +0 -0
  48. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/events.py +0 -0
  49. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/images.py +0 -0
  50. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/lateinject.py +0 -0
  51. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/loop.py +0 -0
  52. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/messages.py +0 -0
  53. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/session.py +0 -0
  54. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/title.py +0 -0
  55. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/core/usage.py +0 -0
  56. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/docs.py +0 -0
  57. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/frontmatter.py +0 -0
  58. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/git.py +0 -0
  59. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/hooks/__init__.py +0 -0
  60. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/hooks/engine.py +0 -0
  61. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/hooks/spec.py +0 -0
  62. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/keys.py +0 -0
  63. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/mcp/__init__.py +0 -0
  64. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/mcp/client.py +0 -0
  65. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/mcp/manager.py +0 -0
  66. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/mcp/oauth.py +0 -0
  67. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/paths.py +0 -0
  68. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/permissions/__init__.py +0 -0
  69. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/permissions/engine.py +0 -0
  70. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/permissions/parser.py +0 -0
  71. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/permissions/sandbox.py +0 -0
  72. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/__init__.py +0 -0
  73. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/base.py +0 -0
  74. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/codex.py +0 -0
  75. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/codex_catalogue.py +0 -0
  76. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/devin.py +0 -0
  77. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/devin_catalogue.py +0 -0
  78. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/devin_wire.py +0 -0
  79. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/openrouter.py +0 -0
  80. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/protowire.py +0 -0
  81. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/providers/responses_codec.py +0 -0
  82. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/skills/__init__.py +0 -0
  83. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/skills/loader.py +0 -0
  84. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/skills/runtime.py +0 -0
  85. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/__init__.py +0 -0
  86. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/ansi.py +0 -0
  87. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/buffer.py +0 -0
  88. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/component.py +0 -0
  89. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/demo.py +0 -0
  90. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/editor.py +0 -0
  91. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/keydecode.py +0 -0
  92. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/loop.py +0 -0
  93. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/markdown.py +0 -0
  94. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/modifiers.py +0 -0
  95. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/primitives.py +0 -0
  96. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/sanitize.py +0 -0
  97. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/screen.py +0 -0
  98. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/syntax.py +0 -0
  99. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/terminal.py +0 -0
  100. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/undo.py +0 -0
  101. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/width.py +0 -0
  102. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/term/word_nav.py +0 -0
  103. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/__init__.py +0 -0
  104. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/anchors.py +0 -0
  105. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/base.py +0 -0
  106. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/bash.py +0 -0
  107. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/edit.py +0 -0
  108. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/grep.py +0 -0
  109. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/output.py +0 -0
  110. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/read.py +0 -0
  111. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/registry.py +0 -0
  112. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/symbols.py +0 -0
  113. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/task.py +0 -0
  114. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/todo.py +0 -0
  115. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/websearch.py +0 -0
  116. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tools/write.py +0 -0
  117. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/trace.py +0 -0
  118. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/__init__.py +0 -0
  119. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/app.py +0 -0
  120. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/clipboard.py +0 -0
  121. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/format.py +0 -0
  122. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/fuzzy.py +0 -0
  123. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/glyphs.py +0 -0
  124. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/killring.py +0 -0
  125. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/limits.py +0 -0
  126. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/paint.py +0 -0
  127. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/renderers.py +0 -0
  128. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/roles.py +0 -0
  129. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/theme.py +0 -0
  130. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/theme_json.py +0 -0
  131. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/themes/ansi.json +0 -0
  132. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/themes/dark.json +0 -0
  133. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/themes/light.json +0 -0
  134. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/__init__.py +0 -0
  135. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/blocks.py +0 -0
  136. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/dialog.py +0 -0
  137. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/login.py +0 -0
  138. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/permission.py +0 -0
  139. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/pickers.py +0 -0
  140. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/prompt.py +0 -0
  141. {hx_cli-0.2.12 → hx_cli-0.2.13}/src/hx/tui/views/transcript.py +0 -0
  142. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/__init__.py +0 -0
  143. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/conftest.py +0 -0
  144. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/__init__.py +0 -0
  145. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/fixtures/echo_server.py +0 -0
  146. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/report.py +0 -0
  147. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/stub.py +0 -0
  148. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/terminal.py +0 -0
  149. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/test_chat.py +0 -0
  150. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/test_cli.py +0 -0
  151. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/test_commands.py +0 -0
  152. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/test_extend.py +0 -0
  153. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/test_input.py +0 -0
  154. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/e2e/test_print.py +0 -0
  155. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/test_live.py +0 -0
  156. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/test_live_codex.py +0 -0
  157. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/test_live_devin.py +0 -0
  158. {hx_cli-0.2.12 → hx_cli-0.2.13}/tests/test_live_tavily.py +0 -0
@@ -28,6 +28,45 @@ 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
+
31
70
  ## [0.2.12] - 2026-10-01
32
71
 
33
72
  ### Fixed
@@ -794,7 +833,8 @@ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/)
794
833
  - `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
795
834
  self-update, and `install.sh` bootstrapping uv with a pinned Python.
796
835
 
797
- [Unreleased]: https://github.com/aletisunil/hx/compare/v0.2.12...HEAD
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
798
838
  [0.2.12]: https://github.com/aletisunil/hx/compare/v0.2.11...v0.2.12
799
839
  [0.2.11]: https://github.com/aletisunil/hx/compare/v0.2.10...v0.2.11
800
840
  [0.2.10]: https://github.com/aletisunil/hx/compare/v0.2.9...v0.2.10
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hx-cli
3
- Version: 0.2.12
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 three routes. Which one serves a turn is decided by the
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 both routes — including the ChatGPT subscription one,
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
- There is no Claude Pro/Max route. Anthropic rejects OAuth tokens unless the
290
- request impersonates Claude Code, which HX will not do. Claude models stay
291
- available through OpenRouter.
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
@@ -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 three routes. Which one serves a turn is decided by the
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 both routes — including the ChatGPT subscription one,
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
- There is no Claude Pro/Max route. Anthropic rejects OAuth tokens unless the
250
- request impersonates Claude Code, which HX will not do. Claude models stay
251
- available through OpenRouter.
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
@@ -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]
@@ -1,5 +1,5 @@
1
1
  """HX - an agent harness for the terminal."""
2
2
 
3
- __version__ = "0.2.12"
3
+ __version__ = "0.2.13"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -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) or "(the subagent returned no text)"
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(