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