Fixes#245.
Running ``headroom init -g`` with no supported agents on PATH previously
produced a single-line ClickException that read like the -g flag had
been removed:
Error: No supported user init targets were auto-detected. Specify one explicitly.
This left reporter #245 concluding the feature was gone. Replace that
message with a structured diagnostic that:
* states which scope (user / local) was tried
* lists every target probed (claude, codex, copilot, openclaw) and the
shutil.which() result for each
* explicitly confirms that -g / --global is still a supported flag
* shows the concrete per-target invocation for each agent
(``headroom init -g claude``, ...) so the user knows the escape hatch
The implementation factors ``detect_init_targets`` into a ``_probe_init_targets``
helper that returns ``[(name, which_result)]``. ``detect_init_targets``
keeps its existing signature so the test suite and external imports
aren't broken; the new helper backs both the auto-detection path and
the diagnostic error formatter.
Unit tests in tests/test_cli/test_init_cli.py cover:
* the end-to-end message shape (structural markers + every target name +
the example invocation)
* the local-scope variant omitting global-only agents (copilot / openclaw)
* that found binaries are surfaced with their absolute path so users can
debug cases where shutil.which returns an unexpected result
No behavior change when at least one target is detected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`headroom wrap codex` injects a `model_provider = "headroom"` block
plus a `[model_providers.headroom]` table into `~/.codex/config.toml`
so Codex routes both HTTP and WebSocket traffic through the proxy. The
matching `unwrap codex` subcommand did not exist, so the injected
block stayed in `config.toml` forever — the moment the proxy stopped,
Codex (CLI and macOS app) started erroring with
`Missing environment variable: OPENAI_API_KEY`, and users had to hand-
edit the file to recover.
Fix:
* `_inject_codex_provider_config` now snapshots the pre-wrap file to
`~/.codex/config.toml.headroom-backup` before the first modification
and leaves that snapshot untouched on subsequent wrap runs. The
injection is also rewritten to use two self-contained marker-
delimited blocks (top-level key and provider table) so stripping
them never consumes user content that sits between them.
* `_inject_memory_mcp_config` takes the same snapshot, so
`wrap codex --memory` without a full provider injection is still
fully reversible.
* New `_restore_codex_provider_config` helper and `unwrap codex`
click command:
* backup present → restore byte-for-byte and delete the backup;
* backup absent but Headroom block present → strip the block and
keep surrounding user content;
* config contained only Headroom content → remove the file so
Codex falls back to defaults;
* nothing to undo → safe no-op.
Codex is the only wrap target that modifies a persistent user config
file: claude/aider/cursor/copilot all go through env vars or project-
scoped files only, so this bug was unique to Codex.
Tests:
* `tests/test_cli/test_wrap_codex.py` adds 20 new cases covering the
strip/snapshot helpers directly, round-trip idempotency of
wrap → wrap → unwrap, handling of malformed prior configs, and
end-to-end CliRunner invocations of `headroom wrap codex
--prepare-only` / `headroom unwrap codex` against a temp `$HOME`.
* All 153 existing `tests/test_cli/` tests continue to pass.
Plugin manifest versions were re-synced from `pyproject.toml` (0.11.2)
by the `sync-plugin-versions` pre-commit hook; the previous values
(0.10.3) had drifted.
Reported by @raenaryl in Discord on 0.6.3; confirmed still broken on
current `main` (0.11.x).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three independent pre-existing test-hygiene regressions on main, all
surfaced as cascading CI failures:
1. tests/test_cli/test_wrap_copilot.py (from #229) mutated
sys.modules["headroom.cli.main"] with a fake click.Group() at
module-import time and never restored it. Any later test that did
`from headroom.cli.main import main` got an empty group with no
version option and no registered subcommands, breaking ~20
test_cli/* and test_cli_proxy_env.py tests. Rewrite to import the
real `main` directly — the fake-group indirection served no
purpose.
2. tests/test_proxy_copilot_auth_hooks.py (from #229) installed fake
httpx / fastapi.responses / headroom.proxy.* modules into
sys.modules inside a helper called from test functions, never
cleaned up. Later tests that imported ASGITransport or JSONResponse
hit the fakes and failed with ImportError. Switch the helper to
monkeypatch.setitem so the fakes are scoped to the owning test.
3. tests/test_release_version.py hardcoded canonical=0.5.25 in the
subprocess-output assertion; the project version in pyproject.toml
has since bumped to 0.9.1. Compute the expected value dynamically
via get_canonical_version(ROOT) so the test tracks pyproject.
- test_memory_sync.py: remove unused imports (asyncio, MagicMock,
AgentMemory, AgentMemoryAdapter, SyncResult), fix import sorting
- test_ws_memory_relay.py: remove unused pytest import and unused
output_index variable, fix import sorting
- test_wrap_copilot.py: provide dummy API keys in test env — the
BYOK validation added in 7a7b8b6 requires ANTHROPIC_API_KEY or
OPENAI_API_KEY to be set
- test_package_init_lazy.py: stop hardcoding version string that
breaks on every bump; assert it's a non-empty string instead
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add focused regression coverage for install, runtime, provider, state, health, supervisor, and persistent wrap flows so the new persistent deployment surfaces are exercised more thoroughly in CI.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align Docker-native wrapper help and runtime behavior with the Python install contract, including persistent deployment metadata, baked install-image defaults, and explicit unsupported wrap targets.
Harden the Python persistent-install path with profile validation, safer provider-scope handling, Windows environment restoration, runtime parity improvements, and rollback-safe apply/update behavior.
Update README, Docker install docs, CI, and focused regressions to cover the Windows BOM failure, wrapper parity, compose coverage, and Docker-native wrap behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add headroom wrap copilot with backend-aware provider routing, health metadata for running proxy detection, focused Copilot tests, and docs updates across the main integration surfaces.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add host-managed OpenClaw wrap and unwrap flows to the Docker-native wrappers so the installed headroom script can configure the OpenClaw plugin on the host while keeping Headroom itself in Docker. Reuse hidden prepare-only hooks for OpenClaw config payloads, preserve existing plugin metadata on unwrap, and update the Docker-native and integration docs to reflect the supported flow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add system-native install scripts and host wrappers for running Headroom from Docker while keeping wrapped tools on the host. Document the Docker-native path, add a complete CLI reference with help output and parity details, and add support for root help/version aliases and proxy env-based binding behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve the PR merge conflict by carrying forward the secure TLS fail-closed rtk download behavior, bring in the latest upstream proxy-handler updates, and fix the OpenClaw linked-install fallback so it copies the required hook-shim directory with regression coverage.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix proxy crash when torch not installed: make kompress_compressor.py
imports lazy so `is_kompress_available()` works without [ml] extra
- Rewrite MCP server from 1 tool (retrieve-only) to 3 tools:
headroom_compress (on-demand compression, no proxy needed),
headroom_retrieve (local store first, proxy fallback),
headroom_stats (session stats + sub-agent aggregation + proxy cache)
- Add shared stats file (~/.headroom/session_stats.jsonl) so sub-agent
compression stats are visible from the main session
- Add mcp to [proxy] extras so proxy users get MCP tools automatically
- Remove dead TextCompressor from exports and pipeline (was never called)
- Update mcp install messaging to clarify proxy vs MCP roles
- Fix fcntl Windows compat, asyncio deprecation, httpx timeout race
Bump version to 0.4.6.
Address code review feedback on the initial MCP fix:
- mcp_uninstall: now also calls `claude mcp remove -s user` when the claude
CLI is available, mirroring mcp_install. Removes from mcp.json fallback
config as well if present. This fixes the broken uninstall->reinstall
roundtrip on machines using Claude Code CLI >=2.x.
- cli/mcp.py: move `import subprocess` to module level (was deferred inside
mcp_install function body, inconsistent with other stdlib imports and
harder to mock).
- tests: add TestMCPInstallWithClaudeCLI and TestMCPUninstallWithClaudeCLI
covering the previously-untested `claude mcp add` code path, including
force-overwrite ordering, -e flag for env vars, and fallback on failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes to get the headroom MCP server working with Claude Code CLI:
1. mcp_server.py: define CCR_TOOL_NAME inline instead of importing from
tool_injection, which triggered headroom/__init__.py -> LiteLLM -> HTTP
requests to GitHub, adding 4-5 seconds to startup time.
2. cli/mcp.py: prefer `claude mcp add -s user` when the claude CLI is
available (Claude Code CLI ≥2.x stores servers in ~/.claude/.claude.json,
not ~/.claude/mcp.json). Falls back to writing mcp.json for older versions
and the claude.ai desktop app.
3. tests/test_cli/test_mcp.py: update mock_claude_config_path fixture to
also stub out the claude CLI so install tests exercise the mcp.json
fallback path, matching the fixture's intent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mock MCP SDK availability for install/status/lifecycle tests
- Skip MCP server initialization tests when SDK not available
- Skip negative test (test without SDK) when SDK is installed
Tests now pass both with and without MCP SDK installed.
- Add `headroom mcp install` to configure ~/.claude/mcp.json
- Add `headroom mcp uninstall` to remove configuration
- Add `headroom mcp status` to check setup
- Add `headroom mcp serve` for MCP server (called by Claude Code)
- Add `mcp` optional dependency in pyproject.toml
- Add docs/mcp.md with full documentation
- Add 22 integration tests for MCP CLI
- Fix mypy errors in trained_router.py (None check, type annotation)
- Update README with subscription user instructions
This enables CCR (Compress-Cache-Retrieve) for subscription users who
don't have API access. MCP is Claude's official extension mechanism
that works with subscriptions.
Usage:
pip install "headroom-ai[mcp]"
headroom mcp install
headroom proxy # Terminal 1
claude # Terminal 2