feat: add durable init command for agent hooks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
JerrettDavis 2026-04-21 13:10:28 -05:00
parent 0d6ba9972a
commit 3a999d1562
11 changed files with 1409 additions and 344 deletions

View file

@ -0,0 +1,30 @@
{
"name": "headroom-marketplace",
"owner": {
"name": "Headroom Contributors"
},
"metadata": {
"description": "Headroom marketplace for Claude Code and GitHub Copilot CLI plugins.",
"version": "0.1.0"
},
"plugins": [
{
"name": "headroom",
"source": "./plugins/headroom-agent-hooks",
"description": "Headroom startup hooks for Claude Code and GitHub Copilot CLI.",
"version": "0.1.0",
"author": {
"name": "Headroom Contributors",
"url": "https://github.com/JerrettDavis/headroom"
},
"homepage": "https://github.com/JerrettDavis/headroom",
"repository": "https://github.com/JerrettDavis/headroom",
"keywords": [
"headroom",
"hooks",
"claude-code",
"copilot-cli"
]
}
]
}

30
.github/plugin/marketplace.json vendored Normal file
View file

@ -0,0 +1,30 @@
{
"name": "headroom-marketplace",
"owner": {
"name": "Headroom Contributors"
},
"metadata": {
"description": "Headroom marketplace for Claude Code and GitHub Copilot CLI plugins.",
"version": "0.1.0"
},
"plugins": [
{
"name": "headroom",
"source": "./plugins/headroom-agent-hooks",
"description": "Headroom startup hooks for Claude Code and GitHub Copilot CLI.",
"version": "0.1.0",
"author": {
"name": "Headroom Contributors",
"url": "https://github.com/JerrettDavis/headroom"
},
"homepage": "https://github.com/JerrettDavis/headroom",
"repository": "https://github.com/JerrettDavis/headroom",
"keywords": [
"headroom",
"hooks",
"claude-code",
"copilot-cli"
]
}
]
}

540
README.md
View file

@ -1,266 +1,274 @@
<div align="center">
# Headroom
**Compress everything your AI agent reads. Same answers, fraction of the tokens.**
[![CI](https://github.com/chopratejas/headroom/actions/workflows/ci.yml/badge.svg)](https://github.com/chopratejas/headroom/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/headroom-ai.svg)](https://pypi.org/project/headroom-ai/)
[![npm](https://img.shields.io/npm/v/headroom-ai.svg)](https://www.npmjs.com/package/headroom-ai)
[![Model: Kompress-base](https://img.shields.io/badge/model-Kompress--base-yellow.svg)](https://huggingface.co/chopratejas/kompress-base)
[![Tokens saved: 60B+](https://img.shields.io/badge/tokens%20saved-60B%2B-2ea44f)](https://headroomlabs.ai/dashboard)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Docs](https://img.shields.io/badge/docs-online-blue.svg)](https://headroom-docs.vercel.app/docs)
<img src="HeadroomDemo-Fast.gif" alt="Headroom in action" width="820">
</div>
---
Every tool call, log line, DB read, RAG chunk, and file your agent injects into a prompt is mostly boilerplate. Headroom strips the noise and keeps the signal — **losslessly, locally, and without touching accuracy.**
> **100 logs. One FATAL error buried at position 67. Both runs found it.**
> Baseline **10,144 tokens** → Headroom **1,260 tokens** — **87% fewer, identical answer.**
> `python examples/needle_in_haystack_test.py`
---
## Quick start
Works with Anthropic, OpenAI, Google, Bedrock, Vertex, Azure, OpenRouter, and 100+ models via LiteLLM.
**Wrap your coding agent — one command:**
```bash
pip install "headroom-ai[all]"
headroom wrap claude # Claude Code
headroom wrap codex # Codex
headroom wrap cursor # Cursor
headroom wrap aider # Aider
headroom wrap copilot # GitHub Copilot CLI
```
**Drop it into your own code — Python or TypeScript:**
```python
from headroom import compress
result = compress(messages, model="claude-sonnet-4-5")
response = client.messages.create(model="claude-sonnet-4-5", messages=result.messages)
print(f"Saved {result.tokens_saved} tokens ({result.compression_ratio:.0%})")
```
```typescript
import { compress } from 'headroom-ai';
const result = await compress(messages, { model: 'gpt-4o' });
```
**Or run it as a proxy — zero code changes, any language:**
```bash
headroom proxy --port 8787
ANTHROPIC_BASE_URL=http://localhost:8787 your-app
OPENAI_BASE_URL=http://localhost:8787/v1 your-app
```
---
## Why Headroom
- **Accuracy-preserving.** GSM8K **0.870 → 0.870** (±0.000). TruthfulQA **+0.030**. SQuAD v2 and BFCL both **97%** accuracy after compression. Validated on public OSS benchmarks you can rerun yourself.
- **Runs on your machine.** No cloud API, no data egress. Compression latency is milliseconds — faster end-to-end for Sonnet / Opus / GPT-4 class models than a hosted service round-trip.
- **[Kompress-base](https://huggingface.co/chopratejas/kompress-base) on HuggingFace.** Our open-source text compressor, fine-tuned on real agentic traces — tool outputs, logs, RAG chunks, code. Install with `pip install "headroom-ai[ml]"`.
- **Cross-agent memory and learning.** Claude Code saves a fact, Codex reads it back. `headroom learn` mines failed sessions and writes corrections straight to `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` — reliability compounds over time.
- **Reversible (CCR).** Compression is not deletion. The model can always call `headroom_retrieve` to pull the original bytes. Nothing is thrown away.
Bundles the [RTK](https://github.com/rtk-ai/rtk) binary for shell-output rewriting — full [attribution below](#compared-to).
---
## How it fits
```
Your agent / app
(Claude Code, Cursor, Codex, LangChain, Agno, Strands, your own code…)
│ prompts · tool outputs · logs · RAG results · files
┌────────────────────────────────────────────────────┐
│ Headroom (runs locally — your data stays here) │
│ ─────────────────────────────────────────────── │
│ CacheAligner → ContentRouter → CCR │
│ ├─ SmartCrusher (JSON) │
│ ├─ CodeCompressor (AST) │
│ └─ Kompress-base (text, HF) │
│ │
│ Cross-agent memory · headroom learn · MCP │
└────────────────────────────────────────────────────┘
│ compressed prompt + retrieval tool
LLM provider (Anthropic · OpenAI · Bedrock · …)
```
→ [Architecture](https://headroom-docs.vercel.app/docs/architecture) · [CCR reversible compression](https://headroom-docs.vercel.app/docs/ccr) · [Kompress-base model card](https://huggingface.co/chopratejas/kompress-base)
---
## Proof
**Savings on real agent workloads:**
| Workload | Before | After | Savings |
|-------------------------------|-------:|-------:|--------:|
| Code search (100 results) | 17,765 | 1,408 | **92%** |
| SRE incident debugging | 65,694 | 5,118 | **92%** |
| GitHub issue triage | 54,174 | 14,761 | **73%** |
| Codebase exploration | 78,502 | 41,254 | **47%** |
**Accuracy preserved on standard benchmarks:**
| Benchmark | Category | N | Baseline | Headroom | Delta |
|------------|----------|----:|---------:|---------:|----------:|
| GSM8K | Math | 100 | 0.870 | 0.870 | **±0.000**|
| TruthfulQA | Factual | 100 | 0.530 | 0.560 | **+0.030**|
| SQuAD v2 | QA | 100 | — | **97%** | 19% compression |
| BFCL | Tools | 100 | — | **97%** | 32% compression |
Reproduce:
```bash
python -m headroom.evals suite --tier 1
```
**Community, live:**
<div align="center">
<a href="https://headroomlabs.ai/dashboard">
<img src="headroom-savings.png" alt="60B+ tokens saved — community leaderboard" width="820">
</a>
<p><b><a href="https://headroomlabs.ai/dashboard">60B+ tokens saved by the community in the last 20 days — live leaderboard →</a></b></p>
</div>
→ [Full benchmarks & methodology](https://headroom-docs.vercel.app/docs/benchmarks)
---
## Built for coding agents
| Agent | One-command wrap | Notes |
|--------------------|------------------------------------|------------------------------------------------------------------|
| **Claude Code** | `headroom wrap claude` | `--memory` for cross-agent memory, `--code-graph` for codebase intel |
| **Codex** | `headroom wrap codex --memory` | Shares the same memory store as Claude |
| **Cursor** | `headroom wrap cursor` | Prints Cursor config — paste once, done |
| **Aider** | `headroom wrap aider` | Starts proxy, launches Aider |
| **Copilot CLI** | `headroom wrap copilot` | Starts proxy, launches Copilot |
| **OpenClaw** | `headroom wrap openclaw` | Installs Headroom as ContextEngine plugin |
MCP-native too — `headroom mcp install` exposes `headroom_compress`, `headroom_retrieve`, and `headroom_stats` to any MCP client.
<div align="center">
<img src="headroom_learn.gif" alt="headroom learn in action" width="720">
</div>
---
## Integrations
<details>
<summary><b>Drop Headroom into any stack</b></summary>
| Your setup | Hook in with |
|-------------------------|------------------------------------------------------------------|
| Any Python app | `compress(messages, model=…)` |
| Any TypeScript app | `await compress(messages, { model })` |
| Anthropic / OpenAI SDK | `withHeadroom(new Anthropic())` · `withHeadroom(new OpenAI())` |
| Vercel AI SDK | `wrapLanguageModel({ model, middleware: headroomMiddleware() })` |
| LiteLLM | `litellm.callbacks = [HeadroomCallback()]` |
| LangChain | `HeadroomChatModel(your_llm)` |
| Agno | `HeadroomAgnoModel(your_model)` |
| Strands | [Strands guide](https://headroom-docs.vercel.app/docs/strands) |
| ASGI apps | `app.add_middleware(CompressionMiddleware)` |
| Multi-agent | `SharedContext().put / .get` |
| MCP clients | `headroom mcp install` |
</details>
<details>
<summary><b>What's inside</b></summary>
- **SmartCrusher** — universal JSON: arrays of dicts, nested objects, mixed types.
- **CodeCompressor** — AST-aware for Python, JS, Go, Rust, Java, C++.
- **Kompress-base** — our HuggingFace model, trained on agentic traces.
- **Image compression** — 4090% reduction via trained ML router.
- **CacheAligner** — stabilizes prefixes so Anthropic/OpenAI KV caches actually hit.
- **IntelligentContext** — score-based context fitting with learned importance.
- **CCR** — reversible compression; LLM retrieves originals on demand.
- **Cross-agent memory** — shared store, agent provenance, auto-dedup.
- **SharedContext** — compressed context passing across multi-agent workflows.
- **`headroom learn`** — plugin-based failure mining for Claude, Codex, Gemini.
</details>
---
## Install
```bash
pip install "headroom-ai[all]" # Python, everything
npm install headroom-ai # TypeScript / Node
docker pull ghcr.io/chopratejas/headroom:latest
```
Granular extras: `[proxy]`, `[mcp]`, `[ml]` (Kompress-base), `[agno]`, `[langchain]`, `[evals]`. Requires **Python 3.10+**.
→ [Installation guide](https://headroom-docs.vercel.app/docs/installation) — Docker tags, persistent service, PowerShell, devcontainers.
---
## Documentation
| Start here | Go deeper |
|-------------------------------------------------------------------------|------------------------------------------------------------------------|
| [Quickstart](https://headroom-docs.vercel.app/docs/quickstart) | [Architecture](https://headroom-docs.vercel.app/docs/architecture) |
| [Proxy](https://headroom-docs.vercel.app/docs/proxy) | [How compression works](https://headroom-docs.vercel.app/docs/how-compression-works) |
| [MCP tools](https://headroom-docs.vercel.app/docs/mcp) | [CCR — reversible compression](https://headroom-docs.vercel.app/docs/ccr) |
| [Memory](https://headroom-docs.vercel.app/docs/memory) | [Cache optimization](https://headroom-docs.vercel.app/docs/cache-optimization) |
| [Failure learning](https://headroom-docs.vercel.app/docs/failure-learning) | [Benchmarks](https://headroom-docs.vercel.app/docs/benchmarks) |
| [Configuration](https://headroom-docs.vercel.app/docs/configuration) | [Limitations](https://headroom-docs.vercel.app/docs/limitations) |
---
## Compared to
Headroom runs **locally**, covers **every** content type (not just CLI or text), works with every major framework, and is **reversible**.
| | Scope | Deploy | Local | Reversible |
|----------------------------------|-------------------------------------------------|-------------------------------------|:-----:|:----------:|
| **Headroom** | All context — tools, RAG, logs, files, history | Proxy · library · middleware · MCP | Yes | Yes |
| [RTK](https://github.com/rtk-ai/rtk) | CLI command outputs | CLI wrapper | Yes | No |
| [Compresr](https://compresr.ai), [Token Co.](https://thetokencompany.ai) | Text sent to their API | Hosted API call | No | No |
| OpenAI Compaction | Conversation history | Provider-native | No | No |
> **Attribution.** Headroom ships with the excellent [RTK](https://github.com/rtk-ai/rtk) binary for shell-output rewriting — `git show``git show --short`, noisy `ls` → scoped, chatty installers → summarized. Huge thanks to the RTK team; their tool is a first-class part of our stack, and Headroom compresses everything downstream of it.
---
## Contributing
```bash
git clone https://github.com/chopratejas/headroom.git && cd headroom
pip install -e ".[dev]" && pytest
```
Devcontainers in `.devcontainer/` (default + `memory-stack` with Qdrant & Neo4j). See [CONTRIBUTING.md](CONTRIBUTING.md).
---
## Community
- **[Live leaderboard](https://headroomlabs.ai/dashboard)** — 60B+ tokens saved and counting.
- **[Discord](https://discord.gg/yRmaUNpsPJ)** — questions, feedback, war stories.
- **[Kompress-base on HuggingFace](https://huggingface.co/chopratejas/kompress-base)** — the model behind our text compression.
## License
Apache 2.0 — see [LICENSE](LICENSE).
<div align="center">
# Headroom
**Compress everything your AI agent reads. Same answers, fraction of the tokens.**
[![CI](https://github.com/chopratejas/headroom/actions/workflows/ci.yml/badge.svg)](https://github.com/chopratejas/headroom/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/headroom-ai.svg)](https://pypi.org/project/headroom-ai/)
[![npm](https://img.shields.io/npm/v/headroom-ai.svg)](https://www.npmjs.com/package/headroom-ai)
[![Model: Kompress-base](https://img.shields.io/badge/model-Kompress--base-yellow.svg)](https://huggingface.co/chopratejas/kompress-base)
[![Tokens saved: 60B+](https://img.shields.io/badge/tokens%20saved-60B%2B-2ea44f)](https://headroomlabs.ai/dashboard)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Docs](https://img.shields.io/badge/docs-online-blue.svg)](https://headroom-docs.vercel.app/docs)
<img src="HeadroomDemo-Fast.gif" alt="Headroom in action" width="820">
</div>
---
Every tool call, log line, DB read, RAG chunk, and file your agent injects into a prompt is mostly boilerplate. Headroom strips the noise and keeps the signal — **losslessly, locally, and without touching accuracy.**
> **100 logs. One FATAL error buried at position 67. Both runs found it.**
> Baseline **10,144 tokens** → Headroom **1,260 tokens** — **87% fewer, identical answer.**
> `python examples/needle_in_haystack_test.py`
---
## Quick start
Works with Anthropic, OpenAI, Google, Bedrock, Vertex, Azure, OpenRouter, and 100+ models via LiteLLM.
**Wrap your coding agent — one command:**
```bash
pip install "headroom-ai[all]"
headroom wrap claude # Claude Code
headroom wrap codex # Codex
headroom wrap cursor # Cursor
headroom wrap aider # Aider
headroom wrap copilot # GitHub Copilot CLI
```
**Prefer a one-time durable install instead of wrapping every launch:**
```bash
headroom init -g # Detect installed user-scoped agents and wire them to Headroom
headroom init claude # Install repo-local Claude hooks for just this project
headroom init copilot -g # Install user-scoped Copilot hooks and provider routing
```
**Drop it into your own code — Python or TypeScript:**
```python
from headroom import compress
result = compress(messages, model="claude-sonnet-4-5")
response = client.messages.create(model="claude-sonnet-4-5", messages=result.messages)
print(f"Saved {result.tokens_saved} tokens ({result.compression_ratio:.0%})")
```
```typescript
import { compress } from 'headroom-ai';
const result = await compress(messages, { model: 'gpt-4o' });
```
**Or run it as a proxy — zero code changes, any language:**
```bash
headroom proxy --port 8787
ANTHROPIC_BASE_URL=http://localhost:8787 your-app
OPENAI_BASE_URL=http://localhost:8787/v1 your-app
```
---
## Why Headroom
- **Accuracy-preserving.** GSM8K **0.870 → 0.870** (±0.000). TruthfulQA **+0.030**. SQuAD v2 and BFCL both **97%** accuracy after compression. Validated on public OSS benchmarks you can rerun yourself.
- **Runs on your machine.** No cloud API, no data egress. Compression latency is milliseconds — faster end-to-end for Sonnet / Opus / GPT-4 class models than a hosted service round-trip.
- **[Kompress-base](https://huggingface.co/chopratejas/kompress-base) on HuggingFace.** Our open-source text compressor, fine-tuned on real agentic traces — tool outputs, logs, RAG chunks, code. Install with `pip install "headroom-ai[ml]"`.
- **Cross-agent memory and learning.** Claude Code saves a fact, Codex reads it back. `headroom learn` mines failed sessions and writes corrections straight to `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` — reliability compounds over time.
- **Reversible (CCR).** Compression is not deletion. The model can always call `headroom_retrieve` to pull the original bytes. Nothing is thrown away.
Bundles the [RTK](https://github.com/rtk-ai/rtk) binary for shell-output rewriting — full [attribution below](#compared-to).
---
## How it fits
```
Your agent / app
(Claude Code, Cursor, Codex, LangChain, Agno, Strands, your own code…)
│ prompts · tool outputs · logs · RAG results · files
┌────────────────────────────────────────────────────┐
│ Headroom (runs locally — your data stays here) │
│ ─────────────────────────────────────────────── │
│ CacheAligner → ContentRouter → CCR │
│ ├─ SmartCrusher (JSON) │
│ ├─ CodeCompressor (AST) │
│ └─ Kompress-base (text, HF) │
│ │
│ Cross-agent memory · headroom learn · MCP │
└────────────────────────────────────────────────────┘
│ compressed prompt + retrieval tool
LLM provider (Anthropic · OpenAI · Bedrock · …)
```
→ [Architecture](https://headroom-docs.vercel.app/docs/architecture) · [CCR reversible compression](https://headroom-docs.vercel.app/docs/ccr) · [Kompress-base model card](https://huggingface.co/chopratejas/kompress-base)
---
## Proof
**Savings on real agent workloads:**
| Workload | Before | After | Savings |
|-------------------------------|-------:|-------:|--------:|
| Code search (100 results) | 17,765 | 1,408 | **92%** |
| SRE incident debugging | 65,694 | 5,118 | **92%** |
| GitHub issue triage | 54,174 | 14,761 | **73%** |
| Codebase exploration | 78,502 | 41,254 | **47%** |
**Accuracy preserved on standard benchmarks:**
| Benchmark | Category | N | Baseline | Headroom | Delta |
|------------|----------|----:|---------:|---------:|----------:|
| GSM8K | Math | 100 | 0.870 | 0.870 | **±0.000**|
| TruthfulQA | Factual | 100 | 0.530 | 0.560 | **+0.030**|
| SQuAD v2 | QA | 100 | — | **97%** | 19% compression |
| BFCL | Tools | 100 | — | **97%** | 32% compression |
Reproduce:
```bash
python -m headroom.evals suite --tier 1
```
**Community, live:**
<div align="center">
<a href="https://headroomlabs.ai/dashboard">
<img src="headroom-savings.png" alt="60B+ tokens saved — community leaderboard" width="820">
</a>
<p><b><a href="https://headroomlabs.ai/dashboard">60B+ tokens saved by the community in the last 20 days — live leaderboard →</a></b></p>
</div>
→ [Full benchmarks & methodology](https://headroom-docs.vercel.app/docs/benchmarks)
---
## Built for coding agents
| Agent | Durable init / one-shot wrap | Notes |
|--------------------|------------------------------------|------------------------------------------------------------------|
| **Claude Code** | `headroom init claude -g` / `headroom wrap claude` | `init` installs user or repo-local hooks; `wrap` is still useful for ad hoc sessions |
| **Codex** | `headroom init codex -g` / `headroom wrap codex --memory` | `init` installs provider config plus lifecycle hooks where supported |
| **Cursor** | `headroom wrap cursor` | Prints Cursor config — durable init not available yet |
| **Aider** | `headroom wrap aider` | Starts proxy, launches Aider |
| **Copilot CLI** | `headroom init copilot -g` / `headroom wrap copilot` | `init` installs hooks and BYOK provider routing for the current user |
| **OpenClaw** | `headroom init openclaw -g` / `headroom wrap openclaw` | Installs Headroom as ContextEngine plugin |
MCP-native too — `headroom mcp install` exposes `headroom_compress`, `headroom_retrieve`, and `headroom_stats` to any MCP client.
<div align="center">
<img src="headroom_learn.gif" alt="headroom learn in action" width="720">
</div>
---
## Integrations
<details>
<summary><b>Drop Headroom into any stack</b></summary>
| Your setup | Hook in with |
|-------------------------|------------------------------------------------------------------|
| Any Python app | `compress(messages, model=…)` |
| Any TypeScript app | `await compress(messages, { model })` |
| Anthropic / OpenAI SDK | `withHeadroom(new Anthropic())` · `withHeadroom(new OpenAI())` |
| Vercel AI SDK | `wrapLanguageModel({ model, middleware: headroomMiddleware() })` |
| LiteLLM | `litellm.callbacks = [HeadroomCallback()]` |
| LangChain | `HeadroomChatModel(your_llm)` |
| Agno | `HeadroomAgnoModel(your_model)` |
| Strands | [Strands guide](https://headroom-docs.vercel.app/docs/strands) |
| ASGI apps | `app.add_middleware(CompressionMiddleware)` |
| Multi-agent | `SharedContext().put / .get` |
| MCP clients | `headroom mcp install` |
</details>
<details>
<summary><b>What's inside</b></summary>
- **SmartCrusher** — universal JSON: arrays of dicts, nested objects, mixed types.
- **CodeCompressor** — AST-aware for Python, JS, Go, Rust, Java, C++.
- **Kompress-base** — our HuggingFace model, trained on agentic traces.
- **Image compression** — 4090% reduction via trained ML router.
- **CacheAligner** — stabilizes prefixes so Anthropic/OpenAI KV caches actually hit.
- **IntelligentContext** — score-based context fitting with learned importance.
- **CCR** — reversible compression; LLM retrieves originals on demand.
- **Cross-agent memory** — shared store, agent provenance, auto-dedup.
- **SharedContext** — compressed context passing across multi-agent workflows.
- **`headroom learn`** — plugin-based failure mining for Claude, Codex, Gemini.
</details>
---
## Install
```bash
pip install "headroom-ai[all]" # Python, everything
npm install headroom-ai # TypeScript / Node
docker pull ghcr.io/chopratejas/headroom:latest
```
Granular extras: `[proxy]`, `[mcp]`, `[ml]` (Kompress-base), `[agno]`, `[langchain]`, `[evals]`. Requires **Python 3.10+**.
→ [Installation guide](https://headroom-docs.vercel.app/docs/installation) — Docker tags, persistent service, PowerShell, devcontainers.
---
## Documentation
| Start here | Go deeper |
|-------------------------------------------------------------------------|------------------------------------------------------------------------|
| [Quickstart](https://headroom-docs.vercel.app/docs/quickstart) | [Architecture](https://headroom-docs.vercel.app/docs/architecture) |
| [Proxy](https://headroom-docs.vercel.app/docs/proxy) | [How compression works](https://headroom-docs.vercel.app/docs/how-compression-works) |
| [MCP tools](https://headroom-docs.vercel.app/docs/mcp) | [CCR — reversible compression](https://headroom-docs.vercel.app/docs/ccr) |
| [Memory](https://headroom-docs.vercel.app/docs/memory) | [Cache optimization](https://headroom-docs.vercel.app/docs/cache-optimization) |
| [Failure learning](https://headroom-docs.vercel.app/docs/failure-learning) | [Benchmarks](https://headroom-docs.vercel.app/docs/benchmarks) |
| [Configuration](https://headroom-docs.vercel.app/docs/configuration) | [Limitations](https://headroom-docs.vercel.app/docs/limitations) |
---
## Compared to
Headroom runs **locally**, covers **every** content type (not just CLI or text), works with every major framework, and is **reversible**.
| | Scope | Deploy | Local | Reversible |
|----------------------------------|-------------------------------------------------|-------------------------------------|:-----:|:----------:|
| **Headroom** | All context — tools, RAG, logs, files, history | Proxy · library · middleware · MCP | Yes | Yes |
| [RTK](https://github.com/rtk-ai/rtk) | CLI command outputs | CLI wrapper | Yes | No |
| [Compresr](https://compresr.ai), [Token Co.](https://thetokencompany.ai) | Text sent to their API | Hosted API call | No | No |
| OpenAI Compaction | Conversation history | Provider-native | No | No |
> **Attribution.** Headroom ships with the excellent [RTK](https://github.com/rtk-ai/rtk) binary for shell-output rewriting — `git show``git show --short`, noisy `ls` → scoped, chatty installers → summarized. Huge thanks to the RTK team; their tool is a first-class part of our stack, and Headroom compresses everything downstream of it.
---
## Contributing
```bash
git clone https://github.com/chopratejas/headroom.git && cd headroom
pip install -e ".[dev]" && pytest
```
Devcontainers in `.devcontainer/` (default + `memory-stack` with Qdrant & Neo4j). See [CONTRIBUTING.md](CONTRIBUTING.md).
---
## Community
- **[Live leaderboard](https://headroomlabs.ai/dashboard)** — 60B+ tokens saved and counting.
- **[Discord](https://discord.gg/yRmaUNpsPJ)** — questions, feedback, war stories.
- **[Kompress-base on HuggingFace](https://huggingface.co/chopratejas/kompress-base)** — the model behind our text compression.
## License
Apache 2.0 — see [LICENSE](LICENSE).

679
headroom/cli/init.py Normal file
View file

@ -0,0 +1,679 @@
"""Durable agent initialization commands."""
from __future__ import annotations
import json
import os
import shlex
import shutil
import subprocess
from hashlib import sha1
from pathlib import Path
from typing import Any
import click
from headroom.install.models import ConfigScope, InstallPreset, RuntimeKind, SupervisorKind
from headroom.install.paths import claude_settings_path, codex_config_path, validate_profile_name
from headroom.install.planner import build_manifest
from headroom.install.providers import _apply_unix_env_scope, _apply_windows_env_scope
from headroom.install.runtime import (
resolve_headroom_command,
start_detached_agent,
start_persistent_docker,
stop_runtime,
wait_ready,
)
from headroom.install.state import load_manifest, save_manifest
from headroom.install.supervisors import start_supervisor
from .main import main
_GLOBAL_PROFILE = "init-user"
_CLAUDE_HOOK_MARKER = "headroom-init-claude"
_COPILOT_HOOK_MARKER = "headroom-init-copilot"
_CODEX_HOOK_MARKER = "headroom-init-codex"
_CODEX_PROVIDER_MARKER_START = "# --- Headroom init provider ---"
_CODEX_PROVIDER_MARKER_END = "# --- end Headroom init provider ---"
_CODEX_FEATURE_MARKER_START = "# --- Headroom init features ---"
_CODEX_FEATURE_MARKER_END = "# --- end Headroom init features ---"
_SUPPORTED_TARGETS = ("claude", "copilot", "codex", "openclaw")
_LOCAL_TARGETS = {"claude", "codex"}
_GLOBAL_TARGETS = {"claude", "copilot", "codex", "openclaw"}
def _command_string(parts: list[str]) -> str:
if os.name == "nt":
return subprocess.list2cmdline(parts)
return shlex.join(parts)
def _hook_command(*parts: str) -> str:
return _command_string([*resolve_headroom_command(), "init", "hook", "ensure", *parts])
def _powershell_matcher() -> str:
return "Bash|PowerShell" if os.name == "nt" else "Bash"
def _local_profile(cwd: Path | None = None) -> str:
root = (cwd or Path.cwd()).resolve()
slug = "".join(ch if ch.isalnum() or ch in "-._" else "-" for ch in root.name.lower()).strip(
"-"
)
digest = sha1(str(root).encode("utf-8")).hexdigest()[:8]
return validate_profile_name(f"init-{slug or 'repo'}-{digest}")
def _runtime_profile(global_scope: bool, cwd: Path | None = None) -> str:
return _GLOBAL_PROFILE if global_scope else _local_profile(cwd)
def _copilot_config_path() -> Path:
return Path.home() / ".copilot" / "config.json"
def _codex_hooks_path(global_scope: bool) -> Path:
return (Path.home() if global_scope else Path.cwd()) / ".codex" / "hooks.json"
def _claude_scope_path(global_scope: bool) -> Path:
if global_scope:
return claude_settings_path()
return Path.cwd() / ".claude" / "settings.local.json"
def _codex_scope_path(global_scope: bool) -> Path:
if global_scope:
return codex_config_path()
return Path.cwd() / ".codex" / "config.toml"
def _json_file(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
content = path.read_text(encoding="utf-8").strip()
if not content:
return {}
payload = json.loads(content)
return payload if isinstance(payload, dict) else {}
def _write_json(path: Path, payload: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
def _ensure_claude_hooks(path: Path, profile: str, port: int) -> None:
payload = _json_file(path)
env_map = dict(payload.get("env") or {}) if isinstance(payload.get("env"), dict) else {}
env_map["ANTHROPIC_BASE_URL"] = f"http://127.0.0.1:{port}"
payload["env"] = env_map
hooks = dict(payload.get("hooks") or {}) if isinstance(payload.get("hooks"), dict) else {}
command = _hook_command("--profile", profile)
for event, matcher in (
("SessionStart", "startup|resume"),
("PreToolUse", _powershell_matcher()),
):
entries = list(hooks.get(event) or []) if isinstance(hooks.get(event), list) else []
retained: list[dict[str, Any]] = []
for entry in entries:
if not isinstance(entry, dict):
retained.append(entry)
continue
hook_items = entry.get("hooks")
if not isinstance(hook_items, list):
retained.append(entry)
continue
has_headroom = any(
isinstance(item, dict)
and item.get("command")
and _CLAUDE_HOOK_MARKER in str(item.get("command"))
for item in hook_items
)
if not has_headroom:
retained.append(entry)
retained.append(
{
"matcher": matcher,
"hooks": [
{
"type": "command",
"command": f"{command} --marker {_CLAUDE_HOOK_MARKER}",
"timeout": 15,
}
],
}
)
hooks[event] = retained
payload["hooks"] = hooks
_write_json(path, payload)
def _ensure_copilot_hooks(path: Path, profile: str) -> None:
payload = _json_file(path)
hooks = dict(payload.get("hooks") or {}) if isinstance(payload.get("hooks"), dict) else {}
command = f"{_hook_command('--profile', profile)} --marker {_COPILOT_HOOK_MARKER}"
for event in ("SessionStart", "PreToolUse"):
entries = list(hooks.get(event) or []) if isinstance(hooks.get(event), list) else []
retained = [
entry
for entry in entries
if not (
isinstance(entry, dict) and _COPILOT_HOOK_MARKER in str(entry.get("command", ""))
)
]
retained.append({"type": "command", "command": command, "cwd": ".", "timeout": 15})
hooks[event] = retained
payload["hooks"] = hooks
_write_json(path, payload)
def _replace_marker_block(content: str, marker_start: str, marker_end: str, block: str) -> str:
if marker_start in content and marker_end in content:
start = content.index(marker_start)
end = content.index(marker_end) + len(marker_end)
content = content[:start].rstrip() + "\n\n" + content[end:].lstrip()
return (content.rstrip() + "\n\n" + block.strip() + "\n").lstrip()
def _ensure_codex_provider(path: Path, port: int) -> None:
block = (
f"{_CODEX_PROVIDER_MARKER_START}\n"
'model_provider = "headroom"\n\n'
"[model_providers.headroom]\n"
'name = "Headroom init proxy"\n'
f'base_url = "http://127.0.0.1:{port}/v1"\n'
'env_key = "OPENAI_API_KEY"\n'
"requires_openai_auth = true\n"
"supports_websockets = true\n"
f"{_CODEX_PROVIDER_MARKER_END}"
)
content = path.read_text(encoding="utf-8") if path.exists() else ""
content = _replace_marker_block(
content, _CODEX_PROVIDER_MARKER_START, _CODEX_PROVIDER_MARKER_END, block
)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
def _ensure_codex_feature_flag(path: Path) -> None:
content = path.read_text(encoding="utf-8") if path.exists() else ""
if _CODEX_FEATURE_MARKER_START in content and _CODEX_FEATURE_MARKER_END in content:
block = f"{_CODEX_FEATURE_MARKER_START}\ncodex_hooks = true\n{_CODEX_FEATURE_MARKER_END}"
content = _replace_marker_block(
content,
_CODEX_FEATURE_MARKER_START,
_CODEX_FEATURE_MARKER_END,
block,
)
elif "[features]" in content:
lines = content.splitlines()
inserted = False
for index, line in enumerate(lines):
if line.strip() != "[features]":
continue
section_end = index + 1
while section_end < len(lines) and not (
lines[section_end].startswith("[") and lines[section_end].endswith("]")
):
if "codex_hooks" in lines[section_end]:
inserted = True
break
section_end += 1
if not inserted:
lines[index + 1 : index + 1] = [
_CODEX_FEATURE_MARKER_START,
"codex_hooks = true",
_CODEX_FEATURE_MARKER_END,
]
inserted = True
break
content = "\n".join(lines).rstrip() + "\n"
if not inserted:
content = (
content.rstrip()
+ "\n\n[features]\n"
+ _CODEX_FEATURE_MARKER_START
+ "\n"
+ "codex_hooks = true\n"
+ _CODEX_FEATURE_MARKER_END
+ "\n"
)
else:
content = (
content.rstrip()
+ "\n\n[features]\n"
+ _CODEX_FEATURE_MARKER_START
+ "\n"
+ "codex_hooks = true\n"
+ _CODEX_FEATURE_MARKER_END
+ "\n"
).lstrip()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
def _ensure_codex_hooks(path: Path, profile: str) -> None:
command = f"{_hook_command('--profile', profile)} --marker {_CODEX_HOOK_MARKER}"
payload = {
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [{"type": "command", "command": command, "timeout": 15}],
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": command, "timeout": 15}],
}
],
}
}
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
def _manifest_changed(
existing: Any,
*,
port: int,
backend: str,
anyllm_provider: str | None,
region: str | None,
memory: bool,
) -> bool:
return any(
[
getattr(existing, "port", port) != port,
getattr(existing, "backend", backend) != backend,
getattr(existing, "anyllm_provider", anyllm_provider) != anyllm_provider,
getattr(existing, "region", region) != region,
getattr(existing, "memory_enabled", memory) != memory,
]
)
def _ensure_runtime_manifest(
*,
global_scope: bool,
targets: list[str],
port: int,
backend: str,
anyllm_provider: str | None,
region: str | None,
memory: bool,
) -> str:
profile = _runtime_profile(global_scope)
existing = load_manifest(profile)
merged_targets = sorted(set(existing.targets if existing else []).union(targets))
manifest = build_manifest(
profile=profile,
preset=InstallPreset.PERSISTENT_TASK.value,
runtime_kind=RuntimeKind.PYTHON.value,
scope=ConfigScope.USER.value,
provider_mode="manual",
targets=merged_targets,
port=port,
backend=backend,
anyllm_provider=anyllm_provider,
region=region,
proxy_mode="token",
memory_enabled=memory,
telemetry_enabled=True,
image="ghcr.io/chopratejas/headroom:latest",
)
manifest.supervisor_kind = SupervisorKind.NONE.value
manifest.artifacts = []
manifest.mutations = existing.mutations if existing else []
if existing is not None and _manifest_changed(
existing,
port=port,
backend=backend,
anyllm_provider=anyllm_provider,
region=region,
memory=memory,
):
try:
stop_runtime(existing)
except Exception:
pass
save_manifest(manifest)
return profile
def _env_manifest(values: dict[str, str]) -> Any:
return build_manifest(
profile="init-env",
preset=InstallPreset.PERSISTENT_TASK.value,
runtime_kind=RuntimeKind.PYTHON.value,
scope=ConfigScope.USER.value,
provider_mode="manual",
targets=["copilot"],
port=8787,
backend="anthropic",
anyllm_provider=None,
region=None,
proxy_mode="token",
memory_enabled=False,
telemetry_enabled=True,
image="ghcr.io/chopratejas/headroom:latest",
)
def _apply_user_env(values: dict[str, str]) -> None:
manifest = _env_manifest(values)
manifest.base_env = {}
manifest.tool_envs = {"copilot": values}
if os.name == "nt":
_apply_windows_env_scope(manifest)
else:
_apply_unix_env_scope(manifest)
def _resolve_copilot_env(port: int, backend: str) -> dict[str, str]:
if backend == "anthropic":
return {
"COPILOT_PROVIDER_TYPE": "anthropic",
"COPILOT_PROVIDER_BASE_URL": f"http://127.0.0.1:{port}",
}
return {
"COPILOT_PROVIDER_TYPE": "openai",
"COPILOT_PROVIDER_BASE_URL": f"http://127.0.0.1:{port}/v1",
"COPILOT_PROVIDER_WIRE_API": "completions",
}
def _marketplace_source() -> str:
override = os.environ.get("HEADROOM_MARKETPLACE_SOURCE")
if override:
return override
repo_root = Path(__file__).resolve().parents[2]
if (repo_root / ".claude-plugin" / "marketplace.json").exists():
return str(repo_root)
return "JerrettDavis/headroom"
def _run_checked(command: list[str], *, action: str) -> None:
result = subprocess.run(
command,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
if result.returncode == 0:
return
detail = "\n".join(part for part in (result.stderr.strip(), result.stdout.strip()) if part)
if "already" in detail.lower() or "exists" in detail.lower():
return
raise click.ClickException(f"{action} failed: {detail or result.returncode}")
def _install_claude_marketplace(scope: str) -> None:
claude_bin = shutil.which("claude")
if not claude_bin:
raise click.ClickException("'claude' not found in PATH. Install Claude Code first.")
source = _marketplace_source()
_run_checked(
[claude_bin, "plugin", "marketplace", "add", source], action="claude marketplace add"
)
_run_checked(
[claude_bin, "plugin", "install", "headroom@headroom-marketplace", "--scope", scope],
action="claude plugin install",
)
def _install_copilot_marketplace() -> None:
copilot_bin = shutil.which("copilot")
if not copilot_bin:
raise click.ClickException("'copilot' not found in PATH. Install GitHub Copilot CLI first.")
source = _marketplace_source()
_run_checked(
[copilot_bin, "plugin", "marketplace", "add", source],
action="copilot marketplace add",
)
_run_checked(
[copilot_bin, "plugin", "install", "headroom@headroom-marketplace"],
action="copilot plugin install",
)
def _ensure_profile_running(profile: str) -> None:
manifest = load_manifest(profile)
if manifest is None:
return
if wait_ready(manifest, timeout_seconds=1):
return
try:
if manifest.preset == InstallPreset.PERSISTENT_DOCKER.value:
start_persistent_docker(manifest)
elif manifest.supervisor_kind == SupervisorKind.SERVICE.value:
start_supervisor(manifest)
else:
start_detached_agent(manifest.profile)
wait_ready(manifest, timeout_seconds=45)
except Exception:
return
def detect_init_targets(global_scope: bool) -> list[str]:
allowed = _GLOBAL_TARGETS if global_scope else _LOCAL_TARGETS
detected: list[str] = []
for target in _SUPPORTED_TARGETS:
if target not in allowed:
continue
if shutil.which(target):
detected.append(target)
return detected
def _init_claude(*, global_scope: bool, profile: str, port: int) -> None:
_ensure_claude_hooks(_claude_scope_path(global_scope), profile, port)
_install_claude_marketplace("user" if global_scope else "local")
click.echo(f"Configured Claude Code ({'user' if global_scope else 'local'} scope).")
click.echo("Restart Claude Code to activate Headroom hooks and provider routing.")
def _init_copilot(*, global_scope: bool, profile: str, port: int, backend: str) -> None:
if not global_scope:
raise click.ClickException(
"Copilot durable init currently requires -g (current-user scope)."
)
_ensure_copilot_hooks(_copilot_config_path(), profile)
_apply_user_env(_resolve_copilot_env(port, backend))
_install_copilot_marketplace()
click.echo("Configured GitHub Copilot CLI (user scope).")
click.echo("Restart Copilot CLI to activate Headroom hooks and provider routing.")
def _init_codex(*, global_scope: bool, profile: str, port: int) -> None:
config_path = _codex_scope_path(global_scope)
_ensure_codex_provider(config_path, port)
_ensure_codex_feature_flag(config_path)
_ensure_codex_hooks(_codex_hooks_path(global_scope), profile)
click.echo(f"Configured Codex ({'user' if global_scope else 'local'} scope).")
if os.name == "nt":
click.echo(
"Codex hooks are currently disabled upstream on Windows; provider routing was still installed."
)
click.echo("Restart Codex to activate Headroom configuration.")
def _init_openclaw(*, global_scope: bool, port: int) -> None:
if not global_scope:
raise click.ClickException(
"OpenClaw durable init currently requires -g (current-user scope)."
)
command = [*resolve_headroom_command(), "wrap", "openclaw", "--proxy-port", str(port)]
result = subprocess.run(command)
if result.returncode != 0:
raise SystemExit(result.returncode)
def _run_init_targets(
*,
targets: list[str],
global_scope: bool,
port: int,
backend: str,
anyllm_provider: str | None,
region: str | None,
memory: bool,
) -> None:
runtime_targets = [target for target in targets if target != "openclaw"]
profile = _ensure_runtime_manifest(
global_scope=global_scope,
targets=runtime_targets,
port=port,
backend=backend,
anyllm_provider=anyllm_provider,
region=region,
memory=memory,
)
for target in targets:
if target == "claude":
_init_claude(global_scope=global_scope, profile=profile, port=port)
elif target == "copilot":
_init_copilot(global_scope=global_scope, profile=profile, port=port, backend=backend)
elif target == "codex":
_init_codex(global_scope=global_scope, profile=profile, port=port)
elif target == "openclaw":
_init_openclaw(global_scope=global_scope, port=port)
@main.group(invoke_without_command=True)
@click.option("-g", "--global", "global_scope", is_flag=True, help="Install for the current user.")
@click.option("--port", default=8787, type=int, show_default=True, help="Headroom proxy port.")
@click.option("--backend", default="anthropic", show_default=True, help="Proxy backend.")
@click.option("--anyllm-provider", default=None, help="Provider for any-llm backends.")
@click.option("--region", default=None, help="Cloud region for Bedrock / Vertex style backends.")
@click.option("--memory", is_flag=True, help="Enable persistent memory in the proxy runtime.")
@click.pass_context
def init(
ctx: click.Context,
global_scope: bool,
port: int,
backend: str,
anyllm_provider: str | None,
region: str | None,
memory: bool,
) -> None:
"""Install durable Headroom integrations for supported agents."""
if ctx.invoked_subcommand is not None:
ctx.obj = {
"global_scope": global_scope,
"port": port,
"backend": backend,
"anyllm_provider": anyllm_provider,
"region": region,
"memory": memory,
}
return
targets = detect_init_targets(global_scope)
if not targets:
scope_label = "user" if global_scope else "local"
raise click.ClickException(
f"No supported {scope_label} init targets were auto-detected. Specify one explicitly."
)
_run_init_targets(
targets=targets,
global_scope=global_scope,
port=port,
backend=backend,
anyllm_provider=anyllm_provider,
region=region,
memory=memory,
)
def _ctx_value(ctx: click.Context, key: str) -> Any:
return (ctx.obj or {}).get(key)
@init.command("claude")
@click.pass_context
def init_claude(ctx: click.Context) -> None:
"""Install Claude Code durable hooks and provider routing."""
_run_init_targets(
targets=["claude"],
global_scope=bool(_ctx_value(ctx, "global_scope")),
port=int(_ctx_value(ctx, "port") or 8787),
backend=str(_ctx_value(ctx, "backend") or "anthropic"),
anyllm_provider=_ctx_value(ctx, "anyllm_provider"),
region=_ctx_value(ctx, "region"),
memory=bool(_ctx_value(ctx, "memory")),
)
@init.command("copilot")
@click.pass_context
def init_copilot(ctx: click.Context) -> None:
"""Install GitHub Copilot CLI durable hooks and provider routing."""
_run_init_targets(
targets=["copilot"],
global_scope=bool(_ctx_value(ctx, "global_scope")),
port=int(_ctx_value(ctx, "port") or 8787),
backend=str(_ctx_value(ctx, "backend") or "anthropic"),
anyllm_provider=_ctx_value(ctx, "anyllm_provider"),
region=_ctx_value(ctx, "region"),
memory=bool(_ctx_value(ctx, "memory")),
)
@init.command("codex")
@click.pass_context
def init_codex(ctx: click.Context) -> None:
"""Install Codex durable hooks and provider routing."""
_run_init_targets(
targets=["codex"],
global_scope=bool(_ctx_value(ctx, "global_scope")),
port=int(_ctx_value(ctx, "port") or 8787),
backend=str(_ctx_value(ctx, "backend") or "anthropic"),
anyllm_provider=_ctx_value(ctx, "anyllm_provider"),
region=_ctx_value(ctx, "region"),
memory=bool(_ctx_value(ctx, "memory")),
)
@init.command("openclaw")
@click.pass_context
def init_openclaw(ctx: click.Context) -> None:
"""Install the durable OpenClaw Headroom plugin."""
_run_init_targets(
targets=["openclaw"],
global_scope=bool(_ctx_value(ctx, "global_scope")),
port=int(_ctx_value(ctx, "port") or 8787),
backend=str(_ctx_value(ctx, "backend") or "anthropic"),
anyllm_provider=_ctx_value(ctx, "anyllm_provider"),
region=_ctx_value(ctx, "region"),
memory=bool(_ctx_value(ctx, "memory")),
)
@init.group("hook", hidden=True)
def init_hook() -> None:
"""Internal hook helpers."""
@init_hook.command("ensure")
@click.option("--profile", default=None, help="Explicit deployment profile to ensure.")
@click.option("--marker", default=None, hidden=True)
def init_hook_ensure(profile: str | None, marker: str | None) -> None:
"""Best-effort ensure used by installed agent hooks."""
del marker
profiles: list[str] = []
if profile:
profiles.append(profile)
else:
local_profile = _local_profile()
if load_manifest(local_profile) is not None:
profiles.append(local_profile)
elif load_manifest(_GLOBAL_PROFILE) is not None:
profiles.append(_GLOBAL_PROFILE)
for name in profiles:
_ensure_profile_running(name)

View file

@ -1,78 +1,79 @@
"""Main CLI entry point for Headroom."""
import click
CLI_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-?"]}
def get_version() -> str:
"""Get the current version."""
try:
from headroom._version import __version__
return __version__
except ImportError:
return "unknown"
@click.group(context_settings=CLI_CONTEXT_SETTINGS)
@click.version_option(get_version(), "--version", "-v", prog_name="headroom")
@click.pass_context
def main(ctx: click.Context) -> None:
"""Headroom - The Context Optimization Layer for LLM Applications.
Manage memories, run the optimization proxy, and analyze metrics.
\b
Examples:
headroom proxy Start the optimization proxy
headroom memory list List stored memories
headroom memory stats Show memory statistics
"""
ctx.ensure_object(dict)
# Import subcommands - these register themselves with the main group
def _register_commands() -> None:
"""Register all subcommand groups."""
from . import (
evals, # noqa: F401
install, # noqa: F401
learn, # noqa: F401
mcp, # noqa: F401
perf, # noqa: F401
proxy, # noqa: F401
tools, # noqa: F401
wrap, # noqa: F401
)
# Memory CLI requires numpy/hnswlib — optional
try:
from . import memory # noqa: F401
except ImportError:
pass
_register_commands()
def _apply_help_aliases(command: click.Command) -> None:
"""Ensure `-?` works everywhere in the Click command tree."""
context_settings = dict(command.context_settings or {})
help_option_names = list(context_settings.get("help_option_names", []))
if "--help" not in help_option_names:
help_option_names.append("--help")
if "-?" not in help_option_names:
help_option_names.append("-?")
context_settings["help_option_names"] = help_option_names
command.context_settings = context_settings
if isinstance(command, click.Group):
for child in command.commands.values():
_apply_help_aliases(child)
_apply_help_aliases(main)
if __name__ == "__main__":
main()
"""Main CLI entry point for Headroom."""
import click
CLI_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-?"]}
def get_version() -> str:
"""Get the current version."""
try:
from headroom._version import __version__
return __version__
except ImportError:
return "unknown"
@click.group(context_settings=CLI_CONTEXT_SETTINGS)
@click.version_option(get_version(), "--version", "-v", prog_name="headroom")
@click.pass_context
def main(ctx: click.Context) -> None:
"""Headroom - The Context Optimization Layer for LLM Applications.
Manage memories, run the optimization proxy, and analyze metrics.
\b
Examples:
headroom proxy Start the optimization proxy
headroom memory list List stored memories
headroom memory stats Show memory statistics
"""
ctx.ensure_object(dict)
# Import subcommands - these register themselves with the main group
def _register_commands() -> None:
"""Register all subcommand groups."""
from . import (
evals, # noqa: F401
init, # noqa: F401
install, # noqa: F401
learn, # noqa: F401
mcp, # noqa: F401
perf, # noqa: F401
proxy, # noqa: F401
tools, # noqa: F401
wrap, # noqa: F401
)
# Memory CLI requires numpy/hnswlib — optional
try:
from . import memory # noqa: F401
except ImportError:
pass
_register_commands()
def _apply_help_aliases(command: click.Command) -> None:
"""Ensure `-?` works everywhere in the Click command tree."""
context_settings = dict(command.context_settings or {})
help_option_names = list(context_settings.get("help_option_names", []))
if "--help" not in help_option_names:
help_option_names.append("--help")
if "-?" not in help_option_names:
help_option_names.append("-?")
context_settings["help_option_names"] = help_option_names
command.context_settings = context_settings
if isinstance(command, click.Group):
for child in command.commands.values():
_apply_help_aliases(child)
_apply_help_aliases(main)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,17 @@
{
"name": "headroom",
"version": "0.1.0",
"description": "Headroom startup hooks for Claude Code and GitHub Copilot CLI.",
"author": {
"name": "Headroom Contributors",
"url": "https://github.com/JerrettDavis/headroom"
},
"homepage": "https://github.com/JerrettDavis/headroom",
"repository": "https://github.com/JerrettDavis/headroom",
"keywords": [
"headroom",
"hooks",
"claude-code",
"copilot-cli"
]
}

View file

@ -0,0 +1,18 @@
{
"name": "headroom",
"version": "0.1.0",
"description": "Headroom startup hooks for Claude Code and GitHub Copilot CLI.",
"author": {
"name": "Headroom Contributors",
"url": "https://github.com/JerrettDavis/headroom"
},
"homepage": "https://github.com/JerrettDavis/headroom",
"repository": "https://github.com/JerrettDavis/headroom",
"keywords": [
"headroom",
"hooks",
"claude-code",
"copilot-cli"
],
"hooks": "./hooks"
}

View file

@ -0,0 +1,11 @@
# Headroom agent hooks
This plugin exposes lightweight startup hooks for Claude Code and GitHub Copilot CLI.
The hooks call:
```bash
headroom init hook ensure
```
That hidden helper checks for a matching durable `headroom init` deployment and starts it if needed.

View file

@ -0,0 +1,29 @@
{
"description": "Headroom plugin hooks — ensure the local Headroom runtime is available for initialized agents.",
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "headroom init hook ensure",
"timeout": 15
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"command": "headroom init hook ensure",
"timeout": 15
}
]
}
]
}
}

View file

@ -0,0 +1,202 @@
from __future__ import annotations
import importlib
import json
import sys
import types
from pathlib import Path
import click
from click.testing import CliRunner
def _load_init_module(monkeypatch):
monkeypatch.delitem(sys.modules, "headroom.cli.init", raising=False)
monkeypatch.delitem(sys.modules, "headroom.cli.main", raising=False)
fake_main_module = types.ModuleType("headroom.cli.main")
@click.group()
def fake_main() -> None:
pass
fake_main_module.main = fake_main
monkeypatch.setitem(sys.modules, "headroom.cli.main", fake_main_module)
importlib.invalidate_caches()
init_cli = importlib.import_module("headroom.cli.init")
monkeypatch.delitem(sys.modules, "headroom.cli.init", raising=False)
return init_cli, fake_main
def test_init_auto_detects_targets(monkeypatch) -> None:
init_cli, fake_main = _load_init_module(monkeypatch)
runner = CliRunner()
captured: dict[str, object] = {}
monkeypatch.setattr(init_cli, "detect_init_targets", lambda global_scope: ["claude", "codex"])
monkeypatch.setattr(init_cli, "_run_init_targets", lambda **kwargs: captured.update(kwargs))
result = runner.invoke(fake_main, ["init", "-g"])
assert result.exit_code == 0, result.output
assert captured["targets"] == ["claude", "codex"]
assert captured["global_scope"] is True
def test_init_fails_when_auto_detection_empty(monkeypatch) -> None:
init_cli, fake_main = _load_init_module(monkeypatch)
runner = CliRunner()
monkeypatch.setattr(init_cli, "detect_init_targets", lambda global_scope: [])
result = runner.invoke(fake_main, ["init"])
assert result.exit_code != 0
assert "auto-detected" in result.output
def test_init_copilot_requires_global(monkeypatch) -> None:
init_cli, fake_main = _load_init_module(monkeypatch)
runner = CliRunner()
monkeypatch.setattr(init_cli, "_ensure_runtime_manifest", lambda **kwargs: "init-local-test")
result = runner.invoke(fake_main, ["init", "copilot"])
assert result.exit_code != 0
assert "requires -g" in result.output
def test_init_claude_local_writes_settings_and_installs_marketplace(
monkeypatch, tmp_path: Path
) -> None:
init_cli, fake_main = _load_init_module(monkeypatch)
runner = CliRunner()
monkeypatch.chdir(tmp_path)
marketplace_calls: list[str] = []
monkeypatch.setattr(init_cli, "_ensure_runtime_manifest", lambda **kwargs: "init-local-demo")
monkeypatch.setattr(
init_cli,
"_install_claude_marketplace",
lambda scope: marketplace_calls.append(scope),
)
result = runner.invoke(fake_main, ["init", "claude"])
assert result.exit_code == 0, result.output
settings_path = tmp_path / ".claude" / "settings.local.json"
payload = json.loads(settings_path.read_text(encoding="utf-8"))
assert payload["env"]["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:8787"
assert marketplace_calls == ["local"]
assert any(
"--profile init-local-demo" in hook["command"] and "init hook ensure" in hook["command"]
for entry in payload["hooks"]["SessionStart"]
for hook in entry["hooks"]
)
def test_init_codex_merges_feature_flag_into_existing_table(monkeypatch, tmp_path: Path) -> None:
init_cli, _ = _load_init_module(monkeypatch)
monkeypatch.chdir(tmp_path)
config_path = tmp_path / ".codex" / "config.toml"
config_path.parent.mkdir(parents=True)
config_path.write_text("[features]\nshell_tool = true\n", encoding="utf-8")
init_cli._init_codex(global_scope=False, profile="init-local-demo", port=9000)
content = config_path.read_text(encoding="utf-8")
assert 'base_url = "http://127.0.0.1:9000/v1"' in content
assert content.count("[features]") == 1
assert "codex_hooks = true" in content
hooks = json.loads((tmp_path / ".codex" / "hooks.json").read_text(encoding="utf-8"))
assert "--profile init-local-demo" in hooks["hooks"]["SessionStart"][0]["hooks"][0]["command"]
assert "init hook ensure" in hooks["hooks"]["SessionStart"][0]["hooks"][0]["command"]
def test_init_claude_uses_custom_port(monkeypatch, tmp_path: Path) -> None:
init_cli, _ = _load_init_module(monkeypatch)
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(init_cli, "_install_claude_marketplace", lambda scope: None)
init_cli._init_claude(global_scope=False, profile="init-local-demo", port=9011)
payload = json.loads((tmp_path / ".claude" / "settings.local.json").read_text(encoding="utf-8"))
assert payload["env"]["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:9011"
def test_init_copilot_global_writes_hooks_and_env(monkeypatch, tmp_path: Path) -> None:
init_cli, _ = _load_init_module(monkeypatch)
captured_env: dict[str, str] = {}
monkeypatch.setattr(init_cli, "_copilot_config_path", lambda: tmp_path / "copilot-config.json")
monkeypatch.setattr(init_cli, "_apply_user_env", lambda values: captured_env.update(values))
monkeypatch.setattr(init_cli, "_install_copilot_marketplace", lambda: None)
init_cli._init_copilot(global_scope=True, profile="init-user", port=9005, backend="openai")
payload = json.loads((tmp_path / "copilot-config.json").read_text(encoding="utf-8"))
assert "SessionStart" in payload["hooks"]
assert "PreToolUse" in payload["hooks"]
assert "--profile init-user" in payload["hooks"]["SessionStart"][0]["command"]
assert captured_env == {
"COPILOT_PROVIDER_TYPE": "openai",
"COPILOT_PROVIDER_BASE_URL": "http://127.0.0.1:9005/v1",
"COPILOT_PROVIDER_WIRE_API": "completions",
}
def test_init_hook_ensure_prefers_local_profile(monkeypatch) -> None:
init_cli, fake_main = _load_init_module(monkeypatch)
ensured: list[str] = []
def fake_load(profile: str):
return object() if profile == "init-repo-12345678" else None
monkeypatch.setattr(init_cli, "_local_profile", lambda cwd=None: "init-repo-12345678")
monkeypatch.setattr(init_cli, "load_manifest", fake_load)
monkeypatch.setattr(
init_cli, "_ensure_profile_running", lambda profile: ensured.append(profile)
)
runner = CliRunner()
result = runner.invoke(fake_main, ["init", "hook", "ensure"])
assert result.exit_code == 0, result.output
assert ensured == ["init-repo-12345678"]
def test_init_openclaw_requires_global(monkeypatch) -> None:
_, fake_main = _load_init_module(monkeypatch)
runner = CliRunner()
result = runner.invoke(fake_main, ["init", "openclaw"])
assert result.exit_code != 0
assert "requires -g" in result.output
def test_init_openclaw_delegates_to_wrap(monkeypatch) -> None:
init_cli, _ = _load_init_module(monkeypatch)
calls: list[list[str]] = []
class _Result:
returncode = 0
monkeypatch.setattr(init_cli, "resolve_headroom_command", lambda: ["headroom"])
monkeypatch.setattr(
init_cli.subprocess,
"run",
lambda cmd: calls.append(cmd) or _Result(),
)
init_cli._init_openclaw(global_scope=True, port=9999)
assert calls == [["headroom", "wrap", "openclaw", "--proxy-port", "9999"]]
def test_detect_init_targets_respects_scope(monkeypatch) -> None:
init_cli, _ = _load_init_module(monkeypatch)
monkeypatch.setattr(
init_cli.shutil,
"which",
lambda name: name if name in {"claude", "copilot", "codex", "openclaw"} else None,
)
assert init_cli.detect_init_targets(False) == ["claude", "codex"]
assert init_cli.detect_init_targets(True) == ["claude", "copilot", "codex", "openclaw"]

View file

@ -0,0 +1,40 @@
from __future__ import annotations
import json
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
def _load_json(relative_path: str) -> object:
return json.loads((REPO_ROOT / relative_path).read_text(encoding="utf-8"))
def test_marketplace_manifests_match() -> None:
assert _load_json(".claude-plugin/marketplace.json") == _load_json(
".github/plugin/marketplace.json"
)
def test_plugin_manifests_share_core_metadata() -> None:
claude = _load_json("plugins/headroom-agent-hooks/.claude-plugin/plugin.json")
copilot = _load_json("plugins/headroom-agent-hooks/.github/plugin/plugin.json")
assert isinstance(claude, dict)
assert isinstance(copilot, dict)
for key in ("name", "version", "description", "author", "homepage", "repository", "keywords"):
assert claude[key] == copilot[key]
assert "hooks" not in claude
assert copilot["hooks"] == "./hooks"
def test_marketplace_entry_points_to_plugin_root() -> None:
marketplace = _load_json(".claude-plugin/marketplace.json")
assert isinstance(marketplace, dict)
plugins = marketplace["plugins"]
assert isinstance(plugins, list)
plugin = plugins[0]
assert plugin["name"] == "headroom"
plugin_root = (REPO_ROOT / plugin["source"]).resolve()
assert plugin_root.is_dir()
assert (plugin_root / ".claude-plugin" / "plugin.json").is_file()
assert (plugin_root / "hooks" / "hooks.json").is_file()