jbai-cli 1.6.0 → 1.8.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 +116 -4
- package/bin/jbai-claude.js +58 -65
- package/bin/jbai-codex.js +97 -91
- package/bin/jbai-gemini.js +53 -60
- package/bin/jbai-opencode.js +147 -151
- package/bin/jbai-proxy.js +857 -0
- package/bin/jbai.js +60 -6
- package/bin/test-models.js +56 -3
- package/lib/config.js +107 -14
- package/lib/ensure-token.js +31 -0
- package/package.json +2 -1
package/bin/jbai.js
CHANGED
|
@@ -41,7 +41,7 @@ jbai-cli v${VERSION} - JetBrains AI Platform CLI Tools
|
|
|
41
41
|
COMMANDS:
|
|
42
42
|
jbai token Show token status
|
|
43
43
|
jbai token set Set token interactively
|
|
44
|
-
jbai token refresh
|
|
44
|
+
jbai token refresh Auto-refresh token via API (no UI needed)
|
|
45
45
|
jbai test Test API endpoints (incl. Codex /responses)
|
|
46
46
|
jbai handoff Continue task in Orca Lab
|
|
47
47
|
jbai env [staging|production] Switch environment
|
|
@@ -51,6 +51,12 @@ COMMANDS:
|
|
|
51
51
|
jbai doctor Check which tools are installed
|
|
52
52
|
jbai help Show this help
|
|
53
53
|
|
|
54
|
+
PROXY (for Codex Desktop, Cursor, etc.):
|
|
55
|
+
jbai proxy setup One-liner setup: proxy + Codex + shell env
|
|
56
|
+
jbai proxy --daemon Start proxy in background
|
|
57
|
+
jbai proxy stop Stop background proxy
|
|
58
|
+
jbai proxy status Check proxy status
|
|
59
|
+
|
|
54
60
|
TOOL WRAPPERS:
|
|
55
61
|
jbai-claude Launch Claude Code with JetBrains AI
|
|
56
62
|
jbai-codex Launch Codex CLI with JetBrains AI
|
|
@@ -92,13 +98,21 @@ async function showTokenStatus() {
|
|
|
92
98
|
const expiry = config.getTokenExpiry(token);
|
|
93
99
|
if (expiry) {
|
|
94
100
|
const now = new Date();
|
|
95
|
-
const
|
|
101
|
+
const hoursLeft = Math.round((expiry - now) / (1000 * 60 * 60));
|
|
96
102
|
|
|
97
103
|
if (config.isTokenExpired(token)) {
|
|
104
|
+
const refreshable = config.isTokenRefreshable(token);
|
|
98
105
|
console.log(`⚠️ Token EXPIRED: ${expiry.toLocaleString()}`);
|
|
99
|
-
|
|
106
|
+
if (refreshable) {
|
|
107
|
+
console.log(` Run: jbai token refresh (auto-refresh via API)`);
|
|
108
|
+
} else {
|
|
109
|
+
console.log(` Token expired >2 weeks ago. Get a new one: ${config.getEndpoints().tokenUrl}`);
|
|
110
|
+
}
|
|
111
|
+
} else if (config.isTokenExpiringSoon(token)) {
|
|
112
|
+
console.log(`⏳ Expiring soon: ${expiry.toLocaleString()} (${hoursLeft}h left)`);
|
|
113
|
+
console.log(` Run: jbai token refresh (auto-refresh via API)`);
|
|
100
114
|
} else {
|
|
101
|
-
console.log(`✅ Expires: ${expiry.toLocaleString()} (${
|
|
115
|
+
console.log(`✅ Expires: ${expiry.toLocaleString()} (${hoursLeft}h left)`);
|
|
102
116
|
}
|
|
103
117
|
}
|
|
104
118
|
}
|
|
@@ -131,13 +145,44 @@ async function setToken() {
|
|
|
131
145
|
});
|
|
132
146
|
}
|
|
133
147
|
|
|
134
|
-
async function
|
|
148
|
+
async function refreshTokenCommand() {
|
|
135
149
|
const token = config.getToken();
|
|
136
150
|
if (!token) {
|
|
137
151
|
console.log('❌ No token found. Run: jbai token set');
|
|
138
152
|
return;
|
|
139
153
|
}
|
|
140
154
|
|
|
155
|
+
if (!config.isTokenExpired(token) && !config.isTokenExpiringSoon(token)) {
|
|
156
|
+
const expiry = config.getTokenExpiry(token);
|
|
157
|
+
const hoursLeft = Math.round((expiry - Date.now()) / (1000 * 60 * 60));
|
|
158
|
+
console.log(`✅ Token is still valid (${hoursLeft}h left). Refreshing anyway...`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (config.isTokenExpired(token) && !config.isTokenRefreshable(token)) {
|
|
162
|
+
console.log('❌ Token expired more than 2 weeks ago. Cannot auto-refresh.');
|
|
163
|
+
console.log(` Get a new token: ${config.getEndpoints().tokenUrl}`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
console.log('🔄 Refreshing token via API...');
|
|
169
|
+
const newToken = await config.refreshTokenApi(token);
|
|
170
|
+
config.setToken(newToken);
|
|
171
|
+
console.log('✅ Token refreshed successfully!');
|
|
172
|
+
showTokenStatus();
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.log(`❌ Refresh failed: ${e.message}`);
|
|
175
|
+
console.log(` Get a new token manually: ${config.getEndpoints().tokenUrl}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function testEndpoints() {
|
|
180
|
+
const token = await config.getValidToken();
|
|
181
|
+
if (!token) {
|
|
182
|
+
console.log('❌ No token found. Run: jbai token set');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
141
186
|
const endpoints = config.getEndpoints();
|
|
142
187
|
console.log(`Testing JetBrains AI Platform (${config.getEnvironment()})\n`);
|
|
143
188
|
const defaultOpenAIModel = config.MODELS.openai.default;
|
|
@@ -561,8 +606,10 @@ const [,, command, ...args] = process.argv;
|
|
|
561
606
|
|
|
562
607
|
switch (command) {
|
|
563
608
|
case 'token':
|
|
564
|
-
if (args[0] === 'set'
|
|
609
|
+
if (args[0] === 'set') {
|
|
565
610
|
setToken();
|
|
611
|
+
} else if (args[0] === 'refresh') {
|
|
612
|
+
refreshTokenCommand();
|
|
566
613
|
} else {
|
|
567
614
|
showTokenStatus();
|
|
568
615
|
}
|
|
@@ -586,6 +633,13 @@ switch (command) {
|
|
|
586
633
|
case 'status':
|
|
587
634
|
doctor();
|
|
588
635
|
break;
|
|
636
|
+
case 'proxy': {
|
|
637
|
+
const proxyMod = require('./jbai-proxy');
|
|
638
|
+
// Re-set argv so jbai-proxy sees the right args
|
|
639
|
+
process.argv = [process.argv[0], process.argv[1], ...args];
|
|
640
|
+
proxyMod.main();
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
589
643
|
case 'help':
|
|
590
644
|
case '--help':
|
|
591
645
|
case '-h':
|
package/bin/test-models.js
CHANGED
|
@@ -154,6 +154,33 @@ async function testGeminiModel(model) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
async function testCodexModel(model) {
|
|
158
|
+
try {
|
|
159
|
+
// Codex models use the OpenAI Responses API (not chat/completions)
|
|
160
|
+
const result = await httpPost(
|
|
161
|
+
`${endpoints.openai}/responses`,
|
|
162
|
+
{
|
|
163
|
+
model: model,
|
|
164
|
+
input: 'Say OK',
|
|
165
|
+
max_output_tokens: 50
|
|
166
|
+
},
|
|
167
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (result.status === 200) {
|
|
171
|
+
return { success: true, message: 'OK' };
|
|
172
|
+
} else if (result.status === 404) {
|
|
173
|
+
return { success: false, message: `404 Not Found`, error: result.data.error?.message || 'Model not found' };
|
|
174
|
+
} else if (result.status === 400) {
|
|
175
|
+
return { success: false, message: `400 Bad Request`, error: result.data.error?.message || 'Invalid request' };
|
|
176
|
+
} else {
|
|
177
|
+
return { success: false, message: `Status ${result.status}`, error: result.data.error?.message || JSON.stringify(result.data).substring(0, 100) };
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
return { success: false, message: 'Error', error: e.message };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
157
184
|
async function runTests() {
|
|
158
185
|
console.log(`\n${'='.repeat(70)}`);
|
|
159
186
|
console.log(`${colors.cyan}JBAI-CLI E2E MODEL COMPATIBILITY TESTING${colors.reset}`);
|
|
@@ -162,6 +189,7 @@ async function runTests() {
|
|
|
162
189
|
|
|
163
190
|
const results = {
|
|
164
191
|
openai: { working: [], failing: [] },
|
|
192
|
+
codex: { working: [], failing: [] },
|
|
165
193
|
anthropic: { working: [], failing: [] },
|
|
166
194
|
gemini: { working: [], failing: [] }
|
|
167
195
|
};
|
|
@@ -198,6 +226,26 @@ async function runTests() {
|
|
|
198
226
|
}
|
|
199
227
|
}
|
|
200
228
|
|
|
229
|
+
// Test Codex-only models (responses API only, not in openai.available)
|
|
230
|
+
const openaiSet = new Set(config.MODELS.openai.available);
|
|
231
|
+
const codexOnlyModels = config.MODELS.codex.available.filter(m => !openaiSet.has(m));
|
|
232
|
+
if (codexOnlyModels.length > 0) {
|
|
233
|
+
console.log(`\n${colors.cyan}Testing Codex-only models (jbai-codex, responses API)...${colors.reset}`);
|
|
234
|
+
console.log('-'.repeat(50));
|
|
235
|
+
for (const model of codexOnlyModels) {
|
|
236
|
+
process.stdout.write(` ${model.padEnd(35)} `);
|
|
237
|
+
const result = await testCodexModel(model);
|
|
238
|
+
if (result.success) {
|
|
239
|
+
console.log(`${colors.green}✓ ${result.message}${colors.reset}`);
|
|
240
|
+
results.codex.working.push(model);
|
|
241
|
+
} else {
|
|
242
|
+
console.log(`${colors.red}✗ ${result.message}${colors.reset}`);
|
|
243
|
+
console.log(` ${colors.dim}${result.error}${colors.reset}`);
|
|
244
|
+
results.codex.failing.push({ model, error: result.error });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
201
249
|
// Test Gemini models
|
|
202
250
|
console.log(`\n${colors.cyan}Testing Gemini models (jbai-gemini)...${colors.reset}`);
|
|
203
251
|
console.log('-'.repeat(50));
|
|
@@ -220,16 +268,17 @@ async function runTests() {
|
|
|
220
268
|
console.log(`${'='.repeat(70)}`);
|
|
221
269
|
|
|
222
270
|
const totalWorking = results.anthropic.working.length + results.openai.working.length +
|
|
223
|
-
results.gemini.working.length;
|
|
271
|
+
results.codex.working.length + results.gemini.working.length;
|
|
224
272
|
const totalFailing = results.anthropic.failing.length + results.openai.failing.length +
|
|
225
|
-
results.gemini.failing.length;
|
|
273
|
+
results.codex.failing.length + results.gemini.failing.length;
|
|
226
274
|
|
|
227
275
|
console.log(`\n${colors.green}Working Models: ${totalWorking}${colors.reset}`);
|
|
228
276
|
console.log(`${colors.red}Failing Models: ${totalFailing}${colors.reset}`);
|
|
229
277
|
|
|
230
278
|
console.log(`\n${colors.cyan}By Provider:${colors.reset}`);
|
|
231
279
|
console.log(` Claude (Anthropic): ${colors.green}${results.anthropic.working.length}${colors.reset} working, ${colors.red}${results.anthropic.failing.length}${colors.reset} failing`);
|
|
232
|
-
console.log(` OpenAI/GPT:
|
|
280
|
+
console.log(` OpenAI/GPT (Chat): ${colors.green}${results.openai.working.length}${colors.reset} working, ${colors.red}${results.openai.failing.length}${colors.reset} failing`);
|
|
281
|
+
console.log(` Codex (Responses): ${colors.green}${results.codex.working.length}${colors.reset} working, ${colors.red}${results.codex.failing.length}${colors.reset} failing`);
|
|
233
282
|
console.log(` Gemini (Google): ${colors.green}${results.gemini.working.length}${colors.reset} working, ${colors.red}${results.gemini.failing.length}${colors.reset} failing`);
|
|
234
283
|
|
|
235
284
|
if (totalFailing > 0) {
|
|
@@ -243,6 +292,10 @@ async function runTests() {
|
|
|
243
292
|
console.log(`\n OpenAI models:`);
|
|
244
293
|
results.openai.failing.forEach(f => console.log(` - ${f.model}`));
|
|
245
294
|
}
|
|
295
|
+
if (results.codex.failing.length > 0) {
|
|
296
|
+
console.log(`\n Codex models:`);
|
|
297
|
+
results.codex.failing.forEach(f => console.log(` - ${f.model}`));
|
|
298
|
+
}
|
|
246
299
|
if (results.gemini.failing.length > 0) {
|
|
247
300
|
console.log(`\n Gemini models:`);
|
|
248
301
|
results.gemini.failing.forEach(f => console.log(` - ${f.model}`));
|
package/lib/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const https = require('https');
|
|
4
5
|
|
|
5
6
|
const CONFIG_DIR = path.join(os.homedir(), '.jbai');
|
|
6
7
|
const TOKEN_FILE = path.join(CONFIG_DIR, 'token');
|
|
@@ -32,7 +33,9 @@ const MODELS = {
|
|
|
32
33
|
claude: {
|
|
33
34
|
default: 'claude-sonnet-4-5-20250929',
|
|
34
35
|
available: [
|
|
35
|
-
// Claude 4.
|
|
36
|
+
// Claude 4.6 series (latest)
|
|
37
|
+
'claude-opus-4-6',
|
|
38
|
+
// Claude 4.5 series
|
|
36
39
|
'claude-opus-4-5-20251101',
|
|
37
40
|
'claude-sonnet-4-5-20250929',
|
|
38
41
|
'claude-haiku-4-5-20251001',
|
|
@@ -75,23 +78,26 @@ const MODELS = {
|
|
|
75
78
|
]
|
|
76
79
|
},
|
|
77
80
|
// Codex CLI uses OpenAI models via the "responses" API (wire_api = "responses")
|
|
78
|
-
//
|
|
81
|
+
// Includes chat-capable models PLUS codex-only models (responses API only)
|
|
79
82
|
codex: {
|
|
80
|
-
default: '
|
|
83
|
+
default: 'gpt-5.3-codex-api-preview',
|
|
81
84
|
available: [
|
|
82
|
-
//
|
|
83
|
-
'
|
|
84
|
-
|
|
85
|
-
'o4-mini-2025-04-16',
|
|
86
|
-
// GPT-5.x series
|
|
85
|
+
// Codex-specific models (responses API only, NOT available via chat/completions)
|
|
86
|
+
'gpt-5.3-codex-api-preview',
|
|
87
|
+
// GPT-5.x chat models (also work via responses API)
|
|
87
88
|
'gpt-5.2-2025-12-11',
|
|
88
89
|
'gpt-5.2',
|
|
89
90
|
'gpt-5.1-2025-11-13',
|
|
90
91
|
'gpt-5-2025-08-07',
|
|
91
|
-
|
|
92
|
-
'gpt-
|
|
93
|
-
'gpt-
|
|
94
|
-
'gpt-
|
|
92
|
+
'gpt-5.2-codex',
|
|
93
|
+
'gpt-5.2-pro-2025-12-11',
|
|
94
|
+
'gpt-5.1-codex-max',
|
|
95
|
+
'gpt-5.1-codex',
|
|
96
|
+
'gpt-5.1-codex-mini',
|
|
97
|
+
'gpt-5-codex',
|
|
98
|
+
// O-series (also work via responses API)
|
|
99
|
+
'o4-mini-2025-04-16',
|
|
100
|
+
'o3-2025-04-16'
|
|
95
101
|
]
|
|
96
102
|
},
|
|
97
103
|
gemini: {
|
|
@@ -109,7 +115,7 @@ const MODELS = {
|
|
|
109
115
|
'gemini-2.0-flash-lite-001'
|
|
110
116
|
]
|
|
111
117
|
}
|
|
112
|
-
// NOTE: Other providers (DeepSeek, Mistral, Qwen, XAI, Meta) are available
|
|
118
|
+
// NOTE: Other providers (DeepSeek, Mistral, Qwen, XAI, Meta, Grok) are available
|
|
113
119
|
// via Grazie native Chat API but NOT via the OpenAI-compatible proxy.
|
|
114
120
|
// They are not supported by CLI tools that use OpenAI API format.
|
|
115
121
|
};
|
|
@@ -187,6 +193,88 @@ function isTokenExpired(token) {
|
|
|
187
193
|
return expiry < new Date();
|
|
188
194
|
}
|
|
189
195
|
|
|
196
|
+
function isTokenExpiringSoon(token, thresholdMs = 60 * 60 * 1000) {
|
|
197
|
+
const expiry = getTokenExpiry(token);
|
|
198
|
+
if (!expiry) return true;
|
|
199
|
+
return (expiry.getTime() - Date.now()) < thresholdMs;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Max age for refresh: token expired less than 2 weeks ago
|
|
203
|
+
function isTokenRefreshable(token) {
|
|
204
|
+
const expiry = getTokenExpiry(token);
|
|
205
|
+
if (!expiry) return false;
|
|
206
|
+
const twoWeeksMs = 14 * 24 * 60 * 60 * 1000;
|
|
207
|
+
return (Date.now() - expiry.getTime()) < twoWeeksMs;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function refreshTokenApi(token) {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const endpoints = ENDPOINTS[getEnvironment()];
|
|
213
|
+
const url = new URL(`${endpoints.base}/user/v5/auth/jwt/refresh/v3`);
|
|
214
|
+
|
|
215
|
+
const req = https.request({
|
|
216
|
+
hostname: url.hostname,
|
|
217
|
+
port: 443,
|
|
218
|
+
path: url.pathname,
|
|
219
|
+
method: 'POST',
|
|
220
|
+
headers: {
|
|
221
|
+
'Content-Type': 'application/json',
|
|
222
|
+
'Grazie-Authenticate-JWT': token
|
|
223
|
+
}
|
|
224
|
+
}, (res) => {
|
|
225
|
+
let body = '';
|
|
226
|
+
res.on('data', chunk => body += chunk);
|
|
227
|
+
res.on('end', () => {
|
|
228
|
+
if (res.statusCode !== 200) {
|
|
229
|
+
reject(new Error(`Refresh failed (HTTP ${res.statusCode}): ${body}`));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const json = JSON.parse(body);
|
|
234
|
+
if (!json.token) {
|
|
235
|
+
reject(new Error('No token in refresh response'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
resolve(json.token);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
reject(new Error(`Failed to parse refresh response: ${body}`));
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
req.on('error', reject);
|
|
246
|
+
req.write('');
|
|
247
|
+
req.end();
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function refreshToken() {
|
|
252
|
+
const token = getToken();
|
|
253
|
+
if (!token) {
|
|
254
|
+
throw new Error('No token found. Run: jbai token set');
|
|
255
|
+
}
|
|
256
|
+
if (!isTokenExpired(token) && !isTokenExpiringSoon(token)) {
|
|
257
|
+
return token; // still valid, no refresh needed
|
|
258
|
+
}
|
|
259
|
+
if (isTokenExpired(token) && !isTokenRefreshable(token)) {
|
|
260
|
+
throw new Error('Token expired more than 2 weeks ago. Get a new one from the UI.');
|
|
261
|
+
}
|
|
262
|
+
const newToken = await refreshTokenApi(token);
|
|
263
|
+
setToken(newToken);
|
|
264
|
+
return newToken;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function getValidToken() {
|
|
268
|
+
const token = getToken();
|
|
269
|
+
if (!token) return null;
|
|
270
|
+
if (!isTokenExpiringSoon(token)) return token;
|
|
271
|
+
try {
|
|
272
|
+
return await refreshToken();
|
|
273
|
+
} catch {
|
|
274
|
+
return token; // return current token as fallback
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
190
278
|
const TOOLS = {
|
|
191
279
|
claude: {
|
|
192
280
|
name: 'Claude Code',
|
|
@@ -227,5 +315,10 @@ module.exports = {
|
|
|
227
315
|
getEndpoints,
|
|
228
316
|
parseJWT,
|
|
229
317
|
getTokenExpiry,
|
|
230
|
-
isTokenExpired
|
|
318
|
+
isTokenExpired,
|
|
319
|
+
isTokenExpiringSoon,
|
|
320
|
+
isTokenRefreshable,
|
|
321
|
+
refreshTokenApi,
|
|
322
|
+
refreshToken,
|
|
323
|
+
getValidToken
|
|
231
324
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const config = require('./config');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get a valid token, auto-refreshing if expiring soon.
|
|
5
|
+
* Exits the process if no token or refresh fails on an expired token.
|
|
6
|
+
*/
|
|
7
|
+
async function ensureToken() {
|
|
8
|
+
let token = config.getToken();
|
|
9
|
+
if (!token) {
|
|
10
|
+
console.error('❌ No token found. Run: jbai token set');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (config.isTokenExpiringSoon(token)) {
|
|
15
|
+
try {
|
|
16
|
+
console.error('🔄 Token expiring soon, auto-refreshing...');
|
|
17
|
+
token = await config.refreshToken();
|
|
18
|
+
console.error('✅ Token refreshed');
|
|
19
|
+
} catch (e) {
|
|
20
|
+
if (config.isTokenExpired(token)) {
|
|
21
|
+
console.error(`❌ Token expired and refresh failed: ${e.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
// Token not expired yet, continue with current
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { ensureToken };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbai-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "CLI wrappers to use AI coding tools (Claude Code, Codex, Gemini CLI, OpenCode) with JetBrains AI Platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jetbrains",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"homepage": "https://github.com/JetBrains/jbai-cli#readme",
|
|
27
27
|
"bin": {
|
|
28
28
|
"jbai": "bin/jbai.js",
|
|
29
|
+
"jbai-proxy": "bin/jbai-proxy.js",
|
|
29
30
|
"jbai-claude": "bin/jbai-claude.js",
|
|
30
31
|
"jbai-codex": "bin/jbai-codex.js",
|
|
31
32
|
"jbai-gemini": "bin/jbai-gemini.js",
|