@polderlabs/bizar 4.5.2 → 4.7.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.
- package/bizar-dash/dist/assets/main-DGGq-iZI.js +361 -0
- package/bizar-dash/dist/assets/main-DGGq-iZI.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-CWqPoGaT.js} +18 -18
- package/bizar-dash/dist/assets/mobile-CWqPoGaT.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-i4Uv9eW8.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-i4Uv9eW8.js.map} +1 -1
- package/bizar-dash/dist/index.html +2 -2
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/bizar-dash/src/server/logger.mjs +71 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +45 -6
- package/bizar-dash/src/server/metrics.mjs +193 -0
- package/bizar-dash/src/server/routes/chat.mjs +6 -3
- package/bizar-dash/src/server/routes/lightrag.mjs +3 -2
- package/bizar-dash/src/server/routes/memory.mjs +5 -4
- package/bizar-dash/src/server/routes/misc.mjs +2 -1
- package/bizar-dash/src/server/routes/overview.mjs +2 -1
- package/bizar-dash/src/server/server.mjs +62 -0
- package/bizar-dash/src/web/components/SearchModal.tsx +3 -2
- package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
- package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
- package/bizar-dash/src/web/lib/i18n.ts +25 -0
- package/bizar-dash/src/web/locales/en.json +52 -0
- package/bizar-dash/src/web/main.tsx +5 -0
- package/bizar-dash/src/web/views/Activity.tsx +24 -17
- package/bizar-dash/src/web/views/History.tsx +90 -75
- package/bizar-dash/src/web/views/Overview.tsx +11 -8
- package/bizar-dash/src/web/views/Settings.tsx +7 -1
- package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
- package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
- package/bizar-dash/tests/components/Button.test.tsx +41 -0
- package/bizar-dash/tests/components/Card.test.tsx +42 -0
- package/bizar-dash/tests/components/Modal.test.tsx +104 -0
- package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
- package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
- package/bizar-dash/tests/components/Toast.test.tsx +108 -0
- package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
- package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
- package/bizar-dash/tests/lib/i18n.test.ts +46 -0
- package/bizar-dash/tests/lib/utils.test.ts +194 -0
- package/bizar-dash/tests/logger.test.mjs +207 -0
- package/bizar-dash/tests/metrics.test.mjs +183 -0
- package/bizar-dash/tests/server-bugfixes.test.mjs +2 -2
- package/bizar-dash/tests/setup.ts +7 -0
- package/bizar-dash/vitest.config.ts +13 -0
- package/cli/artifact-cli.mjs +605 -0
- package/cli/artifact-render.mjs +621 -0
- package/cli/artifact-server.mjs +847 -0
- package/cli/artifact.mjs +38 -2096
- package/cli/bg.mjs +5 -13
- package/cli/bin.mjs +149 -1361
- package/cli/commands/artifact.mjs +20 -0
- package/cli/commands/dash.mjs +160 -0
- package/cli/commands/headroom.mjs +204 -0
- package/cli/commands/install.mjs +169 -0
- package/cli/commands/memory.mjs +25 -0
- package/cli/commands/minimax.mjs +265 -0
- package/cli/commands/mod.mjs +185 -0
- package/cli/commands/service.mjs +65 -0
- package/cli/commands/usage.mjs +109 -0
- package/cli/commands/util.mjs +306 -0
- package/cli/doctor.mjs +1 -13
- package/cli/provision.mjs +2 -13
- package/cli/service-controller.mjs +1 -11
- package/cli/service.mjs +1 -11
- package/cli/utils.mjs +41 -1
- package/package.json +6 -1
- package/bizar-dash/dist/assets/main-B4OfGAwz.js +0 -361
- package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-lbH6szyX.js.map +0 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/artifact.mjs
|
|
3
|
+
*
|
|
4
|
+
* Artifact command dispatcher — delegates to ../artifact-cli.mjs.
|
|
5
|
+
* The actual implementation is split across:
|
|
6
|
+
* artifact-cli.mjs — CLI dispatch + flag parsing + help
|
|
7
|
+
* artifact-server.mjs — HTTP server + routing
|
|
8
|
+
* artifact-render.mjs — HTML rendering + canvas helpers + MDX export
|
|
9
|
+
*/
|
|
10
|
+
export { runArtifact, default } from '../artifact-cli.mjs';
|
|
11
|
+
|
|
12
|
+
export async function run(name, args, isHelpRequest) {
|
|
13
|
+
if (isHelpRequest) {
|
|
14
|
+
const { showHelp } = await import('../artifact-cli.mjs');
|
|
15
|
+
showHelp();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const { runArtifact } = await import('../artifact-cli.mjs');
|
|
19
|
+
await runArtifact(args, {});
|
|
20
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/dash.mjs
|
|
3
|
+
*
|
|
4
|
+
* Dashboard (bizar dash) subcommand dispatcher.
|
|
5
|
+
*/
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
13
|
+
const BIZAR_HOME = join(__dirname, '..', '..');
|
|
14
|
+
|
|
15
|
+
export function showDashHelp() {
|
|
16
|
+
console.log(`
|
|
17
|
+
bizar dash — Manage the Bizar dashboard
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
bizar dash <subcommand> [options]
|
|
21
|
+
|
|
22
|
+
Subcommands:
|
|
23
|
+
start [--bg] [--port N] Start the dashboard (default port 4321)
|
|
24
|
+
stop Stop the running dashboard
|
|
25
|
+
status Show dashboard port and URL
|
|
26
|
+
cleanup Find + kill zombie/orphan dashboards
|
|
27
|
+
tui [--no-web] Launch the TUI
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--bg Detach and run in background (for start)
|
|
31
|
+
--port N Override the default port
|
|
32
|
+
--no-web Skip launching the web UI (for tui)
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
bizar dash start
|
|
36
|
+
bizar dash start --bg
|
|
37
|
+
bizar dash stop
|
|
38
|
+
bizar dash status
|
|
39
|
+
bizar dash tui
|
|
40
|
+
bizar dash tui --no-web
|
|
41
|
+
|
|
42
|
+
Note:
|
|
43
|
+
\`bizar dashboard\` is a deprecated alias for \`bizar dash\` and still
|
|
44
|
+
works, but new code should use \`bizar dash\`.
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Option parsing ──────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse dash-specific options from an array of args.
|
|
52
|
+
* Returns { opts, remaining } where opts have --bg / --port stripped.
|
|
53
|
+
*/
|
|
54
|
+
export function parseDashOpts(dashArgs) {
|
|
55
|
+
const opts = { bg: false, port: null, noWeb: false };
|
|
56
|
+
const remaining = [];
|
|
57
|
+
for (let i = 0; i < dashArgs.length; i++) {
|
|
58
|
+
const a = dashArgs[i];
|
|
59
|
+
if (a === '--bg') {
|
|
60
|
+
opts.bg = true;
|
|
61
|
+
} else if (a === '--no-web') {
|
|
62
|
+
opts.noWeb = true;
|
|
63
|
+
} else if (a === '--port' && i + 1 < dashArgs.length) {
|
|
64
|
+
opts.port = Number(dashArgs[i + 1]);
|
|
65
|
+
i++;
|
|
66
|
+
} else {
|
|
67
|
+
remaining.push(a);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { opts, remaining };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Dashboard CLI loader ───────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Try to load the dashboard CLI module.
|
|
77
|
+
* v4.0.0: the dashboard ships inside this package at bizar-dash/src/cli.mjs.
|
|
78
|
+
* A legacy npm-global fallback is kept for the transitional period.
|
|
79
|
+
*/
|
|
80
|
+
export async function loadDashCli() {
|
|
81
|
+
const primary = join(__dirname, '..', '..', 'bizar-dash', 'src', 'cli.mjs');
|
|
82
|
+
|
|
83
|
+
// Legacy npm-global fallback (only computed if primary is missing)
|
|
84
|
+
let legacyFallbacks = [];
|
|
85
|
+
try {
|
|
86
|
+
const { execFileSync } = await import('node:child_process');
|
|
87
|
+
const npmRoot = execFileSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000 }).trim();
|
|
88
|
+
if (npmRoot) legacyFallbacks = [join(npmRoot, '@polderlabs', 'bizar-dash', 'src', 'cli.mjs')];
|
|
89
|
+
} catch { /* npm not available or no globals — fine */ }
|
|
90
|
+
|
|
91
|
+
const candidates = [primary, ...legacyFallbacks];
|
|
92
|
+
|
|
93
|
+
for (const p of candidates) {
|
|
94
|
+
try {
|
|
95
|
+
const url = pathToFileURL(p).href;
|
|
96
|
+
const mod = await import(url);
|
|
97
|
+
return mod;
|
|
98
|
+
} catch (_e) {
|
|
99
|
+
// try next
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Main dispatcher ────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Dispatch a dashboard subcommand by loading the dashboard module and
|
|
109
|
+
* calling the appropriate exported function.
|
|
110
|
+
*/
|
|
111
|
+
export async function runDash(dashArgs) {
|
|
112
|
+
const { opts, remaining } = parseDashOpts(dashArgs);
|
|
113
|
+
const sub = remaining[0];
|
|
114
|
+
const subOpts = { ...opts, subArgs: remaining.slice(1) };
|
|
115
|
+
|
|
116
|
+
const dashModule = await loadDashCli();
|
|
117
|
+
if (!dashModule) {
|
|
118
|
+
console.error(chalk.red(' ✗ Dashboard not found.'));
|
|
119
|
+
console.error(chalk.dim(' this should not happen — please report a bug at github.com/DrB0rk/BizarHarness/issues'));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
switch (sub) {
|
|
124
|
+
case 'start':
|
|
125
|
+
if (subOpts.bg) {
|
|
126
|
+
await dashModule.startInBackground(['start', ...(subOpts.subArgs || [])]);
|
|
127
|
+
} else {
|
|
128
|
+
await dashModule.start(subOpts);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
case 'stop':
|
|
132
|
+
await dashModule.stop(subOpts);
|
|
133
|
+
break;
|
|
134
|
+
case 'status':
|
|
135
|
+
await dashModule.status(subOpts);
|
|
136
|
+
break;
|
|
137
|
+
case 'cleanup':
|
|
138
|
+
await dashModule.cleanup(subOpts);
|
|
139
|
+
break;
|
|
140
|
+
case 'tui':
|
|
141
|
+
await dashModule.tui(subOpts);
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
console.error(chalk.red(` ✗ Unknown subcommand: ${sub}`));
|
|
145
|
+
showDashHelp();
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function run(name, args, isHelpRequest) {
|
|
151
|
+
// Handle 'dashboard' as deprecated alias for 'dash'
|
|
152
|
+
if (name === 'dashboard') {
|
|
153
|
+
process.stdout.write(chalk.yellow(' ⚠ `bizar dashboard` is deprecated, use `bizar dash` instead.\n'));
|
|
154
|
+
}
|
|
155
|
+
if (args.length === 0 || isHelpRequest) {
|
|
156
|
+
showDashHelp();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
await runDash(args);
|
|
160
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/headroom.mjs
|
|
3
|
+
*
|
|
4
|
+
* Headroom context compression CLI.
|
|
5
|
+
* v3.17.0+ — status, stats, install, wrap, unwrap, start, stop, doctor.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
|
|
13
|
+
const HOME = homedir();
|
|
14
|
+
const BIZAR_HOME = join(HOME, '.config', 'bizar');
|
|
15
|
+
|
|
16
|
+
// ── Dashboard connection ────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function readDashboardConn() {
|
|
19
|
+
const portFile = join(BIZAR_HOME, 'dashboard.port');
|
|
20
|
+
const authFile = join(BIZAR_HOME, 'dashboard-secret');
|
|
21
|
+
let port = 4321;
|
|
22
|
+
let secret = '';
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(portFile)) {
|
|
25
|
+
const parsed = parseInt(readFileSync(portFile, 'utf8').trim(), 10);
|
|
26
|
+
if (Number.isFinite(parsed) && parsed > 0) port = parsed;
|
|
27
|
+
}
|
|
28
|
+
} catch { /* ignore */ }
|
|
29
|
+
try {
|
|
30
|
+
if (existsSync(authFile)) secret = readFileSync(authFile, 'utf8').trim();
|
|
31
|
+
} catch { /* ignore */ }
|
|
32
|
+
return { port, secret };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Help ───────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export function showHeadroomHelp() {
|
|
38
|
+
console.log(`
|
|
39
|
+
bizar headroom — Manage Headroom context compression
|
|
40
|
+
|
|
41
|
+
Usage:
|
|
42
|
+
bizar headroom status Show live status (installed, proxy, wrapped)
|
|
43
|
+
bizar headroom stats Show compression stats for the last 24h
|
|
44
|
+
bizar headroom install Install headroom via pip or npm
|
|
45
|
+
bizar headroom wrap Wrap opencode to route through the proxy
|
|
46
|
+
bizar headroom unwrap Unwrap opencode
|
|
47
|
+
bizar headroom start Start the proxy server
|
|
48
|
+
bizar headroom stop Stop the proxy server
|
|
49
|
+
bizar headroom doctor Run headroom doctor health check
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
bizar headroom status
|
|
53
|
+
bizar headroom stats
|
|
54
|
+
bizar headroom install
|
|
55
|
+
bizar headroom wrap
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── API helpers ────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
async function apiGet(path) {
|
|
62
|
+
const { port, secret } = readDashboardConn();
|
|
63
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
64
|
+
const url = `${baseUrl}${path}`;
|
|
65
|
+
const headers = { accept: 'application/json' };
|
|
66
|
+
if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
|
|
67
|
+
const res = await fetch(url, { method: 'GET', headers });
|
|
68
|
+
const text = await res.text();
|
|
69
|
+
let data = null;
|
|
70
|
+
try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
|
|
71
|
+
if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function apiPost(path, body = {}) {
|
|
76
|
+
const { port, secret } = readDashboardConn();
|
|
77
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
78
|
+
const url = `${baseUrl}${path}`;
|
|
79
|
+
const headers = { 'content-type': 'application/json', accept: 'application/json' };
|
|
80
|
+
if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
|
|
81
|
+
const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) });
|
|
82
|
+
const text = await res.text();
|
|
83
|
+
let data = null;
|
|
84
|
+
try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
|
|
85
|
+
if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Command runner ────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
async function runHeadroomCommand(headroomArgs) {
|
|
92
|
+
const sub = headroomArgs[0];
|
|
93
|
+
const flags = headroomArgs.slice(1).filter((a) => a.startsWith('-'));
|
|
94
|
+
const positional = headroomArgs.slice(1).filter((a) => !a.startsWith('-'));
|
|
95
|
+
|
|
96
|
+
if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
|
|
97
|
+
showHeadroomHelp();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (sub === 'status') {
|
|
102
|
+
const s = await apiGet('/api/headroom/status');
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(chalk.bold(' Headroom status'));
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(` ${chalk.dim('installed:')} ${s.installed ? chalk.green('yes') : chalk.red('no')} ${s.version || ''}`);
|
|
107
|
+
console.log(` ${chalk.dim('proxy:')} ${s.proxyRunning ? chalk.green('running') : chalk.yellow('stopped')} ${s.proxyPort ? `@ ${s.proxyPort}` : ''} ${s.proxyPid ? `(PID ${s.proxyPid})` : ''}`);
|
|
108
|
+
console.log(` ${chalk.dim('wrapped:')} ${s.wrapped ? chalk.green('yes') : chalk.yellow('no')}`);
|
|
109
|
+
console.log(` ${chalk.dim('healthy:')} ${s.healthy === 'ok' ? chalk.green('ok') : s.healthy === 'warn' ? chalk.yellow('warn') : chalk.red('fail')}`);
|
|
110
|
+
if (s.messages && s.messages.length > 0) {
|
|
111
|
+
console.log('');
|
|
112
|
+
for (const m of s.messages) {
|
|
113
|
+
console.log(` ${m}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (sub === 'stats') {
|
|
121
|
+
const hours = parseInt(positional[0], 10) || 24;
|
|
122
|
+
const st = await apiGet(`/api/headroom/stats?hours=${hours}`);
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(chalk.bold(` Headroom stats (${hours}h)`));
|
|
125
|
+
console.log('');
|
|
126
|
+
if (st.error) {
|
|
127
|
+
console.log(chalk.yellow(` ${st.error}`));
|
|
128
|
+
} else {
|
|
129
|
+
console.log(` ${chalk.dim('tokens saved:')} ${(st.tokensSaved || 0).toLocaleString()}`);
|
|
130
|
+
console.log(` ${chalk.dim('compression:')} ${st.compressionRatio ? `${Math.round(st.compressionRatio * 100)}%` : '—'}`);
|
|
131
|
+
console.log(` ${chalk.dim('cache hits:')} ${st.cacheHits ?? '—'}`);
|
|
132
|
+
console.log(` ${chalk.dim('transforms:')} ${st.transforms ?? '—'}`);
|
|
133
|
+
}
|
|
134
|
+
console.log('');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (sub === 'install') {
|
|
139
|
+
const r = await apiPost('/api/headroom/install', { force: true });
|
|
140
|
+
if (r.installed) {
|
|
141
|
+
console.log(chalk.green(` ✓ Headroom installed via ${r.method}${r.version ? ` (${r.version})` : ''}`));
|
|
142
|
+
} else {
|
|
143
|
+
console.log(chalk.red(' ✗ Install failed. Try: pip install "headroom-ai[all]"'));
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (sub === 'wrap') {
|
|
149
|
+
const port = parseInt(positional[0], 10) || 8787;
|
|
150
|
+
const r = await apiPost('/api/headroom/wrap', { port });
|
|
151
|
+
if (r.ok) {
|
|
152
|
+
console.log(chalk.green(` ✓ opencode wrapped with Headroom on port ${port}`));
|
|
153
|
+
} else {
|
|
154
|
+
console.log(chalk.red(` ✗ Wrap failed: ${r.log || 'unknown error'}`));
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (sub === 'unwrap') {
|
|
160
|
+
const r = await apiPost('/api/headroom/unwrap');
|
|
161
|
+
if (r.ok) {
|
|
162
|
+
console.log(chalk.green(' ✓ opencode unwrapped from Headroom'));
|
|
163
|
+
} else {
|
|
164
|
+
console.log(chalk.red(' ✗ Unwrap failed'));
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (sub === 'start') {
|
|
170
|
+
const port = parseInt(positional[0], 10) || 8787;
|
|
171
|
+
const r = await apiPost('/api/headroom/proxy/start', { port, host: '127.0.0.1' });
|
|
172
|
+
if (r.ok) {
|
|
173
|
+
console.log(chalk.green(` ✓ Proxy started on 127.0.0.1:${port} (PID ${r.pid})`));
|
|
174
|
+
} else {
|
|
175
|
+
console.log(chalk.red(' ✗ Proxy start failed'));
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (sub === 'stop') {
|
|
181
|
+
const r = await apiPost('/api/headroom/proxy/stop');
|
|
182
|
+
if (r.ok) {
|
|
183
|
+
console.log(chalk.green(' ✓ Proxy stopped'));
|
|
184
|
+
} else {
|
|
185
|
+
console.log(chalk.red(' ✗ Proxy stop failed'));
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (sub === 'doctor') {
|
|
191
|
+
// Run `headroom doctor` locally
|
|
192
|
+
const child = spawn('headroom', ['doctor'], { stdio: 'inherit' });
|
|
193
|
+
await new Promise((resolve) => child.on('close', resolve));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.error(chalk.red(` ✗ Unknown headroom subcommand: ${sub}`));
|
|
198
|
+
showHeadroomHelp();
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function run(name, args, isHelpRequest) {
|
|
203
|
+
await runHeadroomCommand(args);
|
|
204
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/install.mjs
|
|
3
|
+
*
|
|
4
|
+
* install + update command families.
|
|
5
|
+
* v4.4.11+ — 'bizar install' and 'bizar update' share the same code path.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { runInstaller } from '../install.mjs';
|
|
9
|
+
import { runUpdate } from '../update.mjs';
|
|
10
|
+
import { runRepair } from '../repair.mjs';
|
|
11
|
+
|
|
12
|
+
// ── Help texts ──────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export function showInstallHelp() {
|
|
15
|
+
console.log(`
|
|
16
|
+
bizar install — Run the unified BizarHarness installer
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
bizar install Install (or refresh) every component
|
|
20
|
+
bizar install --dry-run Print what would happen, change nothing
|
|
21
|
+
bizar install --force Overwrite existing files
|
|
22
|
+
bizar install --with-mods a,b,c Opt-in: install specific mods as part of the run
|
|
23
|
+
bizar install --help Show this help
|
|
24
|
+
|
|
25
|
+
Description:
|
|
26
|
+
v4.4.7+ — unified installer. Same code path as 'bizar update'; the
|
|
27
|
+
difference is just mode=install vs mode=update. Every step is
|
|
28
|
+
idempotent — running this twice is safe.
|
|
29
|
+
|
|
30
|
+
1. Installs @polderlabs/bizar via npm (skipped if already current).
|
|
31
|
+
2. Shells to ./install.sh for platform-specific system deps (uv,
|
|
32
|
+
python3.12, jq, gh on Linux; brew on macOS) + service registration
|
|
33
|
+
(systemd / launchd / Task Scheduler).
|
|
34
|
+
3. Syncs agent files, slash commands, and bundled skills into
|
|
35
|
+
~/.config/opencode/.
|
|
36
|
+
4. Copies plugins/bizar/ from the npm install into
|
|
37
|
+
~/.config/opencode/plugins/bizar/ (preserves dev symlinks).
|
|
38
|
+
5. Patches ~/.config/opencode/opencode.json with the Bizar plugin
|
|
39
|
+
entry (skipped if already present).
|
|
40
|
+
6. Runs 'bizar doctor' as a post-install health check.
|
|
41
|
+
|
|
42
|
+
No API key collection, no interactive prompts.
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function showUpdateHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
bizar update — Update opencode + @polderlabs/bizar (which bundles the
|
|
49
|
+
plugin and dashboard). Detects what's installed and only touches what's
|
|
50
|
+
missing or out of date.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
bizar update Update EVERYTHING (default; auto-kills + restarts)
|
|
54
|
+
bizar update --check Only print current vs. latest; do not update
|
|
55
|
+
bizar update --channel=stable|beta Pick the npm dist-tag (default: stable)
|
|
56
|
+
bizar update --no-restart Don't auto-restart the dashboard after update
|
|
57
|
+
bizar update --dry-run Print what would happen, change nothing
|
|
58
|
+
bizar update --force Override .bizar/PRE_PUSH_NOTES.md blockers
|
|
59
|
+
bizar update --yes Same as --force, but named for one-line scripts
|
|
60
|
+
bizar update --with-mods a,b,c Opt-in: install specific mods as part of the run
|
|
61
|
+
bizar update --help Show this help
|
|
62
|
+
|
|
63
|
+
Components updated:
|
|
64
|
+
opencode-ai the opencode CLI itself
|
|
65
|
+
@polderlabs/bizar this CLI + dashboard + plugin (one package)
|
|
66
|
+
|
|
67
|
+
Behavior (v4.4.7+):
|
|
68
|
+
• Single unified provisioner. 'bizar install' and 'bizar update' are
|
|
69
|
+
the same code path with different mode flags. Every step is
|
|
70
|
+
idempotent — re-running is safe.
|
|
71
|
+
• Detects running Bizar instances (background service daemon, web
|
|
72
|
+
dashboard) by reading ~/.config/bizar/{service,dashboard}.pid and
|
|
73
|
+
cleans up any stale or empty PID files.
|
|
74
|
+
• Auto-kills running instances with a brief notice.
|
|
75
|
+
• Sends SIGTERM, waits up to 5s, escalates to SIGKILL if needed.
|
|
76
|
+
• Re-runs the install script so the deployed plugin source matches
|
|
77
|
+
the just-upgraded npm version (avoids the version-skew trap).
|
|
78
|
+
• If the dashboard was running and bizar was updated, spawns a
|
|
79
|
+
fresh detached dashboard process with the new code (skipped with
|
|
80
|
+
--no-restart).
|
|
81
|
+
• Runs 'bizar doctor' after a successful update to catch config
|
|
82
|
+
regressions before opencode tries to start.
|
|
83
|
+
• With --check: prints the version matrix and release-notes excerpt
|
|
84
|
+
between current and latest, exits non-zero if an update is available.
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
bizar update Full auto-update (recommended)
|
|
88
|
+
bizar update --check Show version matrix + notes, do nothing
|
|
89
|
+
bizar update --channel=beta Upgrade to latest beta build
|
|
90
|
+
bizar update --dry-run Preview what would change
|
|
91
|
+
bizar update plugin --no-restart Plugin-only, leave dashboard alone
|
|
92
|
+
|
|
93
|
+
Errors:
|
|
94
|
+
Network failures (registry offline / DNS) and npm permission issues
|
|
95
|
+
are surfaced with the raw npm output. The provisioner never silently
|
|
96
|
+
swallows them — look for the ✗ marker in the step output.
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── parseWithMods flag ─────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* v4.4.11 — Parse `--with-mods <csv>` from the given subargs slice.
|
|
104
|
+
* Returns `null` if the flag isn't present (the provisioner's
|
|
105
|
+
* "don't touch mods" default), or a string[] of mod ids if it is.
|
|
106
|
+
*/
|
|
107
|
+
export function parseWithModsFlag(subargs) {
|
|
108
|
+
const idx = subargs.indexOf('--with-mods');
|
|
109
|
+
if (idx === -1) return null;
|
|
110
|
+
const raw = subargs[idx + 1];
|
|
111
|
+
if (!raw || raw.startsWith('--')) {
|
|
112
|
+
console.error('bizar: --with-mods requires a value (e.g. --with-mods a,b,c)');
|
|
113
|
+
console.error('Usage: bizar install --with-mods <mod-id,mod-id>');
|
|
114
|
+
process.exit(2);
|
|
115
|
+
}
|
|
116
|
+
return raw
|
|
117
|
+
.split(',')
|
|
118
|
+
.map((s) => s.trim())
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Command runners ────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
export async function install(args, isHelpRequest) {
|
|
125
|
+
if (isHelpRequest) {
|
|
126
|
+
showInstallHelp();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// v4.4.11 — Parse --with-mods <csv> to opt into mod installs during the run.
|
|
130
|
+
const withMods = parseWithModsFlag(args);
|
|
131
|
+
await runInstaller({ withMods });
|
|
132
|
+
// v4.4.3 — After install, repair any stale bin symlinks so the
|
|
133
|
+
// user picks up the new code.
|
|
134
|
+
try {
|
|
135
|
+
const r = await runRepair({});
|
|
136
|
+
if (r.fixed.length > 0) {
|
|
137
|
+
console.log(chalk.cyan('\n Repair: repointed stale bin symlinks:'));
|
|
138
|
+
for (const f of r.fixed) console.log(` ${f}`);
|
|
139
|
+
console.log(chalk.dim(' Re-run your shell or `hash -r` to pick up the new path.'));
|
|
140
|
+
}
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.log(chalk.dim(` Repair skipped: ${err.message}`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function update(args, isHelpRequest) {
|
|
147
|
+
if (isHelpRequest) {
|
|
148
|
+
showUpdateHelp();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const withMods = parseWithModsFlag(args);
|
|
152
|
+
// Filter --with-mods <csv> out of subargs since the provisioner takes it via opts.
|
|
153
|
+
const subargs = args.filter((a, i, arr) => {
|
|
154
|
+
if (a === '--with-mods') return false;
|
|
155
|
+
if (arr[i - 1] === '--with-mods') return false;
|
|
156
|
+
return true;
|
|
157
|
+
});
|
|
158
|
+
await runUpdate(subargs, { withMods });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── run() entry point (used by bin.mjs dispatcher) ──────────────────────────────
|
|
162
|
+
|
|
163
|
+
export async function run(name, args, isHelpRequest) {
|
|
164
|
+
if (name === 'install') {
|
|
165
|
+
await install(args, isHelpRequest);
|
|
166
|
+
} else if (name === 'update') {
|
|
167
|
+
await update(args, isHelpRequest);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/memory.mjs
|
|
3
|
+
*
|
|
4
|
+
* Memory subcommand dispatcher — delegates to ../memory.mjs.
|
|
5
|
+
*/
|
|
6
|
+
import { runMemory } from '../memory.mjs';
|
|
7
|
+
|
|
8
|
+
export function showMemoryHelp() {
|
|
9
|
+
console.log(`
|
|
10
|
+
memory <subcommand> Manage project memory (local-only or Git-shared Obsidian vault)
|
|
11
|
+
Subcommands: init, setup, status, link, unlink, write, pull, commit,
|
|
12
|
+
push, sync, reindex, conflicts, doctor
|
|
13
|
+
`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function run(name, args, isHelpRequest) {
|
|
17
|
+
// Only show parent help if no subcommand given. If --help is for a
|
|
18
|
+
// specific subcommand, pass it through so runMemory can show the
|
|
19
|
+
// subcommand's own help (which includes "Usage:").
|
|
20
|
+
if (!args[0]) {
|
|
21
|
+
showMemoryHelp();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
await runMemory(args[0], args.slice(1), { wantJson: false });
|
|
25
|
+
}
|