jbai-cli 1.9.6 → 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,8 +7,8 @@ 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');
11
+ const completions = require('../lib/completions');
12
12
 
13
13
  const TOOLS = {
14
14
  claude: {
@@ -49,23 +49,40 @@ const TOOLS = {
49
49
  }
50
50
  };
51
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
+
52
64
  const VERSION = require('../package.json').version;
53
65
 
54
66
  const HELP = `
55
67
  jbai-cli v${VERSION} - JetBrains AI Platform CLI Tools
56
68
 
69
+ Launch 'jbai' with no arguments to open the interactive terminal control panel.
70
+
57
71
  COMMANDS:
58
72
  jbai token Show token status
59
73
  jbai token set Set token interactively
60
74
  jbai token refresh Auto-refresh token via API
61
75
  jbai token refresh <token> Set new token (saves to ~/.jbai/token + ~/.zshrc)
62
76
  jbai test Test API endpoints (incl. Codex /responses)
63
- jbai handoff Continue task in Orca Lab
64
77
  jbai env [staging|production] Switch environment
65
78
  jbai models [tool] List Grazie models (all|claude|codex|gemini|opencode|goose|continue)
66
79
  jbai install Install all AI tools (claude, codex, gemini, opencode, goose, continue)
67
80
  jbai install claude Install specific tool
68
81
  jbai doctor Check which tools are installed
82
+ jbai completions Print zsh completions to stdout
83
+ jbai completions --install Add completions to ~/.zshrc
84
+ jbai completions --bash Print bash completions
85
+ jbai menu Open interactive control panel
69
86
  jbai help Show this help
70
87
 
71
88
  PROXY (for Codex Desktop, Cursor, etc.):
@@ -81,20 +98,34 @@ TOOL WRAPPERS:
81
98
  jbai-opencode Launch OpenCode with JetBrains AI
82
99
  jbai-council Launch Claude + Codex + OpenCode in tmux council mode
83
100
 
101
+ MODEL SHORTCUTS (super mode by default):
102
+ jbai-claude-opus Claude Code + Opus 4.6
103
+ jbai-claude-sonnet Claude Code + Sonnet 4.6
104
+ jbai-codex-5.4 Codex + GPT-5.4
105
+ jbai-codex-5.3 Codex + GPT-5.3
106
+ jbai-codex-5.2 Codex + GPT-5.2
107
+ jbai-codex-rockhopper Codex + Rockhopper Alpha (OpenAI EAP)
108
+ jbai-gemini-supernova Gemini + Supernova (Google EAP)
109
+ jbai-gemini-3.1 Gemini + 3.1 Pro Preview
110
+ jbai-opencode-rockhopper OpenCode + Rockhopper Alpha
111
+ jbai-opencode-grok OpenCode + Grok 4 (xAI)
112
+ jbai-opencode-deepseek OpenCode + DeepSeek R1
113
+
84
114
  SUPER MODE:
85
115
  Add --super (or --yolo or -s) to skip confirmations:
86
116
  jbai-claude --super # Skip permission prompts
87
117
  jbai-codex --super # Full auto mode
88
118
  jbai-gemini --super # Auto-confirm changes
119
+ (All model shortcuts above run in super mode by default)
89
120
 
90
121
  EXAMPLES:
91
122
  jbai token set # Set your token
92
123
  jbai-claude # Start Claude Code
93
124
  jbai-codex exec "explain code" # Run Codex task
94
- jbai-gemini # Start Gemini CLI
125
+ jbai-codex-rockhopper # Codex with Rockhopper (super mode)
126
+ jbai-gemini-supernova # Gemini with Supernova (super mode)
95
127
  jbai-council # Launch all 3 agents in tmux
96
128
  jbai-council --super # All agents in super mode
97
- jbai handoff --task "fix lint" # Handoff task to Orca Lab
98
129
 
99
130
  TOKEN:
100
131
  Get token: ${config.getEndpoints().tokenUrl}
@@ -506,139 +537,318 @@ async function installTools(toolKey) {
506
537
  console.log('Run: jbai doctor to verify');
507
538
  }
508
539
 
509
- const HANDOFF_HELP = `
510
- jbai handoff - Continue a task in Orca Lab
511
-
512
- Usage:
513
- jbai handoff --task "your task"
514
- jbai handoff "your task"
515
-
516
- Options:
517
- --task, -t Task description (or pass as positional)
518
- --repo, -r Git repo URL (defaults to origin remote)
519
- --ref Git ref (defaults to current branch)
520
- --branch, -b Working branch name for the agent
521
- --model, -m Claude model (default: ${config.MODELS.claude.default})
522
- --grazie-env, -e STAGING | PREPROD | PRODUCTION
523
- --grazie-token Override Grazie token (default: ~/.jbai/token)
524
- --git-token, -g GitHub token (default: GITHUB_TOKEN/GH_TOKEN)
525
- --facade-token, -f Facade JWT token (default: FACADE_JWT_TOKEN)
526
- --orca-url, -o Orca Lab URL (default: http://localhost:3000)
527
- --no-open Do not open the Orca Lab URL
528
- --no-auto-start Do not auto-start the agent task
529
- --help Show this help
530
- `;
540
+ function ask(question) {
541
+ return new Promise((resolve) => {
542
+ const rl = readline.createInterface({
543
+ input: process.stdin,
544
+ output: process.stdout
545
+ });
531
546
 
532
- function parseArgs(argv) {
533
- const opts = {};
534
- const rest = [];
535
- const shortMap = {
536
- t: 'task',
537
- r: 'repo',
538
- b: 'branch',
539
- m: 'model',
540
- e: 'grazie-env',
541
- g: 'git-token',
542
- f: 'facade-token',
543
- o: 'orca-url',
544
- h: 'help'
545
- };
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
+
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
+ }
546
595
 
547
- for (let i = 0; i < argv.length; i++) {
548
- const arg = argv[i];
549
- if (arg === '--') {
550
- rest.push(...argv.slice(i + 1));
551
- break;
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;
552
601
  }
553
- if (arg.startsWith('--')) {
554
- if (arg === '--no-open') {
555
- opts.open = false;
556
- continue;
557
- }
558
- if (arg === '--no-auto-start') {
559
- opts.autoStart = false;
560
- continue;
561
- }
562
- const key = arg.slice(2);
563
- const next = argv[i + 1];
564
- if (next && !next.startsWith('-')) {
565
- opts[key] = next;
566
- i++;
567
- } else {
568
- opts[key] = true;
569
- }
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();
570
674
  continue;
571
675
  }
572
- if (arg.startsWith('-') && arg.length === 2) {
573
- const short = arg.slice(1);
574
- const key = shortMap[short];
575
- if (!key) {
576
- rest.push(arg);
577
- continue;
578
- }
579
- const next = argv[i + 1];
580
- if (next && !next.startsWith('-')) {
581
- opts[key] = next;
582
- i++;
583
- } else {
584
- opts[key] = true;
585
- }
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();
586
717
  continue;
587
718
  }
588
- rest.push(arg);
719
+
720
+ console.log('Invalid selection.');
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
+ };
735
+
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;
589
745
  }
590
746
 
591
- return { opts, rest };
747
+ console.log(`\nStarting ${TOOLS[tool].name}...\n`);
748
+ await launchAgent(tool);
592
749
  }
593
750
 
594
- function readStdin() {
595
- return new Promise((resolve) => {
596
- let data = '';
597
- process.stdin.setEncoding('utf8');
598
- process.stdin.on('data', (chunk) => data += chunk);
599
- process.stdin.on('end', () => resolve(data));
600
- 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}`);
601
762
  });
602
- }
603
763
 
604
- async function handoffToOrca(rawArgs) {
605
- const { opts, rest } = parseArgs(rawArgs);
606
- if (opts.help) {
607
- 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') {
608
767
  return;
609
768
  }
610
769
 
611
- let task = opts.task || rest.join(' ').trim();
612
- if (!task && !process.stdin.isTTY) {
613
- task = (await readStdin()).trim();
770
+ if (action === 'a') {
771
+ await installTools();
772
+ await waitForEnter();
773
+ return;
614
774
  }
615
- if (!task) {
616
- console.log(HANDOFF_HELP);
617
- 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;
618
780
  }
619
781
 
620
- try {
621
- const result = await createHandoff({
622
- task,
623
- repoUrl: opts.repo,
624
- ref: opts.ref,
625
- branchName: opts.branch,
626
- gitToken: opts['git-token'] || opts.gitToken,
627
- facadeToken: opts['facade-token'] || opts.facadeToken,
628
- orcaUrl: opts['orca-url'] || opts.orcaUrl,
629
- grazieToken: opts['grazie-token'] || opts.grazieToken,
630
- grazieEnvironment: opts['grazie-env'] || opts.grazieEnv,
631
- grazieModel: opts.model,
632
- autoStart: opts.autoStart !== false,
633
- shouldOpen: opts.open !== false,
634
- source: 'jbai-cli',
635
- });
636
- console.log('✅ Handoff created');
637
- console.log(`Environment: ${result.environmentId}`);
638
- console.log(`Open: ${result.environmentUrl}`);
639
- } catch (error) {
640
- console.error(`❌ ${error instanceof Error ? error.message : 'Handoff failed'}`);
641
- 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.');
642
852
  }
643
853
  }
644
854
 
@@ -658,9 +868,6 @@ switch (command) {
658
868
  case 'test':
659
869
  testEndpoints();
660
870
  break;
661
- case 'handoff':
662
- handoffToOrca(args);
663
- break;
664
871
  case 'models':
665
872
  if (args[0]) {
666
873
  const allowed = new Set(['all', 'claude', 'codex', 'gemini', 'opencode', 'goose', 'continue']);
@@ -688,12 +895,20 @@ switch (command) {
688
895
  proxyMod.main();
689
896
  break;
690
897
  }
898
+ case 'completions':
899
+ completions.run(args);
900
+ break;
691
901
  case 'help':
692
902
  case '--help':
693
903
  case '-h':
694
- case undefined:
695
904
  console.log(HELP);
696
905
  break;
906
+ case 'menu':
907
+ runStandaloneMenu();
908
+ break;
909
+ case undefined:
910
+ runStandaloneMenu();
911
+ break;
697
912
  case 'version':
698
913
  case '--version':
699
914
  case '-v':