Skip to content

Commit dc25620

Browse files
Add headroom as an optional installer group (#7)
headroom (headroomlabs-ai/headroom) is a standalone Python CLI, not a Claude Code plugin, so it can't go through marketplaceSetup. Adds a toolSetup field for external CLI tools: when uv is on PATH the installer offers to run `uv tool install --python 3.13 "headroom-ai[all]"` (defaults to No since it installs machine-wide), and prints the command otherwise. `headroom wrap claude` is shown as the next step only after a successful install. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent e9173e3 commit dc25620

5 files changed

Lines changed: 90 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ claude plugin install academic-research-skills@academic-research-skills
101101
claude plugin install ponytail@ponytail
102102
```
103103

104+
Optional external CLI — [headroom](https://github.com/headroomlabs-ai/headroom) context compression (not a plugin; installs machine-wide):
105+
106+
```bash
107+
uv tool install --python 3.13 "headroom-ai[all]"
108+
headroom wrap claude
109+
```
110+
104111
Marketing skills can also be installed via the `npx skills` CLI (installs to `.agents/skills/` and symlinks into `.claude/skills/`):
105112

106113
```bash

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ Source: [pbakaus/impeccable](https://github.com/pbakaus/impeccable). Useful when
175175
| Caveman Mode || **caveman** |
176176
| Agent Skills || **agent-skills** |
177177
| Ponytail (Lazy Dev) || **ponytail** |
178+
| Headroom (Context Compression) || — (optional CLI install) |
179+
180+
Headroom ([headroomlabs-ai/headroom](https://github.com/headroomlabs-ai/headroom)) is a standalone CLI, not a plugin. If `uv` is on your PATH, the installer offers to run `uv tool install --python 3.13 "headroom-ai[all]"` for you (defaults to No; installs machine-wide); otherwise it prints the command. Afterwards, run `headroom wrap claude` (undo with `headroom unwrap claude`).
178181

179182
## Repo layout
180183

create-claude-setup/bin/cli.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GROUPS } from '../src/catalog.js';
88
import { install, readManifest } from '../src/installer.js';
99
import { update } from '../src/updater.js';
1010
import { claudeAvailable, installAllPlugins } from '../src/plugin-installer.js';
11+
import { commandAvailable, formatCommand, runCommand } from '../src/tool-installer.js';
1112

1213
// Node version check
1314
const [major] = process.versions.node.split('.').map(Number);
@@ -218,6 +219,29 @@ async function main() {
218219
}
219220
}
220221

222+
// ── External tools ────────────────────────────────────────────────────────
223+
for (const tool of plan.toolSetup) {
224+
const installCmd = formatCommand(tool.install);
225+
let installed = false;
226+
227+
if (commandAvailable(tool.requires)) {
228+
const choice = await confirm({
229+
message: `Install ${tool.name} now? (${pc.dim(installCmd)} — installs machine-wide)`,
230+
initialValue: false,
231+
});
232+
if (!isCancel(choice) && choice) {
233+
installed = runCommand(tool.install);
234+
if (!installed) console.log(pc.red(` ${tool.name} install failed.`));
235+
}
236+
} else {
237+
console.log(pc.dim(` \`${tool.requires}\` not found — install ${tool.name} manually:`));
238+
}
239+
240+
if (installed) console.log(pc.dim(` Then run: `) + pc.yellow(tool.next));
241+
else console.log(pc.yellow(` ${installCmd}`));
242+
console.log('');
243+
}
244+
221245
// Skill summary
222246
console.log(pc.dim(` Installed ${plan.skills.length} skill(s) across ${finalGroups.length} group(s)`));
223247
if (plan.copyAgents) console.log(pc.dim(' Copied agents and workflows'));

create-claude-setup/src/catalog.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,27 @@ export const GROUPS = [
237237
copyAgents: false,
238238
copyTemplates: false,
239239
},
240+
{
241+
id: 'headroom',
242+
label: 'Headroom (Context Compression)',
243+
hint: 'CLI proxy that compresses tool outputs before they reach the LLM',
244+
required: false,
245+
skills: [],
246+
plugins: [],
247+
marketplaceSetup: [],
248+
// External CLI tools. The installer offers to run `install` (machine-wide) if `requires`
249+
// is on PATH; `next` is printed after a successful install for the user to run themselves.
250+
toolSetup: [
251+
{
252+
name: 'headroom',
253+
requires: 'uv',
254+
install: ['uv', 'tool', 'install', '--python', '3.13', 'headroom-ai[all]'],
255+
next: 'headroom wrap claude',
256+
},
257+
],
258+
copyAgents: false,
259+
copyTemplates: false,
260+
},
240261
];
241262

242263
export function getGroupById(id) {
@@ -247,6 +268,7 @@ export function resolveInstallPlan(selectedGroupIds) {
247268
const skills = new Set();
248269
const plugins = new Set();
249270
const marketplaceSetup = [];
271+
const toolSetup = [];
250272
let copyAgents = false;
251273
let copyTemplates = false;
252274

@@ -262,6 +284,7 @@ export function resolveInstallPlan(selectedGroupIds) {
262284
plugins.add(`${m.pluginName}@${m.marketplaceFlag}`);
263285
}
264286
});
287+
toolSetup.push(...(group.toolSetup ?? []));
265288
if (group.copyAgents) copyAgents = true;
266289
if (group.copyTemplates) copyTemplates = true;
267290
}
@@ -270,6 +293,7 @@ export function resolveInstallPlan(selectedGroupIds) {
270293
skills: [...skills],
271294
plugins: [...plugins],
272295
marketplaceSetup,
296+
toolSetup,
273297
copyAgents,
274298
copyTemplates,
275299
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { execFileSync } from 'node:child_process';
2+
3+
/**
4+
* Check whether an executable is available in PATH.
5+
*/
6+
export function commandAvailable(name) {
7+
try {
8+
execFileSync(name, ['--version'], { stdio: 'pipe' });
9+
return true;
10+
} catch {
11+
return false;
12+
}
13+
}
14+
15+
/**
16+
* Render an argv array as a copy-pasteable shell command.
17+
*/
18+
export function formatCommand(argv) {
19+
return argv.map(a => (/[^\w@%+=:,./-]/.test(a) ? `"${a}"` : a)).join(' ');
20+
}
21+
22+
/**
23+
* Run an argv array with output streamed to the terminal. Returns true on success.
24+
*/
25+
export function runCommand([cmd, ...args]) {
26+
try {
27+
execFileSync(cmd, args, { stdio: 'inherit' });
28+
return true;
29+
} catch {
30+
return false;
31+
}
32+
}

0 commit comments

Comments
 (0)