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.
- {hx_cli-0.1.8 → hx_cli-0.2.0}/.github/workflows/ci.yml +10 -2
- {hx_cli-0.1.8 → hx_cli-0.2.0}/CHANGELOG.md +159 -1
- {hx_cli-0.1.8 → hx_cli-0.2.0}/PKG-INFO +175 -37
- {hx_cli-0.1.8 → hx_cli-0.2.0}/README.md +168 -32
- {hx_cli-0.1.8 → hx_cli-0.2.0}/install.sh +71 -28
- {hx_cli-0.1.8 → hx_cli-0.2.0}/pyproject.toml +13 -5
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/__init__.py +1 -1
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/codex.py +63 -2
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/cli.py +87 -7
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/config.py +223 -2
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/context.py +5 -7
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/loop.py +15 -7
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/session.py +106 -4
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/git.py +116 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/keys.py +58 -39
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/paths.py +69 -9
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/engine.py +39 -35
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/codex.py +40 -4
- hx_cli-0.2.0/src/hx/providers/codex_catalogue.py +203 -0
- hx_cli-0.2.0/src/hx/providers/models.py +562 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/registry.py +42 -5
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/responses_codec.py +11 -4
- hx_cli-0.2.0/src/hx/term/__init__.py +18 -0
- hx_cli-0.2.0/src/hx/term/ansi.py +391 -0
- hx_cli-0.2.0/src/hx/term/buffer.py +145 -0
- hx_cli-0.2.0/src/hx/term/component.py +141 -0
- hx_cli-0.2.0/src/hx/term/demo.py +150 -0
- hx_cli-0.2.0/src/hx/term/editor.py +283 -0
- hx_cli-0.2.0/src/hx/term/keydecode.py +296 -0
- hx_cli-0.2.0/src/hx/term/loop.py +247 -0
- hx_cli-0.2.0/src/hx/term/markdown.py +291 -0
- hx_cli-0.2.0/src/hx/term/primitives.py +315 -0
- hx_cli-0.2.0/src/hx/term/sanitize.py +88 -0
- hx_cli-0.2.0/src/hx/term/screen.py +525 -0
- hx_cli-0.2.0/src/hx/term/syntax.py +102 -0
- hx_cli-0.2.0/src/hx/term/terminal.py +457 -0
- hx_cli-0.2.0/src/hx/term/undo.py +63 -0
- hx_cli-0.2.0/src/hx/term/width.py +199 -0
- hx_cli-0.2.0/src/hx/term/word_nav.py +33 -0
- hx_cli-0.2.0/src/hx/tui/__init__.py +7 -0
- hx_cli-0.2.0/src/hx/tui/app.py +56 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/commands.py +339 -57
- hx_cli-0.2.0/src/hx/tui/format.py +70 -0
- hx_cli-0.2.0/src/hx/tui/glyphs.py +63 -0
- hx_cli-0.2.0/src/hx/tui/limits.py +40 -0
- hx_cli-0.2.0/src/hx/tui/paint.py +109 -0
- hx_cli-0.2.0/src/hx/tui/renderers.py +658 -0
- hx_cli-0.2.0/src/hx/tui/runtime.py +1148 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/theme.py +6 -46
- hx_cli-0.2.0/src/hx/tui/views/__init__.py +14 -0
- hx_cli-0.2.0/src/hx/tui/views/blocks.py +267 -0
- hx_cli-0.2.0/src/hx/tui/views/dialog.py +183 -0
- hx_cli-0.2.0/src/hx/tui/views/login.py +259 -0
- hx_cli-0.2.0/src/hx/tui/views/permission.py +286 -0
- hx_cli-0.2.0/src/hx/tui/views/pickers.py +419 -0
- hx_cli-0.2.0/src/hx/tui/views/prompt.py +497 -0
- hx_cli-0.2.0/src/hx/tui/views/status.py +326 -0
- hx_cli-0.2.0/src/hx/tui/views/transcript.py +342 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_codex_oauth.py +34 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/conftest.py +8 -2
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_context.py +13 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_loop.py +0 -32
- hx_cli-0.2.0/tests/core/test_session.py +194 -0
- hx_cli-0.2.0/tests/core/test_settings_layers.py +67 -0
- hx_cli-0.2.0/tests/core/test_settings_migration.py +152 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/test_engine.py +72 -55
- hx_cli-0.2.0/tests/providers/test_catalogue.py +290 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_codex.py +33 -0
- hx_cli-0.2.0/tests/providers/test_codex_catalogue.py +198 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_responses_codec.py +26 -2
- hx_cli-0.2.0/tests/term/conftest.py +59 -0
- hx_cli-0.2.0/tests/term/test_ansi.py +190 -0
- hx_cli-0.2.0/tests/term/test_keydecode.py +233 -0
- hx_cli-0.2.0/tests/term/test_loop.py +147 -0
- hx_cli-0.2.0/tests/term/test_markdown.py +198 -0
- hx_cli-0.2.0/tests/term/test_primitives.py +206 -0
- hx_cli-0.2.0/tests/term/test_sanitize.py +77 -0
- hx_cli-0.2.0/tests/term/test_screen.py +673 -0
- hx_cli-0.2.0/tests/term/test_terminal.py +235 -0
- hx_cli-0.2.0/tests/term/test_width.py +109 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_git.py +97 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_install.py +3 -1
- hx_cli-0.2.0/tests/test_paths.py +54 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_readme.py +58 -8
- hx_cli-0.2.0/tests/tui/__init__.py +0 -0
- hx_cli-0.2.0/tests/tui/support.py +146 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_autocomplete.py +37 -28
- hx_cli-0.2.0/tests/tui/test_effort.py +167 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_keys.py +55 -6
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_login.py +120 -115
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_renderers.py +52 -30
- hx_cli-0.2.0/tests/tui/test_session.py +1123 -0
- hx_cli-0.2.0/tests/tui/views/__init__.py +0 -0
- hx_cli-0.2.0/tests/tui/views/test_blocks.py +247 -0
- hx_cli-0.2.0/tests/tui/views/test_permission.py +328 -0
- hx_cli-0.2.0/tests/tui/views/test_pickers.py +314 -0
- hx_cli-0.2.0/tests/tui/views/test_prompt.py +374 -0
- hx_cli-0.2.0/tests/tui/views/test_runtime.py +492 -0
- hx_cli-0.2.0/tests/tui/views/test_status.py +197 -0
- hx_cli-0.2.0/tests/tui/views/test_transcript.py +211 -0
- hx_cli-0.2.0/uv.lock +946 -0
- hx_cli-0.1.8/src/hx/providers/models.py +0 -334
- hx_cli-0.1.8/src/hx/tui/__init__.py +0 -1
- hx_cli-0.1.8/src/hx/tui/app.py +0 -977
- hx_cli-0.1.8/src/hx/tui/hx.tcss +0 -240
- hx_cli-0.1.8/src/hx/tui/renderers.py +0 -579
- hx_cli-0.1.8/src/hx/tui/widgets/__init__.py +0 -1
- hx_cli-0.1.8/src/hx/tui/widgets/autocomplete.py +0 -105
- hx_cli-0.1.8/src/hx/tui/widgets/configure.py +0 -95
- hx_cli-0.1.8/src/hx/tui/widgets/diff.py +0 -25
- hx_cli-0.1.8/src/hx/tui/widgets/frame.py +0 -61
- hx_cli-0.1.8/src/hx/tui/widgets/header.py +0 -74
- hx_cli-0.1.8/src/hx/tui/widgets/hints.py +0 -75
- hx_cli-0.1.8/src/hx/tui/widgets/input.py +0 -446
- hx_cli-0.1.8/src/hx/tui/widgets/login.py +0 -175
- hx_cli-0.1.8/src/hx/tui/widgets/palette.py +0 -254
- hx_cli-0.1.8/src/hx/tui/widgets/permission.py +0 -243
- hx_cli-0.1.8/src/hx/tui/widgets/rule.py +0 -26
- hx_cli-0.1.8/src/hx/tui/widgets/statusbar.py +0 -227
- hx_cli-0.1.8/src/hx/tui/widgets/todos.py +0 -116
- hx_cli-0.1.8/src/hx/tui/widgets/transcript.py +0 -475
- hx_cli-0.1.8/src/hx/tui/widgets/working.py +0 -151
- hx_cli-0.1.8/tests/core/test_session.py +0 -69
- hx_cli-0.1.8/tests/core/test_settings_layers.py +0 -37
- hx_cli-0.1.8/tests/providers/test_catalogue.py +0 -102
- hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_a_pending_permission_prompt.raw +0 -198
- hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_a_running_turn.raw +0 -192
- hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_a_turn_with_tool_blocks.raw +0 -194
- hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_the_completion_popup.raw +0 -191
- hx_cli-0.1.8/tests/tui/__snapshots__/test_snapshots/test_the_idle_screen.raw +0 -190
- hx_cli-0.1.8/tests/tui/test_app.py +0 -1556
- hx_cli-0.1.8/tests/tui/test_frame.py +0 -82
- hx_cli-0.1.8/tests/tui/test_permission.py +0 -132
- hx_cli-0.1.8/tests/tui/test_snapshots.py +0 -159
- hx_cli-0.1.8/tests/tui/test_statusbar.py +0 -131
- hx_cli-0.1.8/tests/tui/test_transcript.py +0 -129
- hx_cli-0.1.8/uv.lock +0 -1816
- {hx_cli-0.1.8 → hx_cli-0.2.0}/.gitignore +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/agents/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/agents/definitions.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/agents/subagent.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/callback.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/oauth/pkce.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/resolve.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/auth/store.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/checkpoints.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/compaction.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/events.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/lateinject.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/messages.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/title.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/core/usage.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/docs.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/frontmatter.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/mcp/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/mcp/client.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/mcp/manager.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/net.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/parser.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/permissions/sandbox.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/base.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/fake.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/providers/openrouter.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/skills/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/skills/loader.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/skills/runtime.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/base.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/bash.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/edit.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/glob.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/grep.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/output.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/read.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/registry.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/task.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/todo.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/websearch.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tools/write.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/clipboard.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/fuzzy.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/killring.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/roles.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/theme_json.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/themes/ansi.json +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/themes/dark.json +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/src/hx/tui/themes/light.json +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/agents/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/agents/test_subagent.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_pkce.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_resolve.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/auth/test_store.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_checkpoints.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_compaction.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_lateinject.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_rewind.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_steering.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_title.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/core/test_usage.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/mcp/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/mcp/fixtures/echo_server.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/mcp/test_manager.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/test_parser.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/permissions/test_sandbox.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_openrouter.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/providers/test_registry.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/skills/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/skills/test_loader.py +0 -0
- {hx_cli-0.1.8/tests/tools → hx_cli-0.2.0/tests/term}/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_cli.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_docs.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_integration.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_live.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_live_codex.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_live_tavily.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/test_net.py +0 -0
- {hx_cli-0.1.8/tests/tui → hx_cli-0.2.0/tests/tools}/__init__.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_background.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_bash.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_files.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_output.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_todo.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tools/test_websearch.py +0 -0
- {hx_cli-0.1.8 → hx_cli-0.2.0}/tests/tui/test_clipboard.py +0 -0
- {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
|
-
|
|
25
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
|
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` |
|
|
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
|
|
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+
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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` |
|
|
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
|
-
| `/
|
|
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 <
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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"
|
|
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 `
|
|
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
|
-
|
|
|
516
|
-
| `./.hx/settings.
|
|
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
|
-
| `./
|
|
655
|
+
| `./AGENTS.md` | project instructions, loaded into every session |
|
|
521
656
|
|
|
522
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
744
|
-
|
|
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
|
|