@polderlabs/bizar 4.5.2 → 4.7.1

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 (123) hide show
  1. package/bizar-dash/dist/assets/main-DHZmbnxQ.js +361 -0
  2. package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +1 -0
  3. package/bizar-dash/dist/assets/main-DX_Jh8Wc.css +1 -0
  4. package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-BK8-ythT.js} +18 -18
  5. package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +1 -0
  6. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-Chvf9u_B.js} +1 -1
  7. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-Chvf9u_B.js.map} +1 -1
  8. package/bizar-dash/dist/index.html +3 -3
  9. package/bizar-dash/dist/mobile.html +2 -2
  10. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  11. package/bizar-dash/skills/publishing/SKILL.md +146 -0
  12. package/bizar-dash/src/server/api.mjs +8 -0
  13. package/bizar-dash/src/server/backup-store.mjs +525 -0
  14. package/bizar-dash/src/server/digest-store.mjs +558 -0
  15. package/bizar-dash/src/server/lib/rate-limit.mjs +122 -0
  16. package/bizar-dash/src/server/logger.mjs +71 -0
  17. package/bizar-dash/src/server/memory-lightrag.mjs +45 -6
  18. package/bizar-dash/src/server/metrics.mjs +193 -0
  19. package/bizar-dash/src/server/routes/backup.mjs +112 -0
  20. package/bizar-dash/src/server/routes/chat.mjs +20 -3
  21. package/bizar-dash/src/server/routes/digests.mjs +82 -0
  22. package/bizar-dash/src/server/routes/lightrag.mjs +3 -2
  23. package/bizar-dash/src/server/routes/memory.mjs +5 -4
  24. package/bizar-dash/src/server/routes/misc.mjs +2 -1
  25. package/bizar-dash/src/server/routes/overview.mjs +2 -1
  26. package/bizar-dash/src/server/routes-v2/events.mjs +14 -0
  27. package/bizar-dash/src/server/schedules-runner.mjs +126 -0
  28. package/bizar-dash/src/server/server.mjs +79 -0
  29. package/bizar-dash/src/web/App.tsx +8 -1
  30. package/bizar-dash/src/web/components/BackupRestore.tsx +330 -0
  31. package/bizar-dash/src/web/components/SearchModal.tsx +6 -3
  32. package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
  33. package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
  34. package/bizar-dash/src/web/components/chat/Composer.tsx +2 -0
  35. package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
  36. package/bizar-dash/src/web/lib/i18n.ts +25 -0
  37. package/bizar-dash/src/web/locales/en.json +52 -0
  38. package/bizar-dash/src/web/main.tsx +5 -0
  39. package/bizar-dash/src/web/styles/main.css +70 -8
  40. package/bizar-dash/src/web/views/Activity.tsx +35 -18
  41. package/bizar-dash/src/web/views/Agents.tsx +57 -42
  42. package/bizar-dash/src/web/views/Artifacts.tsx +38 -25
  43. package/bizar-dash/src/web/views/Chat.tsx +8 -0
  44. package/bizar-dash/src/web/views/History.tsx +94 -76
  45. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +21 -4
  46. package/bizar-dash/src/web/views/Mods.tsx +30 -17
  47. package/bizar-dash/src/web/views/Overview.tsx +19 -9
  48. package/bizar-dash/src/web/views/Providers.tsx +16 -16
  49. package/bizar-dash/src/web/views/Schedules.tsx +33 -15
  50. package/bizar-dash/src/web/views/Settings.tsx +97 -1745
  51. package/bizar-dash/src/web/views/Skills.tsx +4 -1
  52. package/bizar-dash/src/web/views/Tasks.tsx +11 -2
  53. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +8 -2
  54. package/bizar-dash/src/web/views/memory/LightragPanel.tsx +3 -0
  55. package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +12 -4
  56. package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +3 -0
  57. package/bizar-dash/src/web/views/settings/ActivitySection.tsx +205 -0
  58. package/bizar-dash/src/web/views/settings/AgentSection.tsx +294 -0
  59. package/bizar-dash/src/web/views/settings/AuthSection.tsx +159 -0
  60. package/bizar-dash/src/web/views/settings/BackupSection.tsx +16 -0
  61. package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +16 -0
  62. package/bizar-dash/src/web/views/settings/GeneralSection.tsx +105 -0
  63. package/bizar-dash/src/web/views/settings/HeadroomSection.tsx +39 -0
  64. package/bizar-dash/src/web/views/settings/MemorySection.tsx +16 -0
  65. package/bizar-dash/src/web/views/settings/NetworkSection.tsx +87 -0
  66. package/bizar-dash/src/web/views/settings/NotificationsSection.tsx +34 -0
  67. package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +16 -0
  68. package/bizar-dash/src/web/views/settings/SkillsSection.tsx +16 -0
  69. package/bizar-dash/src/web/views/settings/SystemLlmSection.tsx +81 -0
  70. package/bizar-dash/src/web/views/settings/ThemeSection.tsx +168 -0
  71. package/bizar-dash/src/web/views/settings/UpdatesSection.tsx +256 -0
  72. package/bizar-dash/tests/a11y.test.tsx +206 -0
  73. package/bizar-dash/tests/backup-restore.test.mjs +217 -0
  74. package/bizar-dash/tests/backup-restore.test.tsx +123 -0
  75. package/bizar-dash/tests/backup-store.test.mjs +300 -0
  76. package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
  77. package/bizar-dash/tests/cli-error-visibility.test.mjs +153 -0
  78. package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
  79. package/bizar-dash/tests/components/Button.test.tsx +41 -0
  80. package/bizar-dash/tests/components/Card.test.tsx +42 -0
  81. package/bizar-dash/tests/components/Modal.test.tsx +104 -0
  82. package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
  83. package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
  84. package/bizar-dash/tests/components/Toast.test.tsx +108 -0
  85. package/bizar-dash/tests/digest-generation.test.mjs +191 -0
  86. package/bizar-dash/tests/digest-store.test.mjs +264 -0
  87. package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
  88. package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
  89. package/bizar-dash/tests/lib/i18n.test.ts +46 -0
  90. package/bizar-dash/tests/lib/utils.test.ts +194 -0
  91. package/bizar-dash/tests/logger.test.mjs +207 -0
  92. package/bizar-dash/tests/metrics.test.mjs +183 -0
  93. package/bizar-dash/tests/rate-limit.test.mjs +298 -0
  94. package/bizar-dash/tests/server-bugfixes.test.mjs +2 -2
  95. package/bizar-dash/tests/setup.ts +7 -0
  96. package/bizar-dash/vitest.config.ts +13 -0
  97. package/cli/artifact-cli.mjs +605 -0
  98. package/cli/artifact-render.mjs +621 -0
  99. package/cli/artifact-server.mjs +847 -0
  100. package/cli/artifact.mjs +38 -2096
  101. package/cli/bg.mjs +5 -13
  102. package/cli/bin.mjs +221 -1350
  103. package/cli/commands/artifact.mjs +20 -0
  104. package/cli/commands/dash.mjs +160 -0
  105. package/cli/commands/headroom.mjs +204 -0
  106. package/cli/commands/install.mjs +169 -0
  107. package/cli/commands/memory.mjs +25 -0
  108. package/cli/commands/minimax.mjs +285 -0
  109. package/cli/commands/mod.mjs +185 -0
  110. package/cli/commands/service.mjs +65 -0
  111. package/cli/commands/usage.mjs +109 -0
  112. package/cli/commands/util.mjs +459 -0
  113. package/cli/digest.mjs +149 -0
  114. package/cli/doctor.mjs +1 -13
  115. package/cli/provision.mjs +2 -13
  116. package/cli/service-controller.mjs +1 -11
  117. package/cli/service.mjs +1 -11
  118. package/cli/utils.mjs +41 -1
  119. package/package.json +6 -1
  120. package/bizar-dash/dist/assets/main-B4OfGAwz.js +0 -361
  121. package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +0 -1
  122. package/bizar-dash/dist/assets/main-DAlLdW8I.css +0 -1
  123. 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
+ }