Commit graph

191 commits

Author SHA1 Message Date
JerrettDavis
4c062319f0 fix(init): guide users when no agents are auto-detected
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>
2026-04-23 15:55:12 -05:00
JerrettDavis
281bc171dc fix(wrap): unwrap codex restores prior config.toml
`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>
2026-04-23 11:13:54 -05:00
JerrettDavis
88dc15ea85 Merge upstream/main into feat/canonical-pipeline
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 18:47:41 -05:00
Kayzo
af9af3d2b5 chore(merge): resolve upstream main conflicts 2026-04-22 19:22:40 +00:00
JerrettDavis
87a13ef2cd Merge upstream/main into feat/canonical-pipeline
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 13:10:53 -05:00
Kayzo
3045b36b52 fix: stabilize cli test isolation 2026-04-22 11:28:30 +00:00
Garm
1788d907f0 fix(tests): scope sys.modules mutation and unpin hardcoded version
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.
2026-04-22 12:37:22 +02:00
JerrettDavis
5413e7af47 chore: normalize provider slice line endings
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 23:54:28 -05:00
JerrettDavis
b17c6d81cc refactor: extract provider logic into slices
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 23:54:28 -05:00
JerrettDavis
64fe9763f5 test: skip rtk in BYOK copilot assertion
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 23:53:30 -05:00
JerrettDavis
1d440023b6 Merge upstream/main into fix/copilot-oauth-runtime
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 23:44:58 -05:00
JerrettDavis
af784465df test: restore cli package state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 23:23:58 -05:00
JerrettDavis
f5b959a470 test: isolate copilot oauth suites
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 23:01:31 -05:00
JerrettDavis
d60cf7914c fix: support live copilot oauth runtime
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 22:48:57 -05:00
Tejas Chopra
1f978f9235
Merge pull request #229 from JerrettDavis/feat/copilot-oauth
fix: support GitHub Copilot OAuth sessions
2026-04-21 20:14:36 -07:00
JerrettDavis
7989581350 fix: support copilot oauth sessions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 22:11:30 -05:00
JerrettDavis
9ba9a59f78 test: isolate windows init branches from os globals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 21:11:03 -05:00
JerrettDavis
c1b648664e test: raise init command branch coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 20:59:24 -05:00
JerrettDavis
a278a7b0ba test: cover init install flows end to end
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 20:15:11 -05:00
JerrettDavis
3a999d1562 feat: add durable init command for agent hooks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 19:39:06 -05:00
chopratejas
89061fd430 Fix CI lint errors and test failures
- 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>
2026-04-14 18:23:06 -07:00
JerrettDavis
bd242fc62d test: expand persistent install coverage
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>
2026-04-11 18:24:15 -05:00
JerrettDavis
865bef2216 fix: harden persistent install wrappers and review gaps
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>
2026-04-11 18:03:21 -05:00
JerrettDavis
21896a095c feat: add persistent install lifecycle management
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 13:47:05 -05:00
Tejas Chopra
9f124c99ff
Merge pull request #139 from JerrettDavis/feat/docker-native-cli
feat(cli): add Docker-native install flow and parity docs
2026-04-11 09:05:00 -07:00
JerrettDavis
9fa1763087 feat: add copilot CLI wrap support
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>
2026-04-11 01:20:17 -05:00
JerrettDavis
a1dcda6bc4 feat(cli): support OpenClaw in Docker-native installs
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>
2026-04-11 00:04:15 -05:00
JerrettDavis
38b1483a76 feat(cli): add Docker-native install flow and parity docs
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>
2026-04-10 23:27:24 -05:00
JerrettDavis
37f32a8922 test(openclaw): cover branch routing paths
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 23:46:16 -05:00
JerrettDavis
0da1a7b4de Add OpenClaw unwrap flow and non-blocking proxy startup 2026-04-08 21:16:32 -05:00
JerrettDavis
addcfcffcd Merge upstream/main into feat/docker-e2e-wrap-validation
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>
2026-04-07 23:54:05 -05:00
JerrettDavis
cd2741cfff test(cli): add branch coverage for wrap openclaw flows 2026-04-03 23:55:14 -05:00
JerrettDavis
61dacc94a1 fix(cli): default wrap openclaw spec to headroom-ai/openclaw 2026-04-03 23:36:23 -05:00
JerrettDavis
490cf15a57 style: apply ruff formatting for wrap openclaw changes 2026-04-03 23:20:15 -05:00
JerrettDavis
3a25ed4571 fix(cli): make wrap openclaw npm-first and idempotent 2026-04-03 23:15:24 -05:00
JerrettDavis
c5e3686c89 feat(cli): add one-command OpenClaw wrap bootstrap 2026-04-03 23:08:13 -05:00
chopratejas
87b54fc159 Enhanced MCP server with compress/stats tools, fix proxy torch crash
- 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.
2026-03-16 22:56:30 -07:00
Claude Code Bot
fcafa373e7 fix(mcp): fix uninstall symmetry, move subprocess import, add cli-path tests
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>
2026-03-06 13:02:35 -08:00
Claude Code Bot
c078faa353 fix(mcp): use claude mcp add for install and fix startup import
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>
2026-03-06 12:46:36 -08:00
chopratejas
3095c73282 Fix MCP tests to work when MCP SDK is not installed
- 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.
2026-02-02 11:23:26 -08:00
chopratejas
fe2e30a7ef Add MCP CLI for Claude Code subscription users
- 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
2026-02-02 11:05:24 -08:00