hx-cli 0.1.8__tar.gz → 0.2.0__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 (235) hide show
  1. {hx_cli-0.1.8 → hx_cli-0.2.0}/.github/workflows/ci.yml +10 -2
  2. {hx_cli-0.1.8 → hx_cli-0.2.0}/CHANGELOG.md +159 -1
  3. {hx_cli-0.1.8 → hx_cli-0.2.0}/PKG-INFO +175 -37
  4. {hx_cli-0.1.8 → hx_cli-0.2.0}/README.md +168 -32
  5. {hx_cli-0.1.8 → hx_cli-0.2.0}/install.sh +71 -28
  6. {hx_cli-0.1.8 → hx_cli-0.2.0}/pyproject.toml +13 -5
  7. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/__init__.py +1 -1
  8. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/codex.py +63 -2
  9. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/cli.py +87 -7
  10. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/config.py +223 -2
  11. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/context.py +5 -7
  12. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/loop.py +15 -7
  13. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/session.py +106 -4
  14. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/git.py +116 -0
  15. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/keys.py +58 -39
  16. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/paths.py +69 -9
  17. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/engine.py +39 -35
  18. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/codex.py +40 -4
  19. hx_cli-0.2.0/src/hx/providers/codex_catalogue.py +203 -0
  20. hx_cli-0.2.0/src/hx/providers/models.py +562 -0
  21. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/registry.py +42 -5
  22. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/responses_codec.py +11 -4
  23. hx_cli-0.2.0/src/hx/term/__init__.py +18 -0
  24. hx_cli-0.2.0/src/hx/term/ansi.py +391 -0
  25. hx_cli-0.2.0/src/hx/term/buffer.py +145 -0
  26. hx_cli-0.2.0/src/hx/term/component.py +141 -0
  27. hx_cli-0.2.0/src/hx/term/demo.py +150 -0
  28. hx_cli-0.2.0/src/hx/term/editor.py +283 -0
  29. hx_cli-0.2.0/src/hx/term/keydecode.py +296 -0
  30. hx_cli-0.2.0/src/hx/term/loop.py +247 -0
  31. hx_cli-0.2.0/src/hx/term/markdown.py +291 -0
  32. hx_cli-0.2.0/src/hx/term/primitives.py +315 -0
  33. hx_cli-0.2.0/src/hx/term/sanitize.py +88 -0
  34. hx_cli-0.2.0/src/hx/term/screen.py +525 -0
  35. hx_cli-0.2.0/src/hx/term/syntax.py +102 -0
  36. hx_cli-0.2.0/src/hx/term/terminal.py +457 -0
  37. hx_cli-0.2.0/src/hx/term/undo.py +63 -0
  38. hx_cli-0.2.0/src/hx/term/width.py +199 -0
  39. hx_cli-0.2.0/src/hx/term/word_nav.py +33 -0
  40. hx_cli-0.2.0/src/hx/tui/__init__.py +7 -0
  41. hx_cli-0.2.0/src/hx/tui/app.py +56 -0
  42. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/commands.py +339 -57
  43. hx_cli-0.2.0/src/hx/tui/format.py +70 -0
  44. hx_cli-0.2.0/src/hx/tui/glyphs.py +63 -0
  45. hx_cli-0.2.0/src/hx/tui/limits.py +40 -0
  46. hx_cli-0.2.0/src/hx/tui/paint.py +109 -0
  47. hx_cli-0.2.0/src/hx/tui/renderers.py +658 -0
  48. hx_cli-0.2.0/src/hx/tui/runtime.py +1148 -0
  49. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/theme.py +6 -46
  50. hx_cli-0.2.0/src/hx/tui/views/__init__.py +14 -0
  51. hx_cli-0.2.0/src/hx/tui/views/blocks.py +267 -0
  52. hx_cli-0.2.0/src/hx/tui/views/dialog.py +183 -0
  53. hx_cli-0.2.0/src/hx/tui/views/login.py +259 -0
  54. hx_cli-0.2.0/src/hx/tui/views/permission.py +286 -0
  55. hx_cli-0.2.0/src/hx/tui/views/pickers.py +419 -0
  56. hx_cli-0.2.0/src/hx/tui/views/prompt.py +497 -0
  57. hx_cli-0.2.0/src/hx/tui/views/status.py +326 -0
  58. hx_cli-0.2.0/src/hx/tui/views/transcript.py +342 -0
  59. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_codex_oauth.py +34 -0
  60. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/conftest.py +8 -2
  61. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_context.py +13 -0
  62. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_loop.py +0 -32
  63. hx_cli-0.2.0/tests/core/test_session.py +194 -0
  64. hx_cli-0.2.0/tests/core/test_settings_layers.py +67 -0
  65. hx_cli-0.2.0/tests/core/test_settings_migration.py +152 -0
  66. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/test_engine.py +72 -55
  67. hx_cli-0.2.0/tests/providers/test_catalogue.py +290 -0
  68. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_codex.py +33 -0
  69. hx_cli-0.2.0/tests/providers/test_codex_catalogue.py +198 -0
  70. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_responses_codec.py +26 -2
  71. hx_cli-0.2.0/tests/term/conftest.py +59 -0
  72. hx_cli-0.2.0/tests/term/test_ansi.py +190 -0
  73. hx_cli-0.2.0/tests/term/test_keydecode.py +233 -0
  74. hx_cli-0.2.0/tests/term/test_loop.py +147 -0
  75. hx_cli-0.2.0/tests/term/test_markdown.py +198 -0
  76. hx_cli-0.2.0/tests/term/test_primitives.py +206 -0
  77. hx_cli-0.2.0/tests/term/test_sanitize.py +77 -0
  78. hx_cli-0.2.0/tests/term/test_screen.py +673 -0
  79. hx_cli-0.2.0/tests/term/test_terminal.py +235 -0
  80. hx_cli-0.2.0/tests/term/test_width.py +109 -0
  81. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_git.py +97 -0
  82. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_install.py +3 -1
  83. hx_cli-0.2.0/tests/test_paths.py +54 -0
  84. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_readme.py +58 -8
  85. hx_cli-0.2.0/tests/tui/__init__.py +0 -0
  86. hx_cli-0.2.0/tests/tui/support.py +146 -0
  87. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_autocomplete.py +37 -28
  88. hx_cli-0.2.0/tests/tui/test_effort.py +167 -0
  89. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_keys.py +55 -6
  90. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_login.py +120 -115
  91. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_renderers.py +52 -30
  92. hx_cli-0.2.0/tests/tui/test_session.py +1123 -0
  93. hx_cli-0.2.0/tests/tui/views/__init__.py +0 -0
  94. hx_cli-0.2.0/tests/tui/views/test_blocks.py +247 -0
  95. hx_cli-0.2.0/tests/tui/views/test_permission.py +328 -0
  96. hx_cli-0.2.0/tests/tui/views/test_pickers.py +314 -0
  97. hx_cli-0.2.0/tests/tui/views/test_prompt.py +374 -0
  98. hx_cli-0.2.0/tests/tui/views/test_runtime.py +492 -0
  99. hx_cli-0.2.0/tests/tui/views/test_status.py +197 -0
  100. hx_cli-0.2.0/tests/tui/views/test_transcript.py +211 -0
  101. hx_cli-0.2.0/uv.lock +946 -0
  102. hx_cli-0.1.8/src/hx/providers/models.py +0 -334
  103. hx_cli-0.1.8/src/hx/tui/__init__.py +0 -1
  104. hx_cli-0.1.8/src/hx/tui/app.py +0 -977
  105. hx_cli-0.1.8/src/hx/tui/hx.tcss +0 -240
  106. hx_cli-0.1.8/src/hx/tui/renderers.py +0 -579
  107. hx_cli-0.1.8/src/hx/tui/widgets/__init__.py +0 -1
  108. hx_cli-0.1.8/src/hx/tui/widgets/autocomplete.py +0 -105
  109. hx_cli-0.1.8/src/hx/tui/widgets/configure.py +0 -95
  110. hx_cli-0.1.8/src/hx/tui/widgets/diff.py +0 -25
  111. hx_cli-0.1.8/src/hx/tui/widgets/frame.py +0 -61
  112. hx_cli-0.1.8/src/hx/tui/widgets/header.py +0 -74
  113. hx_cli-0.1.8/src/hx/tui/widgets/hints.py +0 -75
  114. hx_cli-0.1.8/src/hx/tui/widgets/input.py +0 -446
  115. hx_cli-0.1.8/src/hx/tui/widgets/login.py +0 -175
  116. hx_cli-0.1.8/src/hx/tui/widgets/palette.py +0 -254
  117. hx_cli-0.1.8/src/hx/tui/widgets/permission.py +0 -243
  118. hx_cli-0.1.8/src/hx/tui/widgets/rule.py +0 -26
  119. hx_cli-0.1.8/src/hx/tui/widgets/statusbar.py +0 -227
  120. hx_cli-0.1.8/src/hx/tui/widgets/todos.py +0 -116
  121. hx_cli-0.1.8/src/hx/tui/widgets/transcript.py +0 -475
  122. hx_cli-0.1.8/src/hx/tui/widgets/working.py +0 -151
  123. hx_cli-0.1.8/tests/core/test_session.py +0 -69
  124. hx_cli-0.1.8/tests/core/test_settings_layers.py +0 -37
  125. hx_cli-0.1.8/tests/providers/test_catalogue.py +0 -102
  126. hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_a_pending_permission_prompt.raw +0 -198
  127. hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_a_running_turn.raw +0 -192
  128. hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_a_turn_with_tool_blocks.raw +0 -194
  129. hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_the_completion_popup.raw +0 -191
  130. hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_the_idle_screen.raw +0 -190
  131. hx_cli-0.1.8/tests/tui/test_app.py +0 -1556
  132. hx_cli-0.1.8/tests/tui/test_frame.py +0 -82
  133. hx_cli-0.1.8/tests/tui/test_permission.py +0 -132
  134. hx_cli-0.1.8/tests/tui/test_snapshots.py +0 -159
  135. hx_cli-0.1.8/tests/tui/test_statusbar.py +0 -131
  136. hx_cli-0.1.8/tests/tui/test_transcript.py +0 -129
  137. hx_cli-0.1.8/uv.lock +0 -1816
  138. {hx_cli-0.1.8 → hx_cli-0.2.0}/.gitignore +0 -0
  139. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/agents/__init__.py +0 -0
  140. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/agents/definitions.py +0 -0
  141. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/agents/subagent.py +0 -0
  142. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/__init__.py +0 -0
  143. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/__init__.py +0 -0
  144. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/callback.py +0 -0
  145. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/pkce.py +0 -0
  146. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/resolve.py +0 -0
  147. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/store.py +0 -0
  148. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/__init__.py +0 -0
  149. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/checkpoints.py +0 -0
  150. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/compaction.py +0 -0
  151. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/events.py +0 -0
  152. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/lateinject.py +0 -0
  153. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/messages.py +0 -0
  154. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/title.py +0 -0
  155. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/usage.py +0 -0
  156. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/docs.py +0 -0
  157. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/frontmatter.py +0 -0
  158. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/mcp/__init__.py +0 -0
  159. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/mcp/client.py +0 -0
  160. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/mcp/manager.py +0 -0
  161. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/net.py +0 -0
  162. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/__init__.py +0 -0
  163. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/parser.py +0 -0
  164. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/sandbox.py +0 -0
  165. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/__init__.py +0 -0
  166. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/base.py +0 -0
  167. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/fake.py +0 -0
  168. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/openrouter.py +0 -0
  169. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/skills/__init__.py +0 -0
  170. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/skills/loader.py +0 -0
  171. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/skills/runtime.py +0 -0
  172. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/__init__.py +0 -0
  173. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/base.py +0 -0
  174. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/bash.py +0 -0
  175. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/edit.py +0 -0
  176. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/glob.py +0 -0
  177. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/grep.py +0 -0
  178. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/output.py +0 -0
  179. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/read.py +0 -0
  180. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/registry.py +0 -0
  181. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/task.py +0 -0
  182. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/todo.py +0 -0
  183. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/websearch.py +0 -0
  184. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/write.py +0 -0
  185. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/clipboard.py +0 -0
  186. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/fuzzy.py +0 -0
  187. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/killring.py +0 -0
  188. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/roles.py +0 -0
  189. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/theme_json.py +0 -0
  190. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/themes/ansi.json +0 -0
  191. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/themes/dark.json +0 -0
  192. {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/themes/light.json +0 -0
  193. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/__init__.py +0 -0
  194. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/agents/__init__.py +0 -0
  195. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/agents/test_subagent.py +0 -0
  196. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/__init__.py +0 -0
  197. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_pkce.py +0 -0
  198. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_resolve.py +0 -0
  199. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_store.py +0 -0
  200. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/__init__.py +0 -0
  201. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_checkpoints.py +0 -0
  202. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_compaction.py +0 -0
  203. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_lateinject.py +0 -0
  204. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_rewind.py +0 -0
  205. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_steering.py +0 -0
  206. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_title.py +0 -0
  207. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_usage.py +0 -0
  208. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/mcp/__init__.py +0 -0
  209. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/mcp/fixtures/echo_server.py +0 -0
  210. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/mcp/test_manager.py +0 -0
  211. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/__init__.py +0 -0
  212. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/test_parser.py +0 -0
  213. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/test_sandbox.py +0 -0
  214. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/__init__.py +0 -0
  215. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_openrouter.py +0 -0
  216. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_registry.py +0 -0
  217. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/skills/__init__.py +0 -0
  218. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/skills/test_loader.py +0 -0
  219. {hx_cli-0.1.8/tests/tools → hx_cli-0.2.0/tests/term}/__init__.py +0 -0
  220. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_cli.py +0 -0
  221. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_docs.py +0 -0
  222. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_integration.py +0 -0
  223. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_live.py +0 -0
  224. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_live_codex.py +0 -0
  225. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_live_tavily.py +0 -0
  226. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_net.py +0 -0
  227. {hx_cli-0.1.8/tests/tui → hx_cli-0.2.0/tests/tools}/__init__.py +0 -0
  228. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_background.py +0 -0
  229. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_bash.py +0 -0
  230. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_files.py +0 -0
  231. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_output.py +0 -0
  232. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_todo.py +0 -0
  233. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_websearch.py +0 -0
  234. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_clipboard.py +0 -0
  235. {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_theme_json.py +0 -0
@@ -21,8 +21,16 @@ jobs:
21
21
  strategy:
22
22
  fail-fast: false
23
23
  matrix:
24
- os: [ubuntu-latest, macos-latest]
25
- python: ["3.11", "3.12"]
24
+ # Every supported minor on Linux catches version bugs; macOS runs the
25
+ # floor and the ceiling only, to catch platform bugs without paying for
26
+ # the full OS x version grid.
27
+ os: [ubuntu-latest]
28
+ python: ["3.11", "3.12", "3.13", "3.14"]
29
+ include:
30
+ - os: macos-latest
31
+ python: "3.11"
32
+ - os: macos-latest
33
+ python: "3.14"
26
34
  runs-on: ${{ matrix.os }}
27
35
  steps:
28
36
  - uses: actions/checkout@v4
@@ -28,6 +28,162 @@ the project follows [semantic versioning](https://semver.org/).
28
28
 
29
29
  ---
30
30
 
31
+ ## [0.2.0] - 2026-09-14
32
+
33
+ ### Changed
34
+
35
+ - The interface is drawn into your terminal's own scrollback instead of taking
36
+ over the screen. Finished output belongs to the terminal: scroll it, select
37
+ it with the mouse, copy it, pipe it, and it is still there after HX exits.
38
+ `/fullscreen` takes the whole window back for anyone who preferred it that
39
+ way - the prompt pinned to the bottom row, the transcript scrolling above it
40
+ with `pgup`, `pgdn`, `ctrl+home` and `ctrl+end` - and writes the choice to
41
+ `tui.fullscreen` for the next session. On either screen the prompt and the
42
+ status bar sit on the bottom row from the first frame - a conversation that
43
+ does not fill the window is padded above the dock, not below it, and rows a
44
+ closing picker gives back open above it too. The slash-command and `@`-path
45
+ completion lists draw above the prompt for the same reason: under it, every
46
+ keystroke that changed the number of matches moved the prompt and the status
47
+ bar with it. Exiting erases the whole interface - banner, conversation and
48
+ dock - and puts the shell prompt back on the row HX started on, rather than
49
+ leaving a dead prompt box and a status bar on screen above it, from either
50
+ screen: quitting out of `/fullscreen` also takes away the frame that was
51
+ parked underneath it.
52
+ - Approving a tool call is now a framed prompt with one option per line, each
53
+ saying what it does - `always allow` states that it writes a rule to
54
+ `.hx/settings.local.json`. `y`/`s`/`a`/`n` still answer in one keystroke,
55
+ `esc` denies, and the arrow keys work for anyone who wants to read first.
56
+ The command shown is the one that will run, `$` and all. When two subagents
57
+ stop at an approval at once, the older one holds the keyboard and the other
58
+ says it is waiting rather than offering keys that would answer the first.
59
+ - Your theme reaches the whole interface. Links, list bullets, block quotes,
60
+ horizontal rules and inline code in the model's replies now follow the
61
+ palette instead of a hard-coded set of colours, and headings are visibly
62
+ different from each other.
63
+ - Multi-line output from `/help`, `/cost`, `/status` and the rest lines up
64
+ under its own first line instead of at two different indents.
65
+ - The todo list is a block in the conversation rather than a sidebar, and
66
+ `/todos` and `ctrl+t` show it. A scrollback-native interface has no second
67
+ column to put a sidebar in.
68
+ - Mouse selection is the terminal's, across the whole session including what
69
+ has scrolled off, rather than HX's own within the visible screen.
70
+ - Text HX did not write - a file a tool read, a command's output, the model's
71
+ reply, a paste into the prompt - is drawn as plain text. Escape sequences in
72
+ it are removed rather than sent on to your terminal, so a file cannot set
73
+ your clipboard, rename your window, hide a link behind different words, or
74
+ erase what is already on screen by being read.
75
+ - `/clear` now clears the screen and the scrollback above it, instead of
76
+ emptying the transcript and leaving the last session on screen. Terminals
77
+ that do not implement scrollback erasure - macOS Terminal.app - clear the
78
+ screen only.
79
+ - `/model`, the command palette and every other picker are sized to the
80
+ terminal rather than to a fixed eight rows, so a tall window shows a long
81
+ list instead of leaving two thirds of itself empty.
82
+ - `ctrl+z` suspends HX to the background and resuming redraws it; `ctrl+home`
83
+ and `ctrl+end` reach the ends of the conversation; `ctrl+_` undoes in the
84
+ prompt. All three were listed in `/help` and bound to nothing.
85
+ - `ctrl+p` and `ctrl+l` are named in the hints line under the prompt, which is
86
+ where the keys a session offers are supposed to be discoverable.
87
+ - A block drawn against the prompt no longer closes itself with a second rule,
88
+ which left a rule, a blank line and another rule under every picker and under
89
+ every approval. Whichever block ends up against the dock lets the dock's own
90
+ rule close it, and takes its own back as soon as something lands underneath.
91
+ - The installer now opens with a terminal boot animation that draws and lights
92
+ the HX mark, with a clean static version for CI and redirected output.
93
+
94
+ ---
95
+
96
+ ## [0.1.9] - 2026-09-11
97
+
98
+ ### Changed
99
+
100
+ - Project instructions live in `AGENTS.md`, the cross-agent standard, instead
101
+ of `HX.md`. A repo already carrying an `AGENTS.md` for another coding agent
102
+ is understood by HX with no extra file; `/init` writes one. `HX.md` and
103
+ `.hx/HX.md` are no longer read - rename yours.
104
+ - Python 3.13 and 3.14 are supported in fact, not just by the `>=3.11` floor.
105
+ CI runs the suite on every supported minor, so installing on a current
106
+ interpreter is covered by a test run rather than by hope.
107
+
108
+ ### Fixed
109
+
110
+ - `/model` lists the Codex models your ChatGPT account can actually run. HX
111
+ shipped a hard-coded pair — `gpt-5.3-codex` and its spark — that the backend
112
+ now refuses outright, so a paid subscription offered two models and both
113
+ failed. Signing in fetches the account's own list instead; `/models refresh`
114
+ asks again, and the last shipped list stands in when the fetch cannot happen.
115
+ - A Codex model that the account is refused now says so in terms you can act
116
+ on. The backend answers with a 400 naming the model, which reads as a bad
117
+ model id. The message now says entitlement is per model and per account, and
118
+ points at `/models refresh` to get the list that is true for your account.
119
+ - `hx auth` names the plan behind a ChatGPT login (`signed in (plus)`), and
120
+ `/login` says when it is a free one, since Codex is documented as needing a
121
+ paid plan. Neither blocks a model: entitlement is per model, and the fetched
122
+ catalogue is what decides it, so HX reports the plan and lets the backend
123
+ answer.
124
+ - `/model` says what pays for each model. Subscription models showed
125
+ `$0.00/$0.00`, indistinguishable from a free OpenRouter one; they now read
126
+ `subscription`. The status bar tags them too — `gpt-5.6-terra (sub)`.
127
+ - `/resume` counts prompts, not wire-format records. A session you sent one
128
+ prompt to listed as "31 msgs"; it now reads `1 prompt · 31 msgs`, and `/cost`
129
+ says "17 API requests across 1 prompt" so the two numbers reconcile.
130
+ - The status bar follows the branch. It read `git HEAD` once at startup, so
131
+ checking out in an IDE or another terminal left it showing a branch you had
132
+ left; it now re-reads on a timer, worktrees included.
133
+ - Selecting assistant prose or a tool block with the mouse copies it. Textual
134
+ can only extract text from widgets that render plain text, so dragging over
135
+ those blocks highlighted them and copied nothing.
136
+ - Copying goes through `pbcopy`/`wl-copy`/`xclip` before OSC 52. Textual's own
137
+ copy is OSC 52 only, which macOS Terminal ignores and iTerm2 ships disabled,
138
+ so `ctrl+c` on a selection silently did nothing there.
139
+ - The prompt names the steer key the way your keyboard does. It read
140
+ `alt+enter` everywhere, while `/help` two keystrokes away read `option+enter`
141
+ on macOS - the same key, spelled two ways in one window. It now comes from
142
+ the keybinding registry, so it follows the platform and follows a rebind.
143
+ - `ctrl+c` reaches the copy at all. The prompt holds focus for the whole session
144
+ and binds that key itself, so a transcript selection was never what it
145
+ copied. It now copies the selection when there is one, and clears the prompt
146
+ when there is not, exactly as before.
147
+
148
+ ### Added
149
+
150
+ - `models.codex_models` in settings adds Codex model ids to the `/model`
151
+ picker, on top of the ones fetched for your account — for an id the backend
152
+ serves but does not advertise, or a machine that cannot reach it.
153
+ `{"models": {"codex_models": ["gpt-5.6-terra"]}}` adds your own.
154
+ - `/effort` picks how hard the current model thinks, from the levels that model
155
+ actually offers, and the status bar shows the depth in force next to the
156
+ model — `gpt-5.6-terra (sub) · high`. It applies to the next turn and is
157
+ saved for later sessions. Switching models re-resolves it, so a choice the
158
+ new model cannot reach shows as the depth it will really run at.
159
+ - `models.reasoning_effort` sets how hard a reasoning model thinks —
160
+ `none` through `ultra`. Left unset, each Codex model runs at the default its
161
+ own catalogue entry names (`low` on GPT-6 Astra, `medium` on GPT-5.6 Terra)
162
+ instead of one fixed depth for every model. A value is clamped to what the
163
+ chosen model advertises, so `max` runs at `xhigh` on GPT-5.5 rather than
164
+ being refused.
165
+ - `/mouse [on|off]` hands drag-selection back to your terminal, for when you
166
+ want its own selection rather than HX's. Holding alt/option while dragging
167
+ does the same thing without the toggle.
168
+
169
+ ### Changed
170
+
171
+ - "Always allow" grants move out of your repository. They were written to
172
+ `./.hx/settings.local.json`, with a `./.hx/.gitignore` added to hide them —
173
+ two files of HX's in your checkout. They now live in
174
+ `~/.hx/projects/<project>/settings.local.json`, still scoped to the project,
175
+ and an existing local file is migrated on the next run.
176
+ - Grants that much older versions appended to `./.hx/settings.json` — the file
177
+ a project checks in — are lifted into that same local file on the next run,
178
+ and HX says which ones it moved. Only `allow` moves; a project's `deny` and
179
+ `ask` stay shared. It happens once, so a rule you later write there by hand
180
+ is left alone, and an emptied `./.hx` is removed.
181
+ - HX no longer rewrites `./.hx/settings.json` at all. Its one remaining writer
182
+ there, the startup pass that widens stale whole-command rules, now reports
183
+ them and leaves the file to its owner.
184
+
185
+ ---
186
+
31
187
  ## [0.1.8] - 2026-09-10
32
188
 
33
189
  ### Fixed
@@ -234,7 +390,9 @@ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/)
234
390
  - `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
235
391
  self-update, and `install.sh` bootstrapping uv with a pinned Python.
236
392
 
237
- [Unreleased]: https://github.com/aletisunil/hx/compare/v0.1.8...HEAD
393
+ [Unreleased]: https://github.com/aletisunil/hx/compare/v0.2.0...HEAD
394
+ [0.2.0]: https://github.com/aletisunil/hx/compare/v0.1.9...v0.2.0
395
+ [0.1.9]: https://github.com/aletisunil/hx/compare/v0.1.8...v0.1.9
238
396
  [0.1.8]: https://github.com/aletisunil/hx/compare/v0.1.7...v0.1.8
239
397
  [0.1.7]: https://github.com/aletisunil/hx/compare/v0.1.6...v0.1.7
240
398
  [0.1.6]: https://github.com/aletisunil/hx/compare/v0.1.5...v0.1.6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hx-cli
3
- Version: 0.1.8
3
+ Version: 0.2.0
4
4
  Summary: HX - a terminal coding agent
5
5
  Project-URL: Homepage, https://github.com/aletisunil/hx
6
6
  Project-URL: Issues, https://github.com/aletisunil/hx/issues
@@ -11,29 +11,32 @@ Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Environment :: Console
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
14
16
  Requires-Python: >=3.11
15
17
  Requires-Dist: click>=8.1
16
18
  Requires-Dist: httpx>=0.27
19
+ Requires-Dist: markdown-it-py>=3.0
17
20
  Requires-Dist: pydantic>=2.7
21
+ Requires-Dist: pygments>=2.17
18
22
  Requires-Dist: pyyaml>=6.0
19
- Requires-Dist: rich>=13.7
20
- Requires-Dist: textual>=0.85
21
23
  Requires-Dist: truststore>=0.10
24
+ Requires-Dist: wcwidth>=0.2.13
22
25
  Provides-Extra: dev
23
26
  Requires-Dist: mypy>=1.11; extra == 'dev'
27
+ Requires-Dist: pyte>=0.8; extra == 'dev'
24
28
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
25
29
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
26
- Requires-Dist: pytest-textual-snapshot>=1.0; extra == 'dev'
27
30
  Requires-Dist: pytest>=8.2; extra == 'dev'
28
31
  Requires-Dist: ruff>=0.6; extra == 'dev'
29
- Requires-Dist: textual-dev>=1.5; extra == 'dev'
30
32
  Requires-Dist: types-pygments; extra == 'dev'
31
33
  Requires-Dist: types-pyyaml; extra == 'dev'
32
34
  Description-Content-Type: text/markdown
33
35
 
34
36
  # HX
35
37
 
36
- A terminal coding agent. Python core, Textual TUI, OpenRouter for models.
38
+ A terminal coding agent. Python core, its own terminal renderer, OpenRouter for
39
+ models.
37
40
 
38
41
  HX runs in your project directory, reads and edits your code, runs commands in
39
42
  a sandboxed shell, and shows you what every turn costs.
@@ -88,7 +91,7 @@ model id alone, so "what paid for that" is always answerable by reading it:
88
91
  | Model id | Route | Billing |
89
92
  |---|---|---|
90
93
  | `anthropic/claude-sonnet-4.5`, `openai/gpt-5`, … | OpenRouter | per token, API key |
91
- | `openai-codex/gpt-5.3-codex` | ChatGPT Plus/Pro | your subscription |
94
+ | `openai-codex/gpt-5.6-terra` | ChatGPT Plus/Pro | your subscription |
92
95
 
93
96
  Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route.
94
97
 
@@ -111,7 +114,7 @@ subscription instead of paying per token
111
114
 
112
115
  ```sh
113
116
  hx auth login openai-codex # or /login inside the TUI
114
- hx --model openai-codex/gpt-5.3-codex
117
+ hx --model openai-codex/gpt-5.6-terra
115
118
  hx auth logout openai-codex
116
119
  ```
117
120
 
@@ -121,6 +124,67 @@ URL into the prompt instead — or set `HX_LOGIN_DEVICE_CODE=1` for the
121
124
  device-code flow. Access tokens are refreshed automatically; `/model` only
122
125
  offers routes you are actually signed in to.
123
126
 
127
+ `hx auth` names the account's plan next to the login, because "which account is
128
+ this and what is it paying for" is otherwise a JWT to decode by hand:
129
+
130
+ ```sh
131
+ hx auth
132
+ # openrouter key sk-or-…8abd ~/.hx/auth.json
133
+ # openai-codex signed in (plus) ~/.hx/auth.json
134
+ ```
135
+
136
+ Codex is documented as requiring a paid ChatGPT plan, and HX says so when the
137
+ login is on a free one. It does not refuse to try: the plan on the credential
138
+ has been observed deciding nothing — the backend refuses every Codex model on a
139
+ `plus` account with the same error it gives a `free` one — so the switch is
140
+ allowed and the backend's own answer is what reports the problem.
141
+
142
+ **If every Codex model is refused**, the error says so in those terms: it is
143
+ the account that is being refused, not the model, and a different model id will
144
+ not help. Check that the subscription is active and Codex is enabled for the
145
+ account at <https://chatgpt.com/codex>, and that the official Codex CLI can
146
+ reach it at all.
147
+
148
+ **Which Codex models you get** is decided by your ChatGPT account, not by HX.
149
+ Signing in fetches the account's own list from the Codex backend, and `/model`
150
+ offers exactly that; `/models refresh` asks again. Entitlement is per model —
151
+ the same `plus` account can run `gpt-5.6-sol` and be refused `gpt-5.3-codex` —
152
+ so a model the picker does not list is one the subscription does not cover.
153
+
154
+ **How hard they think** comes from the same fetch. Each Codex model publishes
155
+ its own reasoning levels and its own default — GPT-6 Astra starts at `low`,
156
+ GPT-5.6 Terra at `medium` — and HX asks for the model's default rather than one
157
+ fixed depth. `/effort` changes it:
158
+
159
+ ```
160
+ /effort # pick from the levels this model offers
161
+ /effort high # none | minimal | low | medium | high | xhigh | max | ultra
162
+ /effort default # back to the model's own default
163
+ ```
164
+
165
+ It takes effect on the next turn, is saved to `~/.hx/settings.json` for later
166
+ sessions, and shows on the status bar next to the model — `gpt-5.6-terra (sub)
167
+ · high`. Levels differ per model, so a choice is clamped to what the current
168
+ one offers: `max` runs at `xhigh` on GPT-5.5 rather than being refused, and the
169
+ bar shows what is actually happening. Set it by hand instead with:
170
+
171
+ ```jsonc
172
+ // ~/.hx/settings.json
173
+ { "models": { "reasoning_effort": "high" } }
174
+ ```
175
+
176
+ When the model list cannot be fetched (no network, an old cache), HX falls back
177
+ to the models it last shipped. To add an id the backend does not advertise:
178
+
179
+ ```jsonc
180
+ // ~/.hx/settings.json
181
+ { "models": { "codex_models": ["gpt-5.6-terra"] } }
182
+ ```
183
+
184
+ They join the `/model` picker on the next start. HX cannot ask how large an
185
+ unknown model's context window is, so it assumes a conservative 200k — that
186
+ costs an accurate context gauge, not the use of the model.
187
+
124
188
  **Web search.** Optional, and the one credential that is not a model route.
125
189
  It powers two tools:
126
190
 
@@ -256,20 +320,20 @@ be rebound (see [Keybindings](#keybindings)).
256
320
  | `enter` | send |
257
321
  | `ctrl+j` | newline (also `shift+enter`) |
258
322
  | `esc` | interrupt the current turn |
259
- | `ctrl+c` | clear the prompt; twice on an empty prompt exits |
323
+ | `ctrl+c` | copy the selected text; with nothing selected, clear the prompt (twice on an empty prompt exits) |
260
324
  | `ctrl+d` | exit, when the prompt is empty |
261
325
  | `ctrl+z` | suspend to the background |
262
326
  | `shift+tab` | cycle permission mode |
263
327
  | `ctrl+p` | command palette |
264
328
  | `ctrl+l` | model picker |
265
329
  | `ctrl+o` | expand tool output (also `ctrl+r`) |
266
- | `ctrl+t` | toggle the todo sidebar |
330
+ | `ctrl+t` | show the current plan |
267
331
  | `ctrl+x` | copy the selected message to the clipboard |
268
332
  | `ctrl+up` | previous message |
269
333
  | `ctrl+down` | next message |
270
- | `pgup` | scroll the transcript up |
271
- | `pgdn` | scroll the transcript down |
272
- | `ctrl+home` | jump to the start of the transcript |
334
+ | `pgup` | scroll the transcript up (fullscreen; otherwise your terminal scrolls) |
335
+ | `pgdn` | scroll the transcript down (fullscreen; otherwise your terminal scrolls) |
336
+ | `ctrl+home` | jump to the start: the viewport in fullscreen, the first message otherwise |
273
337
  | `ctrl+end` | jump back to the newest output |
274
338
  | `@path` | complete a file path |
275
339
  | `!command` | run a shell command directly, no model turn |
@@ -281,7 +345,8 @@ The prompt is a readline-style editor: `ctrl+a`/`ctrl+e` for line start and end,
281
345
  `ctrl+b`/`ctrl+f` by character, `alt+b`/`alt+f` by word, `ctrl+w` and `alt+d` to
282
346
  kill a word, `ctrl+u` and `ctrl+k` to kill to the start or end of a line, then
283
347
  `ctrl+y` to yank it back and `alt+y` to walk further down the kill ring.
284
- `ctrl+z` undoes, `ctrl+shift+z` redoes.
348
+ `ctrl+_` undoes, `ctrl+shift+z` redoes - `ctrl+z` belongs to the shell, and
349
+ suspends HX.
285
350
 
286
351
  Typing `/` or `@` opens a completion list above the prompt; `tab` cycles it,
287
352
  `enter` accepts, `esc` dismisses.
@@ -293,10 +358,13 @@ HX writes through the platform's own clipboard tool first (`pbcopy`, `wl-copy`,
293
358
  `xclip`, `xsel`) and falls back to OSC 52, which is also always sent over SSH so
294
359
  the text lands on the machine you are actually sitting at.
295
360
 
296
- Because `ctrl+c` clears the prompt, drag-selecting in the transcript is your
297
- terminal's own selection rather than the TUI's — use your terminal's copy key
298
- for that. HX does not emit OSC 133 prompt markers yet, so shell-integration
299
- features that jump between prompts will not see HX's messages.
361
+ Drag-select in the transcript and `ctrl+c` copies the selection — assistant
362
+ prose and tool output included. With nothing selected, `ctrl+c` keeps its usual
363
+ meaning and clears the prompt. See [Selecting and copying
364
+ text](#selecting-and-copying-text) for the terminal's own selection.
365
+
366
+ HX does not emit OSC 133 prompt markers yet, so shell-integration features that
367
+ jump between prompts will not see HX's messages.
300
368
 
301
369
  #### Keybindings
302
370
 
@@ -319,6 +387,7 @@ than being silently resolved.
319
387
  |---|---|
320
388
  | `/model [query]` | pick a model; shows context window, price per Mtok, cache support |
321
389
  | `/models refresh` | re-fetch the catalogue |
390
+ | `/effort [level]` | how hard a reasoning model thinks, from the levels it offers |
322
391
  | `/configure` | session settings and the API key |
323
392
  | `/login [provider]` | sign in to a model route |
324
393
  | `/logout <provider>` | forget a stored credential |
@@ -332,17 +401,71 @@ than being silently resolved.
332
401
  | `/rewind` | go back to an earlier prompt, restoring the files HX changed |
333
402
  | `/title [text]` | show or set this session's name |
334
403
  | `/prompt` | the system prompt this session is running with |
335
- | `/todos` | toggle the sidebar |
404
+ | `/todos` | show the current plan |
336
405
  | `/skills` | installed skills |
337
406
  | `/agents` | subagent types |
338
407
  | `/mcp` | server status |
339
408
  | `/theme [name]` | `dark`, `light`, `ansi`, or any theme in `~/.hx/themes` |
340
409
  | `/queue [steer <n>\|clear]` | messages waiting for the turn to end, and what to do with them |
341
410
  | `/copy` | copy the last reply to the clipboard |
342
- | `/init` | generate an `HX.md` for the project |
411
+ | `/fullscreen [on\|off]` | take the whole window, with the prompt pinned to the bottom |
412
+ | `/mouse [on\|off]` | mouse reporting, and with it your terminal's own text selection |
413
+ | `/init` | generate an `AGENTS.md` for the project |
343
414
  | `/help` | list commands and keys |
344
415
  | `/quit` | exit (also `/exit`, `/q`) |
345
416
 
417
+ ### The two screens
418
+
419
+ By default HX draws into your terminal's own scrollback. Finished output belongs
420
+ to the terminal while the session runs: it scrolls it, selects it, copies it and
421
+ pipes it. Nothing is repainted above the fold, so a long session costs the same
422
+ to draw as a short one.
423
+
424
+ The prompt and the status bar still open on the bottom row of the window: a
425
+ conversation too short to fill the screen is padded above them rather than
426
+ below, so the dock stays where you last saw it instead of creeping down the
427
+ screen a block at a time. The padding is gone by the time the terminal starts
428
+ scrolling.
429
+
430
+ Leaving takes the whole interface off the screen: the banner, the conversation
431
+ and the dock are erased and your shell prompt comes back on the row HX started
432
+ on, the terminal as it was found. The erase stops at the top of the window -
433
+ lines that had already scrolled above it stay in your scrollback, because the
434
+ only sequence that reaches them takes everything the terminal remembers,
435
+ including what was there before HX ran. `/clear` is the command that asks for
436
+ that.
437
+
438
+ `/fullscreen` switches to the alternate screen instead - the whole window, the
439
+ prompt pinned to the bottom row, the transcript scrolling above it with `pgup`,
440
+ `pgdn`, `ctrl+home` and `ctrl+end`. The trade is the thing the default was
441
+ chosen for: the alternate screen is not scrollback, so your terminal cannot
442
+ scroll or select it, and it is gone when HX exits. `/fullscreen off` gives it
443
+ back, and either way the choice is written to `tui.fullscreen` for the next
444
+ session. Quitting from fullscreen leaves the same clean terminal as quitting
445
+ from the default screen: the alternate screen goes, and so does the frame HX
446
+ had drawn underneath it before the switch.
447
+
448
+ `/clear` wipes the screen and the scrollback above it. Terminals that do not
449
+ implement scrollback erasure - macOS Terminal.app is the one people hit - clear
450
+ the screen only, and the old conversation stays above it until you clear it the
451
+ terminal's own way (`cmd+k`).
452
+
453
+ ### Selecting and copying text
454
+
455
+ Drag to select and `ctrl+c` to copy. HX copies through `pbcopy`, `wl-copy` or
456
+ `xclip` first and falls back to OSC 52, so it works in terminals that ignore
457
+ OSC 52 - macOS Terminal ignores it outright, and iTerm2 ships with it off. The
458
+ notice says which mechanism took the text, so a copy that did nothing says so.
459
+
460
+ While HX is running, the terminal reports mouse events to it, which means the
461
+ terminal's *own* click-and-drag selection is unavailable. Two ways around that:
462
+
463
+ * Hold `alt`/`option` while dragging. Most terminals - macOS Terminal, iTerm2,
464
+ GNOME Terminal - take that as "this drag is mine", and you get the native
465
+ selection without changing anything.
466
+ * `/mouse off` turns mouse reporting off for the session. Native selection comes
467
+ back everywhere, and HX stops seeing scroll and clicks until `/mouse on`.
468
+
346
469
  ### Steering a running turn
347
470
 
348
471
  Type while HX is working and `enter` queues the message: it runs when the turn
@@ -373,18 +496,27 @@ collapses after an edit is the visible symptom of a broken prefix.
373
496
  Settings are JSON, merged lowest to highest:
374
497
 
375
498
  ```
376
- defaults < ~/.hx/settings.json < ./.hx/settings.json < ./.hx/settings.local.json < HX_* env < CLI flags
499
+ defaults < ~/.hx/settings.json < ./.hx/settings.json < ~/.hx/projects/<project>/settings.local.json < HX_* env < CLI flags
377
500
  ```
378
501
 
379
502
  Permission rule lists and `prompt.append` are unioned across layers, so a
380
503
  project can add a deny rule — or a line to the system prompt — without
381
504
  discarding yours. Everything else is replaced.
382
505
 
383
- `./.hx/settings.json` is the project's shared file, yours to check in.
384
- `./.hx/settings.local.json` is this machine's, and it is where HX writes
385
- anything it decides on your behalf - an "always allow" grant, for one. HX adds
386
- a `.hx/.gitignore` covering it the first time it writes there, so your grants
387
- stay out of the repo.
506
+ `./.hx/settings.json` is the project's shared file, yours to check in, and
507
+ nothing writes to it but you — HX only ever reads it. Versions before the local
508
+ layer existed did append grants there; those are lifted out into the file below
509
+ on the next run, once, and a rule you put back by hand afterwards stays put.
510
+ Only `allow` moves: a project's `deny` and `ask` are the guardrails it shares,
511
+ and HX never wrote them.
512
+
513
+ Anything HX decides on your behalf - an "always allow" grant, for one - goes in
514
+ `~/.hx/projects/<project>/settings.local.json` instead. Still per project,
515
+ because a grant is: allowing `pytest` in one repo should not allow it
516
+ everywhere. But under your home rather than in the checkout, so HX never leaves
517
+ a file in your repository for you to find in a diff. If an older version
518
+ already wrote `./.hx/settings.local.json`, it is moved there on the next run,
519
+ along with the `.hx/.gitignore` it added to hide it.
388
520
 
389
521
  ```jsonc
390
522
  {
@@ -397,7 +529,9 @@ stay out of the repo.
397
529
  "subagent_model": null, // defaults to "model"
398
530
  "title_model": null, // model that names sessions; defaults to "model"
399
531
  "max_tokens": 8192,
400
- "temperature": null
532
+ "temperature": null,
533
+ "reasoning_effort": null, // Codex route: null = the model's own default
534
+ "codex_models": [] // extra Codex ids, e.g. ["gpt-5.6-terra"]
401
535
  },
402
536
 
403
537
  "permissions": {
@@ -429,7 +563,8 @@ stay out of the repo.
429
563
  },
430
564
 
431
565
  "tui": {
432
- "enterWhileBusy": "queue" // queue | steer - what enter does mid-turn
566
+ "enterWhileBusy": "queue", // queue | steer - what enter does mid-turn
567
+ "fullscreen": false // true takes the whole window (see /fullscreen)
433
568
  }
434
569
  }
435
570
  ```
@@ -461,7 +596,7 @@ Appended text is added after whichever prompt won, in this order:
461
596
  `--append-system-prompt` value (and `prompt.append`) in the order given. Nothing
462
597
  appended is ever discarded by a later layer.
463
598
 
464
- This is a different lever from `HX.md`: `HX.md` describes *the project* and is
599
+ This is a different lever from `AGENTS.md`: `AGENTS.md` describes *the project* and is
465
600
  injected as its own context section, while these files change *the agent's
466
601
  instructions*. Both are read once at startup and frozen, because they sit above
467
602
  the first cache breakpoint — an override edited mid-session applies on the next
@@ -512,14 +647,14 @@ files load here unchanged. `src/hx/tui/themes/dark.json` is the reference.
512
647
  | `~/.hx/system-prompt-append.md` | text appended to whichever prompt is in force |
513
648
  | `~/.hx/sessions/` | transcripts, spilled tool output, subagent sessions |
514
649
  | `~/.hx/skills/`, `~/.hx/agents/` | your skills and agents |
515
- | `./.hx/settings.json` | project settings, checked in if you like |
516
- | `./.hx/settings.local.json` | this machine's project settings, never checked in - where "always allow" lands |
650
+ | `~/.hx/projects/<project>/settings.local.json` | this machine's settings for one project - where "always allow" lands |
651
+ | `./.hx/settings.json` | project settings, checked in if you like — read by HX, never written |
517
652
  | `./.hx/mcp.json` | project MCP servers |
518
653
  | `./.hx/system-prompt.md`, `./.hx/system-prompt-append.md` | project prompt overrides |
519
654
  | `./.hx/skills/`, `./.hx/agents/` | project skills and agents |
520
- | `./HX.md` | project instructions, loaded into every session |
655
+ | `./AGENTS.md` | project instructions, loaded into every session |
521
656
 
522
- `HX.md` is the place for things a newcomer would get wrong: how to run the
657
+ `AGENTS.md` is the place for things a newcomer would get wrong: how to run the
523
658
  tests, conventions, what not to touch. `/init` writes a first draft. It is
524
659
  loaded once per session and frozen, so it costs one prefix, not one per turn.
525
660
 
@@ -701,7 +836,8 @@ src/hx/
701
836
  permissions/ rule engine, shell decomposition, Seatbelt/bubblewrap
702
837
  skills/ agents/ mcp/
703
838
  keys.py keybinding registry: ids, defaults, descriptions, user overrides
704
- tui/ Textual app, commands, per-tool renderers, widgets
839
+ term/ the renderer: raw mode, input decoding, the differ, the screen
840
+ tui/ views, commands, per-tool renderers, themes
705
841
  themes/ the shipped palettes, as JSON
706
842
  ```
707
843
 
@@ -737,11 +873,13 @@ git tag v$(uv run hx --version | cut -d' ' -f2) && git push origin main --tags
737
873
 
738
874
  Step 1 is not optional and is not a convention: `tests/test_docs.py` fails the
739
875
  whole suite when `__version__` has no `CHANGELOG.md` section, so a release that
740
- skips it cannot get past CI to the `publish` job.
876
+ skips it cannot get past CI to the `publish` job. The file ships inside the
877
+ wheel and `hx changelog` prints it, so a version with no section there reaches
878
+ users as a blank release.
741
879
 
742
880
  The `publish` job runs only on `refs/tags/v*` and only after lint, the test
743
- matrix, and an install-script run in a clean Debian container have passed. It
744
- builds with `uv build` and uploads via OIDC.
881
+ suite and an install-script run in a clean Debian container have passed. It builds with
882
+ `uv build` and uploads via OIDC.
745
883
 
746
884
  To check a build before tagging:
747
885