jbai-cli 1.3.1 → 1.5.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/bin/jbai-gemini.js +0 -0
- package/bin/jbai-opencode.js +28 -8
- package/bin/jbai.js +18 -17
- package/bin/test-models.js +261 -0
- package/lib/config.js +49 -11
- package/package.json +3 -7
- package/bin/jbai-aider.js +0 -94
package/bin/jbai-gemini.js
CHANGED
|
File without changes
|
package/bin/jbai-opencode.js
CHANGED
|
@@ -54,12 +54,14 @@ if (!opencodeConfig.provider) {
|
|
|
54
54
|
// Environment variable name for the token
|
|
55
55
|
const envVarName = environment === 'staging' ? 'GRAZIE_STAGING_TOKEN' : 'GRAZIE_API_TOKEN';
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// Provider names for OpenAI and Anthropic
|
|
58
|
+
const anthropicProviderName = environment === 'staging' ? 'jbai-anthropic-staging' : 'jbai-anthropic';
|
|
59
|
+
|
|
60
|
+
// Add/update JetBrains OpenAI provider with custom header (using env var reference)
|
|
58
61
|
// Use openai-compatible SDK which properly sends custom headers
|
|
59
|
-
// Point to OpenAI endpoint for compatibility
|
|
60
62
|
opencodeConfig.provider[providerName] = {
|
|
61
63
|
npm: '@ai-sdk/openai-compatible',
|
|
62
|
-
name: `JetBrains AI (${environment})`,
|
|
64
|
+
name: `JetBrains AI OpenAI (${environment})`,
|
|
63
65
|
options: {
|
|
64
66
|
baseURL: endpoints.openai,
|
|
65
67
|
apiKey: `{env:${envVarName}}`,
|
|
@@ -70,15 +72,33 @@ opencodeConfig.provider[providerName] = {
|
|
|
70
72
|
models: {}
|
|
71
73
|
};
|
|
72
74
|
|
|
73
|
-
// Add OpenAI models
|
|
74
|
-
// Set context and output limits to avoid "max_tokens too large" errors
|
|
75
|
+
// Add OpenAI models
|
|
75
76
|
config.MODELS.openai.available.forEach(model => {
|
|
76
77
|
opencodeConfig.provider[providerName].models[model] = {
|
|
77
78
|
name: model,
|
|
78
|
-
limit: {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
limit: { context: 128000, output: 8192 }
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Add JetBrains Anthropic provider for Claude models
|
|
84
|
+
opencodeConfig.provider[anthropicProviderName] = {
|
|
85
|
+
npm: '@ai-sdk/anthropic',
|
|
86
|
+
name: `JetBrains AI Anthropic (${environment})`,
|
|
87
|
+
options: {
|
|
88
|
+
baseURL: endpoints.anthropic,
|
|
89
|
+
apiKey: `{env:${envVarName}}`,
|
|
90
|
+
headers: {
|
|
91
|
+
'Grazie-Authenticate-JWT': `{env:${envVarName}}`
|
|
81
92
|
}
|
|
93
|
+
},
|
|
94
|
+
models: {}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Add Claude models to Anthropic provider
|
|
98
|
+
config.MODELS.claude.available.forEach(model => {
|
|
99
|
+
opencodeConfig.provider[anthropicProviderName].models[model] = {
|
|
100
|
+
name: model,
|
|
101
|
+
limit: { context: 200000, output: 8192 }
|
|
82
102
|
};
|
|
83
103
|
});
|
|
84
104
|
|
package/bin/jbai.js
CHANGED
|
@@ -18,12 +18,6 @@ const TOOLS = {
|
|
|
18
18
|
install: 'npm install -g @openai/codex',
|
|
19
19
|
check: 'codex --version'
|
|
20
20
|
},
|
|
21
|
-
aider: {
|
|
22
|
-
name: 'Aider',
|
|
23
|
-
command: 'aider',
|
|
24
|
-
install: 'pipx install aider-chat',
|
|
25
|
-
check: 'aider --version'
|
|
26
|
-
},
|
|
27
21
|
gemini: {
|
|
28
22
|
name: 'Gemini CLI',
|
|
29
23
|
command: 'gemini',
|
|
@@ -50,7 +44,7 @@ COMMANDS:
|
|
|
50
44
|
jbai test Test all API endpoints
|
|
51
45
|
jbai env [staging|production] Switch environment
|
|
52
46
|
jbai models List available models
|
|
53
|
-
jbai install Install all AI tools (claude, codex,
|
|
47
|
+
jbai install Install all AI tools (claude, codex, gemini, opencode)
|
|
54
48
|
jbai install claude Install specific tool
|
|
55
49
|
jbai doctor Check which tools are installed
|
|
56
50
|
jbai help Show this help
|
|
@@ -58,7 +52,6 @@ COMMANDS:
|
|
|
58
52
|
TOOL WRAPPERS:
|
|
59
53
|
jbai-claude Launch Claude Code with JetBrains AI
|
|
60
54
|
jbai-codex Launch Codex CLI with JetBrains AI
|
|
61
|
-
jbai-aider Launch Aider with JetBrains AI
|
|
62
55
|
jbai-gemini Launch Gemini CLI with JetBrains AI
|
|
63
56
|
jbai-opencode Launch OpenCode with JetBrains AI
|
|
64
57
|
|
|
@@ -66,13 +59,13 @@ SUPER MODE:
|
|
|
66
59
|
Add --super (or --yolo or -s) to skip confirmations:
|
|
67
60
|
jbai-claude --super # Skip permission prompts
|
|
68
61
|
jbai-codex --super # Full auto mode
|
|
69
|
-
jbai-
|
|
62
|
+
jbai-gemini --super # Auto-confirm changes
|
|
70
63
|
|
|
71
64
|
EXAMPLES:
|
|
72
65
|
jbai token set # Set your token
|
|
73
66
|
jbai-claude # Start Claude Code
|
|
74
67
|
jbai-codex exec "explain code" # Run Codex task
|
|
75
|
-
jbai-
|
|
68
|
+
jbai-gemini # Start Gemini CLI
|
|
76
69
|
|
|
77
70
|
TOKEN:
|
|
78
71
|
Get token: ${config.getEndpoints().tokenUrl}
|
|
@@ -219,25 +212,33 @@ function httpPost(url, body, headers) {
|
|
|
219
212
|
}
|
|
220
213
|
|
|
221
214
|
function showModels() {
|
|
222
|
-
console.log('Available Models:\n');
|
|
215
|
+
console.log('Available Models via JetBrains AI Platform (Grazie):\n');
|
|
223
216
|
|
|
224
|
-
console.log('Claude (Anthropic):');
|
|
225
|
-
config.MODELS.claude.available.forEach((m
|
|
217
|
+
console.log('Claude (Anthropic) - jbai-claude:');
|
|
218
|
+
config.MODELS.claude.available.forEach((m) => {
|
|
226
219
|
const def = m === config.MODELS.claude.default ? ' (default)' : '';
|
|
227
220
|
console.log(` - ${m}${def}`);
|
|
228
221
|
});
|
|
229
222
|
|
|
230
|
-
console.log('\nGPT (OpenAI):');
|
|
231
|
-
config.MODELS.openai.available.forEach((m
|
|
223
|
+
console.log('\nGPT (OpenAI) - jbai-codex, jbai-opencode:');
|
|
224
|
+
config.MODELS.openai.available.forEach((m) => {
|
|
232
225
|
const def = m === config.MODELS.openai.default ? ' (default)' : '';
|
|
233
226
|
console.log(` - ${m}${def}`);
|
|
234
227
|
});
|
|
235
228
|
|
|
236
|
-
console.log('\nGemini (Google):');
|
|
237
|
-
config.MODELS.gemini.available.forEach((m
|
|
229
|
+
console.log('\nGemini (Google) - jbai-gemini:');
|
|
230
|
+
config.MODELS.gemini.available.forEach((m) => {
|
|
238
231
|
const def = m === config.MODELS.gemini.default ? ' (default)' : '';
|
|
239
232
|
console.log(` - ${m}${def}`);
|
|
240
233
|
});
|
|
234
|
+
|
|
235
|
+
// Count total
|
|
236
|
+
const total = config.MODELS.claude.available.length +
|
|
237
|
+
config.MODELS.openai.available.length +
|
|
238
|
+
config.MODELS.gemini.available.length;
|
|
239
|
+
console.log(`\nTotal: ${total} models`);
|
|
240
|
+
console.log('\nNote: Other providers (DeepSeek, Mistral, Qwen, XAI, Meta) are available');
|
|
241
|
+
console.log('via Grazie native API but not via OpenAI-compatible CLI tools.');
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
function setEnvironment(env) {
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* E2E Model Compatibility Testing Script
|
|
5
|
+
* Tests all configured Grazie models against the API endpoints
|
|
6
|
+
* to identify which models are valid and which return 404 or errors.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const config = require('../lib/config');
|
|
11
|
+
|
|
12
|
+
const token = config.getToken();
|
|
13
|
+
if (!token) {
|
|
14
|
+
console.error('❌ No token found. Run: jbai token set');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const endpoints = config.getEndpoints();
|
|
19
|
+
|
|
20
|
+
// Colors for terminal output
|
|
21
|
+
const colors = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
cyan: '\x1b[36m',
|
|
27
|
+
dim: '\x1b[2m'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function httpPost(url, body, headers) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const urlObj = new URL(url);
|
|
33
|
+
const data = JSON.stringify(body);
|
|
34
|
+
|
|
35
|
+
const req = https.request({
|
|
36
|
+
hostname: urlObj.hostname,
|
|
37
|
+
port: 443,
|
|
38
|
+
path: urlObj.pathname + urlObj.search,
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'Content-Length': Buffer.byteLength(data),
|
|
43
|
+
...headers
|
|
44
|
+
},
|
|
45
|
+
timeout: 30000
|
|
46
|
+
}, (res) => {
|
|
47
|
+
let body = '';
|
|
48
|
+
res.on('data', chunk => body += chunk);
|
|
49
|
+
res.on('end', () => {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(body);
|
|
52
|
+
resolve({ status: res.statusCode, data: parsed });
|
|
53
|
+
} catch {
|
|
54
|
+
resolve({ status: res.statusCode, data: body, raw: true });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
req.on('error', (e) => reject(e));
|
|
60
|
+
req.on('timeout', () => {
|
|
61
|
+
req.destroy();
|
|
62
|
+
reject(new Error('Timeout'));
|
|
63
|
+
});
|
|
64
|
+
req.write(data);
|
|
65
|
+
req.end();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function testOpenAIModel(model) {
|
|
70
|
+
try {
|
|
71
|
+
// GPT-5.x and O-series models require max_completion_tokens instead of max_tokens
|
|
72
|
+
const needsCompletionTokens = model.startsWith('gpt-5') || model.startsWith('o1') ||
|
|
73
|
+
model.startsWith('o3') || model.startsWith('o4');
|
|
74
|
+
|
|
75
|
+
const bodyParams = needsCompletionTokens
|
|
76
|
+
? { max_completion_tokens: 500 } // O-series needs more tokens for reasoning
|
|
77
|
+
: { max_tokens: 5 };
|
|
78
|
+
|
|
79
|
+
const result = await httpPost(
|
|
80
|
+
`${endpoints.openai}/chat/completions`,
|
|
81
|
+
{
|
|
82
|
+
model: model,
|
|
83
|
+
messages: [{ role: 'user', content: 'Say OK' }],
|
|
84
|
+
...bodyParams
|
|
85
|
+
},
|
|
86
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (result.status === 200 && result.data.choices) {
|
|
90
|
+
return { success: true, message: 'OK' };
|
|
91
|
+
} else if (result.status === 404) {
|
|
92
|
+
return { success: false, message: `404 Not Found`, error: result.data.error?.message || 'Model not found' };
|
|
93
|
+
} else if (result.status === 400) {
|
|
94
|
+
return { success: false, message: `400 Bad Request`, error: result.data.error?.message || 'Invalid request' };
|
|
95
|
+
} else {
|
|
96
|
+
return { success: false, message: `Status ${result.status}`, error: result.data.error?.message || JSON.stringify(result.data).substring(0, 100) };
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return { success: false, message: 'Error', error: e.message };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function testAnthropicModel(model) {
|
|
104
|
+
try {
|
|
105
|
+
const result = await httpPost(
|
|
106
|
+
`${endpoints.anthropic}/messages`,
|
|
107
|
+
{
|
|
108
|
+
model: model,
|
|
109
|
+
messages: [{ role: 'user', content: 'Say OK' }],
|
|
110
|
+
max_tokens: 10
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
'Grazie-Authenticate-JWT': token,
|
|
114
|
+
'anthropic-version': '2023-06-01'
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (result.status === 200 && result.data.content) {
|
|
119
|
+
return { success: true, message: 'OK' };
|
|
120
|
+
} else if (result.status === 404) {
|
|
121
|
+
return { success: false, message: `404 Not Found`, error: result.data.error?.message || 'Model not found' };
|
|
122
|
+
} else if (result.status === 400) {
|
|
123
|
+
return { success: false, message: `400 Bad Request`, error: result.data.error?.message || 'Invalid request' };
|
|
124
|
+
} else {
|
|
125
|
+
return { success: false, message: `Status ${result.status}`, error: result.data.error?.message || JSON.stringify(result.data).substring(0, 100) };
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return { success: false, message: 'Error', error: e.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function testGeminiModel(model) {
|
|
133
|
+
try {
|
|
134
|
+
// Gemini API format via Vertex AI proxy
|
|
135
|
+
const result = await httpPost(
|
|
136
|
+
`${endpoints.google}/v1/projects/default/locations/default/publishers/google/models/${model}:generateContent`,
|
|
137
|
+
{
|
|
138
|
+
contents: [{ role: 'user', parts: [{ text: 'Say OK' }] }]
|
|
139
|
+
},
|
|
140
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (result.status === 200 && result.data.candidates) {
|
|
144
|
+
return { success: true, message: 'OK' };
|
|
145
|
+
} else if (result.status === 404) {
|
|
146
|
+
return { success: false, message: `404 Not Found`, error: result.data.error?.message || 'Model not found' };
|
|
147
|
+
} else if (result.status === 400) {
|
|
148
|
+
return { success: false, message: `400 Bad Request`, error: result.data.error?.message || 'Invalid request' };
|
|
149
|
+
} else {
|
|
150
|
+
return { success: false, message: `Status ${result.status}`, error: result.data.error?.message || JSON.stringify(result.data).substring(0, 100) };
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return { success: false, message: 'Error', error: e.message };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function runTests() {
|
|
158
|
+
console.log(`\n${'='.repeat(70)}`);
|
|
159
|
+
console.log(`${colors.cyan}JBAI-CLI E2E MODEL COMPATIBILITY TESTING${colors.reset}`);
|
|
160
|
+
console.log(`Environment: ${config.getEnvironment()}`);
|
|
161
|
+
console.log(`${'='.repeat(70)}\n`);
|
|
162
|
+
|
|
163
|
+
const results = {
|
|
164
|
+
openai: { working: [], failing: [] },
|
|
165
|
+
anthropic: { working: [], failing: [] },
|
|
166
|
+
gemini: { working: [], failing: [] }
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Test Claude models (Anthropic)
|
|
170
|
+
console.log(`${colors.cyan}Testing Claude models (jbai-claude)...${colors.reset}`);
|
|
171
|
+
console.log('-'.repeat(50));
|
|
172
|
+
for (const model of config.MODELS.claude.available) {
|
|
173
|
+
process.stdout.write(` ${model.padEnd(35)} `);
|
|
174
|
+
const result = await testAnthropicModel(model);
|
|
175
|
+
if (result.success) {
|
|
176
|
+
console.log(`${colors.green}✓ ${result.message}${colors.reset}`);
|
|
177
|
+
results.anthropic.working.push(model);
|
|
178
|
+
} else {
|
|
179
|
+
console.log(`${colors.red}✗ ${result.message}${colors.reset}`);
|
|
180
|
+
console.log(` ${colors.dim}${result.error}${colors.reset}`);
|
|
181
|
+
results.anthropic.failing.push({ model, error: result.error });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Test OpenAI models (Codex, OpenCode)
|
|
186
|
+
console.log(`\n${colors.cyan}Testing OpenAI/GPT models (jbai-codex, jbai-opencode)...${colors.reset}`);
|
|
187
|
+
console.log('-'.repeat(50));
|
|
188
|
+
for (const model of config.MODELS.openai.available) {
|
|
189
|
+
process.stdout.write(` ${model.padEnd(35)} `);
|
|
190
|
+
const result = await testOpenAIModel(model);
|
|
191
|
+
if (result.success) {
|
|
192
|
+
console.log(`${colors.green}✓ ${result.message}${colors.reset}`);
|
|
193
|
+
results.openai.working.push(model);
|
|
194
|
+
} else {
|
|
195
|
+
console.log(`${colors.red}✗ ${result.message}${colors.reset}`);
|
|
196
|
+
console.log(` ${colors.dim}${result.error}${colors.reset}`);
|
|
197
|
+
results.openai.failing.push({ model, error: result.error });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Test Gemini models
|
|
202
|
+
console.log(`\n${colors.cyan}Testing Gemini models (jbai-gemini)...${colors.reset}`);
|
|
203
|
+
console.log('-'.repeat(50));
|
|
204
|
+
for (const model of config.MODELS.gemini.available) {
|
|
205
|
+
process.stdout.write(` ${model.padEnd(35)} `);
|
|
206
|
+
const result = await testGeminiModel(model);
|
|
207
|
+
if (result.success) {
|
|
208
|
+
console.log(`${colors.green}✓ ${result.message}${colors.reset}`);
|
|
209
|
+
results.gemini.working.push(model);
|
|
210
|
+
} else {
|
|
211
|
+
console.log(`${colors.red}✗ ${result.message}${colors.reset}`);
|
|
212
|
+
console.log(` ${colors.dim}${result.error}${colors.reset}`);
|
|
213
|
+
results.gemini.failing.push({ model, error: result.error });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Summary
|
|
218
|
+
console.log(`\n${'='.repeat(70)}`);
|
|
219
|
+
console.log(`${colors.cyan}SUMMARY${colors.reset}`);
|
|
220
|
+
console.log(`${'='.repeat(70)}`);
|
|
221
|
+
|
|
222
|
+
const totalWorking = results.anthropic.working.length + results.openai.working.length +
|
|
223
|
+
results.gemini.working.length;
|
|
224
|
+
const totalFailing = results.anthropic.failing.length + results.openai.failing.length +
|
|
225
|
+
results.gemini.failing.length;
|
|
226
|
+
|
|
227
|
+
console.log(`\n${colors.green}Working Models: ${totalWorking}${colors.reset}`);
|
|
228
|
+
console.log(`${colors.red}Failing Models: ${totalFailing}${colors.reset}`);
|
|
229
|
+
|
|
230
|
+
console.log(`\n${colors.cyan}By Provider:${colors.reset}`);
|
|
231
|
+
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: ${colors.green}${results.openai.working.length}${colors.reset} working, ${colors.red}${results.openai.failing.length}${colors.reset} failing`);
|
|
233
|
+
console.log(` Gemini (Google): ${colors.green}${results.gemini.working.length}${colors.reset} working, ${colors.red}${results.gemini.failing.length}${colors.reset} failing`);
|
|
234
|
+
|
|
235
|
+
if (totalFailing > 0) {
|
|
236
|
+
console.log(`\n${colors.red}FAILING MODELS TO REMOVE:${colors.reset}`);
|
|
237
|
+
|
|
238
|
+
if (results.anthropic.failing.length > 0) {
|
|
239
|
+
console.log(`\n Claude models:`);
|
|
240
|
+
results.anthropic.failing.forEach(f => console.log(` - ${f.model}`));
|
|
241
|
+
}
|
|
242
|
+
if (results.openai.failing.length > 0) {
|
|
243
|
+
console.log(`\n OpenAI models:`);
|
|
244
|
+
results.openai.failing.forEach(f => console.log(` - ${f.model}`));
|
|
245
|
+
}
|
|
246
|
+
if (results.gemini.failing.length > 0) {
|
|
247
|
+
console.log(`\n Gemini models:`);
|
|
248
|
+
results.gemini.failing.forEach(f => console.log(` - ${f.model}`));
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
console.log(`\n${colors.green}All models are working correctly!${colors.reset}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(`\n${'='.repeat(70)}\n`);
|
|
255
|
+
|
|
256
|
+
// Return results for programmatic use
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Run tests
|
|
261
|
+
runTests().catch(console.error);
|
package/lib/config.js
CHANGED
|
@@ -12,6 +12,7 @@ const ENDPOINTS = {
|
|
|
12
12
|
openai: 'https://api.stgn.jetbrains.ai/user/v5/llm/openai/v1',
|
|
13
13
|
anthropic: 'https://api.stgn.jetbrains.ai/user/v5/llm/anthropic/v1',
|
|
14
14
|
google: 'https://api.stgn.jetbrains.ai/user/v5/llm/google/v1/vertex',
|
|
15
|
+
profiles: 'https://api.stgn.jetbrains.ai/user/v5/llm/profiles',
|
|
15
16
|
tokenUrl: 'https://platform.stgn.jetbrains.ai/'
|
|
16
17
|
},
|
|
17
18
|
production: {
|
|
@@ -19,17 +20,27 @@ const ENDPOINTS = {
|
|
|
19
20
|
openai: 'https://api.jetbrains.ai/user/v5/llm/openai/v1',
|
|
20
21
|
anthropic: 'https://api.jetbrains.ai/user/v5/llm/anthropic/v1',
|
|
21
22
|
google: 'https://api.jetbrains.ai/user/v5/llm/google/v1/vertex',
|
|
23
|
+
profiles: 'https://api.jetbrains.ai/user/v5/llm/profiles',
|
|
22
24
|
tokenUrl: 'https://platform.jetbrains.ai/'
|
|
23
25
|
}
|
|
24
26
|
};
|
|
25
27
|
|
|
28
|
+
// All models available from JetBrains AI Platform (Grazie)
|
|
29
|
+
// Model names must match EXACTLY what the Grazie API accepts
|
|
30
|
+
// Run 'node bin/test-models.js' to verify model availability
|
|
26
31
|
const MODELS = {
|
|
27
32
|
claude: {
|
|
28
33
|
default: 'claude-sonnet-4-5-20250929',
|
|
29
34
|
available: [
|
|
35
|
+
// Claude 4.5 series (latest)
|
|
36
|
+
'claude-opus-4-5-20251101',
|
|
30
37
|
'claude-sonnet-4-5-20250929',
|
|
38
|
+
'claude-haiku-4-5-20251001',
|
|
39
|
+
// Claude 4.x series
|
|
31
40
|
'claude-opus-4-1-20250805',
|
|
41
|
+
'claude-opus-4-20250514',
|
|
32
42
|
'claude-sonnet-4-20250514',
|
|
43
|
+
// Claude 3.x series
|
|
33
44
|
'claude-3-7-sonnet-20250219',
|
|
34
45
|
'claude-3-5-haiku-20241022'
|
|
35
46
|
]
|
|
@@ -37,23 +48,54 @@ const MODELS = {
|
|
|
37
48
|
openai: {
|
|
38
49
|
default: 'gpt-4o-2024-11-20',
|
|
39
50
|
available: [
|
|
40
|
-
|
|
41
|
-
'gpt-5-2025-
|
|
51
|
+
// GPT-5.x series (latest) - require date-versioned names
|
|
52
|
+
'gpt-5.2-2025-12-11',
|
|
53
|
+
'gpt-5.2',
|
|
42
54
|
'gpt-5.1-2025-11-13',
|
|
55
|
+
'gpt-5-2025-08-07',
|
|
43
56
|
'gpt-5-mini-2025-08-07',
|
|
57
|
+
'gpt-5-nano-2025-08-07',
|
|
58
|
+
// GPT-4.x series
|
|
59
|
+
'gpt-4.1-2025-04-14',
|
|
60
|
+
'gpt-4.1-mini-2025-04-14',
|
|
61
|
+
'gpt-4.1-nano-2025-04-14',
|
|
62
|
+
'gpt-4o-2024-11-20',
|
|
63
|
+
'gpt-4o-mini-2024-07-18',
|
|
64
|
+
'gpt-4-turbo-2024-04-09',
|
|
65
|
+
'gpt-4-0613',
|
|
66
|
+
'gpt-3.5-turbo-0125',
|
|
67
|
+
// O-series (reasoning) - use max_completion_tokens instead of max_tokens
|
|
68
|
+
'o4-mini-2025-04-16',
|
|
44
69
|
'o3-2025-04-16',
|
|
45
|
-
'o3-mini-2025-01-31'
|
|
70
|
+
'o3-mini-2025-01-31',
|
|
71
|
+
'o1-2024-12-17'
|
|
46
72
|
]
|
|
47
73
|
},
|
|
48
74
|
gemini: {
|
|
49
75
|
default: 'gemini-2.5-flash',
|
|
50
76
|
available: [
|
|
51
|
-
|
|
52
|
-
'gemini-2.5-pro',
|
|
77
|
+
// Gemini 3.x (preview)
|
|
53
78
|
'gemini-3-pro-preview',
|
|
54
|
-
'gemini-3-flash-preview'
|
|
79
|
+
'gemini-3-flash-preview',
|
|
80
|
+
// Gemini 2.5
|
|
81
|
+
'gemini-2.5-pro',
|
|
82
|
+
'gemini-2.5-flash',
|
|
83
|
+
'gemini-2.5-flash-lite',
|
|
84
|
+
// Gemini 2.0
|
|
85
|
+
'gemini-2.0-flash-001',
|
|
86
|
+
'gemini-2.0-flash-lite-001'
|
|
55
87
|
]
|
|
56
88
|
}
|
|
89
|
+
// NOTE: Other providers (DeepSeek, Mistral, Qwen, XAI, Meta) are available
|
|
90
|
+
// via Grazie native Chat API but NOT via the OpenAI-compatible proxy.
|
|
91
|
+
// They are not supported by CLI tools that use OpenAI API format.
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// All models for tools that support multiple providers (OpenCode, Codex)
|
|
95
|
+
const ALL_MODELS = {
|
|
96
|
+
openai: MODELS.openai.available,
|
|
97
|
+
anthropic: MODELS.claude.available,
|
|
98
|
+
gemini: MODELS.gemini.available
|
|
57
99
|
};
|
|
58
100
|
|
|
59
101
|
function ensureConfigDir() {
|
|
@@ -133,11 +175,6 @@ const TOOLS = {
|
|
|
133
175
|
command: 'codex',
|
|
134
176
|
install: 'npm install -g @openai/codex'
|
|
135
177
|
},
|
|
136
|
-
aider: {
|
|
137
|
-
name: 'Aider',
|
|
138
|
-
command: 'aider',
|
|
139
|
-
install: 'pipx install aider-chat'
|
|
140
|
-
},
|
|
141
178
|
gemini: {
|
|
142
179
|
name: 'Gemini CLI',
|
|
143
180
|
command: 'gemini',
|
|
@@ -156,6 +193,7 @@ module.exports = {
|
|
|
156
193
|
CONFIG_FILE,
|
|
157
194
|
ENDPOINTS,
|
|
158
195
|
MODELS,
|
|
196
|
+
ALL_MODELS,
|
|
159
197
|
TOOLS,
|
|
160
198
|
ensureConfigDir,
|
|
161
199
|
getToken,
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbai-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI wrappers to use AI coding tools (Claude Code, Codex,
|
|
3
|
+
"version": "1.5.0",
|
|
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",
|
|
7
7
|
"ai",
|
|
8
8
|
"claude",
|
|
9
9
|
"codex",
|
|
10
|
-
"aider",
|
|
11
10
|
"gemini",
|
|
12
11
|
"opencode",
|
|
13
12
|
"cli",
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
"jbai": "./bin/jbai.js",
|
|
30
29
|
"jbai-claude": "./bin/jbai-claude.js",
|
|
31
30
|
"jbai-codex": "./bin/jbai-codex.js",
|
|
32
|
-
"jbai-aider": "./bin/jbai-aider.js",
|
|
33
31
|
"jbai-gemini": "./bin/jbai-gemini.js",
|
|
34
32
|
"jbai-opencode": "./bin/jbai-opencode.js"
|
|
35
33
|
},
|
|
@@ -41,9 +39,7 @@
|
|
|
41
39
|
"engines": {
|
|
42
40
|
"node": ">=18.0.0"
|
|
43
41
|
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"yaml": "^2.7.1"
|
|
46
|
-
},
|
|
42
|
+
"dependencies": {},
|
|
47
43
|
"scripts": {
|
|
48
44
|
"postinstall": "node lib/postinstall.js",
|
|
49
45
|
"test": "node bin/jbai.js test"
|
package/bin/jbai-aider.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { spawn } = require('child_process');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const yaml = require('yaml');
|
|
8
|
-
const config = require('../lib/config');
|
|
9
|
-
|
|
10
|
-
const token = config.getToken();
|
|
11
|
-
if (!token) {
|
|
12
|
-
console.error('❌ No token found. Run: jbai token set');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (config.isTokenExpired(token)) {
|
|
17
|
-
console.error('⚠️ Token expired. Run: jbai token refresh');
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const endpoints = config.getEndpoints();
|
|
22
|
-
const environment = config.getEnvironment();
|
|
23
|
-
let args = process.argv.slice(2);
|
|
24
|
-
|
|
25
|
-
// Check for super mode (--super, --yolo, -s)
|
|
26
|
-
const superFlags = ['--super', '--yolo', '-s'];
|
|
27
|
-
const superMode = args.some(a => superFlags.includes(a));
|
|
28
|
-
args = args.filter(a => !superFlags.includes(a));
|
|
29
|
-
|
|
30
|
-
// Create model settings file with extra headers for JetBrains authentication
|
|
31
|
-
const configDir = path.join(os.homedir(), '.jbai');
|
|
32
|
-
const modelSettingsFile = path.join(configDir, 'aider-model-settings.yml');
|
|
33
|
-
|
|
34
|
-
// Ensure config directory exists
|
|
35
|
-
if (!fs.existsSync(configDir)) {
|
|
36
|
-
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Create model settings with extra_headers for authentication
|
|
40
|
-
// Use aider/extra_params for global settings that apply to all models
|
|
41
|
-
const modelSettings = [
|
|
42
|
-
{
|
|
43
|
-
name: 'aider/extra_params',
|
|
44
|
-
extra_params: {
|
|
45
|
-
extra_headers: {
|
|
46
|
-
'Grazie-Authenticate-JWT': token
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
// Write model settings file
|
|
53
|
-
fs.writeFileSync(modelSettingsFile, yaml.stringify(modelSettings), { mode: 0o600 });
|
|
54
|
-
|
|
55
|
-
// Build aider arguments
|
|
56
|
-
// Note: --no-stream is required because JetBrains proxy has stream_options issues
|
|
57
|
-
const hasModel = args.includes('--model');
|
|
58
|
-
const aiderArgs = [
|
|
59
|
-
'--openai-api-base', endpoints.openai,
|
|
60
|
-
'--openai-api-key', 'placeholder',
|
|
61
|
-
'--model-settings-file', modelSettingsFile,
|
|
62
|
-
'--no-stream'
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
if (!hasModel) {
|
|
66
|
-
aiderArgs.push('--model', config.MODELS.openai.default);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Add super mode flags (auto-confirm all)
|
|
70
|
-
if (superMode) {
|
|
71
|
-
aiderArgs.push('--yes');
|
|
72
|
-
console.log('🚀 Super mode: --yes (auto-confirm) enabled');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
aiderArgs.push(...args);
|
|
76
|
-
|
|
77
|
-
const child = spawn('aider', aiderArgs, {
|
|
78
|
-
stdio: 'inherit',
|
|
79
|
-
env: process.env
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
child.on('error', (err) => {
|
|
83
|
-
if (err.code === 'ENOENT') {
|
|
84
|
-
const tool = config.TOOLS.aider;
|
|
85
|
-
console.error(`❌ ${tool.name} not found.\n`);
|
|
86
|
-
console.error(`Install with: ${tool.install}`);
|
|
87
|
-
console.error(`Or run: jbai install aider`);
|
|
88
|
-
} else {
|
|
89
|
-
console.error(`Error: ${err.message}`);
|
|
90
|
-
}
|
|
91
|
-
process.exit(1);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
child.on('exit', (code) => process.exit(code || 0));
|