jbai-cli 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Use AI coding tools with your JetBrains AI subscription** — no separate API keys needed.
4
4
 
5
- One token, all tools: Claude Code, Codex, Aider, Gemini, OpenCode.
5
+ One token, all tools: Claude Code, Codex, Aider, OpenCode.
6
6
 
7
7
  ## Install
8
8
 
@@ -59,15 +59,9 @@ jbai-codex exec "explain this codebase"
59
59
  ### Aider
60
60
  ```bash
61
61
  jbai-aider
62
- ```
63
-
64
- ### Gemini
65
- ```bash
66
- # Interactive chat
67
- jbai-gemini
68
62
 
69
- # One-shot question
70
- jbai-gemini "What is Kubernetes?"
63
+ # Use Gemini models with Aider
64
+ jbai-aider --model gemini/gemini-2.5-pro
71
65
  ```
72
66
 
73
67
  ### OpenCode
@@ -110,8 +104,8 @@ jbai-claude --model claude-opus-4-1-20250805
110
104
  # Codex with GPT-5
111
105
  jbai-codex --model gpt-5-2025-08-07
112
106
 
113
- # Gemini with Pro
114
- jbai-gemini --model gemini-2.5-pro "Your question"
107
+ # Aider with Gemini Pro
108
+ jbai-aider --model gemini/gemini-2.5-pro
115
109
  ```
116
110
 
117
111
  ### Available Models
@@ -135,10 +129,10 @@ jbai-gemini --model gemini-2.5-pro "Your question"
135
129
  | `o3-2025-04-16` | Reasoning |
136
130
  | `o3-mini-2025-01-31` | |
137
131
 
138
- **Gemini (Google)**
132
+ **Gemini (Google)** - Use with Aider: `jbai-aider --model gemini/<model>`
139
133
  | Model | Notes |
140
134
  |-------|-------|
141
- | `gemini-2.5-flash` | Default, fast |
135
+ | `gemini-2.5-flash` | Fast |
142
136
  | `gemini-2.5-pro` | More capable |
143
137
  | `gemini-3-pro-preview` | Preview |
144
138
  | `gemini-3-flash-preview` | Preview |
@@ -152,12 +146,30 @@ jbai-gemini --model gemini-2.5-pro "Your question"
152
146
  | `jbai token set` | Set/update token |
153
147
  | `jbai test` | Test API connections |
154
148
  | `jbai models` | List all models |
149
+ | `jbai install` | Install all AI tools |
150
+ | `jbai install claude` | Install specific tool |
151
+ | `jbai doctor` | Check tool installation status |
155
152
  | `jbai env staging` | Use staging environment |
156
153
  | `jbai env production` | Use production environment |
157
154
 
158
- ## Prerequisites
155
+ ## Installing AI Tools
156
+
157
+ jbai-cli can install the underlying tools for you:
158
+
159
+ ```bash
160
+ # Install all tools at once
161
+ jbai install
162
+
163
+ # Install specific tool
164
+ jbai install claude
165
+ jbai install codex
166
+ jbai install aider
167
+
168
+ # Check what's installed
169
+ jbai doctor
170
+ ```
159
171
 
160
- Install the underlying tools you want to use:
172
+ ### Manual Installation
161
173
 
162
174
  | Tool | Install Command |
163
175
  |------|-----------------|
@@ -165,7 +177,6 @@ Install the underlying tools you want to use:
165
177
  | Codex | `npm i -g @openai/codex` |
166
178
  | Aider | `pip install aider-chat` |
167
179
  | OpenCode | `go install github.com/opencode-ai/opencode@latest` |
168
- | Gemini | Built-in, no install needed |
169
180
 
170
181
  ## Token Management
171
182
 
package/bin/jbai-aider.js CHANGED
@@ -49,7 +49,10 @@ const child = spawn('aider', aiderArgs, {
49
49
 
50
50
  child.on('error', (err) => {
51
51
  if (err.code === 'ENOENT') {
52
- console.error('❌ Aider not found. Install: pip install aider-chat');
52
+ const tool = config.TOOLS.aider;
53
+ console.error(`❌ ${tool.name} not found.\n`);
54
+ console.error(`Install with: ${tool.install}`);
55
+ console.error(`Or run: jbai install aider`);
53
56
  } else {
54
57
  console.error(`Error: ${err.message}`);
55
58
  }
@@ -47,7 +47,10 @@ const child = spawn('claude', finalArgs, {
47
47
 
48
48
  child.on('error', (err) => {
49
49
  if (err.code === 'ENOENT') {
50
- console.error('❌ Claude Code not found. Install: npm install -g @anthropic-ai/claude-code');
50
+ const tool = config.TOOLS.claude;
51
+ console.error(`❌ ${tool.name} not found.\n`);
52
+ console.error(`Install with: ${tool.install}`);
53
+ console.error(`Or run: jbai install claude`);
51
54
  } else {
52
55
  console.error(`Error: ${err.message}`);
53
56
  }
package/bin/jbai-codex.js CHANGED
@@ -82,7 +82,10 @@ const child = spawn('codex', finalArgs, {
82
82
 
83
83
  child.on('error', (err) => {
84
84
  if (err.code === 'ENOENT') {
85
- console.error('❌ Codex not found. Install: npm install -g @openai/codex');
85
+ const tool = config.TOOLS.codex;
86
+ console.error(`❌ ${tool.name} not found.\n`);
87
+ console.error(`Install with: ${tool.install}`);
88
+ console.error(`Or run: jbai install codex`);
86
89
  } else {
87
90
  console.error(`Error: ${err.message}`);
88
91
  }
@@ -48,7 +48,10 @@ const child = spawn('opencode', finalArgs, {
48
48
 
49
49
  child.on('error', (err) => {
50
50
  if (err.code === 'ENOENT') {
51
- console.error('❌ OpenCode not found. Install: go install github.com/opencode-ai/opencode@latest');
51
+ const tool = config.TOOLS.opencode;
52
+ console.error(`❌ ${tool.name} not found.\n`);
53
+ console.error(`Install with: ${tool.install}`);
54
+ console.error(`Or run: jbai install opencode`);
52
55
  } else {
53
56
  console.error(`Error: ${err.message}`);
54
57
  }
package/bin/jbai.js CHANGED
@@ -1,10 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { spawn } = require('child_process');
3
+ const { spawn, execSync } = require('child_process');
4
4
  const readline = require('readline');
5
5
  const https = require('https');
6
6
  const config = require('../lib/config');
7
7
 
8
+ const TOOLS = {
9
+ claude: {
10
+ name: 'Claude Code',
11
+ command: 'claude',
12
+ install: 'npm install -g @anthropic-ai/claude-code',
13
+ check: 'claude --version'
14
+ },
15
+ codex: {
16
+ name: 'Codex CLI',
17
+ command: 'codex',
18
+ install: 'npm install -g @openai/codex',
19
+ check: 'codex --version'
20
+ },
21
+ aider: {
22
+ name: 'Aider',
23
+ command: 'aider',
24
+ install: 'pip install aider-chat',
25
+ check: 'aider --version'
26
+ },
27
+ opencode: {
28
+ name: 'OpenCode',
29
+ command: 'opencode',
30
+ install: 'go install github.com/opencode-ai/opencode@latest',
31
+ check: 'opencode --version'
32
+ }
33
+ };
34
+
8
35
  const VERSION = require('../package.json').version;
9
36
 
10
37
  const HELP = `
@@ -17,13 +44,15 @@ COMMANDS:
17
44
  jbai test Test all API endpoints
18
45
  jbai env [staging|production] Switch environment
19
46
  jbai models List available models
47
+ jbai install Install all AI tools (claude, codex, aider)
48
+ jbai install claude Install specific tool
49
+ jbai doctor Check which tools are installed
20
50
  jbai help Show this help
21
51
 
22
52
  TOOL WRAPPERS:
23
53
  jbai-claude Launch Claude Code with JetBrains AI
24
54
  jbai-codex Launch Codex CLI with JetBrains AI
25
55
  jbai-aider Launch Aider with JetBrains AI
26
- jbai-gemini Launch Gemini with JetBrains AI
27
56
  jbai-opencode Launch OpenCode with JetBrains AI
28
57
 
29
58
  SUPER MODE:
@@ -218,6 +247,131 @@ function setEnvironment(env) {
218
247
  console.log(` Token URL: ${config.ENDPOINTS[env].tokenUrl}`);
219
248
  }
220
249
 
250
+ function isToolInstalled(toolKey) {
251
+ const tool = TOOLS[toolKey];
252
+ if (!tool) return false;
253
+ try {
254
+ execSync(`which ${tool.command}`, { stdio: 'ignore' });
255
+ return true;
256
+ } catch {
257
+ return false;
258
+ }
259
+ }
260
+
261
+ function doctor() {
262
+ console.log('Tool Status:\n');
263
+
264
+ const token = config.getToken();
265
+ console.log(`Token: ${token ? '✅ Set' : '❌ Not set'}`);
266
+ if (token) {
267
+ console.log(`Environment: ${config.getEnvironment()}`);
268
+ console.log(`Expired: ${config.isTokenExpired(token) ? '⚠️ Yes' : '✅ No'}`);
269
+ }
270
+ console.log('');
271
+
272
+ for (const [key, tool] of Object.entries(TOOLS)) {
273
+ const installed = isToolInstalled(key);
274
+ const status = installed ? '✅' : '❌';
275
+ console.log(`${status} ${tool.name.padEnd(12)} ${installed ? 'Installed' : 'Not installed'}`);
276
+ if (!installed) {
277
+ console.log(` Install: ${tool.install}`);
278
+ }
279
+ }
280
+
281
+ console.log('\nTip: For Gemini models, use Aider: jbai-aider --model gemini/gemini-2.5-pro');
282
+ }
283
+
284
+ async function installTools(toolKey) {
285
+ const rl = readline.createInterface({
286
+ input: process.stdin,
287
+ output: process.stdout
288
+ });
289
+
290
+ const askConfirm = (question) => new Promise(resolve => {
291
+ rl.question(question, answer => resolve(answer.toLowerCase() === 'y' || answer === ''));
292
+ });
293
+
294
+ if (toolKey && toolKey !== 'all') {
295
+ // Install specific tool
296
+ const tool = TOOLS[toolKey];
297
+ if (!tool) {
298
+ console.log(`Unknown tool: ${toolKey}`);
299
+ console.log(`Available: ${Object.keys(TOOLS).join(', ')}`);
300
+ rl.close();
301
+ return;
302
+ }
303
+
304
+ if (isToolInstalled(toolKey)) {
305
+ console.log(`✅ ${tool.name} is already installed`);
306
+ rl.close();
307
+ return;
308
+ }
309
+
310
+ console.log(`Installing ${tool.name}...`);
311
+ console.log(`Running: ${tool.install}\n`);
312
+
313
+ try {
314
+ execSync(tool.install, { stdio: 'inherit' });
315
+ console.log(`\n✅ ${tool.name} installed successfully`);
316
+ } catch (e) {
317
+ console.log(`\n❌ Failed to install ${tool.name}`);
318
+ }
319
+ rl.close();
320
+ return;
321
+ }
322
+
323
+ // Install all tools
324
+ console.log('This will install the following tools:\n');
325
+
326
+ const toInstall = [];
327
+ for (const [key, tool] of Object.entries(TOOLS)) {
328
+ const installed = isToolInstalled(key);
329
+ if (installed) {
330
+ console.log(`✅ ${tool.name} - already installed`);
331
+ } else {
332
+ console.log(`📦 ${tool.name} - will install`);
333
+ console.log(` ${tool.install}`);
334
+ toInstall.push({ key, tool });
335
+ }
336
+ }
337
+
338
+ if (toInstall.length === 0) {
339
+ console.log('\n✅ All tools are already installed!');
340
+ rl.close();
341
+ return;
342
+ }
343
+
344
+ console.log('');
345
+ const confirm = await askConfirm(`Install ${toInstall.length} tool(s)? [Y/n] `);
346
+
347
+ if (!confirm) {
348
+ console.log('Cancelled');
349
+ rl.close();
350
+ return;
351
+ }
352
+
353
+ for (const { key, tool } of toInstall) {
354
+ console.log(`\n📦 Installing ${tool.name}...`);
355
+ console.log(`Running: ${tool.install}\n`);
356
+
357
+ try {
358
+ execSync(tool.install, { stdio: 'inherit' });
359
+ console.log(`✅ ${tool.name} installed`);
360
+ } catch (e) {
361
+ console.log(`❌ Failed to install ${tool.name}`);
362
+ const skip = await askConfirm('Continue with other tools? [Y/n] ');
363
+ if (!skip) {
364
+ rl.close();
365
+ return;
366
+ }
367
+ }
368
+ }
369
+
370
+ rl.close();
371
+ console.log('\n✅ Installation complete!');
372
+ console.log('Run: jbai doctor to verify');
373
+ }
374
+
221
375
  // Main
222
376
  const [,, command, ...args] = process.argv;
223
377
 
@@ -238,6 +392,13 @@ switch (command) {
238
392
  case 'env':
239
393
  setEnvironment(args[0]);
240
394
  break;
395
+ case 'install':
396
+ installTools(args[0]);
397
+ break;
398
+ case 'doctor':
399
+ case 'status':
400
+ doctor();
401
+ break;
241
402
  case 'help':
242
403
  case '--help':
243
404
  case '-h':
package/lib/config.js CHANGED
@@ -122,12 +122,36 @@ function isTokenExpired(token) {
122
122
  return expiry < new Date();
123
123
  }
124
124
 
125
+ const TOOLS = {
126
+ claude: {
127
+ name: 'Claude Code',
128
+ command: 'claude',
129
+ install: 'npm install -g @anthropic-ai/claude-code'
130
+ },
131
+ codex: {
132
+ name: 'Codex CLI',
133
+ command: 'codex',
134
+ install: 'npm install -g @openai/codex'
135
+ },
136
+ aider: {
137
+ name: 'Aider',
138
+ command: 'aider',
139
+ install: 'pip install aider-chat'
140
+ },
141
+ opencode: {
142
+ name: 'OpenCode',
143
+ command: 'opencode',
144
+ install: 'go install github.com/opencode-ai/opencode@latest'
145
+ }
146
+ };
147
+
125
148
  module.exports = {
126
149
  CONFIG_DIR,
127
150
  TOKEN_FILE,
128
151
  CONFIG_FILE,
129
152
  ENDPOINTS,
130
153
  MODELS,
154
+ TOOLS,
131
155
  ensureConfigDir,
132
156
  getToken,
133
157
  setToken,
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "jbai-cli",
3
- "version": "1.1.0",
4
- "description": "CLI wrappers to use AI coding tools (Claude Code, Codex, Aider, Gemini) with JetBrains AI Platform",
3
+ "version": "1.2.0",
4
+ "description": "CLI wrappers to use AI coding tools (Claude Code, Codex, Aider, OpenCode) with JetBrains AI Platform",
5
5
  "keywords": [
6
6
  "jetbrains",
7
7
  "ai",
8
8
  "claude",
9
9
  "codex",
10
10
  "aider",
11
- "gemini",
11
+ "opencode",
12
12
  "cli",
13
13
  "openai",
14
14
  "anthropic"
@@ -28,7 +28,6 @@
28
28
  "jbai-claude": "./bin/jbai-claude.js",
29
29
  "jbai-codex": "./bin/jbai-codex.js",
30
30
  "jbai-aider": "./bin/jbai-aider.js",
31
- "jbai-gemini": "./bin/jbai-gemini.js",
32
31
  "jbai-opencode": "./bin/jbai-opencode.js"
33
32
  },
34
33
  "files": [
@@ -1,136 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const https = require('https');
4
- const readline = require('readline');
5
- const config = require('../lib/config');
6
-
7
- const token = config.getToken();
8
- if (!token) {
9
- console.error('❌ No token found. Run: jbai token set');
10
- process.exit(1);
11
- }
12
-
13
- if (config.isTokenExpired(token)) {
14
- console.error('⚠️ Token expired. Run: jbai token refresh');
15
- process.exit(1);
16
- }
17
-
18
- const endpoints = config.getEndpoints();
19
- const args = process.argv.slice(2);
20
-
21
- // Get model from args or use default
22
- let model = config.MODELS.gemini.default;
23
- const modelIdx = args.indexOf('--model');
24
- if (modelIdx !== -1 && args[modelIdx + 1]) {
25
- model = args[modelIdx + 1];
26
- }
27
-
28
- // If prompt provided as argument, run one-shot
29
- const prompt = args.filter((a, i) =>
30
- a !== '--model' && (modelIdx === -1 || i !== modelIdx + 1)
31
- ).join(' ');
32
-
33
- if (prompt) {
34
- runPrompt(prompt, model);
35
- } else {
36
- runInteractive(model);
37
- }
38
-
39
- async function runPrompt(prompt, model) {
40
- const url = `${endpoints.google}/v1/projects/default/locations/default/publishers/google/models/${model}:generateContent`;
41
-
42
- try {
43
- const result = await httpPost(url, {
44
- contents: [{ role: 'user', parts: [{ text: prompt }] }]
45
- }, { 'Grazie-Authenticate-JWT': token });
46
-
47
- if (result.candidates && result.candidates[0]) {
48
- console.log(result.candidates[0].content.parts[0].text);
49
- } else if (result.error) {
50
- console.error(`Error: ${result.error.message}`);
51
- }
52
- } catch (e) {
53
- console.error(`Error: ${e.message}`);
54
- }
55
- }
56
-
57
- async function runInteractive(model) {
58
- console.log(`Gemini Interactive (${model})`);
59
- console.log('Type your message, press Enter to send. Ctrl+C to exit.\n');
60
-
61
- const rl = readline.createInterface({
62
- input: process.stdin,
63
- output: process.stdout
64
- });
65
-
66
- const history = [];
67
-
68
- const askQuestion = () => {
69
- rl.question('You: ', async (input) => {
70
- if (!input.trim()) {
71
- askQuestion();
72
- return;
73
- }
74
-
75
- history.push({ role: 'user', parts: [{ text: input }] });
76
-
77
- const url = `${endpoints.google}/v1/projects/default/locations/default/publishers/google/models/${model}:generateContent`;
78
-
79
- try {
80
- const result = await httpPost(url, { contents: history }, { 'Grazie-Authenticate-JWT': token });
81
-
82
- if (result.candidates && result.candidates[0]) {
83
- const response = result.candidates[0].content.parts[0].text;
84
- history.push({ role: 'model', parts: [{ text: response }] });
85
- console.log(`\nGemini: ${response}\n`);
86
- } else if (result.error) {
87
- console.error(`Error: ${result.error.message}\n`);
88
- }
89
- } catch (e) {
90
- console.error(`Error: ${e.message}\n`);
91
- }
92
-
93
- askQuestion();
94
- });
95
- };
96
-
97
- rl.on('close', () => {
98
- console.log('\nGoodbye!');
99
- process.exit(0);
100
- });
101
-
102
- askQuestion();
103
- }
104
-
105
- function httpPost(url, body, headers) {
106
- return new Promise((resolve, reject) => {
107
- const urlObj = new URL(url);
108
- const data = JSON.stringify(body);
109
-
110
- const req = https.request({
111
- hostname: urlObj.hostname,
112
- port: 443,
113
- path: urlObj.pathname,
114
- method: 'POST',
115
- headers: {
116
- 'Content-Type': 'application/json',
117
- 'Content-Length': Buffer.byteLength(data),
118
- ...headers
119
- }
120
- }, (res) => {
121
- let body = '';
122
- res.on('data', chunk => body += chunk);
123
- res.on('end', () => {
124
- try {
125
- resolve(JSON.parse(body));
126
- } catch {
127
- reject(new Error('Invalid response'));
128
- }
129
- });
130
- });
131
-
132
- req.on('error', reject);
133
- req.write(data);
134
- req.end();
135
- });
136
- }