@polderlabs/bizar 4.5.2 → 4.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js +361 -0
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +1 -0
- package/bizar-dash/dist/assets/main-DX_Jh8Wc.css +1 -0
- package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-BK8-ythT.js} +18 -18
- package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-Chvf9u_B.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-Chvf9u_B.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/skills/publishing/SKILL.md +146 -0
- package/bizar-dash/src/server/api.mjs +8 -0
- package/bizar-dash/src/server/backup-store.mjs +525 -0
- package/bizar-dash/src/server/digest-store.mjs +558 -0
- package/bizar-dash/src/server/lib/rate-limit.mjs +122 -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/backup.mjs +112 -0
- package/bizar-dash/src/server/routes/chat.mjs +20 -3
- package/bizar-dash/src/server/routes/digests.mjs +82 -0
- 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/routes-v2/events.mjs +14 -0
- package/bizar-dash/src/server/schedules-runner.mjs +126 -0
- package/bizar-dash/src/server/server.mjs +79 -0
- package/bizar-dash/src/web/App.tsx +8 -1
- package/bizar-dash/src/web/components/BackupRestore.tsx +330 -0
- package/bizar-dash/src/web/components/SearchModal.tsx +6 -3
- 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/components/chat/Composer.tsx +2 -0
- 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/styles/main.css +70 -8
- package/bizar-dash/src/web/views/Activity.tsx +35 -18
- package/bizar-dash/src/web/views/Agents.tsx +57 -42
- package/bizar-dash/src/web/views/Artifacts.tsx +38 -25
- package/bizar-dash/src/web/views/Chat.tsx +8 -0
- package/bizar-dash/src/web/views/History.tsx +94 -76
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +21 -4
- package/bizar-dash/src/web/views/Mods.tsx +30 -17
- package/bizar-dash/src/web/views/Overview.tsx +19 -9
- package/bizar-dash/src/web/views/Providers.tsx +16 -16
- package/bizar-dash/src/web/views/Schedules.tsx +33 -15
- package/bizar-dash/src/web/views/Settings.tsx +97 -1745
- package/bizar-dash/src/web/views/Skills.tsx +4 -1
- package/bizar-dash/src/web/views/Tasks.tsx +11 -2
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +8 -2
- package/bizar-dash/src/web/views/memory/LightragPanel.tsx +3 -0
- package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +12 -4
- package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +3 -0
- package/bizar-dash/src/web/views/settings/ActivitySection.tsx +205 -0
- package/bizar-dash/src/web/views/settings/AgentSection.tsx +294 -0
- package/bizar-dash/src/web/views/settings/AuthSection.tsx +159 -0
- package/bizar-dash/src/web/views/settings/BackupSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/GeneralSection.tsx +105 -0
- package/bizar-dash/src/web/views/settings/HeadroomSection.tsx +39 -0
- package/bizar-dash/src/web/views/settings/MemorySection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/NetworkSection.tsx +87 -0
- package/bizar-dash/src/web/views/settings/NotificationsSection.tsx +34 -0
- package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/SkillsSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/SystemLlmSection.tsx +81 -0
- package/bizar-dash/src/web/views/settings/ThemeSection.tsx +168 -0
- package/bizar-dash/src/web/views/settings/UpdatesSection.tsx +256 -0
- package/bizar-dash/tests/a11y.test.tsx +206 -0
- package/bizar-dash/tests/backup-restore.test.mjs +217 -0
- package/bizar-dash/tests/backup-restore.test.tsx +123 -0
- package/bizar-dash/tests/backup-store.test.mjs +300 -0
- package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
- package/bizar-dash/tests/cli-error-visibility.test.mjs +153 -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/digest-generation.test.mjs +191 -0
- package/bizar-dash/tests/digest-store.test.mjs +264 -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/rate-limit.test.mjs +298 -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 +221 -1350
- 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 +285 -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 +459 -0
- package/cli/digest.mjs +149 -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/main-DAlLdW8I.css +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,251 @@ 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
|
|
139
|
-
`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function showAuditHelp() {
|
|
143
|
-
console.log(`
|
|
144
|
-
bizar audit — Run security audit on agent configuration
|
|
145
|
-
|
|
146
|
-
Usage:
|
|
147
|
-
bizar audit
|
|
128
|
+
npm install -g @polderlabs/bizar
|
|
129
|
+
npm install -g @polderlabs/bizar-dash
|
|
130
|
+
npm install -g @polderlabs/bizar-plugin
|
|
148
131
|
`);
|
|
149
132
|
}
|
|
150
133
|
|
|
151
|
-
|
|
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.
|
|
134
|
+
// ── Command imports ────────────────────────────────────────────────────────────
|
|
216
135
|
|
|
217
|
-
|
|
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.
|
|
297
|
-
|
|
298
|
-
Examples:
|
|
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
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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);
|
|
138
|
+
return await import(`./commands/${name}.mjs`);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error(chalk.red(` ✗ Failed to load command module '${name}': ${err && err.message ? err.message : String(err)}`));
|
|
141
|
+
if (err && err.stack) {
|
|
142
|
+
const lines = err.stack.split('\n').slice(0, 3);
|
|
143
|
+
console.error(chalk.dim(lines.join('\n')));
|
|
506
144
|
}
|
|
507
|
-
|
|
508
|
-
console.error(chalk.red(` ✗ Unknown mod subcommand: ${sub}`));
|
|
509
|
-
showModHelp();
|
|
510
|
-
process.exit(1);
|
|
145
|
+
return null;
|
|
511
146
|
}
|
|
512
147
|
}
|
|
513
148
|
|
|
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.
|
|
534
|
-
|
|
535
|
-
Flags:
|
|
536
|
-
--base-url <url> Override the Token Plan host (default: https://www.minimax.io)
|
|
537
|
-
--chat-url <url> Override the chat-completions host (default: https://api.minimax.io/v1)
|
|
538
|
-
--yes Skip the confirmation prompt on 'clear' and 'reset-onboarding'
|
|
539
|
-
`);
|
|
540
|
-
}
|
|
149
|
+
// ── Main ───────────────────────────────────────────────────────────────────────
|
|
541
150
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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) };
|
|
151
|
+
async function main() {
|
|
152
|
+
if (args.includes('--check')) {
|
|
153
|
+
const status = checkSetupStatus();
|
|
154
|
+
if (wantJson) process.stdout.write(JSON.stringify(status, null, 2) + '\n');
|
|
155
|
+
else console.log(JSON.stringify(status, null, 2));
|
|
156
|
+
process.exit(status.needed ? EXIT_ERROR : EXIT_OK);
|
|
157
|
+
return;
|
|
577
158
|
}
|
|
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
159
|
|
|
586
|
-
if (
|
|
587
|
-
|
|
160
|
+
if (args.includes('--setup') || args.includes('--postinstall')) {
|
|
161
|
+
await ensureSetup({ silent: false });
|
|
162
|
+
process.exit(EXIT_OK);
|
|
588
163
|
return;
|
|
589
164
|
}
|
|
590
165
|
|
|
591
|
-
if (
|
|
592
|
-
|
|
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
|
-
}
|
|
166
|
+
if (isVersionRequest) {
|
|
167
|
+
console.log(readCliVersion());
|
|
614
168
|
return;
|
|
615
169
|
}
|
|
616
170
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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('');
|
|
171
|
+
const [cmd, ...cmdArgs] = dispatchArgs;
|
|
172
|
+
|
|
173
|
+
if (!cmd) {
|
|
174
|
+
if (isVersionRequest) {
|
|
175
|
+
console.log(readCliVersion());
|
|
176
|
+
return;
|
|
643
177
|
}
|
|
178
|
+
showHelp();
|
|
179
|
+
process.exit(EXIT_OK);
|
|
644
180
|
return;
|
|
645
181
|
}
|
|
646
182
|
|
|
647
|
-
if (
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
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}`);
|
|
183
|
+
if (isHelpRequest && !cmd.startsWith('-')) {
|
|
184
|
+
// Pass --help to the command
|
|
185
|
+
const mod = await importCommand(cmd);
|
|
186
|
+
if (mod && typeof mod.run === 'function') {
|
|
187
|
+
await mod.run(cmd, cmdArgs, true);
|
|
188
|
+
return;
|
|
669
189
|
}
|
|
670
|
-
return;
|
|
671
190
|
}
|
|
672
191
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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}`);
|
|
192
|
+
// `bizar help` — explicit help alias
|
|
193
|
+
if (cmd === 'help') {
|
|
194
|
+
showHelp();
|
|
195
|
+
process.exit(EXIT_OK);
|
|
694
196
|
return;
|
|
695
197
|
}
|
|
696
198
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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.'));
|
|
199
|
+
// Dispatch to command modules
|
|
200
|
+
switch (cmd) {
|
|
201
|
+
case 'install':
|
|
202
|
+
case 'update': {
|
|
203
|
+
const mod = await importCommand('install');
|
|
204
|
+
if (!mod) {
|
|
205
|
+
console.error(chalk.red(` ✗ Could not load install command module`));
|
|
206
|
+
process.exit(EXIT_ERROR);
|
|
709
207
|
return;
|
|
710
208
|
}
|
|
209
|
+
dbg('loaded command module:', 'install');
|
|
210
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
211
|
+
dbg('command returned:', cmd);
|
|
212
|
+
break;
|
|
711
213
|
}
|
|
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
|
-
}
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
214
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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.'));
|
|
215
|
+
case 'service': {
|
|
216
|
+
const mod = await importCommand('service');
|
|
217
|
+
if (!mod) {
|
|
218
|
+
console.error(chalk.red(` ✗ Could not load service command module`));
|
|
219
|
+
process.exit(EXIT_ERROR);
|
|
743
220
|
return;
|
|
744
221
|
}
|
|
222
|
+
dbg('loaded command module:', 'service');
|
|
223
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
224
|
+
dbg('command returned:', cmd);
|
|
225
|
+
break;
|
|
745
226
|
}
|
|
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'));
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
console.error(chalk.red(` ✗ Unknown minimax subcommand: ${sub}`));
|
|
760
|
-
showMinimaxHelp();
|
|
761
|
-
process.exit(1);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// ── Usage subcommand ───────────────────────────────────────────────────────
|
|
765
|
-
|
|
766
|
-
function showUsageHelp() {
|
|
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
227
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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');
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
const t = data.totals;
|
|
800
|
-
console.log('');
|
|
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`);
|
|
228
|
+
case 'dash':
|
|
229
|
+
case 'dashboard': {
|
|
230
|
+
const mod = await importCommand('dash');
|
|
231
|
+
if (!mod) {
|
|
232
|
+
console.error(chalk.red(` ✗ Could not load dash command module`));
|
|
233
|
+
process.exit(EXIT_ERROR);
|
|
234
|
+
return;
|
|
825
235
|
}
|
|
236
|
+
dbg('loaded command module:', 'dash');
|
|
237
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
238
|
+
dbg('command returned:', cmd);
|
|
239
|
+
break;
|
|
826
240
|
}
|
|
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
241
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
showHeadroomHelp();
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// Delegate to dashboard API
|
|
868
|
-
const { port, secret } = readDashboardConn();
|
|
869
|
-
const baseUrl = `http://127.0.0.1:${port}`;
|
|
870
|
-
|
|
871
|
-
async function apiGet(path) {
|
|
872
|
-
const url = `${baseUrl}${path}`;
|
|
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}`);
|
|
242
|
+
case 'minimax': {
|
|
243
|
+
const mod = await importCommand('minimax');
|
|
244
|
+
if (!mod) {
|
|
245
|
+
console.error(chalk.red(` ✗ Could not load minimax command module`));
|
|
246
|
+
process.exit(EXIT_ERROR);
|
|
247
|
+
return;
|
|
908
248
|
}
|
|
249
|
+
dbg('loaded command module:', 'minimax');
|
|
250
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
251
|
+
dbg('command returned:', cmd);
|
|
252
|
+
break;
|
|
909
253
|
}
|
|
910
|
-
console.log('');
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
if (sub === 'stats') {
|
|
915
|
-
const hours = parseInt(positional[0], 10) || 24;
|
|
916
|
-
const st = await apiGet(`/api/headroom/stats?hours=${hours}`);
|
|
917
|
-
console.log('');
|
|
918
|
-
console.log(chalk.bold(` Headroom stats (${hours}h)`));
|
|
919
|
-
console.log('');
|
|
920
|
-
if (st.error) {
|
|
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 ?? '—'}`);
|
|
927
|
-
}
|
|
928
|
-
console.log('');
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (sub === 'install') {
|
|
933
|
-
const r = await apiPost('/api/headroom/install', { force: true });
|
|
934
|
-
if (r.installed) {
|
|
935
|
-
console.log(chalk.green(` ✓ Headroom installed via ${r.method}${r.version ? ` (${r.version})` : ''}`));
|
|
936
|
-
} else {
|
|
937
|
-
console.log(chalk.red(' ✗ Install failed. Try: pip install "headroom-ai[all]"'));
|
|
938
|
-
}
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
if (sub === 'wrap') {
|
|
943
|
-
const port = parseInt(positional[0], 10) || 8787;
|
|
944
|
-
const r = await apiPost('/api/headroom/wrap', { port });
|
|
945
|
-
if (r.ok) {
|
|
946
|
-
console.log(chalk.green(` ✓ opencode wrapped with Headroom on port ${port}`));
|
|
947
|
-
} else {
|
|
948
|
-
console.log(chalk.red(` ✗ Wrap failed: ${r.log || 'unknown error'}`));
|
|
949
|
-
}
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
if (sub === 'unwrap') {
|
|
954
|
-
const r = await apiPost('/api/headroom/unwrap');
|
|
955
|
-
if (r.ok) {
|
|
956
|
-
console.log(chalk.green(' ✓ opencode unwrapped from Headroom'));
|
|
957
|
-
} else {
|
|
958
|
-
console.log(chalk.red(' ✗ Unwrap failed'));
|
|
959
|
-
}
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
254
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
if (sub === 'stop') {
|
|
975
|
-
const r = await apiPost('/api/headroom/proxy/stop');
|
|
976
|
-
if (r.ok) {
|
|
977
|
-
console.log(chalk.green(' ✓ Proxy stopped'));
|
|
978
|
-
} else {
|
|
979
|
-
console.log(chalk.red(' ✗ Proxy stop failed'));
|
|
255
|
+
case 'headroom': {
|
|
256
|
+
const mod = await importCommand('headroom');
|
|
257
|
+
if (!mod) {
|
|
258
|
+
console.error(chalk.red(` ✗ Could not load headroom command module`));
|
|
259
|
+
process.exit(EXIT_ERROR);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
dbg('loaded command module:', 'headroom');
|
|
263
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
264
|
+
dbg('command returned:', cmd);
|
|
265
|
+
break;
|
|
980
266
|
}
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
if (sub === 'doctor') {
|
|
985
|
-
// Run `headroom doctor` locally
|
|
986
|
-
const { spawn } = await import('node:child_process');
|
|
987
|
-
const child = spawn('headroom', ['doctor'], { stdio: 'inherit' });
|
|
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
267
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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();
|
|
268
|
+
case 'mod': {
|
|
269
|
+
const mod = await importCommand('mod');
|
|
270
|
+
if (!mod) {
|
|
271
|
+
console.error(chalk.red(` ✗ Could not load mod command module`));
|
|
272
|
+
process.exit(EXIT_ERROR);
|
|
1085
273
|
return;
|
|
1086
274
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
275
|
+
dbg('loaded command module:', 'mod');
|
|
276
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
277
|
+
dbg('command returned:', cmd);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
case 'artifact': {
|
|
282
|
+
const mod = await importCommand('artifact');
|
|
283
|
+
if (!mod) {
|
|
284
|
+
console.error(chalk.red(` ✗ Could not load artifact command module`));
|
|
285
|
+
process.exit(EXIT_ERROR);
|
|
1089
286
|
return;
|
|
1090
287
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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;
|
|
288
|
+
dbg('loaded command module:', 'artifact');
|
|
289
|
+
await mod.runArtifact(cmdArgs, { wantJson });
|
|
290
|
+
dbg('command returned:', cmd);
|
|
291
|
+
break;
|
|
1140
292
|
}
|
|
1141
|
-
} catch {
|
|
1142
|
-
/* fall through */
|
|
1143
|
-
}
|
|
1144
|
-
return true;
|
|
1145
|
-
}
|
|
1146
293
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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;
|
|
294
|
+
case 'memory': {
|
|
295
|
+
const mod = await importCommand('memory');
|
|
296
|
+
if (!mod) {
|
|
297
|
+
console.error(chalk.red(` ✗ Could not load memory command module`));
|
|
298
|
+
process.exit(EXIT_ERROR);
|
|
299
|
+
return;
|
|
1164
300
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
301
|
+
dbg('loaded command module:', 'memory');
|
|
302
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
303
|
+
dbg('command returned:', cmd);
|
|
304
|
+
break;
|
|
1168
305
|
}
|
|
1169
|
-
}
|
|
1170
|
-
console.log(' No test suite detected. Install one to use the test gate.\n');
|
|
1171
|
-
return false;
|
|
1172
|
-
}
|
|
1173
306
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
*/
|
|
1181
|
-
async function runServiceCommand(sub) {
|
|
1182
|
-
const { runService } = await import('./service.mjs');
|
|
1183
|
-
await runService(sub || 'status', args.slice(2));
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
async function main() {
|
|
1187
|
-
if (args.includes('--check')) {
|
|
1188
|
-
const status = checkSetupStatus();
|
|
1189
|
-
console.log(JSON.stringify(status, null, 2));
|
|
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');
|
|
307
|
+
case 'usage': {
|
|
308
|
+
const mod = await importCommand('usage');
|
|
309
|
+
if (!mod) {
|
|
310
|
+
console.error(chalk.red(` ✗ Could not load usage command module`));
|
|
311
|
+
process.exit(EXIT_ERROR);
|
|
312
|
+
return;
|
|
1261
313
|
}
|
|
1262
|
-
|
|
314
|
+
dbg('loaded command module:', 'usage');
|
|
315
|
+
await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
316
|
+
dbg('command returned:', cmd);
|
|
317
|
+
break;
|
|
1263
318
|
}
|
|
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
319
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
320
|
+
// All other utility commands
|
|
321
|
+
case 'audit':
|
|
322
|
+
case 'init':
|
|
323
|
+
case 'export':
|
|
324
|
+
case 'test-gate':
|
|
325
|
+
case 'dev-link':
|
|
326
|
+
case 'dev-unlink':
|
|
327
|
+
case 'doctor':
|
|
328
|
+
case 'repair':
|
|
329
|
+
case 'heads-up':
|
|
330
|
+
case 'bg':
|
|
331
|
+
case 'digest':
|
|
332
|
+
case 'browser-harness-up':
|
|
333
|
+
case 'providers':
|
|
334
|
+
case 'plan': {
|
|
335
|
+
const mod = await importCommand('util');
|
|
336
|
+
if (!mod) {
|
|
337
|
+
console.error(chalk.red(` ✗ Could not load util command module`));
|
|
338
|
+
process.exit(EXIT_ERROR);
|
|
339
|
+
return;
|
|
1291
340
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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}`));
|
|
341
|
+
dbg('loaded command module:', 'util');
|
|
342
|
+
const found = await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
343
|
+
dbg('command returned:', cmd);
|
|
344
|
+
if (!found) {
|
|
345
|
+
console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
|
|
346
|
+
showHelp();
|
|
347
|
+
process.exit(EXIT_ERROR);
|
|
1318
348
|
}
|
|
349
|
+
break;
|
|
1319
350
|
}
|
|
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);
|
|
1371
|
-
}
|
|
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
|
-
|
|
1383
|
-
/**
|
|
1384
|
-
* Parse dash-specific options from an array of args.
|
|
1385
|
-
* Returns { opts, remaining } where opts have --bg / --port stripped.
|
|
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);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
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
351
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|
|
352
|
+
default: {
|
|
353
|
+
console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
|
|
354
|
+
showHelp();
|
|
355
|
+
process.exit(EXIT_ERROR);
|
|
1434
356
|
}
|
|
1435
357
|
}
|
|
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
358
|
}
|
|
1487
359
|
|
|
1488
|
-
// ──
|
|
360
|
+
// ── Run ───────────────────────────────────────────────────────────────────────
|
|
1489
361
|
|
|
1490
|
-
// Only run main() when bin.mjs is executed directly (not imported as a module in tests)
|
|
1491
362
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1492
363
|
const isMainModule = process.argv[1] === thisFile;
|
|
1493
364
|
if (isMainModule) {
|
|
1494
365
|
await main().catch((err) => {
|
|
1495
366
|
console.error(chalk.red(`bizar: ${err && err.message ? err.message : String(err)}`));
|
|
1496
|
-
process.exit(
|
|
367
|
+
process.exit(EXIT_ERROR);
|
|
1497
368
|
});
|
|
1498
369
|
}
|