@polderlabs/bizar 4.7.0 → 4.7.2

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 (75) 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-CWqPoGaT.js → mobile-BK8-ythT.js} +2 -2
  5. package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +1 -0
  6. package/bizar-dash/dist/assets/{mobile-i4Uv9eW8.js → mobile-Chvf9u_B.js} +1 -1
  7. package/bizar-dash/dist/assets/{mobile-i4Uv9eW8.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 -1
  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/routes/backup.mjs +112 -0
  17. package/bizar-dash/src/server/routes/chat.mjs +14 -0
  18. package/bizar-dash/src/server/routes/digests.mjs +82 -0
  19. package/bizar-dash/src/server/routes-v2/events.mjs +14 -0
  20. package/bizar-dash/src/server/schedules-runner.mjs +126 -0
  21. package/bizar-dash/src/server/server.mjs +17 -0
  22. package/bizar-dash/src/web/App.tsx +8 -1
  23. package/bizar-dash/src/web/components/BackupRestore.tsx +330 -0
  24. package/bizar-dash/src/web/components/SearchModal.tsx +3 -1
  25. package/bizar-dash/src/web/components/chat/Composer.tsx +2 -0
  26. package/bizar-dash/src/web/styles/main.css +70 -8
  27. package/bizar-dash/src/web/views/Activity.tsx +11 -1
  28. package/bizar-dash/src/web/views/Agents.tsx +57 -42
  29. package/bizar-dash/src/web/views/Artifacts.tsx +38 -25
  30. package/bizar-dash/src/web/views/Chat.tsx +8 -0
  31. package/bizar-dash/src/web/views/History.tsx +4 -1
  32. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +21 -4
  33. package/bizar-dash/src/web/views/Mods.tsx +30 -17
  34. package/bizar-dash/src/web/views/Overview.tsx +8 -1
  35. package/bizar-dash/src/web/views/Providers.tsx +16 -16
  36. package/bizar-dash/src/web/views/Schedules.tsx +33 -15
  37. package/bizar-dash/src/web/views/Settings.tsx +97 -1751
  38. package/bizar-dash/src/web/views/Skills.tsx +4 -1
  39. package/bizar-dash/src/web/views/Tasks.tsx +11 -2
  40. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +8 -2
  41. package/bizar-dash/src/web/views/memory/LightragPanel.tsx +3 -0
  42. package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +12 -4
  43. package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +3 -0
  44. package/bizar-dash/src/web/views/settings/ActivitySection.tsx +205 -0
  45. package/bizar-dash/src/web/views/settings/AgentSection.tsx +294 -0
  46. package/bizar-dash/src/web/views/settings/AuthSection.tsx +159 -0
  47. package/bizar-dash/src/web/views/settings/BackupSection.tsx +16 -0
  48. package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +16 -0
  49. package/bizar-dash/src/web/views/settings/GeneralSection.tsx +105 -0
  50. package/bizar-dash/src/web/views/settings/HeadroomSection.tsx +39 -0
  51. package/bizar-dash/src/web/views/settings/MemorySection.tsx +16 -0
  52. package/bizar-dash/src/web/views/settings/NetworkSection.tsx +87 -0
  53. package/bizar-dash/src/web/views/settings/NotificationsSection.tsx +34 -0
  54. package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +16 -0
  55. package/bizar-dash/src/web/views/settings/SkillsSection.tsx +16 -0
  56. package/bizar-dash/src/web/views/settings/SystemLlmSection.tsx +81 -0
  57. package/bizar-dash/src/web/views/settings/ThemeSection.tsx +168 -0
  58. package/bizar-dash/src/web/views/settings/UpdatesSection.tsx +256 -0
  59. package/bizar-dash/tests/a11y.test.tsx +206 -0
  60. package/bizar-dash/tests/backup-restore.test.mjs +217 -0
  61. package/bizar-dash/tests/backup-restore.test.tsx +123 -0
  62. package/bizar-dash/tests/backup-store.test.mjs +300 -0
  63. package/bizar-dash/tests/cli-error-visibility.test.mjs +153 -0
  64. package/bizar-dash/tests/digest-generation.test.mjs +191 -0
  65. package/bizar-dash/tests/digest-store.test.mjs +264 -0
  66. package/bizar-dash/tests/rate-limit.test.mjs +298 -0
  67. package/cli/bin.mjs +96 -2
  68. package/cli/commands/minimax.mjs +20 -0
  69. package/cli/commands/util.mjs +154 -1
  70. package/cli/digest.mjs +149 -0
  71. package/package.json +1 -1
  72. package/bizar-dash/dist/assets/main-DAlLdW8I.css +0 -1
  73. package/bizar-dash/dist/assets/main-DGGq-iZI.js +0 -361
  74. package/bizar-dash/dist/assets/main-DGGq-iZI.js.map +0 -1
  75. package/bizar-dash/dist/assets/mobile-CWqPoGaT.js.map +0 -1
package/cli/bin.mjs CHANGED
@@ -136,7 +136,12 @@ function showHelp() {
136
136
  async function importCommand(name) {
137
137
  try {
138
138
  return await import(`./commands/${name}.mjs`);
139
- } catch {
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')));
144
+ }
140
145
  return null;
141
146
  }
142
147
  }
@@ -184,61 +189,131 @@ async function main() {
184
189
  }
185
190
  }
186
191
 
192
+ // `bizar help` — explicit help alias
193
+ if (cmd === 'help') {
194
+ showHelp();
195
+ process.exit(EXIT_OK);
196
+ return;
197
+ }
198
+
187
199
  // Dispatch to command modules
188
200
  switch (cmd) {
189
201
  case 'install':
190
202
  case 'update': {
191
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);
207
+ return;
208
+ }
209
+ dbg('loaded command module:', 'install');
192
210
  await mod.run(cmd, cmdArgs, isHelpRequest);
211
+ dbg('command returned:', cmd);
193
212
  break;
194
213
  }
195
214
 
196
215
  case 'service': {
197
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);
220
+ return;
221
+ }
222
+ dbg('loaded command module:', 'service');
198
223
  await mod.run(cmd, cmdArgs, isHelpRequest);
224
+ dbg('command returned:', cmd);
199
225
  break;
200
226
  }
201
227
 
202
228
  case 'dash':
203
229
  case 'dashboard': {
204
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;
235
+ }
236
+ dbg('loaded command module:', 'dash');
205
237
  await mod.run(cmd, cmdArgs, isHelpRequest);
238
+ dbg('command returned:', cmd);
206
239
  break;
207
240
  }
208
241
 
209
242
  case 'minimax': {
210
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;
248
+ }
249
+ dbg('loaded command module:', 'minimax');
211
250
  await mod.run(cmd, cmdArgs, isHelpRequest);
251
+ dbg('command returned:', cmd);
212
252
  break;
213
253
  }
214
254
 
215
255
  case 'headroom': {
216
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');
217
263
  await mod.run(cmd, cmdArgs, isHelpRequest);
264
+ dbg('command returned:', cmd);
218
265
  break;
219
266
  }
220
267
 
221
268
  case 'mod': {
222
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);
273
+ return;
274
+ }
275
+ dbg('loaded command module:', 'mod');
223
276
  await mod.run(cmd, cmdArgs, isHelpRequest);
277
+ dbg('command returned:', cmd);
224
278
  break;
225
279
  }
226
280
 
227
281
  case 'artifact': {
228
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);
286
+ return;
287
+ }
288
+ dbg('loaded command module:', 'artifact');
229
289
  await mod.runArtifact(cmdArgs, { wantJson });
290
+ dbg('command returned:', cmd);
230
291
  break;
231
292
  }
232
293
 
233
294
  case 'memory': {
234
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;
300
+ }
301
+ dbg('loaded command module:', 'memory');
235
302
  await mod.run(cmd, cmdArgs, isHelpRequest);
303
+ dbg('command returned:', cmd);
236
304
  break;
237
305
  }
238
306
 
239
307
  case 'usage': {
240
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;
313
+ }
314
+ dbg('loaded command module:', 'usage');
241
315
  await mod.run(cmd, cmdArgs, isHelpRequest);
316
+ dbg('command returned:', cmd);
242
317
  break;
243
318
  }
244
319
 
@@ -253,11 +328,19 @@ async function main() {
253
328
  case 'repair':
254
329
  case 'heads-up':
255
330
  case 'bg':
331
+ case 'digest':
256
332
  case 'browser-harness-up':
257
333
  case 'providers':
258
334
  case 'plan': {
259
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;
340
+ }
341
+ dbg('loaded command module:', 'util');
260
342
  const found = await mod.run(cmd, cmdArgs, isHelpRequest);
343
+ dbg('command returned:', cmd);
261
344
  if (!found) {
262
345
  console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
263
346
  showHelp();
@@ -277,7 +360,18 @@ async function main() {
277
360
  // ── Run ───────────────────────────────────────────────────────────────────────
278
361
 
279
362
  const thisFile = fileURLToPath(import.meta.url);
280
- const isMainModule = process.argv[1] === thisFile;
363
+ // Resolve symlinks: when invoked via a symlink (e.g. /home/drb0rk/.local/bin/bizar),
364
+ // process.argv[1] is the symlink path, but import.meta.url is the resolved target.
365
+ // Compare via realpath so main() runs regardless of how the script is invoked.
366
+ const { realpathSync } = await import('node:fs');
367
+ const resolvedArgv = (() => {
368
+ try {
369
+ return realpathSync(process.argv[1]);
370
+ } catch {
371
+ return process.argv[1];
372
+ }
373
+ })();
374
+ const isMainModule = resolvedArgv === thisFile;
281
375
  if (isMainModule) {
282
376
  await main().catch((err) => {
283
377
  console.error(chalk.red(`bizar: ${err && err.message ? err.message : String(err)}`));
@@ -100,6 +100,11 @@ async function runMinimaxCommand(minimaxArgs) {
100
100
  console.error(chalk.red(` ✗ ${r.message || r.error}`));
101
101
  process.exit(1);
102
102
  }
103
+ if (!r.data) {
104
+ console.error(chalk.red(' ✗ Dashboard returned no data. Is the dashboard running?'));
105
+ console.error(chalk.dim(` Tried: http://127.0.0.1:4321/api/minimax/status`));
106
+ process.exit(1);
107
+ }
103
108
  const s = r.data;
104
109
  console.log('');
105
110
  console.log(chalk.bold(' MiniMax Token Plan status'));
@@ -126,6 +131,11 @@ async function runMinimaxCommand(minimaxArgs) {
126
131
  console.error(chalk.red(` ✗ ${r.message || r.error}`));
127
132
  process.exit(1);
128
133
  }
134
+ if (!r.data) {
135
+ console.error(chalk.red(' ✗ Dashboard returned no data. Is the dashboard running?'));
136
+ console.error(chalk.dim(` Tried: http://127.0.0.1:4321/api/minimax/remains`));
137
+ process.exit(1);
138
+ }
129
139
  if (!r.data?.ok) {
130
140
  console.error(chalk.red(` ✗ ${r.data?.message || r.data?.error || 'unknown'}`));
131
141
  process.exit(1);
@@ -160,6 +170,11 @@ async function runMinimaxCommand(minimaxArgs) {
160
170
  console.error(chalk.red(` ✗ ${r.message || r.error}`));
161
171
  process.exit(1);
162
172
  }
173
+ if (!r.data) {
174
+ console.error(chalk.red(' ✗ Dashboard returned no data. Is the dashboard running?'));
175
+ console.error(chalk.dim(` Tried: http://127.0.0.1:4321/api/minimax/test`));
176
+ process.exit(1);
177
+ }
163
178
  const data = r.data;
164
179
  if (!data?.ok) {
165
180
  console.error(chalk.red(` ✗ ${data?.message || data?.error || 'unknown'}`));
@@ -195,6 +210,11 @@ async function runMinimaxCommand(minimaxArgs) {
195
210
  console.error(chalk.red(` ✗ ${r.message || r.error}`));
196
211
  process.exit(1);
197
212
  }
213
+ if (!r.data) {
214
+ console.error(chalk.red(' ✗ Dashboard returned no data. Is the dashboard running?'));
215
+ console.error(chalk.dim(` Tried: http://127.0.0.1:4321/api/minimax/onboarding/save-key`));
216
+ process.exit(1);
217
+ }
198
218
  console.log(chalk.green(` ✓ Saved to ${r.data?.path}`));
199
219
  console.log(` ${chalk.dim('key hint:')} ${r.data?.apiKeyHint}`);
200
220
  return;
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Miscellaneous utility commands:
5
5
  * audit, init, export, test-gate, dev-link, dev-unlink,
6
- * doctor, repair, heads-up, bg, browser-harness-up, providers detect
6
+ * doctor, repair, heads-up, bg, browser-harness-up, providers detect,
7
+ * backup, restore
7
8
  */
8
9
  import chalk from 'chalk';
9
10
  import { existsSync, readFileSync } from 'node:fs';
@@ -130,6 +131,51 @@ export function showRepairHelp() {
130
131
  `);
131
132
  }
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
+
133
179
  // ── Test gate ──────────────────────────────────────────────────────────────────
134
180
 
135
181
  export async function runTestGate() {
@@ -245,6 +291,102 @@ export async function run(name, args, isHelpRequest) {
245
291
  }
246
292
  break;
247
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
+
248
390
  case 'heads-up':
249
391
  if (isHelpRequest) {
250
392
  console.log(' heads-up <subcommand> Manage pre-push / pre-release heads-ups (list/check/archive)');
@@ -263,6 +405,17 @@ export async function run(name, args, isHelpRequest) {
263
405
  }
264
406
  break;
265
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
+
266
419
  case 'browser-harness-up': {
267
420
  const { execFileSync } = await import('node:child_process');
268
421
  const sub = args[0] || 'start';
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "4.7.0",
3
+ "version": "4.7.2",
4
4
  "description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness. v4 ships as a single npm package bundling the dashboard server, opencode plugin, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {