jbai-cli 2.1.0 → 2.1.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/bin/jbai.js CHANGED
@@ -7,7 +7,6 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
9
  const config = require('../lib/config');
10
- const { createHandoff } = require('../lib/handoff');
11
10
  const { getGroupsForTool, showModelsForTool } = require('../lib/model-list');
12
11
  const completions = require('../lib/completions');
13
12
 
@@ -50,18 +49,31 @@ const TOOLS = {
50
49
  }
51
50
  };
52
51
 
52
+ const MENU_WRAPPERS = {
53
+ claude: 'jbai-claude.js',
54
+ codex: 'jbai-codex.js',
55
+ opencode: 'jbai-opencode.js',
56
+ gemini: 'jbai-gemini.js',
57
+ goose: 'jbai-goose.js',
58
+ continue: 'jbai-continue.js',
59
+ };
60
+
61
+ const CLI_PACKAGE = 'jbai-cli';
62
+ const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm';
63
+
53
64
  const VERSION = require('../package.json').version;
54
65
 
55
66
  const HELP = `
56
67
  jbai-cli v${VERSION} - JetBrains AI Platform CLI Tools
57
68
 
69
+ Launch 'jbai' with no arguments to open the interactive terminal control panel.
70
+
58
71
  COMMANDS:
59
72
  jbai token Show token status
60
73
  jbai token set Set token interactively
61
74
  jbai token refresh Auto-refresh token via API
62
75
  jbai token refresh <token> Set new token (saves to ~/.jbai/token + ~/.zshrc)
63
76
  jbai test Test API endpoints (incl. Codex /responses)
64
- jbai handoff Continue task in Orca Lab
65
77
  jbai env [staging|production] Switch environment
66
78
  jbai models [tool] List Grazie models (all|claude|codex|gemini|opencode|goose|continue)
67
79
  jbai install Install all AI tools (claude, codex, gemini, opencode, goose, continue)
@@ -70,6 +82,7 @@ COMMANDS:
70
82
  jbai completions Print zsh completions to stdout
71
83
  jbai completions --install Add completions to ~/.zshrc
72
84
  jbai completions --bash Print bash completions
85
+ jbai menu Open interactive control panel
73
86
  jbai help Show this help
74
87
 
75
88
  PROXY (for Codex Desktop, Cursor, etc.):
@@ -88,6 +101,7 @@ TOOL WRAPPERS:
88
101
  MODEL SHORTCUTS (super mode by default):
89
102
  jbai-claude-opus Claude Code + Opus 4.6
90
103
  jbai-claude-sonnet Claude Code + Sonnet 4.6
104
+ jbai-codex-5.4 Codex + GPT-5.4
91
105
  jbai-codex-5.3 Codex + GPT-5.3
92
106
  jbai-codex-5.2 Codex + GPT-5.2
93
107
  jbai-codex-rockhopper Codex + Rockhopper Alpha (OpenAI EAP)
@@ -112,7 +126,6 @@ EXAMPLES:
112
126
  jbai-gemini-supernova # Gemini with Supernova (super mode)
113
127
  jbai-council # Launch all 3 agents in tmux
114
128
  jbai-council --super # All agents in super mode
115
- jbai handoff --task "fix lint" # Handoff task to Orca Lab
116
129
 
117
130
  TOKEN:
118
131
  Get token: ${config.getEndpoints().tokenUrl}
@@ -524,139 +537,318 @@ async function installTools(toolKey) {
524
537
  console.log('Run: jbai doctor to verify');
525
538
  }
526
539
 
527
- const HANDOFF_HELP = `
528
- jbai handoff - Continue a task in Orca Lab
529
-
530
- Usage:
531
- jbai handoff --task "your task"
532
- jbai handoff "your task"
533
-
534
- Options:
535
- --task, -t Task description (or pass as positional)
536
- --repo, -r Git repo URL (defaults to origin remote)
537
- --ref Git ref (defaults to current branch)
538
- --branch, -b Working branch name for the agent
539
- --model, -m Claude model (default: ${config.MODELS.claude.default})
540
- --grazie-env, -e STAGING | PREPROD | PRODUCTION
541
- --grazie-token Override Grazie token (default: ~/.jbai/token)
542
- --git-token, -g GitHub token (default: GITHUB_TOKEN/GH_TOKEN)
543
- --facade-token, -f Facade JWT token (default: FACADE_JWT_TOKEN)
544
- --orca-url, -o Orca Lab URL (default: http://localhost:3000)
545
- --no-open Do not open the Orca Lab URL
546
- --no-auto-start Do not auto-start the agent task
547
- --help Show this help
548
- `;
540
+ function ask(question) {
541
+ return new Promise((resolve) => {
542
+ const rl = readline.createInterface({
543
+ input: process.stdin,
544
+ output: process.stdout
545
+ });
549
546
 
550
- function parseArgs(argv) {
551
- const opts = {};
552
- const rest = [];
553
- const shortMap = {
554
- t: 'task',
555
- r: 'repo',
556
- b: 'branch',
557
- m: 'model',
558
- e: 'grazie-env',
559
- g: 'git-token',
560
- f: 'facade-token',
561
- o: 'orca-url',
562
- h: 'help'
563
- };
547
+ rl.question(question, (answer) => {
548
+ rl.close();
549
+ resolve((answer || '').trim());
550
+ });
551
+ });
552
+ }
553
+
554
+ function waitForEnter() {
555
+ return ask('\nPress Enter to continue...');
556
+ }
557
+
558
+ function runNodeScript(scriptName, args = []) {
559
+ return new Promise((resolve) => {
560
+ const scriptPath = path.join(__dirname, scriptName);
561
+ const child = spawn(process.execPath, [scriptPath, ...args], {
562
+ stdio: 'inherit'
563
+ });
564
564
 
565
- for (let i = 0; i < argv.length; i++) {
566
- const arg = argv[i];
567
- if (arg === '--') {
568
- rest.push(...argv.slice(i + 1));
569
- break;
565
+ child.on('close', (code) => resolve(code || 0));
566
+ child.on('error', (error) => {
567
+ console.error(`Unable to run ${scriptName}: ${error.message}`);
568
+ resolve(1);
569
+ });
570
+ });
571
+ }
572
+
573
+ function runCommand(command, args = []) {
574
+ return new Promise((resolve) => {
575
+ const child = spawn(command, args, {
576
+ stdio: 'inherit',
577
+ shell: false
578
+ });
579
+
580
+ child.on('close', (code) => resolve(code || 0));
581
+ child.on('error', (error) => {
582
+ console.error(`Command failed: ${command} ${args.join(' ')}`);
583
+ console.error(error.message);
584
+ resolve(1);
585
+ });
586
+ });
587
+ }
588
+
589
+ async function launchAgent(tool) {
590
+ const script = MENU_WRAPPERS[tool];
591
+ if (!script) {
592
+ console.log(`Unsupported agent: ${tool}`);
593
+ return;
594
+ }
595
+
596
+ if (!isToolInstalled(tool)) {
597
+ console.log(`❌ ${TOOLS[tool].name} is not installed.`);
598
+ const installNow = (await ask(`Install ${TOOLS[tool].name} now? [y/N]: `)).toLowerCase() === 'y';
599
+ if (!installNow) {
600
+ return;
570
601
  }
571
- if (arg.startsWith('--')) {
572
- if (arg === '--no-open') {
573
- opts.open = false;
574
- continue;
575
- }
576
- if (arg === '--no-auto-start') {
577
- opts.autoStart = false;
578
- continue;
579
- }
580
- const key = arg.slice(2);
581
- const next = argv[i + 1];
582
- if (next && !next.startsWith('-')) {
583
- opts[key] = next;
584
- i++;
585
- } else {
586
- opts[key] = true;
587
- }
602
+
603
+ await installTools(tool);
604
+ if (!isToolInstalled(tool)) {
605
+ return;
606
+ }
607
+ }
608
+
609
+ await runNodeScript(script);
610
+ }
611
+
612
+ async function setupClients() {
613
+ const installed = Object.keys(TOOLS).filter(isToolInstalled);
614
+ if (installed.length === 0) {
615
+ console.log('No tools installed yet. Install a tool first, then wire it up.');
616
+ return;
617
+ }
618
+
619
+ const code = await runNodeScript('jbai-proxy.js', ['setup']);
620
+ if (code !== 0) {
621
+ console.log('Client setup reported issues. Fix the errors above and try again.');
622
+ return;
623
+ }
624
+
625
+ console.log('\nClient wiring complete.');
626
+ console.log('- Codex Desktop: config.toml updated');
627
+ console.log('- Shell env: JBAI_PROXY_KEY added');
628
+ console.log('- Proxy: started + launch settings configured');
629
+ }
630
+
631
+ async function updateCli() {
632
+ const confirm = (await ask('Update jbai-cli to latest from npm? [Y/n]: ')).toLowerCase();
633
+ if (confirm && confirm !== 'y' && confirm !== '') {
634
+ return;
635
+ }
636
+
637
+ const code = await runCommand(NPM_BIN, ['install', '-g', `${CLI_PACKAGE}@latest`]);
638
+ if (code === 0) {
639
+ console.log('✅ Update completed. Restart terminal to load updated binary.');
640
+ } else {
641
+ console.log('❌ Update failed. Check npm output and try again.');
642
+ }
643
+ }
644
+
645
+ async function uninstallCli() {
646
+ const confirm = (await ask(`This will uninstall ${CLI_PACKAGE} and close this app. Continue? [y/N]: `)).toLowerCase();
647
+ if (confirm !== 'y') {
648
+ return;
649
+ }
650
+
651
+ const code = await runCommand(NPM_BIN, ['uninstall', '-g', CLI_PACKAGE]);
652
+ if (code === 0) {
653
+ console.log('✅ jbai-cli removed.');
654
+ process.exit(0);
655
+ }
656
+ console.log('❌ Uninstall failed. Check npm output and try again.');
657
+ }
658
+
659
+ function environmentLabel() {
660
+ return config.getEnvironment() === 'production' ? 'PRODUCTION' : 'STAGING';
661
+ }
662
+
663
+ async function runTokenMenu() {
664
+ while (true) {
665
+ const action = (await ask(`\nToken Management\n1) Show status\n2) Set token\n3) Refresh token\n0) Back\n\nSelect: `)).toLowerCase();
666
+
667
+ if (action === '0' || action === 'b' || action === 'back') {
668
+ return;
669
+ }
670
+
671
+ if (action === '1') {
672
+ showTokenStatus();
673
+ await waitForEnter();
588
674
  continue;
589
675
  }
590
- if (arg.startsWith('-') && arg.length === 2) {
591
- const short = arg.slice(1);
592
- const key = shortMap[short];
593
- if (!key) {
594
- rest.push(arg);
595
- continue;
596
- }
597
- const next = argv[i + 1];
598
- if (next && !next.startsWith('-')) {
599
- opts[key] = next;
600
- i++;
601
- } else {
602
- opts[key] = true;
603
- }
676
+
677
+ if (action === '2') {
678
+ await setToken();
679
+ await waitForEnter();
680
+ continue;
681
+ }
682
+
683
+ if (action === '3') {
684
+ await refreshTokenCommand();
685
+ await waitForEnter();
686
+ continue;
687
+ }
688
+
689
+ console.log('Invalid selection.');
690
+ }
691
+ }
692
+
693
+ async function runSettingsMenu() {
694
+ while (true) {
695
+ const action = (await ask(`\nSettings\n1) Show environment (${environmentLabel()})\n2) Switch to staging\n3) Switch to production\n0) Back\n\nSelect: `)).toLowerCase();
696
+
697
+ if (action === '0' || action === 'b' || action === 'back') {
698
+ return;
699
+ }
700
+
701
+ if (action === '1') {
702
+ console.log(`Current environment: ${environmentLabel()}`);
703
+ console.log(`Token URL: ${config.getEndpoints().tokenUrl}`);
704
+ await waitForEnter();
705
+ continue;
706
+ }
707
+
708
+ if (action === '2') {
709
+ setEnvironment('staging');
710
+ await waitForEnter();
711
+ continue;
712
+ }
713
+
714
+ if (action === '3') {
715
+ setEnvironment('production');
716
+ await waitForEnter();
604
717
  continue;
605
718
  }
606
- rest.push(arg);
719
+
720
+ console.log('Invalid selection.');
607
721
  }
722
+ }
723
+
724
+ async function runAgentMenu() {
725
+ const action = (await ask(`\nStart Agent\n1) Claude\n2) Codex\n3) OpenCode\n4) Gemini\n5) Goose\n6) Continue\n0) Back\n\nSelect: `)).toLowerCase();
726
+
727
+ const map = {
728
+ '1': 'claude',
729
+ '2': 'codex',
730
+ '3': 'opencode',
731
+ '4': 'gemini',
732
+ '5': 'goose',
733
+ '6': 'continue'
734
+ };
608
735
 
609
- return { opts, rest };
736
+ if (action === '0' || action === 'b' || action === 'back') {
737
+ return;
738
+ }
739
+
740
+ const tool = map[action];
741
+ if (!tool) {
742
+ console.log('Invalid selection.');
743
+ await waitForEnter();
744
+ return;
745
+ }
746
+
747
+ console.log(`\nStarting ${TOOLS[tool].name}...\n`);
748
+ await launchAgent(tool);
610
749
  }
611
750
 
612
- function readStdin() {
613
- return new Promise((resolve) => {
614
- let data = '';
615
- process.stdin.setEncoding('utf8');
616
- process.stdin.on('data', (chunk) => data += chunk);
617
- process.stdin.on('end', () => resolve(data));
618
- process.stdin.on('error', () => resolve(''));
751
+ async function runInstallMenu() {
752
+ const missing = Object.entries(TOOLS).filter(([key]) => !isToolInstalled(key));
753
+ if (missing.length === 0) {
754
+ console.log('✅ All agents are already installed.');
755
+ await waitForEnter();
756
+ return;
757
+ }
758
+
759
+ const choices = [];
760
+ missing.forEach(([key], index) => {
761
+ choices.push(`${index + 1}) ${TOOLS[key].name}`);
619
762
  });
620
- }
621
763
 
622
- async function handoffToOrca(rawArgs) {
623
- const { opts, rest } = parseArgs(rawArgs);
624
- if (opts.help) {
625
- console.log(HANDOFF_HELP);
764
+ const action = (await ask(`\nInstall Agents\n${choices.join('\n')}\nA) Install all missing\n0) Back\n\nSelect: `)).toLowerCase();
765
+
766
+ if (action === '0' || action === 'b' || action === 'back') {
626
767
  return;
627
768
  }
628
769
 
629
- let task = opts.task || rest.join(' ').trim();
630
- if (!task && !process.stdin.isTTY) {
631
- task = (await readStdin()).trim();
770
+ if (action === 'a') {
771
+ await installTools();
772
+ await waitForEnter();
773
+ return;
632
774
  }
633
- if (!task) {
634
- console.log(HANDOFF_HELP);
635
- process.exit(1);
775
+
776
+ const index = parseInt(action, 10);
777
+ if (!Number.isInteger(index) || index < 1 || index > missing.length) {
778
+ console.log('Invalid selection.');
779
+ return;
636
780
  }
637
781
 
638
- try {
639
- const result = await createHandoff({
640
- task,
641
- repoUrl: opts.repo,
642
- ref: opts.ref,
643
- branchName: opts.branch,
644
- gitToken: opts['git-token'] || opts.gitToken,
645
- facadeToken: opts['facade-token'] || opts.facadeToken,
646
- orcaUrl: opts['orca-url'] || opts.orcaUrl,
647
- grazieToken: opts['grazie-token'] || opts.grazieToken,
648
- grazieEnvironment: opts['grazie-env'] || opts.grazieEnv,
649
- grazieModel: opts.model,
650
- autoStart: opts.autoStart !== false,
651
- shouldOpen: opts.open !== false,
652
- source: 'jbai-cli',
653
- });
654
- console.log('✅ Handoff created');
655
- console.log(`Environment: ${result.environmentId}`);
656
- console.log(`Open: ${result.environmentUrl}`);
657
- } catch (error) {
658
- console.error(`❌ ${error instanceof Error ? error.message : 'Handoff failed'}`);
659
- process.exit(1);
782
+ const tool = missing[index - 1][0];
783
+ await installTools(tool);
784
+ await waitForEnter();
785
+ }
786
+
787
+ async function runStandaloneMenu() {
788
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
789
+ console.log('Interactive mode requires a TTY terminal. Run: jbai help');
790
+ return;
791
+ }
792
+
793
+ while (true) {
794
+ const action = (await ask(`\n┌─ jbai control panel\n│1) Token management\n│2) Settings (environment)\n│3) Install agents\n│4) Wire configured clients\n│5) Doctor (health + install status)\n│6) Start agent chat\n│7) Update jbai-cli\n│8) Uninstall jbai-cli\n│9) Version\n│0) Exit\n└─ Select: `)).toLowerCase();
795
+
796
+ if (action === '0' || action === 'q' || action === 'exit') {
797
+ return;
798
+ }
799
+
800
+ if (action === '1') {
801
+ await runTokenMenu();
802
+ continue;
803
+ }
804
+
805
+ if (action === '2') {
806
+ await runSettingsMenu();
807
+ continue;
808
+ }
809
+
810
+ if (action === '3') {
811
+ await runInstallMenu();
812
+ continue;
813
+ }
814
+
815
+ if (action === '4') {
816
+ await setupClients();
817
+ await waitForEnter();
818
+ continue;
819
+ }
820
+
821
+ if (action === '5') {
822
+ doctor();
823
+ await waitForEnter();
824
+ continue;
825
+ }
826
+
827
+ if (action === '6') {
828
+ await runAgentMenu();
829
+ continue;
830
+ }
831
+
832
+ if (action === '7') {
833
+ await updateCli();
834
+ await waitForEnter();
835
+ continue;
836
+ }
837
+
838
+ if (action === '8') {
839
+ await uninstallCli();
840
+ continue;
841
+ }
842
+
843
+ if (action === '9') {
844
+ console.log(`jbai-cli v${VERSION}`);
845
+ console.log(`Node: ${process.version}`);
846
+ console.log(`Environment: ${environmentLabel()}`);
847
+ await waitForEnter();
848
+ continue;
849
+ }
850
+
851
+ console.log('Invalid selection.');
660
852
  }
661
853
  }
662
854
 
@@ -676,9 +868,6 @@ switch (command) {
676
868
  case 'test':
677
869
  testEndpoints();
678
870
  break;
679
- case 'handoff':
680
- handoffToOrca(args);
681
- break;
682
871
  case 'models':
683
872
  if (args[0]) {
684
873
  const allowed = new Set(['all', 'claude', 'codex', 'gemini', 'opencode', 'goose', 'continue']);
@@ -712,9 +901,14 @@ switch (command) {
712
901
  case 'help':
713
902
  case '--help':
714
903
  case '-h':
715
- case undefined:
716
904
  console.log(HELP);
717
905
  break;
906
+ case 'menu':
907
+ runStandaloneMenu();
908
+ break;
909
+ case undefined:
910
+ runStandaloneMenu();
911
+ break;
718
912
  case 'version':
719
913
  case '--version':
720
914
  case '-v':
@@ -15,12 +15,9 @@ const config = require('./config');
15
15
  const pkg = require('../package.json');
16
16
  const ALL_BINS = Object.keys(pkg.bin).sort();
17
17
 
18
- // Shortcuts = bins that have a dash after the tool name (jbai-codex-5.3, etc.)
19
- const TOOLS = ['claude', 'codex', 'gemini', 'opencode', 'goose', 'continue', 'council', 'proxy'];
20
-
21
18
  // jbai subcommands
22
19
  const JBAI_SUBCOMMANDS = [
23
- 'token', 'test', 'handoff', 'env', 'models', 'install', 'doctor',
20
+ 'menu', 'token', 'test', 'env', 'models', 'install', 'doctor',
24
21
  'status', 'proxy', 'help', 'version', 'completions',
25
22
  ];
26
23
 
@@ -51,7 +48,7 @@ function generateZsh() {
51
48
  shortcutDescriptions[bin] = bin;
52
49
  }
53
50
 
54
- return `#compdef jbai jbai-claude jbai-codex jbai-gemini jbai-opencode jbai-goose jbai-continue jbai-council jbai-proxy jbai-claude-opus jbai-claude-sonnet jbai-codex-5.2 jbai-codex-5.3 jbai-codex-rockhopper jbai-gemini-3.1 jbai-gemini-supernova jbai-opencode-rockhopper jbai-opencode-grok jbai-opencode-deepseek
51
+ return `#compdef jbai jbai-claude jbai-codex jbai-gemini jbai-opencode jbai-goose jbai-continue jbai-council jbai-proxy jbai-claude-opus jbai-claude-sonnet jbai-codex-5.2 jbai-codex-5.3 jbai-codex-5.4 jbai-codex-rockhopper jbai-gemini-3.1 jbai-gemini-supernova jbai-opencode-rockhopper jbai-opencode-grok jbai-opencode-deepseek
55
52
  # ─── jbai-cli zsh completions (auto-generated) ───
56
53
 
57
54
  # Complete model names after --model flag
@@ -65,9 +62,9 @@ ${UNIQUE_MODELS.map(m => ` '${m}'`).join('\n')}
65
62
  # Main jbai command completions
66
63
  _jbai() {
67
64
  local -a subcommands=(
65
+ 'menu:Open interactive control panel'
68
66
  'token:Show or manage authentication token'
69
67
  'test:Test API endpoints'
70
- 'handoff:Continue task in Orca Lab'
71
68
  'env:Switch environment (staging/production)'
72
69
  'models:List available models'
73
70
  'install:Install AI tools'
@@ -137,7 +134,7 @@ _jbai_tool_wrapper() {
137
134
  compdef _jbai jbai
138
135
  compdef _jbai_tool_wrapper jbai-claude jbai-codex jbai-gemini jbai-opencode jbai-goose jbai-continue
139
136
  compdef _jbai_tool_wrapper jbai-claude-opus jbai-claude-sonnet
140
- compdef _jbai_tool_wrapper jbai-codex-5.2 jbai-codex-5.3 jbai-codex-rockhopper
137
+ compdef _jbai_tool_wrapper jbai-codex-5.2 jbai-codex-5.3 jbai-codex-5.4 jbai-codex-rockhopper
141
138
  compdef _jbai_tool_wrapper jbai-gemini-3.1 jbai-gemini-supernova
142
139
  compdef _jbai_tool_wrapper jbai-opencode-rockhopper jbai-opencode-grok jbai-opencode-deepseek
143
140
  `;
@@ -205,7 +202,7 @@ _jbai_tool_completions() {
205
202
  complete -F _jbai_completions jbai
206
203
  complete -F _jbai_tool_completions jbai-claude jbai-codex jbai-gemini jbai-opencode jbai-goose jbai-continue
207
204
  complete -F _jbai_tool_completions jbai-claude-opus jbai-claude-sonnet
208
- complete -F _jbai_tool_completions jbai-codex-5.2 jbai-codex-5.3 jbai-codex-rockhopper
205
+ complete -F _jbai_tool_completions jbai-codex-5.2 jbai-codex-5.3 jbai-codex-5.4 jbai-codex-rockhopper
209
206
  complete -F _jbai_tool_completions jbai-gemini-3.1 jbai-gemini-supernova
210
207
  complete -F _jbai_tool_completions jbai-opencode-rockhopper jbai-opencode-grok jbai-opencode-deepseek
211
208
  `;