@polderlabs/bizar 3.2.0 → 3.2.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.
package/cli/bin.mjs CHANGED
@@ -26,15 +26,29 @@ import { runInit } from './init.mjs';
26
26
  import { runExport } from './export.mjs';
27
27
  import runPlan from './plan.mjs';
28
28
  import { runUpdate } from './update.mjs';
29
+ import { ensureSetup, checkSetupStatus } from './bootstrap.mjs';
29
30
 
30
31
  const args = process.argv.slice(2);
31
32
 
33
+ // ── Bootstrap ─────────────────────────────────────────────────────────────────
34
+ // Every bin command checks setup status on first invocation.
35
+ // Skip only when: --postinstall (manual trigger), --check (status only),
36
+ // BIZAR_SKIP_INSTALL=1 (disabled), or already handled via npm script.
37
+ if (
38
+ !args.includes('--postinstall') &&
39
+ !args.includes('--check') &&
40
+ !process.env.BIZAR_SKIP_INSTALL
41
+ ) {
42
+ await ensureSetup({ silent: true });
43
+ }
44
+ // ─────────────────────────────────────────────────────────────────────────────
45
+
32
46
  function showHelp() {
33
47
  console.log(`
34
48
  Bizar — Norse Pantheon Agent System for opencode
35
49
 
36
50
  Usage:
37
- bizar Launch the TUI dashboard (default)
51
+ bizar Launch the TUI dashboard (auto-runs first-time setup if needed)
38
52
  bizar --web Launch TUI + auto-open web dashboard
39
53
  bizar --no-web Launch TUI only (no browser)
40
54
  bizar --web-only Web dashboard only (no TUI, in browser)
@@ -49,6 +63,8 @@ function showHelp() {
49
63
  bizar update Update opencode, bizar, and/or bizar-plugin
50
64
  bizar service Manage the background service daemon
51
65
  bizar dashboard Launch the web dashboard (uses bizar-dash)
66
+ bizar --setup Re-run setup manually (agents, plugin, RTK, Semble, Skills CLI)
67
+ bizar --check Print setup status as JSON, exit 1 if setup needed
52
68
  bizar --help Show this help
53
69
 
54
70
  Install:
@@ -229,8 +245,17 @@ async function runServiceCommand(sub) {
229
245
  await runService(sub || 'status', args.slice(2));
230
246
  }
231
247
 
232
- if (args[0] === '--postinstall') {
233
- await runPostInstall();
248
+ if (args.includes('--check')) {
249
+ const status = checkSetupStatus();
250
+ console.log(JSON.stringify(status, null, 2));
251
+ process.exit(status.needed ? 1 : 0);
252
+ } else if (args.includes('--setup')) {
253
+ await ensureSetup({ silent: false });
254
+ process.exit(0);
255
+ } else if (args.includes('--postinstall')) {
256
+ // Legacy manual trigger — now an alias for --setup
257
+ await ensureSetup({ silent: false });
258
+ process.exit(0);
234
259
  } else if (args[0] === 'audit') {
235
260
  if (args.includes('--help') || args.includes('-h')) showAuditHelp();
236
261
  else await runAudit();
@@ -0,0 +1,77 @@
1
+ /**
2
+ * cli/bootstrap.mjs
3
+ *
4
+ * v3.2.2 — Self-bootstrap on first bin invocation.
5
+ *
6
+ * Replaces the postinstall hook (which npm v10+ blocks by default).
7
+ * Every `bizar` bin command now checks setup status on entry and runs
8
+ * the postinstall logic automatically if anything is missing.
9
+ *
10
+ * Key design:
11
+ * - Checks for SETUP_MARKERS (odyssey files that prove setup was done)
12
+ * - If ALL markers exist → silent no-op
13
+ * - If ANY marker is missing → runs postinstall logic
14
+ * - Idempotent: already-installed components are skipped by the postinstall
15
+ */
16
+ import { existsSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import chalk from 'chalk';
19
+
20
+ import { opencodeAgentsDir, opencodeConfigDir } from './utils.mjs';
21
+ import { runPostInstall } from './install.mjs';
22
+
23
+ /**
24
+ * Key files that prove setup has been run at least once.
25
+ * If ALL exist → setup is considered complete.
26
+ * If ANY are missing → setup is needed.
27
+ */
28
+ const SETUP_MARKERS = [
29
+ join(opencodeAgentsDir(), 'odin.md'), // core agent installed
30
+ join(opencodeConfigDir(), 'plugins', 'bizar'), // plugin installed
31
+ ];
32
+
33
+ /**
34
+ * Check which markers are currently present.
35
+ * Returns { needed: true, missing: [...] } if any marker is absent.
36
+ * Returns { needed: false } if all markers are present.
37
+ */
38
+ export function checkSetupStatus() {
39
+ const missing = SETUP_MARKERS.filter((p) => !existsSync(p));
40
+ if (missing.length > 0) {
41
+ return { needed: true, missing };
42
+ }
43
+ return { needed: false };
44
+ }
45
+
46
+ /**
47
+ * Print a first-run setup message.
48
+ * Uses chalk — kept minimal to avoid polluting stdout before the TUI loads.
49
+ */
50
+ function printSetupBanner() {
51
+ // Use console.log directly to avoid chalk formatting issues in non-TTY
52
+ console.log('\n ⚡ First-time setup — Bizar needs to install agents, plugin, RTK, Semble, Skills CLI...\n');
53
+ }
54
+
55
+ /**
56
+ * Run setup if anything is missing.
57
+ *
58
+ * Options:
59
+ * autoApprove — if true, skip interactive prompts (BIZAR_SKIP_OPTIONAL_INSTALLS already set by caller)
60
+ * silent — if true, suppress the "first-time setup" banner (used when invoked silently on bin entry)
61
+ *
62
+ * Idempotent: runPostInstall() skips already-installed components.
63
+ */
64
+ export async function ensureSetup({ silent = false } = {}) {
65
+ const status = checkSetupStatus();
66
+
67
+ if (!status.needed) {
68
+ return; // already done, nothing to do
69
+ }
70
+
71
+ // Something is missing — run setup
72
+ if (!silent) {
73
+ printSetupBanner();
74
+ }
75
+
76
+ await runPostInstall();
77
+ }
package/cli/install.mjs CHANGED
@@ -294,7 +294,102 @@ export async function runInstaller() {
294
294
  console.log(chalk.dim('\n Odin watches. The Pantheon awaits. ᛟ\n'));
295
295
  }
296
296
 
297
+ // ── Interactive prompts for optional packages ─────────────────────────────────
298
+
299
+ import { execSync } from 'node:child_process';
300
+ import { createInterface } from 'node:readline/promises';
301
+ import { stdin, stdout } from 'node:process';
302
+
303
+ async function promptYesNo(question, defaultYes = true) {
304
+ // If not a TTY (CI, automated install), skip the prompt
305
+ if (!stdin.isTTY || !stdout.isTTY) {
306
+ return false;
307
+ }
308
+
309
+ const rl = createInterface({ input: stdin, output: stdout });
310
+ try {
311
+ const hint = defaultYes ? '[Y/n]' : '[y/N]';
312
+ const answer = (await rl.question(` ${question} ${hint}: `)).trim().toLowerCase();
313
+ rl.close();
314
+
315
+ if (answer === '') return defaultYes;
316
+ if (['y', 'yes'].includes(answer)) return true;
317
+ if (['n', 'no'].includes(answer)) return false;
318
+ return defaultYes;
319
+ } catch {
320
+ rl.close();
321
+ return false;
322
+ }
323
+ }
324
+
325
+ async function isPackageInstalled(name) {
326
+ try {
327
+ const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
328
+ require.resolve(name, { paths: [globalRoot] });
329
+ return true;
330
+ } catch {
331
+ return false;
332
+ }
333
+ }
334
+
335
+ async function promptAndInstallOptional() {
336
+ // Plugin
337
+ const pluginInstalled = await isPackageInstalled('@polderlabs/bizar-plugin');
338
+ if (!pluginInstalled) {
339
+ console.log('');
340
+ console.log(' The Bizar opencode plugin is required for the /bizar command and agent integration.');
341
+ const install = await promptYesNo(
342
+ 'Install @polderlabs/bizar-plugin?',
343
+ true,
344
+ );
345
+ if (install) {
346
+ try {
347
+ console.log(' Installing @polderlabs/bizar-plugin...');
348
+ execSync('npm install -g @polderlabs/bizar-plugin', { stdio: 'inherit' });
349
+ console.log(' ✓ @polderlabs/bizar-plugin installed');
350
+ } catch (err) {
351
+ console.log(` ✗ Failed to install @polderlabs/bizar-plugin: ${err.message}`);
352
+ console.log(' You can install it later with: npm install -g @polderlabs/bizar-plugin');
353
+ }
354
+ } else {
355
+ console.log(' Skipped. Install later with: npm install -g @polderlabs/bizar-plugin');
356
+ }
357
+ } else {
358
+ console.log(' ✓ @polderlabs/bizar-plugin already installed');
359
+ }
360
+
361
+ // Dashboard
362
+ const dashInstalled = await isPackageInstalled('@polderlabs/bizar-dash');
363
+ if (!dashInstalled) {
364
+ console.log('');
365
+ console.log(' The Bizar dashboard provides the web UI (React + Vite) and TUI (blessed).');
366
+ console.log(' It\'s optional — install it for the full experience, or use the CLI alone.');
367
+ const install = await promptYesNo(
368
+ 'Install @polderlabs/bizar-dash?',
369
+ true,
370
+ );
371
+ if (install) {
372
+ try {
373
+ console.log(' Installing @polderlabs/bizar-dash...');
374
+ execSync('npm install -g @polderlabs/bizar-dash', { stdio: 'inherit' });
375
+ console.log(' ✓ @polderlabs/bizar-dash installed');
376
+ } catch (err) {
377
+ console.log(` ✗ Failed to install @polderlabs/bizar-dash: ${err.message}`);
378
+ console.log(' You can install it later with: npm install -g @polderlabs/bizar-dash');
379
+ }
380
+ } else {
381
+ console.log(' Skipped. Install later with: npm install -g @polderlabs/bizar-dash');
382
+ }
383
+ } else {
384
+ console.log(' ✓ @polderlabs/bizar-dash already installed');
385
+ }
386
+ }
387
+
297
388
  export async function runPostInstall() {
389
+ // Skip interactive prompts in CI / non-TTY environments
390
+ if (!process.env.BIZAR_SKIP_OPTIONAL_INSTALLS) {
391
+ await promptAndInstallOptional();
392
+ }
298
393
  const { mkdirSync, copyFileSync, existsSync } = await import('node:fs');
299
394
  const { execSync } = await import('node:child_process');
300
395
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, per-project Hindsight memory, mods, schedules, and a background service daemon",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,8 +13,7 @@
13
13
  "templates/"
14
14
  ],
15
15
  "scripts": {
16
- "typecheck": "tsc --noEmit",
17
- "postinstall": "node cli/bin.mjs --postinstall"
16
+ "typecheck": "tsc --noEmit"
18
17
  },
19
18
  "keywords": [
20
19
  "opencode",