@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
package/cli/bin.mjs
CHANGED
|
@@ -2,42 +2,44 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* cli/bin.mjs
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* v4.6 — `bizar` runtime CLI entrypoint.
|
|
6
6
|
*
|
|
7
7
|
* Architecture:
|
|
8
8
|
* - `bizar` is the core runtime + installer + audit/init/export/update/artifact
|
|
9
9
|
* + service + dash commands.
|
|
10
10
|
* - The dashboard lives in `@polderlabs/bizar-dash` as a library.
|
|
11
|
-
*
|
|
12
|
-
* `bizar dashboard` is a deprecated alias (still works, prints warning).
|
|
11
|
+
* - Subcommand implementations are in `cli/commands/*.mjs`.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
* install, audit, init, export, artifact, update, test-gate, service, dash
|
|
13
|
+
* Commands:
|
|
14
|
+
* install, audit, init, export, artifact, update, test-gate, service, dash,
|
|
15
|
+
* memory, headroom, minimax, usage, mod, doctor, repair, dev-link, dev-unlink,
|
|
16
|
+
* heads-up, bg, browser-harness-up, providers
|
|
16
17
|
*/
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
18
|
+
import chalk from 'chalk';
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
19
20
|
import { homedir } from 'node:os';
|
|
20
|
-
import {
|
|
21
|
+
import { join } from 'node:path';
|
|
21
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { ensureSetup, checkSetupStatus } from './bootstrap.mjs';
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
const BIZAR_HOME = join(HOME, '.config', 'bizar');
|
|
25
|
-
|
|
26
|
-
// Exit codes
|
|
25
|
+
// ── Exit codes ─────────────────────────────────────────────────────────────────
|
|
27
26
|
const EXIT_OK = 0;
|
|
28
27
|
const EXIT_ERROR = 1;
|
|
29
28
|
const EXIT_USAGE = 2;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
|
|
30
|
+
// ── CLI version ───────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function readCliVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const packageJsonPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
35
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
36
|
+
return pkg.version || 'unknown';
|
|
37
|
+
} catch {
|
|
38
|
+
return 'unknown';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Argument parsing ──────────────────────────────────────────────────────────
|
|
41
43
|
|
|
42
44
|
const args = process.argv.slice(2);
|
|
43
45
|
const isHelpRequest = args.includes('--help') || args.includes('-h');
|
|
@@ -54,11 +56,14 @@ function dbg(...msg) {
|
|
|
54
56
|
if (wantDebug) console.error('[DEBUG]', ...msg);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// --help
|
|
61
|
-
|
|
59
|
+
// When invoked with ONLY a global flag (e.g. `bizar --help` or
|
|
60
|
+
// `bizar --version`), strip it so `cmd` is undefined and we fall
|
|
61
|
+
// through to showHelp() / readCliVersion(). When invoked as
|
|
62
|
+
// `bizar <cmd> --help`, the flag stays for the subcommand to handle.
|
|
63
|
+
const onlyGlobalFlag = args.length === 1 && (args[0] === '--help' || args[0] === '-h' || args[0] === '--version' || args[0] === '-v' || args[0] === '--json');
|
|
64
|
+
const dispatchArgs = onlyGlobalFlag ? [] : args;
|
|
65
|
+
|
|
66
|
+
// ── Bootstrap ──────────────────────────────────────────────────────────────────
|
|
62
67
|
if (
|
|
63
68
|
!args.includes('--postinstall') &&
|
|
64
69
|
!args.includes('--check') &&
|
|
@@ -68,33 +73,19 @@ if (
|
|
|
68
73
|
) {
|
|
69
74
|
await ensureSetup({ silent: true });
|
|
70
75
|
}
|
|
71
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const packageJsonPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
76
|
-
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
77
|
-
return pkg.version || 'unknown';
|
|
78
|
-
} catch {
|
|
79
|
-
return 'unknown';
|
|
80
|
-
}
|
|
81
|
-
}
|
|
77
|
+
// ── Banner ─────────────────────────────────────────────────────────────────────
|
|
82
78
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
? join(process.env.APPDATA, 'bizar')
|
|
87
|
-
: join(process.env.HOME || process.cwd(), '.config', 'bizar');
|
|
88
|
-
}
|
|
89
|
-
return process.env.XDG_CONFIG_HOME
|
|
90
|
-
? join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
91
|
-
: join(process.env.HOME || process.cwd(), '.config', 'bizar');
|
|
79
|
+
function showBanner() {
|
|
80
|
+
console.log(chalk.bold.cyan(' ᛭ Bizar — Norse Pantheon Agent System for opencode'));
|
|
81
|
+
console.log();
|
|
92
82
|
}
|
|
93
83
|
|
|
84
|
+
// ── Help ──────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
94
86
|
function showHelp() {
|
|
87
|
+
showBanner();
|
|
95
88
|
console.log(`
|
|
96
|
-
Bizar — Norse Pantheon Agent System for opencode
|
|
97
|
-
|
|
98
89
|
Usage:
|
|
99
90
|
bizar <command> [options]
|
|
100
91
|
|
|
@@ -107,18 +98,20 @@ function showHelp() {
|
|
|
107
98
|
test-gate Detect & run the project's test suite
|
|
108
99
|
update Auto-update everything (opencode + bizar + dash + plugin)
|
|
109
100
|
service Manage the background service daemon
|
|
110
|
-
|
|
101
|
+
dash <subcommand> Manage the dashboard (start/stop/status/cleanup/tui)
|
|
111
102
|
memory <subcommand> Manage project memory (Bizar Memory Service)
|
|
112
103
|
headroom <subcommand> Manage Headroom context compression
|
|
113
|
-
|
|
114
|
-
browser-harness-up Start Chromium for browser-harness (start/stop/status/restart)
|
|
115
|
-
dev-link [src] Symlink the local plugin source into opencode's plugin dir
|
|
116
|
-
dev-unlink Remove the dev symlink and restore the deployed copy
|
|
117
|
-
doctor Check the BizarHarness install for health issues
|
|
118
|
-
heads-up <subcommand> Manage pre-push / pre-release heads-ups (list/check/archive)
|
|
119
|
-
minimax <subcommand> Manage MiniMax Token Plan integration (key + onboarding)
|
|
120
|
-
usage Show compact usage analytics summary (24h rolling)
|
|
104
|
+
minimax <subcommand> Manage MiniMax Token Plan integration
|
|
121
105
|
mod <subcommand> Manage mods (install/upgrade/list via the dashboard API)
|
|
106
|
+
usage Show compact usage analytics summary (24h rolling)
|
|
107
|
+
doctor Check the BizarHarness install for health issues
|
|
108
|
+
repair Fix common install issues
|
|
109
|
+
dev-link Symlink local plugin source into opencode's plugin dir
|
|
110
|
+
dev-unlink Remove the dev symlink and restore the deployed copy
|
|
111
|
+
heads-up <subcommand> Manage pre-push / pre-release heads-ups
|
|
112
|
+
bg <subcommand> Manage background agents (list/view/kill/logs)
|
|
113
|
+
browser-harness-up Start Chromium for browser-harness (start/stop/status)
|
|
114
|
+
providers detect Auto-detect provider API keys from env + opencode.json
|
|
122
115
|
|
|
123
116
|
Examples:
|
|
124
117
|
bizar install
|
|
@@ -126,1373 +119,168 @@ function showHelp() {
|
|
|
126
119
|
bizar dash start
|
|
127
120
|
bizar dash start --bg
|
|
128
121
|
bizar dash stop
|
|
129
|
-
bizar dash status
|
|
130
122
|
bizar doctor
|
|
131
123
|
bizar update --all --dry-run
|
|
132
124
|
|
|
133
125
|
Run \`bizar <command> --help\` for per-command help.
|
|
134
126
|
|
|
135
127
|
Install:
|
|
136
|
-
npm install -g @polderlabs/bizar
|
|
137
|
-
npm install -g @polderlabs/bizar-dash
|
|
138
|
-
npm install -g @polderlabs/bizar-plugin
|
|
128
|
+
npm install -g @polderlabs/bizar
|
|
129
|
+
npm install -g @polderlabs/bizar-dash
|
|
130
|
+
npm install -g @polderlabs/bizar-plugin
|
|
139
131
|
`);
|
|
140
132
|
}
|
|
141
133
|
|
|
142
|
-
|
|
143
|
-
console.log(`
|
|
144
|
-
bizar audit — Run security audit on agent configuration
|
|
145
|
-
|
|
146
|
-
Usage:
|
|
147
|
-
bizar audit
|
|
148
|
-
`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function showInitHelp() {
|
|
152
|
-
console.log(`
|
|
153
|
-
bizar init — Initialize .bizar/ in current project
|
|
154
|
-
|
|
155
|
-
Usage:
|
|
156
|
-
bizar init
|
|
157
|
-
|
|
158
|
-
Description:
|
|
159
|
-
Detects the project stack, creates .bizar/PROJECT.md and
|
|
160
|
-
.bizar/AGENTS_SELF_IMPROVEMENT.md and installs relevant skills.
|
|
161
|
-
The per-project knowledge graph (in .bizar/graph/) is provided
|
|
162
|
-
by the graphify mod — install it from the mod registry for
|
|
163
|
-
that feature.
|
|
164
|
-
`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function showExportHelp() {
|
|
168
|
-
console.log(`
|
|
169
|
-
bizar export — Export agents/rules to another harness
|
|
170
|
-
|
|
171
|
-
Usage:
|
|
172
|
-
bizar export [claude|cursor|opencode]
|
|
173
|
-
|
|
174
|
-
Description:
|
|
175
|
-
Copies installed Bizar agents and rules into another harness format.
|
|
176
|
-
`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function showInstallHelp() {
|
|
180
|
-
console.log(`
|
|
181
|
-
bizar install — Run the unified BizarHarness installer
|
|
182
|
-
|
|
183
|
-
Usage:
|
|
184
|
-
bizar install Install (or refresh) every component
|
|
185
|
-
bizar install --dry-run Print what would happen, change nothing
|
|
186
|
-
bizar install --force Overwrite existing files
|
|
187
|
-
bizar install --with-mods a,b,c Opt-in: install specific mods as part of the run
|
|
188
|
-
bizar install --help Show this help
|
|
189
|
-
|
|
190
|
-
Description:
|
|
191
|
-
v4.4.7+ — unified installer. Same code path as 'bizar update'; the
|
|
192
|
-
difference is just mode=install vs mode=update. Every step is
|
|
193
|
-
idempotent — running this twice is safe.
|
|
194
|
-
|
|
195
|
-
1. Installs @polderlabs/bizar via npm (skipped if already current).
|
|
196
|
-
2. Shells to ./install.sh for platform-specific system deps (uv,
|
|
197
|
-
python3.12, jq, gh on Linux; brew on macOS) + service registration
|
|
198
|
-
(systemd / launchd / Task Scheduler).
|
|
199
|
-
3. Syncs agent files, slash commands, and bundled skills into
|
|
200
|
-
~/.config/opencode/.
|
|
201
|
-
4. Copies plugins/bizar/ from the npm install into
|
|
202
|
-
~/.config/opencode/plugins/bizar/ (preserves dev symlinks).
|
|
203
|
-
5. Patches ~/.config/opencode/opencode.json with the Bizar plugin
|
|
204
|
-
entry (skipped if already present).
|
|
205
|
-
6. Runs 'bizar doctor' as a post-install health check.
|
|
206
|
-
|
|
207
|
-
No API key collection, no interactive prompts.
|
|
208
|
-
`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function showUpdateHelp() {
|
|
212
|
-
console.log(`
|
|
213
|
-
bizar update — Update opencode + @polderlabs/bizar (which bundles the
|
|
214
|
-
plugin and dashboard). Detects what's installed and only touches what's
|
|
215
|
-
missing or out of date.
|
|
216
|
-
|
|
217
|
-
Usage:
|
|
218
|
-
bizar update Update EVERYTHING (default; auto-kills + restarts)
|
|
219
|
-
bizar update --check Only print current vs. latest; do not update
|
|
220
|
-
bizar update --channel=stable|beta Pick the npm dist-tag (default: stable)
|
|
221
|
-
bizar update --no-restart Don't auto-restart the dashboard after update
|
|
222
|
-
bizar update --dry-run Print what would happen, change nothing
|
|
223
|
-
bizar update --force Override .bizar/PRE_PUSH_NOTES.md blockers
|
|
224
|
-
bizar update --yes Same as --force, but named for one-line scripts
|
|
225
|
-
bizar update --with-mods a,b,c Opt-in: install specific mods as part of the run
|
|
226
|
-
bizar update --help Show this help
|
|
227
|
-
|
|
228
|
-
Components updated:
|
|
229
|
-
opencode-ai the opencode CLI itself
|
|
230
|
-
@polderlabs/bizar this CLI + dashboard + plugin (one package)
|
|
231
|
-
|
|
232
|
-
Behavior (v4.4.7+):
|
|
233
|
-
• Single unified provisioner. 'bizar install' and 'bizar update' are
|
|
234
|
-
the same code path with different mode flags. Every step is
|
|
235
|
-
idempotent — re-running is safe.
|
|
236
|
-
• Detects running Bizar instances (background service daemon, web
|
|
237
|
-
dashboard) by reading ~/.config/bizar/{service,dashboard}.pid and
|
|
238
|
-
cleans up any stale or empty PID files.
|
|
239
|
-
• Auto-kills running instances with a brief notice.
|
|
240
|
-
• Sends SIGTERM, waits up to 5s, escalates to SIGKILL if needed.
|
|
241
|
-
• Re-runs the install script so the deployed plugin source matches
|
|
242
|
-
the just-upgraded npm version (avoids the version-skew trap).
|
|
243
|
-
• If the dashboard was running and bizar was updated, spawns a
|
|
244
|
-
fresh detached dashboard process with the new code (skipped with
|
|
245
|
-
--no-restart).
|
|
246
|
-
• Runs 'bizar doctor' after a successful update to catch config
|
|
247
|
-
regressions before opencode tries to start.
|
|
248
|
-
• With --check: prints the version matrix and release-notes excerpt
|
|
249
|
-
between current and latest, exits non-zero if an update is available.
|
|
250
|
-
|
|
251
|
-
Examples:
|
|
252
|
-
bizar update Full auto-update (recommended)
|
|
253
|
-
bizar update --check Show version matrix + notes, do nothing
|
|
254
|
-
bizar update --channel=beta Upgrade to latest beta build
|
|
255
|
-
bizar update --dry-run Preview what would change
|
|
256
|
-
bizar update plugin --no-restart Plugin-only, leave dashboard alone
|
|
257
|
-
|
|
258
|
-
Errors:
|
|
259
|
-
Network failures (registry offline / DNS) and npm permission issues
|
|
260
|
-
are surfaced with the raw npm output. The provisioner never silently
|
|
261
|
-
swallows them — look for the ✗ marker in the step output.
|
|
262
|
-
`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function showTestGateHelp() {
|
|
266
|
-
console.log(`
|
|
267
|
-
bizar test-gate — Detect & run the project's test suite
|
|
268
|
-
`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function showDevLinkHelp() {
|
|
272
|
-
console.log(`
|
|
273
|
-
bizar dev-link / dev-unlink — Manage a symlink from the opencode plugin dir
|
|
274
|
-
to a local source checkout, so edits propagate to opencode on next session.
|
|
275
|
-
|
|
276
|
-
Usage:
|
|
277
|
-
bizar dev-link [source-dir] Symlink source-dir (default: ./plugins/bizar)
|
|
278
|
-
to ~/.config/opencode/plugins/bizar
|
|
279
|
-
bizar dev-link --force Replace an existing deployed copy
|
|
280
|
-
bizar dev-unlink Remove the dev symlink + restore from npm
|
|
281
|
-
bizar dev-unlink --force Remove even if not a symlink (destructive)
|
|
282
|
-
|
|
283
|
-
Description:
|
|
284
|
-
By default, opencode loads the Bizar plugin from
|
|
285
|
-
~/.config/opencode/plugins/bizar, which is a real directory copied
|
|
286
|
-
from the npm package. Edits to plugins/bizar/ in the BizarHarness
|
|
287
|
-
repo don't propagate until you re-run the installer.
|
|
288
|
-
|
|
289
|
-
\`bizar dev-link\` replaces that directory with a symlink pointing
|
|
290
|
-
at your local checkout, so source edits are picked up immediately.
|
|
291
|
-
\`bizar dev-unlink\` reverses the change by removing the symlink
|
|
292
|
-
and re-installing the deployed copy from the npm package.
|
|
293
|
-
|
|
294
|
-
While the dev link is in place, \`bizar update\` will skip the
|
|
295
|
-
plugin-copy step (and print a warning) so it doesn't clobber the
|
|
296
|
-
link. Run \`bizar dev-unlink\` first, or pass --force to overwrite.
|
|
134
|
+
// ── Command imports ────────────────────────────────────────────────────────────
|
|
297
135
|
|
|
298
|
-
|
|
299
|
-
bizar dev-link
|
|
300
|
-
bizar dev-link /home/me/projects/bizar/plugins/bizar
|
|
301
|
-
bizar dev-link --force
|
|
302
|
-
bizar dev-unlink
|
|
303
|
-
`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function showDoctorHelp() {
|
|
307
|
-
console.log(`
|
|
308
|
-
bizar doctor — Check the BizarHarness install for health issues
|
|
309
|
-
|
|
310
|
-
Usage:
|
|
311
|
-
bizar doctor
|
|
312
|
-
|
|
313
|
-
Description:
|
|
314
|
-
Runs a battery of health checks against the local install:
|
|
315
|
-
• opencode CLI reachable
|
|
316
|
-
• ~/.config/opencode/opencode.json parses as JSON
|
|
317
|
-
• the Bizar plugin is registered
|
|
318
|
-
• plugin path resolves
|
|
319
|
-
• @polderlabs/bizar-plugin is installed globally
|
|
320
|
-
• core agent files are installed (odin, quick, thor, tyr)
|
|
321
|
-
• headroom / semble / skills on PATH (lenient — at least one)
|
|
322
|
-
• dashboard reachable (skipped if no port file)
|
|
323
|
-
• provider.minimax block + MiniMax model flags are sane
|
|
324
|
-
|
|
325
|
-
Prints ✓/✗ for each check and a final summary. Exits non-zero
|
|
326
|
-
if any check fails. Use \`bizar doctor\` after a manual config
|
|
327
|
-
edit or to diagnose "why is opencode misbehaving?" questions.
|
|
328
|
-
|
|
329
|
-
Related:
|
|
330
|
-
bizar update Update + auto-run doctor on success
|
|
331
|
-
`);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function showServiceHelp() {
|
|
335
|
-
const bizarConfigDir = getBizarConfigDir();
|
|
336
|
-
console.log(`
|
|
337
|
-
bizar service — Manage the background service daemon
|
|
338
|
-
|
|
339
|
-
Usage:
|
|
340
|
-
bizar service start Start the service in background
|
|
341
|
-
bizar service stop Stop the running service
|
|
342
|
-
bizar service status Show whether the service is running
|
|
343
|
-
bizar service logs Tail the service log
|
|
344
|
-
bizar service follow Follow the service log until Ctrl-C
|
|
345
|
-
bizar service install Register with systemd / launchd / scheduled task
|
|
346
|
-
bizar service install --force Re-install even when the unit matches
|
|
347
|
-
bizar service uninstall Remove the OS-level autostart
|
|
348
|
-
bizar service uninstall --force Force-uninstall even when nothing is registered
|
|
349
|
-
|
|
350
|
-
Description:
|
|
351
|
-
The service watches per-project schedules (cron / interval / once)
|
|
352
|
-
and runs them at the right time. It logs to
|
|
353
|
-
${bizarConfigDir}/service.log and writes its PID to
|
|
354
|
-
${bizarConfigDir}/service.pid.
|
|
355
|
-
|
|
356
|
-
install registers the daemon under the OS init system — systemd user
|
|
357
|
-
unit on Linux, launchd LaunchAgent on macOS, scheduled task
|
|
358
|
-
("BizarDashboardService", ONSTART, HIGHEST) on Windows. After
|
|
359
|
-
install, a normal user does not need to run \`bizar service start\`
|
|
360
|
-
for the dashboard background process — the OS does it at login.
|
|
361
|
-
`);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function showModHelp() {
|
|
365
|
-
console.log(`
|
|
366
|
-
bizar mod — Manage mods (via the dashboard's HTTP API)
|
|
367
|
-
|
|
368
|
-
Usage:
|
|
369
|
-
bizar mod upgrade <id> [--backup] Upgrade an installed mod to the latest version
|
|
370
|
-
bizar mod install <id> Install a mod from the registry
|
|
371
|
-
bizar mod list List installed mods
|
|
372
|
-
bizar mod registry Show registry URL + available mods
|
|
373
|
-
|
|
374
|
-
Description:
|
|
375
|
-
Subcommands call the running dashboard's HTTP API. If no dashboard is
|
|
376
|
-
reachable, you'll be told to run \`bizar dash start\` first.
|
|
377
|
-
|
|
378
|
-
\`bizar mod upgrade <id>\` will:
|
|
379
|
-
1. Snapshot the existing version of the mod
|
|
380
|
-
2. Optionally back up the folder (--backup)
|
|
381
|
-
3. Remove the existing copy (and its opencode-config instructions)
|
|
382
|
-
4. Install the latest version from the registry
|
|
383
|
-
5. Re-install the new mod's instruction files into opencode config
|
|
384
|
-
6. Print from-version → to-version
|
|
385
|
-
`);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
async function runModCommand(modArgs) {
|
|
389
|
-
const sub = modArgs[0];
|
|
390
|
-
const positional = modArgs.slice(1).filter((a) => !a.startsWith('-'));
|
|
391
|
-
const flags = modArgs.slice(1).filter((a) => a.startsWith('-'));
|
|
392
|
-
|
|
393
|
-
if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
|
|
394
|
-
showModHelp();
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Read dashboard port from the port file the dashboard writes on start.
|
|
399
|
-
const { readFileSync: rfs } = await import('node:fs');
|
|
400
|
-
const { join: joinPath } = await import('node:path');
|
|
401
|
-
const portFile = joinPath(getBizarConfigDir(), 'dashboard.port');
|
|
402
|
-
let port = null;
|
|
136
|
+
async function importCommand(name) {
|
|
403
137
|
try {
|
|
404
|
-
|
|
405
|
-
if (!Number.isFinite(port) || port <= 0) port = null;
|
|
138
|
+
return await import(`./commands/${name}.mjs`);
|
|
406
139
|
} catch {
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
if (!port) {
|
|
410
|
-
console.error(chalk.red(' ✗ Dashboard is not running (no port file at ' + portFile + ').'));
|
|
411
|
-
console.error(chalk.dim(' Start it first: `bizar dash start --bg`'));
|
|
412
|
-
process.exit(1);
|
|
413
|
-
}
|
|
414
|
-
const baseUrl = `http://127.0.0.1:${port}`;
|
|
415
|
-
|
|
416
|
-
async function postJson(path, body) {
|
|
417
|
-
const res = await fetch(`${baseUrl}${path}`, {
|
|
418
|
-
method: 'POST',
|
|
419
|
-
headers: { 'Content-Type': 'application/json' },
|
|
420
|
-
body: JSON.stringify(body || {}),
|
|
421
|
-
});
|
|
422
|
-
const text = await res.text();
|
|
423
|
-
let json = null;
|
|
424
|
-
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
425
|
-
if (!res.ok) {
|
|
426
|
-
const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
|
|
427
|
-
throw new Error(`${path} failed: ${msg}`);
|
|
428
|
-
}
|
|
429
|
-
return json;
|
|
430
|
-
}
|
|
431
|
-
async function getJson(path) {
|
|
432
|
-
const res = await fetch(`${baseUrl}${path}`);
|
|
433
|
-
const text = await res.text();
|
|
434
|
-
let json = null;
|
|
435
|
-
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
436
|
-
if (!res.ok) {
|
|
437
|
-
const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
|
|
438
|
-
throw new Error(`${path} failed: ${msg}`);
|
|
439
|
-
}
|
|
440
|
-
return json;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (sub === 'install') {
|
|
444
|
-
const id = positional[0];
|
|
445
|
-
if (!id) {
|
|
446
|
-
console.error(chalk.red(' ✗ Missing mod id. Usage: bizar mod install <id>'));
|
|
447
|
-
process.exit(1);
|
|
448
|
-
}
|
|
449
|
-
try {
|
|
450
|
-
const m = await postJson('/api/mods', { id });
|
|
451
|
-
console.log(chalk.green(` ✓ Installed "${m.id}" v${m.version}`));
|
|
452
|
-
} catch (err) {
|
|
453
|
-
console.error(chalk.red(` ✗ ${err.message}`));
|
|
454
|
-
process.exit(1);
|
|
455
|
-
}
|
|
456
|
-
} else if (sub === 'upgrade') {
|
|
457
|
-
const id = positional[0];
|
|
458
|
-
if (!id) {
|
|
459
|
-
console.error(chalk.red(' ✗ Missing mod id. Usage: bizar mod upgrade <id> [--backup]'));
|
|
460
|
-
process.exit(1);
|
|
461
|
-
}
|
|
462
|
-
const backup = flags.includes('--backup') || flags.includes('-b');
|
|
463
|
-
try {
|
|
464
|
-
const r = await postJson(`/api/mods/${encodeURIComponent(id)}/upgrade`, { backup });
|
|
465
|
-
const note = r.backupPath ? chalk.dim(` (backup: ${r.backupPath})`) : '';
|
|
466
|
-
console.log(chalk.green(` ✓ Upgraded "${id}" v${r.from} → v${r.to}`) + note);
|
|
467
|
-
} catch (err) {
|
|
468
|
-
console.error(chalk.red(` ✗ ${err.message}`));
|
|
469
|
-
process.exit(1);
|
|
470
|
-
}
|
|
471
|
-
} else if (sub === 'list') {
|
|
472
|
-
try {
|
|
473
|
-
const r = await getJson('/api/mods');
|
|
474
|
-
const mods = r.mods || [];
|
|
475
|
-
if (mods.length === 0) {
|
|
476
|
-
console.log(chalk.dim(' (no mods installed)'));
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
for (const m of mods) {
|
|
480
|
-
const state = m.enabled ? chalk.green('enabled ') : chalk.yellow('disabled');
|
|
481
|
-
console.log(` ${m.id.padEnd(20)} v${m.version.padEnd(10)} ${state} ${m.name || ''}`);
|
|
482
|
-
}
|
|
483
|
-
} catch (err) {
|
|
484
|
-
console.error(chalk.red(` ✗ ${err.message}`));
|
|
485
|
-
process.exit(1);
|
|
486
|
-
}
|
|
487
|
-
} else if (sub === 'registry') {
|
|
488
|
-
try {
|
|
489
|
-
const r = await getJson('/api/mods/registry');
|
|
490
|
-
console.log(chalk.dim(` Source: ${r.registry?.source || '(unknown)'}`));
|
|
491
|
-
console.log(chalk.dim(` Updated: ${r.registry?.updatedAt || '(unknown)'}`));
|
|
492
|
-
console.log('');
|
|
493
|
-
const mods = r.mods || [];
|
|
494
|
-
if (mods.length === 0) {
|
|
495
|
-
console.log(chalk.dim(' (no mods in registry)'));
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
for (const m of mods) {
|
|
499
|
-
const installed = m.installed ? chalk.green(`installed v${m.installedVersion || '?'}`) : chalk.dim('not installed');
|
|
500
|
-
const upgrade = m.upgradeAvailable ? chalk.yellow(` ↑ v${m.upgradeAvailable} available`) : '';
|
|
501
|
-
console.log(` ${m.id.padEnd(20)} v${(m.latest || '?').padEnd(10)} ${installed}${upgrade} ${m.name || ''}`);
|
|
502
|
-
}
|
|
503
|
-
} catch (err) {
|
|
504
|
-
console.error(chalk.red(` ✗ ${err.message}`));
|
|
505
|
-
process.exit(1);
|
|
506
|
-
}
|
|
507
|
-
} else {
|
|
508
|
-
console.error(chalk.red(` ✗ Unknown mod subcommand: ${sub}`));
|
|
509
|
-
showModHelp();
|
|
510
|
-
process.exit(1);
|
|
140
|
+
return null;
|
|
511
141
|
}
|
|
512
142
|
}
|
|
513
143
|
|
|
514
|
-
|
|
515
|
-
console.log(`
|
|
516
|
-
bizar minimax — Manage the MiniMax Token Plan integration
|
|
517
|
-
|
|
518
|
-
Usage:
|
|
519
|
-
bizar minimax status Show whether the Subscription Key is configured
|
|
520
|
-
and where it was resolved from (auth.json,
|
|
521
|
-
opencode.json, env var, or none).
|
|
522
|
-
bizar minimax remains Fetch the live 5-hour + weekly remaining
|
|
523
|
-
quota per model. Shows reset times.
|
|
524
|
-
bizar minimax test Smoke-test the key with a one-shot chat
|
|
525
|
-
completion. Prints the usage block.
|
|
526
|
-
bizar minimax config <key> Save a new Subscription Key to
|
|
527
|
-
~/.local/share/opencode/auth.json. The
|
|
528
|
-
key never leaves this machine.
|
|
529
|
-
bizar minimax clear Remove the Subscription Key from
|
|
530
|
-
opencode's auth.json.
|
|
531
|
-
bizar minimax reset-onboarding Re-trigger the first-run wizard. Use
|
|
532
|
-
this if the key was changed outside the
|
|
533
|
-
dashboard and you want to re-enter it.
|
|
144
|
+
// ── Main ───────────────────────────────────────────────────────────────────────
|
|
534
145
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
// Find the dashboard's port + password so the CLI can talk to /api/minimax/*.
|
|
543
|
-
function readDashboardConn() {
|
|
544
|
-
const portFile = join(BIZAR_HOME, 'dashboard.port');
|
|
545
|
-
const authFile = join(BIZAR_HOME, 'dashboard-secret');
|
|
546
|
-
let port = 4321;
|
|
547
|
-
let secret = '';
|
|
548
|
-
try {
|
|
549
|
-
if (existsSync(portFile)) {
|
|
550
|
-
const parsed = parseInt(readFileSync(portFile, 'utf8').trim(), 10);
|
|
551
|
-
if (Number.isFinite(parsed) && parsed > 0) port = parsed;
|
|
552
|
-
}
|
|
553
|
-
} catch { /* ignore */ }
|
|
554
|
-
try {
|
|
555
|
-
if (existsSync(authFile)) secret = readFileSync(authFile, 'utf8').trim();
|
|
556
|
-
} catch { /* ignore */ }
|
|
557
|
-
return { port, secret };
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
async function minimaxApi(path, opts = {}) {
|
|
561
|
-
const { port, secret } = readDashboardConn();
|
|
562
|
-
const url = `http://127.0.0.1:${port}${path}`;
|
|
563
|
-
const headers = { 'content-type': 'application/json', accept: 'application/json' };
|
|
564
|
-
if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
|
|
565
|
-
const method = (opts.method || 'GET').toUpperCase();
|
|
566
|
-
try {
|
|
567
|
-
const resp = await fetch(url, { method, headers, body: opts.body ? JSON.stringify(opts.body) : undefined });
|
|
568
|
-
const text = await resp.text();
|
|
569
|
-
let data = null;
|
|
570
|
-
try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
|
|
571
|
-
if (!resp.ok) {
|
|
572
|
-
return { ok: false, error: `http_${resp.status}`, message: data?.message || data?.error || resp.statusText, status: resp.status };
|
|
573
|
-
}
|
|
574
|
-
return { ok: true, status: resp.status, data };
|
|
575
|
-
} catch (err) {
|
|
576
|
-
return { ok: false, error: 'network_error', message: err && err.message ? err.message : String(err) };
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
async function runMinimaxCommand(minimaxArgs) {
|
|
581
|
-
const sub = minimaxArgs[0];
|
|
582
|
-
const flags = minimaxArgs.slice(1).filter((a) => a.startsWith('-'));
|
|
583
|
-
const positional = minimaxArgs.slice(1).filter((a) => !a.startsWith('-'));
|
|
584
|
-
const yes = flags.includes('--yes') || flags.includes('-y');
|
|
585
|
-
|
|
586
|
-
if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
|
|
587
|
-
showMinimaxHelp();
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (sub === 'status') {
|
|
592
|
-
const r = await minimaxApi('/api/minimax/status');
|
|
593
|
-
if (!r.ok) {
|
|
594
|
-
console.error(chalk.red(` ✗ ${r.message || r.error}`));
|
|
595
|
-
process.exit(1);
|
|
596
|
-
}
|
|
597
|
-
const s = r.data;
|
|
598
|
-
console.log('');
|
|
599
|
-
console.log(chalk.bold(' MiniMax Token Plan status'));
|
|
600
|
-
console.log('');
|
|
601
|
-
console.log(` ${chalk.dim('configured:')} ${s.configured ? chalk.green('yes') : chalk.red('no')}`);
|
|
602
|
-
console.log(` ${chalk.dim('key source:')} ${chalk.cyan(s.source)}`);
|
|
603
|
-
if (s.apiKeyHint) console.log(` ${chalk.dim('key hint:')} ${s.apiKeyHint}`);
|
|
604
|
-
console.log(` ${chalk.dim('group id:')} ${s.groupId}`);
|
|
605
|
-
console.log(` ${chalk.dim('token host:')} ${s.tokenBaseUrl}`);
|
|
606
|
-
console.log(` ${chalk.dim('chat host:')} ${s.chatBaseUrl}`);
|
|
607
|
-
console.log(` ${chalk.dim('key format:')} ${s.keyPatternValid === true ? chalk.green('ok') : s.keyPatternValid === false ? chalk.red('unexpected prefix') : chalk.dim('n/a')}`);
|
|
608
|
-
console.log('');
|
|
609
|
-
if (s.cache) {
|
|
610
|
-
console.log(` ${chalk.dim('cached:')} ${new Date(s.cache.fetchedAt).toLocaleString()} (${s.cache.modelCount} models)`);
|
|
611
|
-
} else {
|
|
612
|
-
console.log(` ${chalk.dim('cached:')} none yet — run \`bizar minimax remains\` to populate`);
|
|
613
|
-
}
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if (sub === 'remains') {
|
|
618
|
-
const r = await minimaxApi('/api/minimax/remains');
|
|
619
|
-
if (!r.ok) {
|
|
620
|
-
console.error(chalk.red(` ✗ ${r.message || r.error}`));
|
|
621
|
-
process.exit(1);
|
|
622
|
-
}
|
|
623
|
-
if (!r.data?.ok) {
|
|
624
|
-
console.error(chalk.red(` ✗ ${r.data?.message || r.data?.error || 'unknown'}`));
|
|
625
|
-
process.exit(1);
|
|
626
|
-
}
|
|
627
|
-
const data = r.data;
|
|
628
|
-
console.log('');
|
|
629
|
-
console.log(chalk.bold(` MiniMax Token Plan quota (${new Date(data.fetchedAt).toLocaleString()})`));
|
|
630
|
-
if (data.apiKeyHint) console.log(chalk.dim(` Key: ${data.apiKeyHint} · source: ${data.keySource} · group: ${data.groupId}`));
|
|
631
|
-
console.log('');
|
|
632
|
-
for (const m of data.models || []) {
|
|
633
|
-
const five = m.current_interval_remaining_percent;
|
|
634
|
-
const week = m.current_weekly_remaining_percent;
|
|
635
|
-
const fiveBar = '█'.repeat(Math.round(five / 5)) + '░'.repeat(20 - Math.round(five / 5));
|
|
636
|
-
const weekBar = '█'.repeat(Math.round(week / 5)) + '░'.repeat(20 - Math.round(week / 5));
|
|
637
|
-
const fiveColor = five >= 75 ? chalk.green : five >= 25 ? chalk.yellow : chalk.red;
|
|
638
|
-
const weekColor = week >= 75 ? chalk.green : week >= 25 ? chalk.yellow : chalk.red;
|
|
639
|
-
console.log(` ${chalk.bold(m.model_name)}`);
|
|
640
|
-
console.log(` ${chalk.dim('5h:')} ${fiveColor(five + '%'.padStart(4))} ${fiveBar} resets in ${m.intervalResetInHuman}`);
|
|
641
|
-
console.log(` ${chalk.dim('week:')} ${weekColor(week + '%'.padStart(4))} ${weekBar} resets in ${m.weeklyResetInHuman}`);
|
|
642
|
-
console.log('');
|
|
643
|
-
}
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
if (sub === 'test') {
|
|
648
|
-
const prompt = positional[0] || 'Reply with the single word: pong';
|
|
649
|
-
const r = await minimaxApi('/api/minimax/test', {
|
|
650
|
-
method: 'POST',
|
|
651
|
-
body: { prompt, model: 'MiniMax-M3', maxTokens: 32 },
|
|
652
|
-
});
|
|
653
|
-
if (!r.ok) {
|
|
654
|
-
console.error(chalk.red(` ✗ ${r.message || r.error}`));
|
|
655
|
-
process.exit(1);
|
|
656
|
-
}
|
|
657
|
-
const data = r.data;
|
|
658
|
-
if (!data?.ok) {
|
|
659
|
-
console.error(chalk.red(` ✗ ${data?.message || data?.error || 'unknown'}`));
|
|
660
|
-
process.exit(1);
|
|
661
|
-
}
|
|
662
|
-
console.log('');
|
|
663
|
-
console.log(chalk.green(' ✓ Key works'));
|
|
664
|
-
console.log(` ${chalk.dim('model:')} ${data.model}`);
|
|
665
|
-
console.log(` ${chalk.dim('finish:')} ${data.finishReason}`);
|
|
666
|
-
if (data.content) console.log(` ${chalk.dim('content:')} ${JSON.stringify(data.content.slice(0, 80))}${data.content.length > 80 ? '…' : ''}`);
|
|
667
|
-
if (data.usage) {
|
|
668
|
-
console.log(` ${chalk.dim('usage:')} total=${data.usage.total_tokens} prompt=${data.usage.prompt_tokens} completion=${data.usage.completion_tokens}`);
|
|
669
|
-
}
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if (sub === 'config' || sub === 'set') {
|
|
674
|
-
const key = positional[0];
|
|
675
|
-
if (!key) {
|
|
676
|
-
console.error(chalk.red(' ✗ Missing key. Usage: bizar minimax config <sk-cp-…>'));
|
|
677
|
-
process.exit(1);
|
|
678
|
-
}
|
|
679
|
-
if (!/^sk-(cp|ant|or)-[A-Za-z0-9_-]{20,}$/.test(key)) {
|
|
680
|
-
console.error(chalk.red(' ✗ Key does not look like a MiniMax key (expected sk-cp-…, sk-ant-…, or sk-or-… prefix)'));
|
|
681
|
-
process.exit(1);
|
|
682
|
-
}
|
|
683
|
-
console.log(chalk.dim(' Saving to opencode auth.json…'));
|
|
684
|
-
const r = await minimaxApi('/api/minimax/onboarding/save-key', {
|
|
685
|
-
method: 'POST',
|
|
686
|
-
body: { key, groupId: 'default' },
|
|
687
|
-
});
|
|
688
|
-
if (!r.ok) {
|
|
689
|
-
console.error(chalk.red(` ✗ ${r.message || r.error}`));
|
|
690
|
-
process.exit(1);
|
|
691
|
-
}
|
|
692
|
-
console.log(chalk.green(` ✓ Saved to ${r.data?.path}`));
|
|
693
|
-
console.log(` ${chalk.dim('key hint:')} ${r.data?.apiKeyHint}`);
|
|
146
|
+
async function main() {
|
|
147
|
+
if (args.includes('--check')) {
|
|
148
|
+
const status = checkSetupStatus();
|
|
149
|
+
if (wantJson) process.stdout.write(JSON.stringify(status, null, 2) + '\n');
|
|
150
|
+
else console.log(JSON.stringify(status, null, 2));
|
|
151
|
+
process.exit(status.needed ? EXIT_ERROR : EXIT_OK);
|
|
694
152
|
return;
|
|
695
153
|
}
|
|
696
154
|
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
console.log(chalk.dim(' Continue? [y/N]'));
|
|
701
|
-
// simple readline confirmation
|
|
702
|
-
const buf = [];
|
|
703
|
-
process.stdin.setEncoding('utf8');
|
|
704
|
-
process.stdin.on('data', (c) => { buf.push(c); if (c.includes('\n')) process.stdin.pause(); });
|
|
705
|
-
await new Promise((resolve) => process.stdin.once('close', resolve));
|
|
706
|
-
const answer = buf.join('').trim().toLowerCase();
|
|
707
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
708
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
// We don't have a "clear key" route; instead write an empty auth.json
|
|
713
|
-
// entry. We piggyback on the onboarding save-key by saving an empty
|
|
714
|
-
// marker isn't allowed — use the providers-store side instead. For
|
|
715
|
-
// now, recommend using the dashboard's "clear" button. But the
|
|
716
|
-
// simplest CLI path is to write auth.json directly.
|
|
717
|
-
const authFile = join(HOME, '.local', 'share', 'opencode', 'auth.json');
|
|
718
|
-
let auth = {};
|
|
719
|
-
try {
|
|
720
|
-
if (existsSync(authFile)) auth = JSON.parse(readFileSync(authFile, 'utf8'));
|
|
721
|
-
} catch { /* ignore */ }
|
|
722
|
-
if (auth.minimax) {
|
|
723
|
-
delete auth.minimax;
|
|
724
|
-
writeFileSync(authFile, JSON.stringify(auth, null, 2) + '\n', 'utf8');
|
|
725
|
-
console.log(chalk.green(' ✓ MiniMax key removed from auth.json'));
|
|
726
|
-
} else {
|
|
727
|
-
console.log(chalk.dim(' No MiniMax key was configured.'));
|
|
728
|
-
}
|
|
155
|
+
if (args.includes('--setup') || args.includes('--postinstall')) {
|
|
156
|
+
await ensureSetup({ silent: false });
|
|
157
|
+
process.exit(EXIT_OK);
|
|
729
158
|
return;
|
|
730
159
|
}
|
|
731
160
|
|
|
732
|
-
if (
|
|
733
|
-
|
|
734
|
-
console.log(chalk.yellow(' ⚠ This will re-trigger the first-run MiniMax onboarding wizard.'));
|
|
735
|
-
console.log(chalk.dim(' Continue? [y/N]'));
|
|
736
|
-
const buf = [];
|
|
737
|
-
process.stdin.setEncoding('utf8');
|
|
738
|
-
process.stdin.on('data', (c) => { buf.push(c); if (c.includes('\n')) process.stdin.pause(); });
|
|
739
|
-
await new Promise((resolve) => process.stdin.once('close', resolve));
|
|
740
|
-
const answer = buf.join('').trim().toLowerCase();
|
|
741
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
742
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
// Reset via the dashboard API.
|
|
747
|
-
const r = await minimaxApi('/api/minimax/onboarding', {
|
|
748
|
-
method: 'POST',
|
|
749
|
-
body: { dismissedAt: null },
|
|
750
|
-
});
|
|
751
|
-
if (!r.ok) {
|
|
752
|
-
console.error(chalk.red(` ✗ ${r.message || r.error}`));
|
|
753
|
-
process.exit(1);
|
|
754
|
-
}
|
|
755
|
-
console.log(chalk.green(' ✓ Onboarding wizard will show on next dashboard load'));
|
|
161
|
+
if (isVersionRequest) {
|
|
162
|
+
console.log(readCliVersion());
|
|
756
163
|
return;
|
|
757
164
|
}
|
|
758
165
|
|
|
759
|
-
|
|
760
|
-
showMinimaxHelp();
|
|
761
|
-
process.exit(1);
|
|
762
|
-
}
|
|
166
|
+
const [cmd, ...cmdArgs] = dispatchArgs;
|
|
763
167
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
console.log(`
|
|
768
|
-
bizar usage — Show compact usage analytics summary
|
|
769
|
-
|
|
770
|
-
Usage:
|
|
771
|
-
bizar usage [24h|7d|30d] Show summary for the given range (default: 24h)
|
|
772
|
-
|
|
773
|
-
Examples:
|
|
774
|
-
bizar usage
|
|
775
|
-
bizar usage 7d
|
|
776
|
-
bizar usage 30d
|
|
777
|
-
`);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
async function runUsageCommand(args, wantJson = false) {
|
|
781
|
-
const range = (args[0] && ['24h', '7d', '30d'].includes(args[0])) ? args[0] : '24h';
|
|
782
|
-
const { port, secret } = readDashboardConn();
|
|
783
|
-
const url = `http://127.0.0.1:${port}/api/usage?range=${range}`;
|
|
784
|
-
const headers = { accept: 'application/json' };
|
|
785
|
-
if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
|
|
786
|
-
try {
|
|
787
|
-
const resp = await fetch(url, { method: 'GET', headers });
|
|
788
|
-
const text = await resp.text();
|
|
789
|
-
let data = null;
|
|
790
|
-
try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
|
|
791
|
-
if (!resp.ok || !data) {
|
|
792
|
-
console.error(chalk.red(` ✗ Failed to load usage data: ${data?.message ?? resp.statusText}`));
|
|
793
|
-
process.exit(1);
|
|
794
|
-
}
|
|
795
|
-
if (wantJson) {
|
|
796
|
-
process.stdout.write(JSON.stringify(data) + '\n');
|
|
168
|
+
if (!cmd) {
|
|
169
|
+
if (isVersionRequest) {
|
|
170
|
+
console.log(readCliVersion());
|
|
797
171
|
return;
|
|
798
172
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
console.log(chalk.bold(` Usage summary — ${range} (from JSONL store)`));
|
|
802
|
-
console.log('');
|
|
803
|
-
console.log(` ${chalk.dim('Requests:')} ${t.requests.toLocaleString()} (${t.errors} errors)`);
|
|
804
|
-
console.log(` ${chalk.dim('Tokens:')} ${t.totalTokens.toLocaleString()} total (${t.promptTokens.toLocaleString()} prompt · ${t.completionTokens.toLocaleString()} completion)`);
|
|
805
|
-
console.log(` ${chalk.dim('Cached:')} ${t.cachedTokens.toLocaleString()} tokens`);
|
|
806
|
-
console.log(` ${chalk.dim('Reasoning:')} ${t.reasoningTokens.toLocaleString()} tokens`);
|
|
807
|
-
console.log(` ${chalk.dim('Avg latency:')} ${t.avgLatencyMs}ms (p95: ${t.p95LatencyMs}ms)`);
|
|
808
|
-
if (t.costEstimate > 0) {
|
|
809
|
-
console.log(` ${chalk.dim('Est. cost:')} $${t.costEstimate.toFixed(4)} USD`);
|
|
810
|
-
}
|
|
811
|
-
console.log('');
|
|
812
|
-
if (data.daily && data.daily.length > 0) {
|
|
813
|
-
console.log(chalk.dim(` ${chalk.bold('Daily breakdown')}`));
|
|
814
|
-
for (const day of data.daily.slice(-7)) {
|
|
815
|
-
const barLen = Math.round((day.totalTokens / Math.max(...data.daily.map(d => d.totalTokens))) * 20);
|
|
816
|
-
const bar = '█'.repeat(barLen) + '░'.repeat(20 - barLen);
|
|
817
|
-
console.log(` ${day.date} ${bar} ${day.totalTokens.toLocaleString()} tok ${day.requests} req`);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
if (data.perModel && data.perModel.length > 0) {
|
|
821
|
-
console.log('');
|
|
822
|
-
console.log(chalk.dim(` ${chalk.bold('Per model')}`));
|
|
823
|
-
for (const m of data.perModel.slice(0, 8)) {
|
|
824
|
-
console.log(` ${m.modelId.padEnd(24)} ${String(m.requests).padStart(6)} req ${String(m.totalTokens).padStart(8)} tok`);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
console.log('');
|
|
828
|
-
} catch (err) {
|
|
829
|
-
console.error(chalk.red(` ✗ Network error: ${err && err.message ? err.message : String(err)}`));
|
|
830
|
-
console.error(chalk.dim(' Is the dashboard running? Run `bizar dash start` first.'));
|
|
831
|
-
process.exit(1);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
function showHeadroomHelp() {
|
|
836
|
-
console.log(`
|
|
837
|
-
bizar headroom — Manage Headroom context compression
|
|
838
|
-
|
|
839
|
-
Usage:
|
|
840
|
-
bizar headroom status Show live status (installed, proxy, wrapped)
|
|
841
|
-
bizar headroom stats Show compression stats for the last 24h
|
|
842
|
-
bizar headroom install Install headroom via pip or npm
|
|
843
|
-
bizar headroom wrap Wrap opencode to route through the proxy
|
|
844
|
-
bizar headroom unwrap Unwrap opencode
|
|
845
|
-
bizar headroom start Start the proxy server
|
|
846
|
-
bizar headroom stop Stop the proxy server
|
|
847
|
-
bizar headroom doctor Run headroom doctor health check
|
|
848
|
-
|
|
849
|
-
Examples:
|
|
850
|
-
bizar headroom status
|
|
851
|
-
bizar headroom stats
|
|
852
|
-
bizar headroom install
|
|
853
|
-
bizar headroom wrap
|
|
854
|
-
`);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
async function runHeadroomCommand(headroomArgs) {
|
|
858
|
-
const sub = headroomArgs[0];
|
|
859
|
-
const flags = headroomArgs.slice(1).filter((a) => a.startsWith('-'));
|
|
860
|
-
const positional = headroomArgs.slice(1).filter((a) => !a.startsWith('-'));
|
|
861
|
-
|
|
862
|
-
if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
|
|
863
|
-
showHeadroomHelp();
|
|
173
|
+
showHelp();
|
|
174
|
+
process.exit(EXIT_OK);
|
|
864
175
|
return;
|
|
865
176
|
}
|
|
866
177
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const headers = { accept: 'application/json' };
|
|
874
|
-
if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
|
|
875
|
-
const res = await fetch(url, { method: 'GET', headers });
|
|
876
|
-
const text = await res.text();
|
|
877
|
-
let data = null;
|
|
878
|
-
try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
|
|
879
|
-
if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
|
|
880
|
-
return data;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
async function apiPost(path, body = {}) {
|
|
884
|
-
const url = `${baseUrl}${path}`;
|
|
885
|
-
const headers = { 'content-type': 'application/json', accept: 'application/json' };
|
|
886
|
-
if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
|
|
887
|
-
const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) });
|
|
888
|
-
const text = await res.text();
|
|
889
|
-
let data = null;
|
|
890
|
-
try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
|
|
891
|
-
if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
|
|
892
|
-
return data;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
if (sub === 'status') {
|
|
896
|
-
const s = await apiGet('/api/headroom/status');
|
|
897
|
-
console.log('');
|
|
898
|
-
console.log(chalk.bold(' Headroom status'));
|
|
899
|
-
console.log('');
|
|
900
|
-
console.log(` ${chalk.dim('installed:')} ${s.installed ? chalk.green('yes') : chalk.red('no')} ${s.version || ''}`);
|
|
901
|
-
console.log(` ${chalk.dim('proxy:')} ${s.proxyRunning ? chalk.green('running') : chalk.yellow('stopped')} ${s.proxyPort ? `@ ${s.proxyPort}` : ''} ${s.proxyPid ? `(PID ${s.proxyPid})` : ''}`);
|
|
902
|
-
console.log(` ${chalk.dim('wrapped:')} ${s.wrapped ? chalk.green('yes') : chalk.yellow('no')}`);
|
|
903
|
-
console.log(` ${chalk.dim('healthy:')} ${s.healthy === 'ok' ? chalk.green('ok') : s.healthy === 'warn' ? chalk.yellow('warn') : chalk.red('fail')}`);
|
|
904
|
-
if (s.messages && s.messages.length > 0) {
|
|
905
|
-
console.log('');
|
|
906
|
-
for (const m of s.messages) {
|
|
907
|
-
console.log(` ${m}`);
|
|
908
|
-
}
|
|
178
|
+
if (isHelpRequest && !cmd.startsWith('-')) {
|
|
179
|
+
// Pass --help to the command
|
|
180
|
+
const mod = await importCommand(cmd);
|
|
181
|
+
if (mod && typeof mod.run === 'function') {
|
|
182
|
+
await mod.run(cmd, cmdArgs, true);
|
|
183
|
+
return;
|
|
909
184
|
}
|
|
910
|
-
console.log('');
|
|
911
|
-
return;
|
|
912
185
|
}
|
|
913
186
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
console.log(chalk.yellow(` ${st.error}`));
|
|
922
|
-
} else {
|
|
923
|
-
console.log(` ${chalk.dim('tokens saved:')} ${(st.tokensSaved || 0).toLocaleString()}`);
|
|
924
|
-
console.log(` ${chalk.dim('compression:')} ${st.compressionRatio ? `${Math.round(st.compressionRatio * 100)}%` : '—'}`);
|
|
925
|
-
console.log(` ${chalk.dim('cache hits:')} ${st.cacheHits ?? '—'}`);
|
|
926
|
-
console.log(` ${chalk.dim('transforms:')} ${st.transforms ?? '—'}`);
|
|
187
|
+
// Dispatch to command modules
|
|
188
|
+
switch (cmd) {
|
|
189
|
+
case 'install':
|
|
190
|
+
case 'update': {
|
|
191
|
+
const mod = await importCommand('install');
|
|
192
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
193
|
+
break;
|
|
927
194
|
}
|
|
928
|
-
console.log('');
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
195
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
} else {
|
|
937
|
-
console.log(chalk.red(' ✗ Install failed. Try: pip install "headroom-ai[all]"'));
|
|
196
|
+
case 'service': {
|
|
197
|
+
const mod = await importCommand('service');
|
|
198
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
199
|
+
break;
|
|
938
200
|
}
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
201
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
} else {
|
|
948
|
-
console.log(chalk.red(` ✗ Wrap failed: ${r.log || 'unknown error'}`));
|
|
202
|
+
case 'dash':
|
|
203
|
+
case 'dashboard': {
|
|
204
|
+
const mod = await importCommand('dash');
|
|
205
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
206
|
+
break;
|
|
949
207
|
}
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
208
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
} else {
|
|
958
|
-
console.log(chalk.red(' ✗ Unwrap failed'));
|
|
209
|
+
case 'minimax': {
|
|
210
|
+
const mod = await importCommand('minimax');
|
|
211
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
212
|
+
break;
|
|
959
213
|
}
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
214
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
console.log(chalk.green(` ✓ Proxy started on 127.0.0.1:${port} (PID ${r.pid})`));
|
|
968
|
-
} else {
|
|
969
|
-
console.log(chalk.red(' ✗ Proxy start failed'));
|
|
215
|
+
case 'headroom': {
|
|
216
|
+
const mod = await importCommand('headroom');
|
|
217
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
218
|
+
break;
|
|
970
219
|
}
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
220
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
} else {
|
|
979
|
-
console.log(chalk.red(' ✗ Proxy stop failed'));
|
|
221
|
+
case 'mod': {
|
|
222
|
+
const mod = await importCommand('mod');
|
|
223
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
224
|
+
break;
|
|
980
225
|
}
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
226
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
await new Promise((resolve) => child.on('close', resolve));
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
console.error(chalk.red(` ✗ Unknown headroom subcommand: ${sub}`));
|
|
993
|
-
showHeadroomHelp();
|
|
994
|
-
process.exit(1);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
function showDashHelp() {
|
|
998
|
-
console.log(`
|
|
999
|
-
bizar dash — Manage the Bizar dashboard
|
|
1000
|
-
|
|
1001
|
-
Usage:
|
|
1002
|
-
bizar dash <subcommand> [options]
|
|
1003
|
-
|
|
1004
|
-
Subcommands:
|
|
1005
|
-
start [--bg] [--port N] Start the dashboard (default port 4321)
|
|
1006
|
-
stop Stop the running dashboard
|
|
1007
|
-
status Show dashboard port and URL
|
|
1008
|
-
cleanup Find + kill zombie/orphan dashboards
|
|
1009
|
-
tui [--no-web] Launch the TUI
|
|
1010
|
-
|
|
1011
|
-
Options:
|
|
1012
|
-
--bg Detach and run in background (for start)
|
|
1013
|
-
--port N Override the default port
|
|
1014
|
-
--no-web Skip launching the web UI (for tui)
|
|
1015
|
-
|
|
1016
|
-
Examples:
|
|
1017
|
-
bizar dash start
|
|
1018
|
-
bizar dash start --bg
|
|
1019
|
-
bizar dash stop
|
|
1020
|
-
bizar dash status
|
|
1021
|
-
bizar dash tui
|
|
1022
|
-
bizar dash tui --no-web
|
|
1023
|
-
|
|
1024
|
-
Note:
|
|
1025
|
-
\`bizar dashboard\` is a deprecated alias for \`bizar dash\` and still
|
|
1026
|
-
works, but new code should use \`bizar dash\`.
|
|
1027
|
-
`);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
function showMemoryHelp() {
|
|
1031
|
-
console.log(`
|
|
1032
|
-
memory <subcommand> Manage project memory (local-only or Git-shared Obsidian vault)
|
|
1033
|
-
Subcommands: init, setup, status, link, unlink, write, pull, commit,
|
|
1034
|
-
push, sync, reindex, conflicts, doctor
|
|
1035
|
-
`);
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* Detect whether the dashboard CLI is available.
|
|
1040
|
-
* v4.0.0: primary path is the relative one (dashboard ships inside the
|
|
1041
|
-
* same package at bizar-dash/src/cli.mjs). A global-npm fallback is
|
|
1042
|
-
* kept for legacy users who still have @polderlabs/bizar-dash installed.
|
|
1043
|
-
*/
|
|
1044
|
-
async function findBizarDash() {
|
|
1045
|
-
// v4.0.0 — primary: relative import inside the same package
|
|
1046
|
-
const primaryPath = join(import.meta.dirname, '..', 'bizar-dash', 'src', 'cli.mjs');
|
|
1047
|
-
if (existsSync(primaryPath)) return primaryPath;
|
|
1048
|
-
|
|
1049
|
-
// Legacy fallback: global npm install of @polderlabs/bizar-dash
|
|
1050
|
-
const { execSync } = await import('node:child_process');
|
|
1051
|
-
try {
|
|
1052
|
-
const root = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
1053
|
-
const dashPath = join(root, '@polderlabs', 'bizar-dash', 'src', 'cli.mjs');
|
|
1054
|
-
if (existsSync(dashPath)) return dashPath;
|
|
1055
|
-
} catch {
|
|
1056
|
-
/* fall through */
|
|
1057
|
-
}
|
|
1058
|
-
// Local fallback — node_modules of this package
|
|
1059
|
-
const localPath = fileURLToPath(new URL('../node_modules/@polderlabs/bizar-dash/src/cli.mjs', import.meta.url));
|
|
1060
|
-
if (existsSync(localPath)) return localPath;
|
|
1061
|
-
return null;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* Delegate a subcommand to the bizar-dash CLI, if installed.
|
|
1066
|
-
*/
|
|
1067
|
-
async function delegateToDash(argsForDash) {
|
|
1068
|
-
const dashPath = await findBizarDash();
|
|
1069
|
-
if (!dashPath) {
|
|
1070
|
-
console.log('The Bizar dashboard is part of this package.');
|
|
1071
|
-
console.log('If you see this error, the install may be corrupted.');
|
|
1072
|
-
console.log('Please report at: github.com/DrB0rk/BizarHarness/issues');
|
|
1073
|
-
return;
|
|
1074
|
-
}
|
|
1075
|
-
const { spawn } = await import('node:child_process');
|
|
1076
|
-
const child = spawn(process.execPath, [dashPath, ...argsForDash], {
|
|
1077
|
-
stdio: 'inherit',
|
|
1078
|
-
cwd: process.cwd(),
|
|
1079
|
-
env: process.env,
|
|
1080
|
-
});
|
|
1081
|
-
await new Promise((resolve, reject) => {
|
|
1082
|
-
child.on('exit', (code, signal) => {
|
|
1083
|
-
if (code === 0) {
|
|
1084
|
-
resolve();
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
if (signal) {
|
|
1088
|
-
reject(new Error(`dashboard exited via signal ${signal}`));
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
reject(new Error(`dashboard exited with code ${code}`));
|
|
1092
|
-
});
|
|
1093
|
-
child.on('error', reject);
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
function parseFlag(name) {
|
|
1098
|
-
const idx = args.indexOf(name);
|
|
1099
|
-
if (idx === -1) return null;
|
|
1100
|
-
return args[idx + 1] || null;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
/**
|
|
1104
|
-
* v4.4.11 — Parse `--with-mods <csv>` from the given subargs slice.
|
|
1105
|
-
* Returns `null` if the flag isn't present (the provisioner's
|
|
1106
|
-
* "don't touch mods" default), or a string[] of mod ids if it is.
|
|
1107
|
-
*/
|
|
1108
|
-
export function parseWithModsFlag(subargs) {
|
|
1109
|
-
const idx = subargs.indexOf('--with-mods');
|
|
1110
|
-
if (idx === -1) return null;
|
|
1111
|
-
const raw = subargs[idx + 1];
|
|
1112
|
-
if (!raw || raw.startsWith('--')) {
|
|
1113
|
-
console.error('bizar: --with-mods requires a value (e.g. --with-mods a,b,c)');
|
|
1114
|
-
console.error('Usage: bizar install --with-mods <mod-id,mod-id>');
|
|
1115
|
-
process.exit(2);
|
|
1116
|
-
}
|
|
1117
|
-
return raw
|
|
1118
|
-
.split(',')
|
|
1119
|
-
.map((s) => s.trim())
|
|
1120
|
-
.filter(Boolean);
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
async function readAutoLaunchWeb() {
|
|
1124
|
-
try {
|
|
1125
|
-
const fs = await import('node:fs');
|
|
1126
|
-
const os = await import('node:os');
|
|
1127
|
-
const path = await import('node:path');
|
|
1128
|
-
const bizarConfigDir = process.platform === 'win32'
|
|
1129
|
-
? (process.env.APPDATA
|
|
1130
|
-
? path.join(process.env.APPDATA, 'bizar')
|
|
1131
|
-
: path.join(os.homedir(), '.config', 'bizar'))
|
|
1132
|
-
: (process.env.XDG_CONFIG_HOME
|
|
1133
|
-
? path.join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
1134
|
-
: path.join(os.homedir(), '.config', 'bizar'));
|
|
1135
|
-
const file = path.join(bizarConfigDir, 'settings.json');
|
|
1136
|
-
if (!fs.existsSync(file)) return true;
|
|
1137
|
-
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
1138
|
-
if (parsed && parsed.dashboard && typeof parsed.dashboard.autoLaunchWeb === 'boolean') {
|
|
1139
|
-
return parsed.dashboard.autoLaunchWeb;
|
|
227
|
+
case 'artifact': {
|
|
228
|
+
const mod = await importCommand('artifact');
|
|
229
|
+
await mod.runArtifact(cmdArgs, { wantJson });
|
|
230
|
+
break;
|
|
1140
231
|
}
|
|
1141
|
-
} catch {
|
|
1142
|
-
/* fall through */
|
|
1143
|
-
}
|
|
1144
|
-
return true;
|
|
1145
|
-
}
|
|
1146
232
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const possible = [
|
|
1152
|
-
{ cmd: 'npm test', check: 'package.json' },
|
|
1153
|
-
{ cmd: 'pytest', check: 'pyproject.toml' },
|
|
1154
|
-
{ cmd: 'cargo test', check: 'Cargo.toml' },
|
|
1155
|
-
{ cmd: 'go test ./...', check: 'go.mod' },
|
|
1156
|
-
];
|
|
1157
|
-
for (const suite of possible) {
|
|
1158
|
-
try {
|
|
1159
|
-
if (existsSync(join(cwd, suite.check))) {
|
|
1160
|
-
console.log(` Running: ${suite.cmd}`);
|
|
1161
|
-
execSync(suite.cmd, { stdio: 'inherit', timeout: 120000, cwd });
|
|
1162
|
-
console.log('\n ✓ Test gate passed\n');
|
|
1163
|
-
return true;
|
|
1164
|
-
}
|
|
1165
|
-
} catch {
|
|
1166
|
-
console.log(`\n ✗ Test gate failed: ${suite.cmd}\n`);
|
|
1167
|
-
process.exit(1);
|
|
233
|
+
case 'memory': {
|
|
234
|
+
const mod = await importCommand('memory');
|
|
235
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
236
|
+
break;
|
|
1168
237
|
}
|
|
1169
|
-
}
|
|
1170
|
-
console.log(' No test suite detected. Install one to use the test gate.\n');
|
|
1171
|
-
return false;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
/**
|
|
1175
|
-
* Service commands — start / stop / status / logs / install / uninstall.
|
|
1176
|
-
*
|
|
1177
|
-
* Implementation lives in cli/service.mjs. The install / uninstall
|
|
1178
|
-
* branches delegate to cli/service-controller.mjs for OS-level
|
|
1179
|
-
* registration (systemd / launchd / schtasks).
|
|
1180
|
-
*/
|
|
1181
|
-
async function runServiceCommand(sub) {
|
|
1182
|
-
const { runService } = await import('./service.mjs');
|
|
1183
|
-
await runService(sub || 'status', args.slice(2));
|
|
1184
|
-
}
|
|
1185
238
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
process.exit(status.needed ? 1 : 0);
|
|
1191
|
-
} else if (args.includes('--setup')) {
|
|
1192
|
-
await ensureSetup({ silent: false });
|
|
1193
|
-
process.exit(0);
|
|
1194
|
-
} else if (args.includes('--postinstall')) {
|
|
1195
|
-
// Legacy manual trigger — now an alias for --setup
|
|
1196
|
-
await ensureSetup({ silent: false });
|
|
1197
|
-
process.exit(0);
|
|
1198
|
-
} else if (isVersionRequest) {
|
|
1199
|
-
console.log(readCliVersion());
|
|
1200
|
-
} else if (args[0] === 'audit') {
|
|
1201
|
-
if (isHelpRequest) showAuditHelp();
|
|
1202
|
-
else await runAudit();
|
|
1203
|
-
} else if (args[0] === 'init') {
|
|
1204
|
-
if (isHelpRequest) showInitHelp();
|
|
1205
|
-
else await runInit(process.cwd());
|
|
1206
|
-
} else if (args[0] === 'memory') {
|
|
1207
|
-
if (isHelpRequest && !args[1]) showMemoryHelp();
|
|
1208
|
-
else {
|
|
1209
|
-
const { runMemory } = await import('./memory.mjs');
|
|
1210
|
-
await runMemory(args[1], args.slice(2), { wantJson });
|
|
1211
|
-
}
|
|
1212
|
-
} else if (args[0] === 'headroom') {
|
|
1213
|
-
await runHeadroomCommand(args.slice(1));
|
|
1214
|
-
} else if (args[0] === 'export') {
|
|
1215
|
-
if (isHelpRequest) showExportHelp();
|
|
1216
|
-
else await runExport(parseFlag('--target'));
|
|
1217
|
-
} else if (args[0] === 'test-gate') {
|
|
1218
|
-
if (isHelpRequest) showTestGateHelp();
|
|
1219
|
-
else await runTestGate();
|
|
1220
|
-
} else if (args[0] === 'update') {
|
|
1221
|
-
if (isHelpRequest) showUpdateHelp();
|
|
1222
|
-
else {
|
|
1223
|
-
// v4.4.11 — Same --with-mods opt-in for update.
|
|
1224
|
-
const withMods = parseWithModsFlag(args.slice(1));
|
|
1225
|
-
// runUpdate expects (subargs: string[], opts?: object). Splice
|
|
1226
|
-
// --with-mods <csv> out of subargs since the provisioner now
|
|
1227
|
-
// takes it via opts, not as a positional arg.
|
|
1228
|
-
const subargs = args.slice(1).filter((a, i, arr) => {
|
|
1229
|
-
if (a === '--with-mods') return false;
|
|
1230
|
-
if (arr[i - 1] === '--with-mods') return false;
|
|
1231
|
-
return true;
|
|
1232
|
-
});
|
|
1233
|
-
await runUpdate(subargs, { withMods });
|
|
1234
|
-
}
|
|
1235
|
-
} else if (args[0] === 'dev-link') {
|
|
1236
|
-
if (isHelpRequest) showDevLinkHelp();
|
|
1237
|
-
else {
|
|
1238
|
-
const { createDevLink } = await import('./dev-link.mjs');
|
|
1239
|
-
const positional = args.slice(1).filter((a) => !a.startsWith('-'));
|
|
1240
|
-
const flags = args.slice(1).filter((a) => a.startsWith('-'));
|
|
1241
|
-
const sourceDir = positional[0] ?? null;
|
|
1242
|
-
const force = flags.includes('--force') || flags.includes('-f');
|
|
1243
|
-
const ok = createDevLink(sourceDir, { force });
|
|
1244
|
-
if (!ok) process.exit(1);
|
|
1245
|
-
}
|
|
1246
|
-
} else if (args[0] === 'dev-unlink') {
|
|
1247
|
-
if (isHelpRequest) showDevLinkHelp();
|
|
1248
|
-
else {
|
|
1249
|
-
const { removeDevLink } = await import('./dev-link.mjs');
|
|
1250
|
-
const force = args.includes('--force') || args.includes('-f');
|
|
1251
|
-
const ok = await removeDevLink({ force });
|
|
1252
|
-
if (!ok) process.exit(1);
|
|
1253
|
-
}
|
|
1254
|
-
} else if (args[0] === 'doctor') {
|
|
1255
|
-
if (isHelpRequest) showDoctorHelp();
|
|
1256
|
-
else {
|
|
1257
|
-
const { runDoctor } = await import('./doctor.mjs');
|
|
1258
|
-
const result = await runDoctor({ silent: wantJson, json: wantJson });
|
|
1259
|
-
if (wantJson) {
|
|
1260
|
-
process.stdout.write(JSON.stringify(result) + '\n');
|
|
1261
|
-
}
|
|
1262
|
-
if (result.failed > 0) process.exit(EXIT_ERROR);
|
|
239
|
+
case 'usage': {
|
|
240
|
+
const mod = await importCommand('usage');
|
|
241
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
242
|
+
break;
|
|
1263
243
|
}
|
|
1264
|
-
} else if (args[0] === 'repair') {
|
|
1265
|
-
// v4.4.3 — One-shot repair for stale `bizar` bin symlinks.
|
|
1266
|
-
// Symptom: after `npm i -g @polderlabs/bizar`, the package is
|
|
1267
|
-
// installed under `npm root -g` but the `bizar` symlink on PATH
|
|
1268
|
-
// still points at a legacy install path (e.g. ~/.local/lib/...).
|
|
1269
|
-
// Calls of `bizar ...` then run the old code with the old bugs.
|
|
1270
|
-
if (isHelpRequest) {
|
|
1271
|
-
console.log(`
|
|
1272
|
-
bizar repair — Fix common install issues
|
|
1273
244
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
245
|
+
// All other utility commands
|
|
246
|
+
case 'audit':
|
|
247
|
+
case 'init':
|
|
248
|
+
case 'export':
|
|
249
|
+
case 'test-gate':
|
|
250
|
+
case 'dev-link':
|
|
251
|
+
case 'dev-unlink':
|
|
252
|
+
case 'doctor':
|
|
253
|
+
case 'repair':
|
|
254
|
+
case 'heads-up':
|
|
255
|
+
case 'bg':
|
|
256
|
+
case 'browser-harness-up':
|
|
257
|
+
case 'providers':
|
|
258
|
+
case 'plan': {
|
|
259
|
+
const mod = await importCommand('util');
|
|
260
|
+
const found = await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
261
|
+
if (!found) {
|
|
262
|
+
console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
|
|
263
|
+
showHelp();
|
|
264
|
+
process.exit(EXIT_ERROR);
|
|
1287
265
|
}
|
|
1288
|
-
|
|
1289
|
-
if (result.fixed.length > 0) {
|
|
1290
|
-
console.log(chalk.green('\n Repair complete. Re-run your shell or `hash -r` to pick up the new path.'));
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
} else if (args[0] === 'heads-up') {
|
|
1294
|
-
// v3.21.0 — Pre-push / pre-release heads-ups
|
|
1295
|
-
await runHeadsUp(args[1], args.slice(2));
|
|
1296
|
-
} else if (args[0] === 'plan') {
|
|
1297
|
-
await runArtifact(args.slice(1), {});
|
|
1298
|
-
} else if (args[0] === 'install') {
|
|
1299
|
-
if (isHelpRequest) showInstallHelp();
|
|
1300
|
-
else {
|
|
1301
|
-
// v4.4.11 — Parse --with-mods <csv> to opt into mod installs
|
|
1302
|
-
// during the run. Default: mods are NEVER touched.
|
|
1303
|
-
const withMods = parseWithModsFlag(args.slice(1));
|
|
1304
|
-
await runInstaller({ withMods });
|
|
1305
|
-
// v4.4.3 — After install, repair any stale bin symlinks so the
|
|
1306
|
-
// user picks up the new code (the installer itself may have
|
|
1307
|
-
// been running from a stale install path).
|
|
1308
|
-
try {
|
|
1309
|
-
const { runRepair } = await import('./repair.mjs');
|
|
1310
|
-
const r = await runRepair({});
|
|
1311
|
-
if (r.fixed.length > 0) {
|
|
1312
|
-
console.log(chalk.cyan('\n Repair: repointed stale bin symlinks:'));
|
|
1313
|
-
for (const f of r.fixed) console.log(` ${f}`);
|
|
1314
|
-
console.log(chalk.dim(' Re-run your shell or `hash -r` to pick up the new path.'));
|
|
1315
|
-
}
|
|
1316
|
-
} catch (err) {
|
|
1317
|
-
console.log(chalk.dim(` Repair skipped: ${err.message}`));
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
} else if (args[0] === 'service') {
|
|
1321
|
-
if (isHelpRequest) showServiceHelp();
|
|
1322
|
-
else await runServiceCommand(args[1]);
|
|
1323
|
-
} else if (args[0] === 'bg') {
|
|
1324
|
-
// v3.11.1 — Background agent manager (list / view / kill / logs).
|
|
1325
|
-
const { runBg } = await import('./bg.mjs');
|
|
1326
|
-
await runBg(args[1], args.slice(2));
|
|
1327
|
-
} else if (args[0] === 'browser-harness-up') {
|
|
1328
|
-
// v3.20.7 — Browser-harness daemon: start / stop / status / restart
|
|
1329
|
-
// Chromium with remote debugging so the browser-harness Python tool
|
|
1330
|
-
// (from https://github.com/browser-use/browser-harness) can connect.
|
|
1331
|
-
const { execFileSync } = await import('node:child_process');
|
|
1332
|
-
const sub = args[1] || 'start';
|
|
1333
|
-
const scriptPath = join(import.meta.dirname || process.cwd(), 'browser-harness-up.sh');
|
|
1334
|
-
try {
|
|
1335
|
-
const out = execFileSync('bash', [scriptPath, sub], {
|
|
1336
|
-
encoding: 'utf8',
|
|
1337
|
-
stdio: 'inherit',
|
|
1338
|
-
});
|
|
1339
|
-
if (out) process.stdout.write(out);
|
|
1340
|
-
} catch (err) {
|
|
1341
|
-
console.error(chalk.red(` ✗ browser-harness-up ${sub} failed (exit ${err.status ?? 1})`));
|
|
1342
|
-
process.exit(err.status || 1);
|
|
1343
|
-
}
|
|
1344
|
-
} else if (args[0] === 'providers' && args[1] === 'detect') {
|
|
1345
|
-
// v3.16.0 — Auto-detect provider API keys from env + opencode.json.
|
|
1346
|
-
const { runProvidersDetect } = await import('./providers-detect.mjs');
|
|
1347
|
-
await runProvidersDetect(args.slice(2));
|
|
1348
|
-
} else if (args[0] === 'mod') {
|
|
1349
|
-
// v3.20.5 — Mod manager. Talks to the running dashboard over HTTP
|
|
1350
|
-
// (the dashboard owns the actual mod install/upgrade logic and the
|
|
1351
|
-
// opencode-config install/uninstall of instruction files).
|
|
1352
|
-
await runModCommand(args.slice(1));
|
|
1353
|
-
} else if (args[0] === 'minimax') {
|
|
1354
|
-
// v4.5.0 — MiniMax Token Plan integration CLI. Status, remains,
|
|
1355
|
-
// test, config, clear, reset-onboarding. All commands shell out
|
|
1356
|
-
// to the dashboard's /api/minimax/* routes.
|
|
1357
|
-
await runMinimaxCommand(args.slice(1));
|
|
1358
|
-
} else if (args[0] === 'usage') {
|
|
1359
|
-
// v4.6.0 — Compact usage analytics summary from the JSONL store.
|
|
1360
|
-
await runUsageCommand(args.slice(1), wantJson);
|
|
1361
|
-
} else if (args[0] === 'dash' || args[0] === 'dashboard') {
|
|
1362
|
-
// `bizar dashboard` is a deprecated alias for `bizar dash`
|
|
1363
|
-
if (args[0] === 'dashboard') {
|
|
1364
|
-
process.stdout.write(chalk.yellow(' ⚠ `bizar dashboard` is deprecated, use `bizar dash` instead.\n'));
|
|
1365
|
-
}
|
|
1366
|
-
const dashArgs = args.slice(1); // everything after 'dash' or 'dashboard'
|
|
1367
|
-
if (dashArgs.length === 0 || isHelpRequest) {
|
|
1368
|
-
showDashHelp();
|
|
1369
|
-
} else {
|
|
1370
|
-
await runDash(dashArgs);
|
|
266
|
+
break;
|
|
1371
267
|
}
|
|
1372
|
-
} else if (isHelpRequest) {
|
|
1373
|
-
showHelp();
|
|
1374
|
-
} else {
|
|
1375
|
-
// No args — show help (breaking: previously launched TUI)
|
|
1376
|
-
showHelp();
|
|
1377
|
-
process.exit(1);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// ── Dashboard subcommand ───────────────────────────────────────────────────────
|
|
1382
268
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
function parseDashOpts(dashArgs) {
|
|
1388
|
-
const opts = { bg: false, port: null, noWeb: false };
|
|
1389
|
-
const remaining = [];
|
|
1390
|
-
for (let i = 0; i < dashArgs.length; i++) {
|
|
1391
|
-
const a = dashArgs[i];
|
|
1392
|
-
if (a === '--bg') {
|
|
1393
|
-
opts.bg = true;
|
|
1394
|
-
} else if (a === '--no-web') {
|
|
1395
|
-
opts.noWeb = true;
|
|
1396
|
-
} else if (a === '--port' && i + 1 < dashArgs.length) {
|
|
1397
|
-
opts.port = Number(dashArgs[i + 1]);
|
|
1398
|
-
i++;
|
|
1399
|
-
} else {
|
|
1400
|
-
remaining.push(a);
|
|
269
|
+
default: {
|
|
270
|
+
console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
|
|
271
|
+
showHelp();
|
|
272
|
+
process.exit(EXIT_ERROR);
|
|
1401
273
|
}
|
|
1402
274
|
}
|
|
1403
|
-
return { opts, remaining };
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
/**
|
|
1407
|
-
* Try to load the dashboard CLI module.
|
|
1408
|
-
* v4.0.0: the dashboard ships inside this package at bizar-dash/src/cli.mjs.
|
|
1409
|
-
* A legacy npm-global fallback is kept for the transitional period.
|
|
1410
|
-
*/
|
|
1411
|
-
async function loadDashCli() {
|
|
1412
|
-
const { pathToFileURL } = await import('node:url');
|
|
1413
|
-
const { join } = await import('node:path');
|
|
1414
|
-
|
|
1415
|
-
const primary = join(import.meta.dirname, '..', 'bizar-dash', 'src', 'cli.mjs');
|
|
1416
|
-
|
|
1417
|
-
// Legacy npm-global fallback (only computed if primary is missing)
|
|
1418
|
-
let legacyFallbacks = [];
|
|
1419
|
-
try {
|
|
1420
|
-
const { execFileSync } = await import('node:child_process');
|
|
1421
|
-
const npmRoot = execFileSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000 }).trim();
|
|
1422
|
-
if (npmRoot) legacyFallbacks = [join(npmRoot, '@polderlabs', 'bizar-dash', 'src', 'cli.mjs')];
|
|
1423
|
-
} catch { /* npm not available or no globals — fine */ }
|
|
1424
|
-
|
|
1425
|
-
const candidates = [primary, ...legacyFallbacks];
|
|
1426
|
-
|
|
1427
|
-
for (const p of candidates) {
|
|
1428
|
-
try {
|
|
1429
|
-
const url = pathToFileURL(p).href;
|
|
1430
|
-
const mod = await import(url);
|
|
1431
|
-
return mod;
|
|
1432
|
-
} catch (_e) {
|
|
1433
|
-
// try next
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
return null;
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
* Dispatch a dashboard subcommand by loading the dashboard module and
|
|
1441
|
-
* calling the appropriate exported function.
|
|
1442
|
-
*/
|
|
1443
|
-
async function runDash(dashArgs) {
|
|
1444
|
-
const { opts, remaining } = parseDashOpts(dashArgs);
|
|
1445
|
-
const sub = remaining[0];
|
|
1446
|
-
const subOpts = { ...opts, subArgs: remaining.slice(1) };
|
|
1447
|
-
|
|
1448
|
-
const dashModule = await loadDashCli();
|
|
1449
|
-
if (!dashModule) {
|
|
1450
|
-
console.error(chalk.red(' ✗ Dashboard not found.'));
|
|
1451
|
-
console.error(chalk.dim(' this should not happen — please report a bug at github.com/DrB0rk/BizarHarness/issues'));
|
|
1452
|
-
process.exit(1);
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
switch (sub) {
|
|
1456
|
-
case 'start':
|
|
1457
|
-
if (subOpts.bg) {
|
|
1458
|
-
// v4.4.0 — Background mode: spawn the dashboard as a detached
|
|
1459
|
-
// child process via dashModule.startInBackground(). The child
|
|
1460
|
-
// runs `node cli.mjs start --bg` which keeps itself alive via
|
|
1461
|
-
// the bg-* pollers and the (v4.4.3) module-scope backgroundHandle.
|
|
1462
|
-
await dashModule.startInBackground(['start', ...(subOpts.subArgs || [])]);
|
|
1463
|
-
} else {
|
|
1464
|
-
await dashModule.start(subOpts);
|
|
1465
|
-
}
|
|
1466
|
-
break;
|
|
1467
|
-
case 'stop':
|
|
1468
|
-
await dashModule.stop(subOpts);
|
|
1469
|
-
break;
|
|
1470
|
-
case 'status':
|
|
1471
|
-
await dashModule.status(subOpts);
|
|
1472
|
-
break;
|
|
1473
|
-
case 'cleanup':
|
|
1474
|
-
// v3.20.7 — Find + kill zombie/orphan dashboards.
|
|
1475
|
-
// Pass through --force and --kill flags to the dash module.
|
|
1476
|
-
await dashModule.cleanup(subOpts);
|
|
1477
|
-
break;
|
|
1478
|
-
case 'tui':
|
|
1479
|
-
await dashModule.tui(subOpts);
|
|
1480
|
-
break;
|
|
1481
|
-
default:
|
|
1482
|
-
console.error(chalk.red(` ✗ Unknown subcommand: ${sub}`));
|
|
1483
|
-
showDashHelp();
|
|
1484
|
-
process.exit(1);
|
|
1485
|
-
}
|
|
1486
275
|
}
|
|
1487
276
|
|
|
1488
|
-
// ──
|
|
277
|
+
// ── Run ───────────────────────────────────────────────────────────────────────
|
|
1489
278
|
|
|
1490
|
-
// Only run main() when bin.mjs is executed directly (not imported as a module in tests)
|
|
1491
279
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1492
280
|
const isMainModule = process.argv[1] === thisFile;
|
|
1493
281
|
if (isMainModule) {
|
|
1494
282
|
await main().catch((err) => {
|
|
1495
283
|
console.error(chalk.red(`bizar: ${err && err.message ? err.message : String(err)}`));
|
|
1496
|
-
process.exit(
|
|
284
|
+
process.exit(EXIT_ERROR);
|
|
1497
285
|
});
|
|
1498
286
|
}
|