@polderlabs/bizar 4.4.11 → 4.4.13

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
@@ -15,9 +15,13 @@
15
15
  * install, audit, init, export, artifact, update, test-gate, service, dash
16
16
  */
17
17
  import { existsSync } from 'node:fs';
18
- import { readFileSync } from 'node:fs';
19
- import { join } from 'node:path';
18
+ import { readFileSync, writeFileSync } from 'node:fs';
19
+ import { homedir } from 'node:os';
20
+ import { dirname, join } from 'node:path';
20
21
  import { fileURLToPath } from 'node:url';
22
+
23
+ const HOME = homedir();
24
+ const BIZAR_HOME = join(HOME, '.config', 'bizar');
21
25
  import chalk from 'chalk';
22
26
  import { runInstaller } from './install.mjs';
23
27
  import { runAudit } from './audit.mjs';
@@ -93,7 +97,8 @@ function showHelp() {
93
97
  dev-unlink Remove the dev symlink and restore the deployed copy
94
98
  doctor Check the BizarHarness install for health issues
95
99
  heads-up <subcommand> Manage pre-push / pre-release heads-ups (list/check/archive)
96
- mod <subcommand> Manage mods (install/upgrade/list via the dashboard API)
100
+ minimax <subcommand> Manage MiniMax Token Plan integration (key + onboarding)
101
+ mod <subcommand> Manage mods (install/upgrade/list via the dashboard API)
97
102
 
98
103
  Examples:
99
104
  bizar install
@@ -476,6 +481,256 @@ async function runModCommand(modArgs) {
476
481
  }
477
482
  }
478
483
 
484
+ function showMinimaxHelp() {
485
+ console.log(`
486
+ bizar minimax — Manage the MiniMax Token Plan integration
487
+
488
+ Usage:
489
+ bizar minimax status Show whether the Subscription Key is configured
490
+ and where it was resolved from (auth.json,
491
+ opencode.json, env var, or none).
492
+ bizar minimax remains Fetch the live 5-hour + weekly remaining
493
+ quota per model. Shows reset times.
494
+ bizar minimax test Smoke-test the key with a one-shot chat
495
+ completion. Prints the usage block.
496
+ bizar minimax config <key> Save a new Subscription Key to
497
+ ~/.local/share/opencode/auth.json. The
498
+ key never leaves this machine.
499
+ bizar minimax clear Remove the Subscription Key from
500
+ opencode's auth.json.
501
+ bizar minimax reset-onboarding Re-trigger the first-run wizard. Use
502
+ this if the key was changed outside the
503
+ dashboard and you want to re-enter it.
504
+
505
+ Flags:
506
+ --base-url <url> Override the Token Plan host (default: https://www.minimax.io)
507
+ --chat-url <url> Override the chat-completions host (default: https://api.minimax.io/v1)
508
+ --yes Skip the confirmation prompt on 'clear' and 'reset-onboarding'
509
+ `);
510
+ }
511
+
512
+ // Find the dashboard's port + password so the CLI can talk to /api/minimax/*.
513
+ function readDashboardConn() {
514
+ const portFile = join(BIZAR_HOME, 'dashboard.port');
515
+ const authFile = join(BIZAR_HOME, 'dashboard-secret');
516
+ let port = 4321;
517
+ let secret = '';
518
+ try {
519
+ if (existsSync(portFile)) {
520
+ const parsed = parseInt(readFileSync(portFile, 'utf8').trim(), 10);
521
+ if (Number.isFinite(parsed) && parsed > 0) port = parsed;
522
+ }
523
+ } catch { /* ignore */ }
524
+ try {
525
+ if (existsSync(authFile)) secret = readFileSync(authFile, 'utf8').trim();
526
+ } catch { /* ignore */ }
527
+ return { port, secret };
528
+ }
529
+
530
+ async function minimaxApi(path, opts = {}) {
531
+ const { port, secret } = readDashboardConn();
532
+ const url = `http://127.0.0.1:${port}${path}`;
533
+ const headers = { 'content-type': 'application/json', accept: 'application/json' };
534
+ if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
535
+ const method = (opts.method || 'GET').toUpperCase();
536
+ try {
537
+ const resp = await fetch(url, { method, headers, body: opts.body ? JSON.stringify(opts.body) : undefined });
538
+ const text = await resp.text();
539
+ let data = null;
540
+ try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
541
+ if (!resp.ok) {
542
+ return { ok: false, error: `http_${resp.status}`, message: data?.message || data?.error || resp.statusText, status: resp.status };
543
+ }
544
+ return { ok: true, status: resp.status, data };
545
+ } catch (err) {
546
+ return { ok: false, error: 'network_error', message: err && err.message ? err.message : String(err) };
547
+ }
548
+ }
549
+
550
+ async function runMinimaxCommand(minimaxArgs) {
551
+ const sub = minimaxArgs[0];
552
+ const flags = minimaxArgs.slice(1).filter((a) => a.startsWith('-'));
553
+ const positional = minimaxArgs.slice(1).filter((a) => !a.startsWith('-'));
554
+ const yes = flags.includes('--yes') || flags.includes('-y');
555
+
556
+ if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
557
+ showMinimaxHelp();
558
+ return;
559
+ }
560
+
561
+ if (sub === 'status') {
562
+ const r = await minimaxApi('/api/minimax/status');
563
+ if (!r.ok) {
564
+ console.error(chalk.red(` ✗ ${r.message || r.error}`));
565
+ process.exit(1);
566
+ }
567
+ const s = r.data;
568
+ console.log('');
569
+ console.log(chalk.bold(' MiniMax Token Plan status'));
570
+ console.log('');
571
+ console.log(` ${chalk.dim('configured:')} ${s.configured ? chalk.green('yes') : chalk.red('no')}`);
572
+ console.log(` ${chalk.dim('key source:')} ${chalk.cyan(s.source)}`);
573
+ if (s.apiKeyHint) console.log(` ${chalk.dim('key hint:')} ${s.apiKeyHint}`);
574
+ console.log(` ${chalk.dim('group id:')} ${s.groupId}`);
575
+ console.log(` ${chalk.dim('token host:')} ${s.tokenBaseUrl}`);
576
+ console.log(` ${chalk.dim('chat host:')} ${s.chatBaseUrl}`);
577
+ console.log(` ${chalk.dim('key format:')} ${s.keyPatternValid === true ? chalk.green('ok') : s.keyPatternValid === false ? chalk.red('unexpected prefix') : chalk.dim('n/a')}`);
578
+ console.log('');
579
+ if (s.cache) {
580
+ console.log(` ${chalk.dim('cached:')} ${new Date(s.cache.fetchedAt).toLocaleString()} (${s.cache.modelCount} models)`);
581
+ } else {
582
+ console.log(` ${chalk.dim('cached:')} none yet — run \`bizar minimax remains\` to populate`);
583
+ }
584
+ return;
585
+ }
586
+
587
+ if (sub === 'remains') {
588
+ const r = await minimaxApi('/api/minimax/remains');
589
+ if (!r.ok) {
590
+ console.error(chalk.red(` ✗ ${r.message || r.error}`));
591
+ process.exit(1);
592
+ }
593
+ if (!r.data?.ok) {
594
+ console.error(chalk.red(` ✗ ${r.data?.message || r.data?.error || 'unknown'}`));
595
+ process.exit(1);
596
+ }
597
+ const data = r.data;
598
+ console.log('');
599
+ console.log(chalk.bold(` MiniMax Token Plan quota (${new Date(data.fetchedAt).toLocaleString()})`));
600
+ if (data.apiKeyHint) console.log(chalk.dim(` Key: ${data.apiKeyHint} · source: ${data.keySource} · group: ${data.groupId}`));
601
+ console.log('');
602
+ for (const m of data.models || []) {
603
+ const five = m.current_interval_remaining_percent;
604
+ const week = m.current_weekly_remaining_percent;
605
+ const fiveBar = '█'.repeat(Math.round(five / 5)) + '░'.repeat(20 - Math.round(five / 5));
606
+ const weekBar = '█'.repeat(Math.round(week / 5)) + '░'.repeat(20 - Math.round(week / 5));
607
+ const fiveColor = five >= 75 ? chalk.green : five >= 25 ? chalk.yellow : chalk.red;
608
+ const weekColor = week >= 75 ? chalk.green : week >= 25 ? chalk.yellow : chalk.red;
609
+ console.log(` ${chalk.bold(m.model_name)}`);
610
+ console.log(` ${chalk.dim('5h:')} ${fiveColor(five + '%'.padStart(4))} ${fiveBar} resets in ${m.intervalResetInHuman}`);
611
+ console.log(` ${chalk.dim('week:')} ${weekColor(week + '%'.padStart(4))} ${weekBar} resets in ${m.weeklyResetInHuman}`);
612
+ console.log('');
613
+ }
614
+ return;
615
+ }
616
+
617
+ if (sub === 'test') {
618
+ const prompt = positional[0] || 'Reply with the single word: pong';
619
+ const r = await minimaxApi('/api/minimax/test', {
620
+ method: 'POST',
621
+ body: { prompt, model: 'MiniMax-M3', maxTokens: 32 },
622
+ });
623
+ if (!r.ok) {
624
+ console.error(chalk.red(` ✗ ${r.message || r.error}`));
625
+ process.exit(1);
626
+ }
627
+ const data = r.data;
628
+ if (!data?.ok) {
629
+ console.error(chalk.red(` ✗ ${data?.message || data?.error || 'unknown'}`));
630
+ process.exit(1);
631
+ }
632
+ console.log('');
633
+ console.log(chalk.green(' ✓ Key works'));
634
+ console.log(` ${chalk.dim('model:')} ${data.model}`);
635
+ console.log(` ${chalk.dim('finish:')} ${data.finishReason}`);
636
+ if (data.content) console.log(` ${chalk.dim('content:')} ${JSON.stringify(data.content.slice(0, 80))}${data.content.length > 80 ? '…' : ''}`);
637
+ if (data.usage) {
638
+ console.log(` ${chalk.dim('usage:')} total=${data.usage.total_tokens} prompt=${data.usage.prompt_tokens} completion=${data.usage.completion_tokens}`);
639
+ }
640
+ return;
641
+ }
642
+
643
+ if (sub === 'config' || sub === 'set') {
644
+ const key = positional[0];
645
+ if (!key) {
646
+ console.error(chalk.red(' ✗ Missing key. Usage: bizar minimax config <sk-cp-…>'));
647
+ process.exit(1);
648
+ }
649
+ if (!/^sk-(cp|ant|or)-[A-Za-z0-9_-]{20,}$/.test(key)) {
650
+ console.error(chalk.red(' ✗ Key does not look like a MiniMax key (expected sk-cp-…, sk-ant-…, or sk-or-… prefix)'));
651
+ process.exit(1);
652
+ }
653
+ console.log(chalk.dim(' Saving to opencode auth.json…'));
654
+ const r = await minimaxApi('/api/minimax/onboarding/save-key', {
655
+ method: 'POST',
656
+ body: { key, groupId: 'default' },
657
+ });
658
+ if (!r.ok) {
659
+ console.error(chalk.red(` ✗ ${r.message || r.error}`));
660
+ process.exit(1);
661
+ }
662
+ console.log(chalk.green(` ✓ Saved to ${r.data?.path}`));
663
+ console.log(` ${chalk.dim('key hint:')} ${r.data?.apiKeyHint}`);
664
+ return;
665
+ }
666
+
667
+ if (sub === 'clear' || sub === 'remove') {
668
+ if (!yes) {
669
+ console.log(chalk.yellow(` ⚠ This will remove the MiniMax Subscription Key from opencode's auth.json.`));
670
+ console.log(chalk.dim(' Continue? [y/N]'));
671
+ // simple readline confirmation
672
+ const buf = [];
673
+ process.stdin.setEncoding('utf8');
674
+ process.stdin.on('data', (c) => { buf.push(c); if (c.includes('\n')) process.stdin.pause(); });
675
+ await new Promise((resolve) => process.stdin.once('close', resolve));
676
+ const answer = buf.join('').trim().toLowerCase();
677
+ if (answer !== 'y' && answer !== 'yes') {
678
+ console.log(chalk.dim(' Cancelled.'));
679
+ return;
680
+ }
681
+ }
682
+ // We don't have a "clear key" route; instead write an empty auth.json
683
+ // entry. We piggyback on the onboarding save-key by saving an empty
684
+ // marker isn't allowed — use the providers-store side instead. For
685
+ // now, recommend using the dashboard's "clear" button. But the
686
+ // simplest CLI path is to write auth.json directly.
687
+ const authFile = join(HOME, '.local', 'share', 'opencode', 'auth.json');
688
+ let auth = {};
689
+ try {
690
+ if (existsSync(authFile)) auth = JSON.parse(readFileSync(authFile, 'utf8'));
691
+ } catch { /* ignore */ }
692
+ if (auth.minimax) {
693
+ delete auth.minimax;
694
+ writeFileSync(authFile, JSON.stringify(auth, null, 2) + '\n', 'utf8');
695
+ console.log(chalk.green(' ✓ MiniMax key removed from auth.json'));
696
+ } else {
697
+ console.log(chalk.dim(' No MiniMax key was configured.'));
698
+ }
699
+ return;
700
+ }
701
+
702
+ if (sub === 'reset-onboarding' || sub === 'reset') {
703
+ if (!yes) {
704
+ console.log(chalk.yellow(' ⚠ This will re-trigger the first-run MiniMax onboarding wizard.'));
705
+ console.log(chalk.dim(' Continue? [y/N]'));
706
+ const buf = [];
707
+ process.stdin.setEncoding('utf8');
708
+ process.stdin.on('data', (c) => { buf.push(c); if (c.includes('\n')) process.stdin.pause(); });
709
+ await new Promise((resolve) => process.stdin.once('close', resolve));
710
+ const answer = buf.join('').trim().toLowerCase();
711
+ if (answer !== 'y' && answer !== 'yes') {
712
+ console.log(chalk.dim(' Cancelled.'));
713
+ return;
714
+ }
715
+ }
716
+ // Reset via the dashboard API.
717
+ const r = await minimaxApi('/api/minimax/onboarding', {
718
+ method: 'POST',
719
+ body: { dismissedAt: null },
720
+ });
721
+ if (!r.ok) {
722
+ console.error(chalk.red(` ✗ ${r.message || r.error}`));
723
+ process.exit(1);
724
+ }
725
+ console.log(chalk.green(' ✓ Onboarding wizard will show on next dashboard load'));
726
+ return;
727
+ }
728
+
729
+ console.error(chalk.red(` ✗ Unknown minimax subcommand: ${sub}`));
730
+ showMinimaxHelp();
731
+ process.exit(1);
732
+ }
733
+
479
734
  function showDashHelp() {
480
735
  console.log(`
481
736
  bizar dash — Manage the Bizar dashboard
@@ -823,6 +1078,11 @@ async function main() {
823
1078
  // (the dashboard owns the actual mod install/upgrade logic and the
824
1079
  // opencode-config install/uninstall of instruction files).
825
1080
  await runModCommand(args.slice(1));
1081
+ } else if (args[0] === 'minimax') {
1082
+ // v4.5.0 — MiniMax Token Plan integration CLI. Status, remains,
1083
+ // test, config, clear, reset-onboarding. All commands shell out
1084
+ // to the dashboard's /api/minimax/* routes.
1085
+ await runMinimaxCommand(args.slice(1));
826
1086
  } else if (args[0] === 'dash' || args[0] === 'dashboard') {
827
1087
  // `bizar dashboard` is a deprecated alias for `bizar dash`
828
1088
  if (args[0] === 'dashboard') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "4.4.11",
3
+ "version": "4.4.13",
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": {