@polderlabs/bizar 4.5.2 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/bizar-dash/dist/assets/main-DHZmbnxQ.js +361 -0
  2. package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +1 -0
  3. package/bizar-dash/dist/assets/main-DX_Jh8Wc.css +1 -0
  4. package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-BK8-ythT.js} +18 -18
  5. package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +1 -0
  6. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-Chvf9u_B.js} +1 -1
  7. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-Chvf9u_B.js.map} +1 -1
  8. package/bizar-dash/dist/index.html +3 -3
  9. package/bizar-dash/dist/mobile.html +2 -2
  10. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  11. package/bizar-dash/skills/publishing/SKILL.md +146 -0
  12. package/bizar-dash/src/server/api.mjs +8 -0
  13. package/bizar-dash/src/server/backup-store.mjs +525 -0
  14. package/bizar-dash/src/server/digest-store.mjs +558 -0
  15. package/bizar-dash/src/server/lib/rate-limit.mjs +122 -0
  16. package/bizar-dash/src/server/logger.mjs +71 -0
  17. package/bizar-dash/src/server/memory-lightrag.mjs +45 -6
  18. package/bizar-dash/src/server/metrics.mjs +193 -0
  19. package/bizar-dash/src/server/routes/backup.mjs +112 -0
  20. package/bizar-dash/src/server/routes/chat.mjs +20 -3
  21. package/bizar-dash/src/server/routes/digests.mjs +82 -0
  22. package/bizar-dash/src/server/routes/lightrag.mjs +3 -2
  23. package/bizar-dash/src/server/routes/memory.mjs +5 -4
  24. package/bizar-dash/src/server/routes/misc.mjs +2 -1
  25. package/bizar-dash/src/server/routes/overview.mjs +2 -1
  26. package/bizar-dash/src/server/routes-v2/events.mjs +14 -0
  27. package/bizar-dash/src/server/schedules-runner.mjs +126 -0
  28. package/bizar-dash/src/server/server.mjs +79 -0
  29. package/bizar-dash/src/web/App.tsx +8 -1
  30. package/bizar-dash/src/web/components/BackupRestore.tsx +330 -0
  31. package/bizar-dash/src/web/components/SearchModal.tsx +6 -3
  32. package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
  33. package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
  34. package/bizar-dash/src/web/components/chat/Composer.tsx +2 -0
  35. package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
  36. package/bizar-dash/src/web/lib/i18n.ts +25 -0
  37. package/bizar-dash/src/web/locales/en.json +52 -0
  38. package/bizar-dash/src/web/main.tsx +5 -0
  39. package/bizar-dash/src/web/styles/main.css +70 -8
  40. package/bizar-dash/src/web/views/Activity.tsx +35 -18
  41. package/bizar-dash/src/web/views/Agents.tsx +57 -42
  42. package/bizar-dash/src/web/views/Artifacts.tsx +38 -25
  43. package/bizar-dash/src/web/views/Chat.tsx +8 -0
  44. package/bizar-dash/src/web/views/History.tsx +94 -76
  45. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +21 -4
  46. package/bizar-dash/src/web/views/Mods.tsx +30 -17
  47. package/bizar-dash/src/web/views/Overview.tsx +19 -9
  48. package/bizar-dash/src/web/views/Providers.tsx +16 -16
  49. package/bizar-dash/src/web/views/Schedules.tsx +33 -15
  50. package/bizar-dash/src/web/views/Settings.tsx +97 -1745
  51. package/bizar-dash/src/web/views/Skills.tsx +4 -1
  52. package/bizar-dash/src/web/views/Tasks.tsx +11 -2
  53. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +8 -2
  54. package/bizar-dash/src/web/views/memory/LightragPanel.tsx +3 -0
  55. package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +12 -4
  56. package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +3 -0
  57. package/bizar-dash/src/web/views/settings/ActivitySection.tsx +205 -0
  58. package/bizar-dash/src/web/views/settings/AgentSection.tsx +294 -0
  59. package/bizar-dash/src/web/views/settings/AuthSection.tsx +159 -0
  60. package/bizar-dash/src/web/views/settings/BackupSection.tsx +16 -0
  61. package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +16 -0
  62. package/bizar-dash/src/web/views/settings/GeneralSection.tsx +105 -0
  63. package/bizar-dash/src/web/views/settings/HeadroomSection.tsx +39 -0
  64. package/bizar-dash/src/web/views/settings/MemorySection.tsx +16 -0
  65. package/bizar-dash/src/web/views/settings/NetworkSection.tsx +87 -0
  66. package/bizar-dash/src/web/views/settings/NotificationsSection.tsx +34 -0
  67. package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +16 -0
  68. package/bizar-dash/src/web/views/settings/SkillsSection.tsx +16 -0
  69. package/bizar-dash/src/web/views/settings/SystemLlmSection.tsx +81 -0
  70. package/bizar-dash/src/web/views/settings/ThemeSection.tsx +168 -0
  71. package/bizar-dash/src/web/views/settings/UpdatesSection.tsx +256 -0
  72. package/bizar-dash/tests/a11y.test.tsx +206 -0
  73. package/bizar-dash/tests/backup-restore.test.mjs +217 -0
  74. package/bizar-dash/tests/backup-restore.test.tsx +123 -0
  75. package/bizar-dash/tests/backup-store.test.mjs +300 -0
  76. package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
  77. package/bizar-dash/tests/cli-error-visibility.test.mjs +153 -0
  78. package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
  79. package/bizar-dash/tests/components/Button.test.tsx +41 -0
  80. package/bizar-dash/tests/components/Card.test.tsx +42 -0
  81. package/bizar-dash/tests/components/Modal.test.tsx +104 -0
  82. package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
  83. package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
  84. package/bizar-dash/tests/components/Toast.test.tsx +108 -0
  85. package/bizar-dash/tests/digest-generation.test.mjs +191 -0
  86. package/bizar-dash/tests/digest-store.test.mjs +264 -0
  87. package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
  88. package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
  89. package/bizar-dash/tests/lib/i18n.test.ts +46 -0
  90. package/bizar-dash/tests/lib/utils.test.ts +194 -0
  91. package/bizar-dash/tests/logger.test.mjs +207 -0
  92. package/bizar-dash/tests/metrics.test.mjs +183 -0
  93. package/bizar-dash/tests/rate-limit.test.mjs +298 -0
  94. package/bizar-dash/tests/server-bugfixes.test.mjs +2 -2
  95. package/bizar-dash/tests/setup.ts +7 -0
  96. package/bizar-dash/vitest.config.ts +13 -0
  97. package/cli/artifact-cli.mjs +605 -0
  98. package/cli/artifact-render.mjs +621 -0
  99. package/cli/artifact-server.mjs +847 -0
  100. package/cli/artifact.mjs +38 -2096
  101. package/cli/bg.mjs +5 -13
  102. package/cli/bin.mjs +221 -1350
  103. package/cli/commands/artifact.mjs +20 -0
  104. package/cli/commands/dash.mjs +160 -0
  105. package/cli/commands/headroom.mjs +204 -0
  106. package/cli/commands/install.mjs +169 -0
  107. package/cli/commands/memory.mjs +25 -0
  108. package/cli/commands/minimax.mjs +285 -0
  109. package/cli/commands/mod.mjs +185 -0
  110. package/cli/commands/service.mjs +65 -0
  111. package/cli/commands/usage.mjs +109 -0
  112. package/cli/commands/util.mjs +459 -0
  113. package/cli/digest.mjs +149 -0
  114. package/cli/doctor.mjs +1 -13
  115. package/cli/provision.mjs +2 -13
  116. package/cli/service-controller.mjs +1 -11
  117. package/cli/service.mjs +1 -11
  118. package/cli/utils.mjs +41 -1
  119. package/package.json +6 -1
  120. package/bizar-dash/dist/assets/main-B4OfGAwz.js +0 -361
  121. package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +0 -1
  122. package/bizar-dash/dist/assets/main-DAlLdW8I.css +0 -1
  123. package/bizar-dash/dist/assets/mobile-lbH6szyX.js.map +0 -1
@@ -0,0 +1,459 @@
1
+ /**
2
+ * cli/commands/util.mjs
3
+ *
4
+ * Miscellaneous utility commands:
5
+ * audit, init, export, test-gate, dev-link, dev-unlink,
6
+ * doctor, repair, heads-up, bg, browser-harness-up, providers detect,
7
+ * backup, restore
8
+ */
9
+ import chalk from 'chalk';
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { spawn } from 'node:child_process';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ // ── Help texts ──────────────────────────────────────────────────────────────────
16
+
17
+ export function showAuditHelp() {
18
+ console.log(`
19
+ bizar audit — Run security audit on agent configuration
20
+
21
+ Usage:
22
+ bizar audit
23
+ `);
24
+ }
25
+
26
+ export function showInitHelp() {
27
+ console.log(`
28
+ bizar init — Initialize .bizar/ in current project
29
+
30
+ Usage:
31
+ bizar init
32
+
33
+ Description:
34
+ Detects the project stack, creates .bizar/PROJECT.md and
35
+ .bizar/AGENTS_SELF_IMPROVEMENT.md and installs relevant skills.
36
+ The per-project knowledge graph (in .bizar/graph/) is provided
37
+ by the graphify mod — install it from the mod registry for
38
+ that feature.
39
+ `);
40
+ }
41
+
42
+ export function showExportHelp() {
43
+ console.log(`
44
+ bizar export — Export agents/rules to another harness
45
+
46
+ Usage:
47
+ bizar export [claude|cursor|opencode]
48
+
49
+ Description:
50
+ Copies installed Bizar agents and rules into another harness format.
51
+ `);
52
+ }
53
+
54
+ export function showTestGateHelp() {
55
+ console.log(`
56
+ bizar test-gate — Detect & run the project's test suite
57
+ `);
58
+ }
59
+
60
+ export function showDevLinkHelp() {
61
+ console.log(`
62
+ bizar dev-link / dev-unlink — Manage a symlink from the opencode plugin dir
63
+ to a local source checkout, so edits propagate to opencode on next session.
64
+
65
+ Usage:
66
+ bizar dev-link [source-dir] Symlink source-dir (default: ./plugins/bizar)
67
+ to ~/.config/opencode/plugins/bizar
68
+ bizar dev-link --force Replace an existing deployed copy
69
+ bizar dev-unlink Remove the dev symlink + restore from npm
70
+ bizar dev-unlink --force Remove even if not a symlink (destructive)
71
+
72
+ Description:
73
+ By default, opencode loads the Bizar plugin from
74
+ ~/.config/opencode/plugins/bizar, which is a real directory copied
75
+ from the npm package. Edits to plugins/bizar/ in the BizarHarness
76
+ repo don't propagate until you re-run the installer.
77
+
78
+ \`bizar dev-link\` replaces that directory with a symlink pointing
79
+ at your local checkout, so source edits are picked up immediately.
80
+ \`bizar dev-unlink\` reverses the change by removing the symlink
81
+ and re-installing the deployed copy from the npm package.
82
+
83
+ While the dev link is in place, \`bizar update\` will skip the
84
+ plugin-copy step (and print a warning) so it doesn't clobber the
85
+ link. Run \`bizar dev-unlink\` first, or pass --force to overwrite.
86
+
87
+ Examples:
88
+ bizar dev-link
89
+ bizar dev-link /home/me/projects/bizar/plugins/bizar
90
+ bizar dev-link --force
91
+ bizar dev-unlink
92
+ `);
93
+ }
94
+
95
+ export function showDoctorHelp() {
96
+ console.log(`
97
+ bizar doctor — Check the BizarHarness install for health issues
98
+
99
+ Usage:
100
+ bizar doctor
101
+
102
+ Description:
103
+ Runs a battery of health checks against the local install:
104
+ • opencode CLI reachable
105
+ • ~/.config/opencode/opencode.json parses as JSON
106
+ • the Bizar plugin is registered
107
+ • plugin path resolves
108
+ • @polderlabs/bizar-plugin is installed globally
109
+ • core agent files are installed (odin, quick, thor, tyr)
110
+ • headroom / semble / skills on PATH (lenient — at least one)
111
+ • dashboard reachable (skipped if no port file)
112
+ • provider.minimax block + MiniMax model flags are sane
113
+
114
+ Prints ✓/✗ for each check and a final summary. Exits non-zero
115
+ if any check fails. Use \`bizar doctor\` after a manual config
116
+ edit or to diagnose "why is opencode misbehaving?" questions.
117
+
118
+ Related:
119
+ bizar update Update + auto-run doctor on success
120
+ `);
121
+ }
122
+
123
+ export function showRepairHelp() {
124
+ console.log(`
125
+ bizar repair — Fix common install issues
126
+
127
+ Usage:
128
+ bizar repair Diagnose + fix stale bin symlinks and mismatched versions
129
+ bizar repair --dry-run Show what would change without modifying anything
130
+ bizar repair --bin-only Only fix the bin symlink; skip version checks
131
+ `);
132
+ }
133
+
134
+ export function showBackupHelp() {
135
+ console.log(`
136
+ bizar backup — Backup BizarHarness state
137
+
138
+ Usage:
139
+ bizar backup [label] Create a new backup (with optional label)
140
+ bizar backup list List available backups
141
+ bizar backup verify <path> Verify a backup's integrity
142
+ bizar backup delete <path> Delete a backup
143
+
144
+ Description:
145
+ Backs up config (~/.config/bizar/, ~/.config/opencode/), memory,
146
+ usage logs, and optionally project-level state (.bizar/, skills/).
147
+ Backups are stored under ~/.local/share/bizar/backups/.
148
+
149
+ Examples:
150
+ bizar backup
151
+ bizar backup "before-upgrade"
152
+ bizar backup list
153
+ bizar backup verify ~/.local/share/bizar/backups/bizar-2025-07-05-120000
154
+ `);
155
+ }
156
+
157
+ export function showRestoreHelp() {
158
+ console.log(`
159
+ bizar restore — Restore BizarHarness from a backup
160
+
161
+ Usage:
162
+ bizar restore <path> Restore from a backup
163
+ bizar restore <path> --dry-run Preview restore without modifying files
164
+ bizar restore <path> --overwrite Replace existing files (default: merge)
165
+ bizar restore <path> --skip Keep existing files, don't overwrite
166
+
167
+ Description:
168
+ Restores files from a backup directory. Default strategy is 'merge'
169
+ (newer files from backup overlay existing files). Use --overwrite to
170
+ replace everything, or --skip to leave existing files untouched.
171
+
172
+ Examples:
173
+ bizar restore ~/.local/share/bizar/backups/bizar-2025-07-05-120000
174
+ bizar restore ~/.local/share/bizar/backups/bizar-2025-07-05-120000 --dry-run
175
+ bizar restore ~/.local/share/bizar/backups/bizar-2025-07-05-120000 --overwrite
176
+ `);
177
+ }
178
+
179
+ // ── Test gate ──────────────────────────────────────────────────────────────────
180
+
181
+ export async function runTestGate() {
182
+ console.log(chalk.bold.hex('#a855f7')('\n ᚦ TEST GATE ᚦ\n'));
183
+ const { execSync } = await import('node:child_process');
184
+ const cwd = process.cwd();
185
+ const possible = [
186
+ { cmd: 'npm test', check: 'package.json' },
187
+ { cmd: 'pytest', check: 'pyproject.toml' },
188
+ { cmd: 'cargo test', check: 'Cargo.toml' },
189
+ { cmd: 'go test ./...', check: 'go.mod' },
190
+ ];
191
+ for (const suite of possible) {
192
+ try {
193
+ if (existsSync(join(cwd, suite.check))) {
194
+ console.log(` Running: ${suite.cmd}`);
195
+ execSync(suite.cmd, { stdio: 'inherit', timeout: 120000, cwd });
196
+ console.log('\n ✓ Test gate passed\n');
197
+ return true;
198
+ }
199
+ } catch {
200
+ console.log(`\n ✗ Test gate failed: ${suite.cmd}\n`);
201
+ process.exit(1);
202
+ }
203
+ }
204
+ console.log(' No test suite detected. Install one to use the test gate.\n');
205
+ return false;
206
+ }
207
+
208
+ // ── Main dispatcher ────────────────────────────────────────────────────────────
209
+
210
+ export async function run(name, args, isHelpRequest) {
211
+ switch (name) {
212
+ case 'audit':
213
+ if (isHelpRequest) showAuditHelp();
214
+ else {
215
+ const { runAudit } = await import('../audit.mjs');
216
+ await runAudit();
217
+ }
218
+ break;
219
+
220
+ case 'init':
221
+ if (isHelpRequest) showInitHelp();
222
+ else {
223
+ const { runInit } = await import('../init.mjs');
224
+ await runInit(process.cwd());
225
+ }
226
+ break;
227
+
228
+ case 'export':
229
+ if (isHelpRequest) showExportHelp();
230
+ else {
231
+ const { runExport } = await import('../export.mjs');
232
+ const targetFlag = args.includes('--target') ? args[args.indexOf('--target') + 1] : null;
233
+ await runExport(targetFlag);
234
+ }
235
+ break;
236
+
237
+ case 'test-gate':
238
+ if (isHelpRequest) showTestGateHelp();
239
+ else await runTestGate();
240
+ break;
241
+
242
+ case 'dev-link':
243
+ if (isHelpRequest) showDevLinkHelp();
244
+ else {
245
+ const { createDevLink } = await import('../dev-link.mjs');
246
+ const positional = args.filter((a) => !a.startsWith('-'));
247
+ const flags = args.filter((a) => a.startsWith('-'));
248
+ const sourceDir = positional[0] ?? null;
249
+ const force = flags.includes('--force') || flags.includes('-f');
250
+ const ok = createDevLink(sourceDir, { force });
251
+ if (!ok) process.exit(1);
252
+ }
253
+ break;
254
+
255
+ case 'dev-unlink':
256
+ if (isHelpRequest) showDevLinkHelp();
257
+ else {
258
+ const { removeDevLink } = await import('../dev-link.mjs');
259
+ const force = args.includes('--force') || args.includes('-f');
260
+ const ok = await removeDevLink({ force });
261
+ if (!ok) process.exit(1);
262
+ }
263
+ break;
264
+
265
+ case 'doctor':
266
+ if (isHelpRequest) showDoctorHelp();
267
+ else {
268
+ const { runDoctor } = await import('../doctor.mjs');
269
+ const wantJson = args.includes('--json');
270
+ const result = await runDoctor({ silent: wantJson, json: wantJson });
271
+ if (wantJson) process.stdout.write(JSON.stringify(result) + '\n');
272
+ if (result.failed > 0) process.exit(1);
273
+ }
274
+ break;
275
+
276
+ case 'repair':
277
+ if (isHelpRequest) showRepairHelp();
278
+ else {
279
+ const { runRepair } = await import('../repair.mjs');
280
+ const dryRun = args.includes('--dry-run');
281
+ const binOnly = args.includes('--bin-only');
282
+ const result = await runRepair({ dryRun, binOnly });
283
+ if (!result.ok) {
284
+ for (const n of result.notes) console.log(` ${n}`);
285
+ process.exit(1);
286
+ }
287
+ for (const n of result.notes) console.log(` ${n}`);
288
+ if (result.fixed.length > 0) {
289
+ console.log(chalk.green('\n Repair complete. Re-run your shell or `hash -r` to pick up the new path.'));
290
+ }
291
+ }
292
+ break;
293
+
294
+ case 'backup': {
295
+ if (isHelpRequest || args.length === 0) {
296
+ showBackupHelp();
297
+ break;
298
+ }
299
+ const sub = args[0];
300
+ if (sub === 'list') {
301
+ const { listBackups } = await import('../../bizar-dash/src/server/backup-store.mjs');
302
+ const backups = await listBackups();
303
+ if (backups.length === 0) {
304
+ console.log(' No backups found.');
305
+ } else {
306
+ for (const b of backups) {
307
+ console.log(` ${b.path}`);
308
+ console.log(` Created: ${b.createdAt}`);
309
+ console.log(` Size: ${b.sizeFormatted}`);
310
+ if (b.manifest?.label) console.log(` Label: ${b.manifest.label}`);
311
+ console.log('');
312
+ }
313
+ }
314
+ break;
315
+ }
316
+ if (sub === 'verify') {
317
+ const path = args[1];
318
+ if (!path) { console.error(' Usage: bizar backup verify <path>'); process.exit(1); }
319
+ const { verifyBackup } = await import('../../bizar-dash/src/server/backup-store.mjs');
320
+ const result = await verifyBackup({ backupPath: path });
321
+ if (result.ok) {
322
+ console.log(chalk.green(' ✓ Backup is valid'));
323
+ } else {
324
+ console.error(chalk.red(' ✗ Backup has issues:'));
325
+ for (const issue of result.issues) console.error(` - ${issue}`);
326
+ process.exit(1);
327
+ }
328
+ break;
329
+ }
330
+ if (sub === 'delete') {
331
+ const path = args[1];
332
+ if (!path) { console.error(' Usage: bizar backup delete <path>'); process.exit(1); }
333
+ const { deleteBackup } = await import('../../bizar-dash/src/server/backup-store.mjs');
334
+ const result = await deleteBackup({ backupPath: path });
335
+ if (result.ok) {
336
+ console.log(chalk.green(' ✓ Backup deleted'));
337
+ } else {
338
+ console.error(chalk.red(` ✗ Delete failed: ${result.error}`));
339
+ process.exit(1);
340
+ }
341
+ break;
342
+ }
343
+ // Default: create backup
344
+ const label = sub || null;
345
+ const { createBackup } = await import('../../bizar-dash/src/server/backup-store.mjs');
346
+ const result = await createBackup({ label });
347
+ if (result.ok) {
348
+ console.log(chalk.green(' ✓ Backup created'));
349
+ console.log(` Path: ${result.path}`);
350
+ console.log(` Size: ${result.sizeBytes > 0 ? `${(result.sizeBytes / 1024).toFixed(1)} KB` : '0 B'}`);
351
+ console.log(` Duration: ${result.durationMs}ms`);
352
+ } else {
353
+ console.error(chalk.red(' ✗ Backup failed'));
354
+ process.exit(1);
355
+ }
356
+ break;
357
+ }
358
+
359
+ case 'restore': {
360
+ if (isHelpRequest) {
361
+ showRestoreHelp();
362
+ break;
363
+ }
364
+ const path = args[0];
365
+ if (!path) { showRestoreHelp(); process.exit(1); }
366
+ const dryRun = args.includes('--dry-run');
367
+ const conflictStrategy = args.includes('--overwrite') ? 'overwrite' : args.includes('--skip') ? 'skip' : 'merge';
368
+ const { restoreBackup } = await import('../../bizar-dash/src/server/backup-store.mjs');
369
+ const result = await restoreBackup({ backupPath: path, dryRun, conflictStrategy });
370
+ if (dryRun) {
371
+ console.log(' Dry-run mode — no files were modified.');
372
+ }
373
+ if (result.restored.length > 0) {
374
+ console.log(` Restored: ${result.restored.join(', ')}`);
375
+ }
376
+ if (result.skipped.length > 0) {
377
+ console.log(` Skipped: ${result.skipped.join(', ')}`);
378
+ }
379
+ if (result.errors.length > 0) {
380
+ console.error(chalk.red(' Errors:'));
381
+ for (const e of result.errors) console.error(` - ${e}`);
382
+ process.exit(1);
383
+ }
384
+ if (result.ok) {
385
+ console.log(chalk.green(` ✓ Restore complete`));
386
+ }
387
+ break;
388
+ }
389
+
390
+ case 'heads-up':
391
+ if (isHelpRequest) {
392
+ console.log(' heads-up <subcommand> Manage pre-push / pre-release heads-ups (list/check/archive)');
393
+ } else {
394
+ const { runHeadsUp } = await import('../heads-up.mjs');
395
+ await runHeadsUp(args[0], args.slice(1));
396
+ }
397
+ break;
398
+
399
+ case 'bg':
400
+ if (isHelpRequest) {
401
+ console.log(' bg <subcommand> Manage background agents (list/view/kill/logs)');
402
+ } else {
403
+ const { runBg } = await import('../bg.mjs');
404
+ await runBg(args[0], args.slice(1));
405
+ }
406
+ break;
407
+
408
+ case 'digest':
409
+ // v4.8.0 — Weekly digest management
410
+ if (isHelpRequest) {
411
+ const { showDigestHelp } = await import('../digest.mjs');
412
+ showDigestHelp();
413
+ } else {
414
+ const { runDigest } = await import('../digest.mjs');
415
+ await runDigest(args[0], args.slice(1));
416
+ }
417
+ break;
418
+
419
+ case 'browser-harness-up': {
420
+ const { execFileSync } = await import('node:child_process');
421
+ const sub = args[0] || 'start';
422
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
423
+ const scriptPath = join(__dirname, '..', 'browser-harness-up.sh');
424
+ try {
425
+ const out = execFileSync('bash', [scriptPath, sub], {
426
+ encoding: 'utf8',
427
+ stdio: 'inherit',
428
+ });
429
+ if (out) process.stdout.write(out);
430
+ } catch (err) {
431
+ console.error(chalk.red(` ✗ browser-harness-up ${sub} failed (exit ${err.status ?? 1})`));
432
+ process.exit(err.status || 1);
433
+ }
434
+ break;
435
+ }
436
+
437
+ case 'providers':
438
+ if (args[0] === 'detect') {
439
+ const { runProvidersDetect } = await import('../providers-detect.mjs');
440
+ await runProvidersDetect(args.slice(1));
441
+ }
442
+ break;
443
+
444
+ case 'plan':
445
+ // Alias for artifact
446
+ if (isHelpRequest) {
447
+ const { showHelp } = await import('../artifact-cli.mjs');
448
+ showHelp();
449
+ } else {
450
+ const { runArtifact } = await import('../artifact-cli.mjs');
451
+ await runArtifact(args, {});
452
+ }
453
+ break;
454
+
455
+ default:
456
+ return false;
457
+ }
458
+ return true;
459
+ }
package/cli/digest.mjs ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * cli/digest.mjs
3
+ *
4
+ * v4.8.0 — `bizar digest` CLI subcommand.
5
+ *
6
+ * Usage:
7
+ * bizar digest Show latest digest
8
+ * bizar digest list List all digests
9
+ * bizar digest generate [--days N] Generate now (default 7 days)
10
+ * bizar digest view <path> View a specific digest
11
+ * bizar digest delete <path> Delete a specific digest
12
+ */
13
+ import chalk from 'chalk';
14
+ import { readFileSync, existsSync } from 'node:fs';
15
+
16
+ function showDigestHelp() {
17
+ console.log(`
18
+ bizar digest — Manage weekly digests
19
+
20
+ Usage:
21
+ bizar digest Show the latest digest
22
+ bizar digest list List all digests
23
+ bizar digest generate [--days N] Generate a digest now (default 7 days)
24
+ bizar digest view <path> View a specific digest
25
+ bizar digest delete <path> Delete a specific digest
26
+ bizar digest --help Show this help
27
+ `);
28
+ }
29
+
30
+ export async function runDigest(subcommand, args) {
31
+ const { generateAndSave, listDigests, getDigest, deleteDigest } = await import(
32
+ '../bizar-dash/src/server/digest-store.mjs'
33
+ );
34
+
35
+ switch (subcommand) {
36
+ case undefined:
37
+ case null:
38
+ case '': {
39
+ // Show latest digest
40
+ const digests = await listDigests({ limit: 1 });
41
+ if (digests.length === 0) {
42
+ console.log(chalk.yellow(' No digests yet. Run `bizar digest generate` to create one.'));
43
+ return;
44
+ }
45
+ const latest = digests[0];
46
+ const digest = await getDigest(latest.path);
47
+ if (!digest) {
48
+ console.log(chalk.red(` ✗ Digest not found at ${latest.path}`));
49
+ return;
50
+ }
51
+ console.log(digest.content);
52
+ break;
53
+ }
54
+
55
+ case 'list': {
56
+ const digests = await listDigests({ limit: 50 });
57
+ if (digests.length === 0) {
58
+ console.log(chalk.yellow(' No digests yet.'));
59
+ return;
60
+ }
61
+ console.log(chalk.bold(` ${digests.length} digest(s):\n`));
62
+ for (const d of digests) {
63
+ const date = new Date(d.createdAt).toLocaleDateString();
64
+ const size = (d.sizeBytes / 1024).toFixed(1);
65
+ console.log(` ${chalk.cyan(d.filename)}`);
66
+ console.log(` Path: ${d.path}`);
67
+ console.log(` Created: ${date} (${size} KB)`);
68
+ console.log();
69
+ }
70
+ break;
71
+ }
72
+
73
+ case 'generate': {
74
+ const daysIdx = args.indexOf('--days');
75
+ const days = daysIdx >= 0 ? parseInt(args[daysIdx + 1], 10) || 7 : 7;
76
+ const dryRun = args.includes('--dry-run');
77
+ const now = new Date();
78
+ const weekEnd = now.toISOString().slice(0, 10);
79
+ const weekStart = new Date(now.getTime() - days * 24 * 60 * 60 * 1000)
80
+ .toISOString()
81
+ .slice(0, 10);
82
+
83
+ console.log(chalk.bold(` Generating digest for ${weekStart} → ${weekEnd}...`));
84
+ if (dryRun) console.log(chalk.yellow(' (dry run — will not save)'));
85
+
86
+ const result = await generateAndSave({
87
+ weekStart,
88
+ weekEnd,
89
+ projectRoot: process.cwd(),
90
+ dryRun,
91
+ });
92
+
93
+ if (result.dryRun) {
94
+ console.log('\n' + result.markdown);
95
+ } else {
96
+ console.log(chalk.green(` ✓ Digest saved:`));
97
+ for (const p of result.saveResult.paths) {
98
+ console.log(` ${p}`);
99
+ }
100
+ console.log(chalk.green(` Sections: ${Object.keys(result.sections).filter(k => Array.isArray(result.sections[k]) ? result.sections[k].length > 0 : result.sections[k] !== null).length}/7 populated`));
101
+ }
102
+ break;
103
+ }
104
+
105
+ case 'view': {
106
+ const path = args[0];
107
+ if (!path) {
108
+ console.log(chalk.red(' ✗ Usage: bizar digest view <path>'));
109
+ process.exit(1);
110
+ }
111
+ if (!existsSync(path)) {
112
+ console.log(chalk.red(` ✗ File not found: ${path}`));
113
+ process.exit(1);
114
+ }
115
+ const digest = await getDigest(path);
116
+ if (!digest) {
117
+ console.log(chalk.red(` ✗ Could not read digest at ${path}`));
118
+ process.exit(1);
119
+ }
120
+ console.log(digest.content);
121
+ break;
122
+ }
123
+
124
+ case 'delete': {
125
+ const path = args[0];
126
+ if (!path) {
127
+ console.log(chalk.red(' ✗ Usage: bizar digest delete <path>'));
128
+ process.exit(1);
129
+ }
130
+ const result = await deleteDigest(path);
131
+ if (result.ok) {
132
+ console.log(chalk.green(` ✓ Deleted ${path}`));
133
+ } else {
134
+ console.log(chalk.red(` ✗ Delete failed: ${result.error || 'unknown'}`));
135
+ process.exit(1);
136
+ }
137
+ break;
138
+ }
139
+
140
+ default:
141
+ if (subcommand === '--help' || subcommand === '-h') {
142
+ showDigestHelp();
143
+ } else {
144
+ console.log(chalk.red(` ✗ Unknown digest subcommand: ${subcommand}`));
145
+ showDigestHelp();
146
+ process.exit(1);
147
+ }
148
+ }
149
+ }
package/cli/doctor.mjs CHANGED
@@ -21,9 +21,8 @@
21
21
  import chalk from 'chalk';
22
22
  import { spawnSync } from 'node:child_process';
23
23
  import { existsSync, readFileSync } from 'node:fs';
24
- import { homedir } from 'node:os';
25
24
  import { join } from 'node:path';
26
- import { opencodeConfigDir, opencodeAgentsDir } from './utils.mjs';
25
+ import { opencodeConfigDir, opencodeAgentsDir, which, bizarConfigDir } from './utils.mjs';
27
26
 
28
27
  // v3.20.11: list every agent the install script is expected to deploy.
29
28
  // Adding a new agent to `config/agents/` without adding it here causes
@@ -50,12 +49,6 @@ const REQUIRED_AGENTS = [
50
49
 
51
50
  // ── helpers ─────────────────────────────────────────────────────────────────
52
51
 
53
- function bizarConfigDir() {
54
- return process.env.XDG_CONFIG_HOME
55
- ? join(process.env.XDG_CONFIG_HOME, 'bizar')
56
- : join(homedir(), '.config', 'bizar');
57
- }
58
-
59
52
  /**
60
53
  * Run a check function and capture its result. The check either
61
54
  * returns a string message (pass) or throws an Error (fail).
@@ -73,11 +66,6 @@ async function runCheck(name, fn) {
73
66
  }
74
67
  }
75
68
 
76
- function which(cmd) {
77
- const probe = spawnSync('which', [cmd], { stdio: 'ignore' });
78
- return probe.status === 0;
79
- }
80
-
81
69
  // ── individual checks ───────────────────────────────────────────────────────
82
70
 
83
71
  async function checkOpencodeReachable() {
package/cli/provision.mjs CHANGED
@@ -42,9 +42,11 @@ import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync, mkdirSync
42
42
  import { homedir } from 'node:os';
43
43
  import { join, dirname } from 'node:path';
44
44
  import { fileURLToPath } from 'node:url';
45
+ import { bizarConfigDir } from './utils.mjs';
45
46
 
46
47
  const __filename = fileURLToPath(import.meta.url);
47
48
  const __dirname = dirname(__filename);
49
+ const HOME = homedir();
48
50
 
49
51
  // `cli/provision.mjs` lives at `<pkg>/cli/provision.mjs`. The repo root
50
52
  // (where `plugins/bizar/`, `bizar-dash/`, `package.json` etc. live) is
@@ -53,19 +55,6 @@ const __dirname = dirname(__filename);
53
55
  export const REPO_ROOT = join(__dirname, '..');
54
56
  export const PKG_MAIN = '@polderlabs/bizar';
55
57
 
56
- const HOME = homedir();
57
-
58
- function bizarConfigDir() {
59
- if (process.platform === 'win32') {
60
- return process.env.APPDATA
61
- ? join(process.env.APPDATA, 'bizar')
62
- : join(HOME, '.config', 'bizar');
63
- }
64
- return process.env.XDG_CONFIG_HOME
65
- ? join(process.env.XDG_CONFIG_HOME, 'bizar')
66
- : join(HOME, '.config', 'bizar');
67
- }
68
-
69
58
  export const BIZAR_HOME = bizarConfigDir();
70
59
  export const OPENCODE_DIR =
71
60
  process.platform === 'win32'
@@ -39,6 +39,7 @@ import {
39
39
  import { homedir, platform } from 'node:os';
40
40
  import { join, dirname, resolve, isAbsolute } from 'node:path';
41
41
  import { fileURLToPath } from 'node:url';
42
+ import { bizarConfigDir } from './utils.mjs';
42
43
 
43
44
  const __filename = fileURLToPath(import.meta.url);
44
45
  const __dirname = dirname(__filename);
@@ -47,17 +48,6 @@ const PLATFORM = platform();
47
48
 
48
49
  // ── Path helpers ──────────────────────────────────────────────────────────────
49
50
 
50
- function bizarConfigDir() {
51
- if (PLATFORM === 'win32') {
52
- return process.env.APPDATA
53
- ? join(process.env.APPDATA, 'bizar')
54
- : join(HOME, '.config', 'bizar');
55
- }
56
- return process.env.XDG_CONFIG_HOME
57
- ? join(process.env.XDG_CONFIG_HOME, 'bizar')
58
- : join(HOME, '.config', 'bizar');
59
- }
60
-
61
51
  function userUnitDir() {
62
52
  if (PLATFORM === 'win32') return null;
63
53
  return process.env.XDG_CONFIG_HOME