@polderlabs/bizar 10.0.7 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/bizar-dash/src/server/api.mjs +2 -6
  2. package/bizar-dash/src/server/diagnostics-store.mjs +5 -35
  3. package/bizar-dash/src/server/mod-security.mjs +1 -2
  4. package/bizar-dash/src/server/providers-store.mjs +1 -1
  5. package/bizar-dash/src/server/routes/_shared.mjs +0 -17
  6. package/bizar-dash/src/server/routes/model-router.mjs +103 -0
  7. package/bizar-dash/src/server/routes/model-router.test.mjs +76 -0
  8. package/bizar-dash/src/server/server.mjs +1 -27
  9. package/bizar-dash/tests/cli-bugfixes.test.mjs +1 -1
  10. package/bizar-dash/tests/cli-error-visibility.test.mjs +1 -1
  11. package/bizar-dash/tests/cli-refactor.test.mjs +0 -1
  12. package/bizar-dash/tests/diagnostics-store.test.mjs +0 -1
  13. package/cli/bin.mjs +1 -15
  14. package/cli/cli-commands-validation.test.mjs +1 -1
  15. package/cli/commands/lightrag.mjs +1 -1
  16. package/cli/commands/util.mjs +1 -1
  17. package/cli/copy.mjs +1 -47
  18. package/cli/doctor.mjs +3 -3
  19. package/cli/install.mjs +3 -37
  20. package/cli/service-controller.mjs +0 -192
  21. package/cli/service-env.mjs +2 -9
  22. package/cli/utils.mjs +0 -7
  23. package/package.json +1 -1
  24. package/packages/sdk/package.json +1 -1
  25. package/scripts/check-deps.mjs +0 -24
  26. package/bizar-dash/skills/headroom/SKILL.md +0 -94
  27. package/bizar-dash/src/server/headroom.mjs +0 -649
  28. package/bizar-dash/src/server/routes/headroom.mjs +0 -126
  29. package/bizar-dash/tests/headroom-install.test.mjs +0 -173
  30. package/bizar-dash/tests/headroom-settings.test.mjs +0 -126
  31. package/bizar-dash/tests/headroom-status.test.mjs +0 -117
  32. package/cli/commands/headroom.mjs +0 -204
@@ -32,6 +32,7 @@ import { createArtifactsRouter } from './routes/artifacts.mjs';
32
32
  import { createSchedulesRouter } from './routes/schedules.mjs';
33
33
  import { createModsRouter } from './routes/mods.mjs';
34
34
  import { createAgentsRouter } from './routes/agents.mjs';
35
+ import { createModelRouterRouter } from './routes/model-router.mjs';
35
36
  import { createBackgroundRouter } from './routes/background.mjs';
36
37
  import { createActivityRouter } from './routes/activity.mjs';
37
38
  import { createHistoryRouter } from './routes/history.mjs';
@@ -58,7 +59,6 @@ import { createEnvVarsRouter } from './routes/env-vars.mjs';
58
59
  import { createUpdateRouter } from './routes/update.mjs';
59
60
  import { createSpawnRouter } from './routes/spawn.mjs';
60
61
  import { createUsageRouter } from './routes/usage.mjs';
61
- import { createHeadroomRouter } from './routes/headroom.mjs';
62
62
  import { createEvalRouter } from './routes/eval.mjs';
63
63
  import { createWorkspacesRouter } from './routes/workspaces.mjs';
64
64
  import { createUsersRouter } from './routes/users.mjs';
@@ -125,6 +125,7 @@ export async function createApiRouter({
125
125
  router.use(createSchedulesRouter({ broadcast }));
126
126
  router.use(createModsRouter());
127
127
  router.use(createAgentsRouter({ state, broadcast }));
128
+ router.use(createModelRouterRouter({ state }));
128
129
  router.use(createBackgroundRouter({ broadcast }));
129
130
  // Sprint S10 — live CC agents + SSE stream for the dashboard output panel.
130
131
  router.use(createCCAgentsRouter({ broadcast }));
@@ -181,11 +182,6 @@ export async function createApiRouter({
181
182
  router.use(createArtifactsRouter({ state, broadcast, projectRoot }));
182
183
  router.use(createMinimaxRouter({ state, broadcast }));
183
184
  router.use(createUsageRouter());
184
- // v5.0.0 — Headroom context compression endpoints.
185
- // Mounted at /api/headroom/* so the mount-prefix stripping works correctly.
186
- // Each route handler inside the router is at its bare path (e.g. '/status'),
187
- // which becomes '/api/headroom/status' at the top level.
188
- router.use('/headroom', createHeadroomRouter());
189
185
  // v5.0.0 — Eval framework endpoints.
190
186
  router.use(createEvalRouter({ state, broadcast }));
191
187
  // v5.0.0 — Workspace and user management endpoints.
@@ -119,10 +119,7 @@ export function getRecentErrors({ since = 0, limit = 200 } = {}) {
119
119
  }
120
120
 
121
121
  /**
122
- * v6.0.0 — Probe the local headroom proxy on `host:port`. Returns a
123
- * `{ running, port?, error? }` shape so the Doctor page can show a
124
- * "Headroom proxy on 8787 ✓" line without forcing the route to
125
- * swallow a 1.5s timeout on every poll.
122
+ * v6.0.0 — Generic TCP probe used by the Doctor page.
126
123
  *
127
124
  * @param {string} host
128
125
  * @param {number} port
@@ -184,23 +181,6 @@ async function probeLightRAG() {
184
181
  }
185
182
  }
186
183
 
187
- async function probeHeadroom() {
188
- // Headroom's default port — matches headroom.mjs's
189
- // DEFAULT_HEADROOM_PORT. Settings override takes precedence.
190
- let port = 8787;
191
- let host = '127.0.0.1';
192
- try {
193
- const settingsPath = join(HOME, '.config', 'bizar', 'settings.json');
194
- if (existsSync(settingsPath)) {
195
- const parsed = JSON.parse(readFileSync(settingsPath, 'utf8'));
196
- port = Number(parsed?.headroom?.port) || port;
197
- host = String(parsed?.headroom?.host) || host;
198
- }
199
- } catch { /* ignore */ }
200
- const result = await probeTcp(host, port, 800);
201
- return { ...result, port, host };
202
- }
203
-
204
184
  /**
205
185
  * v6.0.0 — Probe whether the cline plugin's `serve-info.json` is
206
186
  * reachable on disk and parseable. The dashboard reads serve-info to
@@ -321,12 +301,11 @@ async function runConfigChecks() {
321
301
  }
322
302
 
323
303
  /**
324
- * v6.0.0 — Service health checks. TCP probes for headroom and
325
- * lightrag (both best-effort), cline plugin file probe, and the
326
- * dashboard itself (always `ok` — we're running because we got here).
304
+ * v6.0.0 — Service health checks. TCP probe for lightrag
305
+ * (best-effort), cline plugin file probe, and the dashboard itself
306
+ * (always `ok` — we're running because we got here).
327
307
  */
328
308
  async function runServiceChecks() {
329
- const headroom = await probeHeadroom();
330
309
  const lightrag = await probeLightRAG();
331
310
  const serveInfo = clineServeInfo();
332
311
  const checks = [
@@ -335,13 +314,6 @@ async function runServiceChecks() {
335
314
  status: 'ok',
336
315
  message: `running on pid ${process.pid}, up ${Math.floor(process.uptime())}s`,
337
316
  },
338
- {
339
- name: 'headroom',
340
- status: headroom.running ? 'ok' : 'warn',
341
- message: headroom.running
342
- ? `proxy on ${headroom.host}:${headroom.port}`
343
- : `not running on ${headroom.host}:${headroom.port}${headroom.error ? ` (${headroom.error})` : ''}`,
344
- },
345
317
  {
346
318
  name: 'lightrag',
347
319
  status: lightrag.running ? 'ok' : 'warn',
@@ -447,7 +419,7 @@ export async function health() {
447
419
  * uptime: number,
448
420
  * memory: NodeJS.MemoryUsage,
449
421
  * disk: { exists: boolean, path: string, sizeBytes: number },
450
- * services: { dashboard: object, headroom: object, lightrag: object, cline: object },
422
+ * services: { dashboard: object, lightrag: object, cline: object },
451
423
  * counts: ReturnType<typeof collectCounts>,
452
424
  * recentErrors: ReturnType<typeof getRecentErrors>,
453
425
  * configHealth: Array<{ name: string, status: string, message: string }>,
@@ -467,7 +439,6 @@ export async function collectDiagnostics() {
467
439
  let status = 'ok';
468
440
  if (issues.some((c) => c.status === 'fail')) status = 'fail';
469
441
  else if (issues.length > 0) status = 'warn';
470
- const headroom = await probeHeadroom();
471
442
  const lightrag = await probeLightRAG();
472
443
  const serveInfo = clineServeInfo();
473
444
  const clineConfig = providersStore.CLINE_JSON;
@@ -505,7 +476,6 @@ export async function collectDiagnostics() {
505
476
  logPath: dashboardLogPath,
506
477
  logExists: dashboardLogExists,
507
478
  },
508
- headroom,
509
479
  lightrag,
510
480
  cline: serveInfo,
511
481
  },
@@ -22,7 +22,7 @@
22
22
  * Path-traversal attacks (e.g. `../`) are blocked.
23
23
  * 3. **Subprocess allowlist** — only whitelisted binaries
24
24
  * (`bizar`, `cline`, `python3`, `graphify`, `git`, `node`,
25
- * `npm`, `pip`, `pipx`, `uv`, `headroom`) can be spawned. Custom
25
+ * `npm`, `pip`, `pipx`, `uv`) can be spawned. Custom
26
26
  * binaries require an explicit `process:spawn:<bin>` permission.
27
27
  * 4. **Audit log** — every privileged operation (fs read/write,
28
28
  * process spawn, network fetch) is logged to
@@ -73,7 +73,6 @@ export const ALLOWED_BINARIES = new Set([
73
73
  'npm',
74
74
  'npx',
75
75
  'git',
76
- 'headroom',
77
76
  'jq',
78
77
  ]);
79
78
 
@@ -164,7 +164,7 @@ export { readClineJsonCached, invalidateClineJsonCache, CLINE_JSON_CACHE_TTL_MS
164
164
  // MAX_ROTATION_ATTEMPTS: bound on the number of times `withKeyRotation`
165
165
  // will retry the user's fn with a different key before throwing. With
166
166
  // `cooldown` skipping, this matches the typical "1 primary + 1 backup"
167
- // deployment (2 attempts) with headroom for a 3rd emergency key.
167
+ // deployment (2 attempts) with a 3rd emergency key.
168
168
  //
169
169
  // ROTATION_ERROR_PATTERNS: substring match against the lowercased error
170
170
  // message — used as a fallback when no structured `status` is present
@@ -148,22 +148,6 @@ export const DEFAULT_SETTINGS = {
148
148
  baseUrl: 'https://www.minimax.io',
149
149
  chatBaseUrl: 'https://api.minimax.io/v1',
150
150
  },
151
- // v5.0.0 — Headroom context compression integration.
152
- // Headroom sits between cline and LLM providers, compressing tool
153
- // outputs, logs, RAG chunks, and conversation history by 60–95%.
154
- headroom: {
155
- enabled: true,
156
- autoInstall: true,
157
- port: 8787,
158
- host: '127.0.0.1',
159
- outputShaper: false,
160
- telemetry: false,
161
- budget: 0,
162
- backend: 'anthropic',
163
- autoStart: true,
164
- autoWrap: true,
165
- routeAllProviders: true,
166
- },
167
151
  };
168
152
 
169
153
  /**
@@ -190,7 +174,6 @@ export function mergeSettings(existing) {
190
174
  merged.agents = { ...DEFAULT_SETTINGS.agents, ...(existing.agents || {}) };
191
175
  merged.systemLlm = { ...DEFAULT_SETTINGS.systemLlm, ...(existing.systemLlm || {}) };
192
176
  merged.minimax = { ...DEFAULT_SETTINGS.minimax, ...(existing.minimax || {}) };
193
- merged.headroom = { ...DEFAULT_SETTINGS.headroom, ...(existing.headroom || {}) };
194
177
  // Always use the package version — never let user settings override it
195
178
  merged.about.version = DEFAULT_SETTINGS.about.version;
196
179
  return merged;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * src/server/routes/model-router.mjs
3
+ *
4
+ * Surfaces the local model-router configuration and live model list.
5
+ *
6
+ * GET /api/model-router — full policy + agent→model map
7
+ * GET /api/model-router/models — live model list from the local router
8
+ * GET /api/model-router/agents — agent→model map only
9
+ *
10
+ * The local router URL defaults to ANTHROPIC_BASE_URL or
11
+ * BIZAR_MODEL_ROUTER_URL or `http://localhost:20128/v1`.
12
+ */
13
+ import { Router } from 'express';
14
+ import { readFile } from 'node:fs/promises';
15
+ import { resolve, dirname } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { wrap } from './_shared.mjs';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ // .claude/model-router.json — repo root.
21
+ // From src/server/routes/ up to repo root is 3 levels (routes -> server -> src -> repo).
22
+ const ROUTER_POLICY_PATH = resolve(__dirname, '../../../../.claude/model-router.json');
23
+
24
+ const DEFAULT_ENDPOINT = 'http://localhost:20128/v1';
25
+
26
+ function resolveEndpoint(state) {
27
+ const fromEnv =
28
+ process.env.BIZAR_MODEL_ROUTER_URL ||
29
+ process.env.ANTHROPIC_BASE_URL ||
30
+ state?.config?.modelRouterUrl;
31
+ return (fromEnv || DEFAULT_ENDPOINT).replace(/\/+$/, '');
32
+ }
33
+
34
+ async function loadPolicy() {
35
+ try {
36
+ const raw = await readFile(ROUTER_POLICY_PATH, 'utf8');
37
+ return JSON.parse(raw);
38
+ } catch {
39
+ return {
40
+ version: '0.0.0',
41
+ endpoint: DEFAULT_ENDPOINT,
42
+ tiers: {},
43
+ agents: {},
44
+ policies: {},
45
+ };
46
+ }
47
+ }
48
+
49
+ async function fetchLiveModels(endpoint) {
50
+ try {
51
+ const res = await fetch(`${endpoint}/models`, {
52
+ signal: AbortSignal.timeout(2000),
53
+ });
54
+ if (!res.ok) return { reachable: false, error: `HTTP ${res.status}` };
55
+ const body = await res.json();
56
+ return {
57
+ reachable: true,
58
+ endpoint,
59
+ models: Array.isArray(body?.data) ? body.data : [],
60
+ };
61
+ } catch (err) {
62
+ return {
63
+ reachable: false,
64
+ endpoint,
65
+ error: err instanceof Error ? err.message : String(err),
66
+ };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @param {object} deps
72
+ * @param {object} deps.state
73
+ * @returns {import('express').Router}
74
+ */
75
+ export function createModelRouterRouter({ state }) {
76
+ const router = Router();
77
+
78
+ router.get('/model-router', wrap(async (_req, res) => {
79
+ const policy = await loadPolicy();
80
+ const endpoint = resolveEndpoint(state);
81
+ const live = await fetchLiveModels(endpoint);
82
+ res.json({
83
+ endpoint,
84
+ policy,
85
+ live,
86
+ agents: policy.agents || {},
87
+ tiers: policy.tiers || {},
88
+ });
89
+ }));
90
+
91
+ router.get('/model-router/models', wrap(async (_req, res) => {
92
+ const endpoint = resolveEndpoint(state);
93
+ const live = await fetchLiveModels(endpoint);
94
+ res.json(live);
95
+ }));
96
+
97
+ router.get('/model-router/agents', wrap(async (_req, res) => {
98
+ const policy = await loadPolicy();
99
+ res.json({ agents: policy.agents || {}, tiers: policy.tiers || {} });
100
+ }));
101
+
102
+ return router;
103
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Smoke test for /api/model-router. Verifies:
3
+ * - the route resolves and returns the agent→model map
4
+ * - live model probe gracefully degrades when the endpoint is unreachable
5
+ * - the policy file is read from `.claude/model-router.json`
6
+ *
7
+ * Run with: bun test src/server/routes/model-router.test.mjs
8
+ * or: node --test src/server/routes/model-router.test.mjs
9
+ */
10
+ import { test } from 'node:test';
11
+ import assert from 'node:assert/strict';
12
+ import express from 'express';
13
+ import { createModelRouterRouter } from './model-router.mjs';
14
+
15
+ function buildApp() {
16
+ const app = express();
17
+ // Mount under /api to match the dashboard's mount prefix.
18
+ app.use('/api', createModelRouterRouter({ state: {} }));
19
+ return app;
20
+ }
21
+
22
+ async function getJson(app, path) {
23
+ const server = app.listen(0);
24
+ try {
25
+ const port = server.address().port;
26
+ const res = await fetch(`http://127.0.0.1:${port}${path}`);
27
+ return { status: res.status, body: await res.json() };
28
+ } finally {
29
+ await new Promise((r) => server.close(r));
30
+ }
31
+ }
32
+
33
+ test('GET /api/model-router returns agent→model map from .claude/model-router.json', async () => {
34
+ const app = buildApp();
35
+ const { status, body } = await getJson(app, '/api/model-router');
36
+ assert.equal(status, 200);
37
+ assert.ok(typeof body.endpoint === 'string' && body.endpoint.length > 0, 'endpoint present');
38
+ assert.ok(body.policy, 'policy present');
39
+ assert.ok(body.agents, 'agents map present');
40
+ // Forseti / Baldr / Vidarr must be GPT (premium tier per the routing rules).
41
+ assert.match(body.agents.forseti.model, /gpt-5\.6-sol/, 'forseti uses premium GPT');
42
+ assert.match(body.agents.baldr.model, /gpt-5\.6-sol/, 'baldr uses premium GPT');
43
+ assert.match(body.agents.vidarr.model, /gpt-5\.6-sol/, 'vidarr uses premium GPT');
44
+ // Tyr + Odin use the high-tier terra model.
45
+ assert.match(body.agents.tyr.model, /gpt-5\.6-terra/, 'tyr uses high-tier GPT');
46
+ assert.match(body.agents.odin.model, /gpt-5\.6-terra/, 'odin uses high-tier GPT');
47
+ // Everyday default agents use MiniMax-M3.
48
+ assert.equal(body.agents.frigg.model, 'bizar/MiniMax-M3', 'frigg uses default');
49
+ assert.equal(body.agents.hermod.model, 'bizar/MiniMax-M3', 'hermod uses default');
50
+ // Vor uses the budget tier.
51
+ assert.match(body.agents.vor.model, /mimo|deepseek/, 'vor uses budget tier');
52
+ });
53
+
54
+ test('GET /api/model-router/agents returns just the agents+tiers map', async () => {
55
+ const app = buildApp();
56
+ const { status, body } = await getJson(app, '/api/model-router/agents');
57
+ assert.equal(status, 200);
58
+ assert.ok(body.agents.thor, 'thor present');
59
+ assert.ok(body.tiers.default, 'default tier present');
60
+ });
61
+
62
+ test('GET /api/model-router/models probe handles unreachable endpoint gracefully', async () => {
63
+ // Override env to a guaranteed-unreachable port.
64
+ const prev = process.env.BIZAR_MODEL_ROUTER_URL;
65
+ process.env.BIZAR_MODEL_ROUTER_URL = 'http://127.0.0.1:1';
66
+ try {
67
+ const app = buildApp();
68
+ const { status, body } = await getJson(app, '/api/model-router/models');
69
+ assert.equal(status, 200);
70
+ assert.equal(body.reachable, false);
71
+ assert.ok(typeof body.error === 'string', 'error string present');
72
+ } finally {
73
+ if (prev === undefined) delete process.env.BIZAR_MODEL_ROUTER_URL;
74
+ else process.env.BIZAR_MODEL_ROUTER_URL = prev;
75
+ }
76
+ });
@@ -436,32 +436,6 @@ export async function createServer({
436
436
  broadcast: localBroadcast,
437
437
  });
438
438
 
439
- // v5.0.0 — Headroom startup hook. Runs after api.mjs is loaded so the
440
- // headroom routes are registered. Errors are caught and logged — startup
441
- // must not fail if Headroom has issues.
442
- //
443
- // v10-S9 — opt-out via BIZAR_HEADROOM_AUTOSTART=0. The hook runs
444
- // `npm install` + a child process spawn which can take 2-5s on cold
445
- // boot. Real users hit this on every dashboard launch; tests hit it
446
- // too. Setting the env var skips the install + child-process spawn
447
- // entirely. Default behaviour is unchanged (settings.json
448
- // `headroom.enabled` still controls).
449
- if (/^(0|false|no|off)$/i.test((process.env.BIZAR_HEADROOM_AUTOSTART || '').trim())) {
450
- console.log('[bizar-dash] headroom startup skipped (BIZAR_HEADROOM_AUTOSTART=0)');
451
- } else {
452
- const { headroomStartupHook } = await import('./headroom.mjs');
453
- try {
454
- const settings = readSettings();
455
- if (settings?.data?.headroom) {
456
- headroomStartupHook(settings.data.headroom).catch((err) => {
457
- console.warn('[bizar-dash] headroomStartupHook error:', err?.message || err);
458
- });
459
- }
460
- } catch (err) {
461
- console.warn('[bizar-dash] headroom startup hook skipped:', err?.message || err);
462
- }
463
- }
464
-
465
439
  // v5.5.2 — Auto-migrate legacy git.repoPath from the old vault location
466
440
  // to the new default. Runs before the lightrag hook so git-dependent
467
441
  // checks are already correct.
@@ -479,7 +453,7 @@ export async function createServer({
479
453
  console.warn('[bizar-dash] git.repoPath migration check failed:', err?.message || err);
480
454
  }
481
455
 
482
- // v5.x — LightRAG startup hook (issue #6). Mirrors the headroom hook:
456
+ // v5.x — LightRAG startup hook (issue #6).
483
457
  // reads config from .bizar/memory.json + env vars, then calls
484
458
  // lightragStartupHook() which respects `lightrag.enabled` and the
485
459
  // BIZAR_LIGHTRAG_AUTOSTART env override. All errors are caught — the
@@ -86,7 +86,7 @@ describe('B7 — check-deps.mjs which() uses path.join()', () => {
86
86
  const { checkDeps } = await import('../../scripts/check-deps.mjs');
87
87
  const result = await checkDeps({ strict: false });
88
88
  const names = [...result.present, ...result.missing].map(d => d.name);
89
- const required = ['node', 'bun', 'cline', 'tmux', 'git', 'python3', 'pip', 'jq', 'gh', 'headroom', 'semble', 'skills'];
89
+ const required = ['node', 'bun', 'cline', 'tmux', 'git', 'python3', 'pip', 'jq', 'gh', 'semble', 'skills'];
90
90
  for (const dep of required) {
91
91
  assert.ok(names.includes(dep), `expected ${dep} to be checked`);
92
92
  }
@@ -138,7 +138,7 @@ describe('command module null-check produces user-visible error', () => {
138
138
  // Count `if (!mod)` checks that follow `importCommand`
139
139
  const nullCheckCount = (binSrc.match(/if \(!mod\)/g) || []).length;
140
140
  // We have 9 case groups that call importCommand: install/update, service, dash,
141
- // minimax, headroom, mod, artifact, memory, usage, and util (utility commands)
141
+ // minimax, mod, artifact, memory, usage, and util (utility commands)
142
142
  assert.ok(
143
143
  nullCheckCount >= 9,
144
144
  `Expected at least 9 null checks after importCommand, found ${nullCheckCount}`,
@@ -73,7 +73,6 @@ const COMMAND_FILES = [
73
73
  'service',
74
74
  'dash',
75
75
  'minimax',
76
- 'headroom',
77
76
  'mod',
78
77
  'artifact',
79
78
  'memory',
@@ -151,7 +151,6 @@ describe('collectDiagnostics', () => {
151
151
  assert.ok(snap.disk && typeof snap.disk.exists === 'boolean');
152
152
  assert.ok(snap.services);
153
153
  assert.ok(snap.services.dashboard);
154
- assert.ok(snap.services.headroom);
155
154
  assert.ok(snap.services.lightrag);
156
155
  assert.ok(snap.services.cline);
157
156
  assert.ok(snap.counts);
package/cli/bin.mjs CHANGED
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Commands:
14
14
  * install, audit, init, export, artifact, update, test-gate, service, dash,
15
- * memory, headroom, minimax, usage, mod, doctor, repair, dev-link, dev-unlink,
15
+ * memory, minimax, usage, mod, doctor, repair, dev-link, dev-unlink,
16
16
  * heads-up, bg, agent-browser, agent-browser-up, providers, deploy, plugin,
17
17
  * marketplace, plan, digest, backup, restore, clip, ocr, voice, workspace, eval
18
18
  */
@@ -133,7 +133,6 @@ function showHelp() {
133
133
  service Manage the background service daemon
134
134
  dash <subcommand> Manage the dashboard (start/stop/status/cleanup/tui)
135
135
  memory <subcommand> Manage project memory (Bizar Memory Service)
136
- headroom <subcommand> Manage Headroom context compression
137
136
  lightrag <subcommand> Manage the LightRAG knowledge-graph server (status/start/autostart)
138
137
  minimax <subcommand> Manage MiniMax Token Plan integration
139
138
  tailscale <subcommand> Manage Tailscale integration (auth, serve, status)
@@ -347,19 +346,6 @@ async function main() {
347
346
  break;
348
347
  }
349
348
 
350
- case 'headroom': {
351
- const mod = await importCommand('headroom');
352
- if (!mod) {
353
- console.error(chalk.red(` ✗ Could not load headroom command module`));
354
- process.exit(EXIT_ERROR);
355
- return;
356
- }
357
- dbg('loaded command module:', 'headroom');
358
- await mod.run(cmd, cmdArgs, isHelpRequest);
359
- dbg('command returned:', cmd);
360
- break;
361
- }
362
-
363
349
  case 'lightrag': {
364
350
  // v5.x — LightRAG management CLI (issue #6).
365
351
  // Subcommands: status, start, autostart.
@@ -25,7 +25,7 @@ const ALL_COMMANDS = [
25
25
  'install', 'update',
26
26
  'service',
27
27
  'dash', 'dashboard',
28
- 'minimax', 'tailscale', 'headroom', 'lightrag',
28
+ 'minimax', 'tailscale', 'lightrag',
29
29
  'mod', 'usage',
30
30
  'deploy', 'plugin', 'marketplace',
31
31
  'artifact',
@@ -8,7 +8,7 @@
8
8
  * the current working directory's project. Used by
9
9
  * `install.sh` and by operators who want to manually
10
10
  * start LightRAG without opening the dashboard.
11
- * start Alias for `autostart` (matches `headroom start`).
11
+ * start Alias for `autostart`.
12
12
  * status Print whether the LightRAG server is running.
13
13
  *
14
14
  * The dashboard server already auto-invokes `lightragStartupHook()` on
@@ -120,7 +120,7 @@ export function showDoctorHelp() {
120
120
  • plugin path resolves
121
121
  • @polderlabs/bizar-plugin is installed globally
122
122
  • core agent files are installed (odin, quick, thor, tyr)
123
- headroom / semble / skills on PATH (lenient — at least one)
123
+ • semble / skills on PATH (lenient — at least one)
124
124
  • dashboard reachable (skipped if no port file)
125
125
  • provider.minimax block + MiniMax model flags are sane
126
126
 
package/cli/copy.mjs CHANGED
@@ -3,7 +3,7 @@ import { join, dirname } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import ora from 'ora';
5
5
  import chalk from 'chalk';
6
- import { repoPath, clineConfigDir, clineAgentsDir, detectRtk, detectSemble, detectUv, detectSkillsCli } from './utils.mjs';
6
+ import { repoPath, clineConfigDir, clineAgentsDir, detectSemble, detectUv, detectSkillsCli } from './utils.mjs';
7
7
 
8
8
  async function fileExists(path) {
9
9
  try {
@@ -341,52 +341,6 @@ export async function installPluginBizar(projectRoot) {
341
341
  }
342
342
  }
343
343
 
344
- export async function installHeadroom() {
345
- const { execSync } = await import('node:child_process');
346
-
347
- const already = await detectRtk();
348
- if (already) {
349
- const spinner = ora({ text: 'Configuring Headroom for cline...', color: 'magenta' }).start();
350
- try {
351
- execSync('headroom wrap cline', { stdio: 'pipe' });
352
- spinner.succeed(chalk.green('Headroom configured for cline'));
353
- } catch {
354
- spinner.warn(chalk.yellow('Could not auto-configure Headroom — run `headroom wrap cline` manually'));
355
- }
356
- return true;
357
- }
358
-
359
- const spinner = ora({ text: 'Installing Headroom (context compressor)...', color: 'magenta' }).start();
360
-
361
- if (process.platform === 'win32') {
362
- spinner.fail(chalk.red('Automatic Headroom install not supported on Windows. Install manually: pip install "headroom-ai[all]"'));
363
- return false;
364
- }
365
-
366
- try {
367
- execSync(
368
- 'pip install --user "headroom-ai[all]"',
369
- { stdio: 'pipe', timeout: 60000 },
370
- );
371
- spinner.text = 'Configuring Headroom for cline...';
372
- execSync('headroom wrap cline', { stdio: 'pipe' });
373
- spinner.succeed(chalk.green('Headroom installed and configured for cline'));
374
- return true;
375
- } catch {
376
- // Fall back to npm
377
- try {
378
- execSync('npm install -g headroom-ai', { stdio: 'pipe', timeout: 60000 });
379
- spinner.text = 'Configuring Headroom for cline...';
380
- execSync('headroom wrap cline', { stdio: 'pipe' });
381
- spinner.succeed(chalk.green('Headroom installed (npm) and configured for cline'));
382
- return true;
383
- } catch {
384
- spinner.fail(chalk.red('Headroom install failed. Install manually: pip install "headroom-ai[all]" or npm install -g headroom-ai'));
385
- return false;
386
- }
387
- }
388
- }
389
-
390
344
  export async function installSemble() {
391
345
  const { execSync } = await import('node:child_process');
392
346
 
package/cli/doctor.mjs CHANGED
@@ -15,7 +15,7 @@
15
15
  * hooks-wired: settings.json has PreToolUse/PostToolUse/etc
16
16
  * agent-files-installed: agent .md files deployed
17
17
  * skill-files-installed: SKILL.md files deployed
18
- * tools-on-path: at least one of headroom/semble/skills/claude
18
+ * tools-on-path: at least one of semble/skills/claude
19
19
  * memory-vault: BIZAR_HOME exists (memory subdir lazy)
20
20
  * 9router-reachable: provider gateway responds
21
21
  *
@@ -109,13 +109,13 @@ async function checkSkillFilesInstalled() {
109
109
  }
110
110
 
111
111
  /**
112
- * Lenient: passes if at least one of headroom/semble/skills is on PATH.
112
+ * Lenient: passes if at least one of semble/skills/claude is on PATH.
113
113
  * These are informational — none of them are strictly required for
114
114
  * `bizar doctor` to do its job, and missing them shouldn't fail the
115
115
  * overall health report.
116
116
  */
117
117
  async function checkToolsAvailable() {
118
- const tools = ['headroom', 'semble', 'skills', 'claude'];
118
+ const tools = ['semble', 'skills', 'claude'];
119
119
  const found = tools.filter(which);
120
120
  if (found.length === 0) {
121
121
  throw new Error(`none of ${tools.join('/')} on PATH`);
package/cli/install.mjs CHANGED
@@ -15,8 +15,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
15
15
 
16
16
  import { showBanner, showPantheon, sectionHeading } from './banner.mjs';
17
17
  import { promptComponents, promptInstallMode, promptAgents, promptSkillPacks, promptApiKeys, promptConfirmInstall, promptRestartCline } from './prompts.mjs';
18
- import { detectClaude, detectHeadroom, detectSemble, detectSkillsCli, detectUv, buildSummary, claudeAgentsDir, claudeConfigDir, repoPath } from './utils.mjs';
19
- import { installAgents, installAgentsMd, installSkill, installClineJson, installBizarFolder, installPluginBizar, installHeadroom, installSemble, installSkillsCli, installCuratedSkills, installRules, installHooks, installCommands, installCommandsBizar, mergeToolsIntoUserConfig } from './copy.mjs';
18
+ import { detectClaude, detectSemble, detectSkillsCli, detectUv, buildSummary, claudeAgentsDir, claudeConfigDir, repoPath } from './utils.mjs';
19
+ import { installAgents, installAgentsMd, installSkill, installClineJson, installBizarFolder, installPluginBizar, installSemble, installSkillsCli, installCuratedSkills, installRules, installHooks, installCommands, installCommandsBizar, mergeToolsIntoUserConfig } from './copy.mjs';
20
20
 
21
21
  const AGENT_FILES = [
22
22
  'odin.md', 'vor.md', 'frigg.md', 'quick.md',
@@ -514,7 +514,7 @@ export async function runPostInstall() {
514
514
  // is a thin Claude Code-native bootstrap that:
515
515
  // - copies `config/settings.json` template on first install
516
516
  // - installs agents into `~/.claude/agents/`
517
- // - probes for headroom / semble / skills-cli
517
+ // - probes for semble / skills-cli
518
518
  // - installs Headroom via pip or npm
519
519
  const { mkdirSync, copyFileSync, existsSync } = await import('node:fs');
520
520
  const { execSync } = await import('node:child_process');
@@ -546,40 +546,6 @@ export async function runPostInstall() {
546
546
  }
547
547
  console.log('BizarHarness: agents installed.');
548
548
 
549
- // Install Headroom
550
- const headroomPresent = await detectHeadroom();
551
- if (!headroomPresent) {
552
- console.log('BizarHarness: installing Headroom (context compressor)...');
553
- try {
554
- if (process.platform === 'win32') {
555
- console.log('BizarHarness: Automatic Headroom install not supported on Windows. Install manually: pip install "headroom-ai[all]"');
556
- } else {
557
- execSync(
558
- 'pip install --user "headroom-ai[all]"',
559
- { stdio: 'pipe', timeout: 60000 },
560
- );
561
- execSync('headroom wrap claude', { stdio: 'pipe' });
562
- console.log('BizarHarness: Headroom installed and configured.');
563
- }
564
- } catch {
565
- // Fall back to npm
566
- try {
567
- execSync('npm install -g headroom-ai', { stdio: 'pipe', timeout: 60000 });
568
- execSync('headroom wrap claude', { stdio: 'pipe' });
569
- console.log('BizarHarness: Headroom installed (npm) and configured.');
570
- } catch {
571
- console.log('BizarHarness: Headroom install failed. Install manually: pip install "headroom-ai[all]" or npm install -g headroom-ai');
572
- }
573
- }
574
- } else {
575
- try {
576
- execSync('headroom wrap claude', { stdio: 'pipe' });
577
- console.log('BizarHarness: Headroom configured for claude.');
578
- } catch {
579
- console.log('BizarHarness: could not configure Headroom. Run `headroom wrap claude` manually.');
580
- }
581
- }
582
-
583
549
  // Install Semble
584
550
  const semblePresent = await detectSemble();
585
551
  if (!semblePresent) {