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/lib/config.js CHANGED
@@ -31,131 +31,50 @@ const ENDPOINTS = {
31
31
  // Run 'node bin/test-models.js' to verify model availability
32
32
  const MODELS = {
33
33
  claude: {
34
- default: 'claude-sonnet-4-5-20250929',
34
+ default: 'claude-sonnet-4-6',
35
35
  available: [
36
- // Claude 4.6 series (latest)
37
36
  'claude-opus-4-6',
38
37
  'claude-sonnet-4-6',
39
- // Claude 4.5 series
40
- 'claude-opus-4-5-20251101',
41
- 'claude-sonnet-4-5-20250929',
42
- 'claude-haiku-4-5-20251001',
43
- // Claude 4.x series
44
- 'claude-opus-4-1-20250805',
45
- 'claude-sonnet-4-20250514',
46
- // Claude 3.x series
47
- 'claude-3-7-sonnet-20250219',
48
- 'claude-3-5-haiku-20241022'
49
38
  ]
50
39
  },
51
40
  openai: {
52
41
  // Chat/Completions models (used by OpenCode)
53
- // Keep in sync with the OpenAI proxy's advertised list.
54
- default: 'gpt-5.2-2025-12-11',
42
+ // NOTE: codex-only models (gpt-5.x-codex) do NOT work on chat/completions
43
+ default: 'gpt-5.4',
55
44
  available: [
56
- // GPT-5.x series (latest) - require date-versioned names
57
- 'gpt-5.3-codex',
58
- 'gpt-5.2-2025-12-11',
45
+ 'gpt-5.4',
59
46
  'gpt-5.2',
60
- 'gpt-5.1-2025-11-13',
61
- 'gpt-5-2025-08-07',
62
- 'gpt-5-mini-2025-08-07',
63
- 'gpt-5-nano-2025-08-07',
64
- // GPT-4.1 series
65
- 'gpt-4.1-2025-04-14',
66
- 'gpt-4.1-mini-2025-04-14',
67
- 'gpt-4.1-nano-2025-04-14',
68
- // GPT-4o/4-turbo
69
- 'gpt-4o-2024-11-20',
70
- 'gpt-4o-mini-2024-07-18',
71
- 'gpt-4-turbo-2024-04-09',
72
- 'gpt-4-0613',
73
- // O-series (reasoning) - use max_completion_tokens instead of max_tokens
74
- 'o4-mini-2025-04-16',
75
47
  'o3-2025-04-16',
76
- 'o3-mini-2025-01-31',
77
- 'o1-2024-12-17',
78
- // Legacy
79
- 'gpt-3.5-turbo-0125'
80
48
  ]
81
49
  },
82
50
  // Codex CLI uses OpenAI models via the "responses" API (wire_api = "responses")
83
- // Includes chat-capable models PLUS codex-only models (responses API only)
84
51
  codex: {
85
- default: 'gpt-5.3-codex',
52
+ default: 'gpt-5.4',
86
53
  available: [
87
- // Hidden/EAP models (JB internal NDA)
88
- 'rockhopper-alpha',
89
- 'gpt-5.3-codex-api-preview',
90
- // Codex-specific models (responses API only, NOT available via chat/completions)
54
+ 'gpt-5.4',
91
55
  'gpt-5.3-codex',
92
- // GPT-5.x chat models (also work via responses API)
93
- 'gpt-5.2-2025-12-11',
94
- 'gpt-5.2',
95
- 'gpt-5.1-2025-11-13',
96
- 'gpt-5-2025-08-07',
97
56
  'gpt-5.2-codex',
98
- 'gpt-5.2-pro-2025-12-11',
99
- 'gpt-5.1-codex-max',
100
- 'gpt-5.1-codex',
101
- 'gpt-5.1-codex-mini',
102
- 'gpt-5-codex',
103
- // O-series (also work via responses API)
104
- 'o4-mini-2025-04-16',
105
- 'o3-2025-04-16'
57
+ 'o3-2025-04-16',
106
58
  ]
107
59
  },
108
60
  gemini: {
109
- default: 'gemini-2.5-flash',
61
+ default: 'gemini-2.5-pro',
110
62
  available: [
111
- // Hidden/EAP models (JB internal NDA)
112
- 'supernova',
113
63
  'gemini-3.1-pro-preview',
114
- // Gemini 3.x (preview)
115
64
  'gemini-3-pro-preview',
116
- 'gemini-3-flash-preview',
117
- // Gemini 2.5
118
65
  'gemini-2.5-pro',
119
66
  'gemini-2.5-flash',
120
- 'gemini-2.5-flash-lite',
121
- // Gemini 2.0
122
- 'gemini-2.0-flash-001',
123
- 'gemini-2.0-flash-lite-001'
124
67
  ]
125
68
  },
126
69
  // Grazie native Chat API models — accessible via /grazie-openai/v1 translation layer.
127
70
  // Full list is dynamic (fetched from /user/v5/llm/profiles), this is a static fallback.
71
+ // Only coding-capable models with tool use support.
128
72
  grazie: {
129
- default: 'google-gemini-3-0-flash',
73
+ default: 'xai-grok-4',
130
74
  available: [
131
- // Google
132
- 'google-gemini-3-1-pro',
133
- 'google-gemini-3-0-flash',
134
- 'google-gemini-3-0-pro-snowball',
135
- 'google-chat-gemini-pro-2.5',
136
- 'google-gemini-2.5-flash',
137
- // DeepSeek
138
- 'deepseek-r1',
139
- 'openrouter-deepseek-v3-2',
140
- // Mistral
141
- 'mistral-large',
142
- 'mistral-small',
143
- 'openrouter-mistral-large-2512',
144
- // xAI / Grok
145
75
  'xai-grok-4',
146
- 'xai-grok-4-1-fast',
147
76
  'xai-grok-code-fast-1',
148
- // Qwen
149
- 'qwen-max',
150
- 'qwen-plus',
151
- // Kimi
152
- 'openrouter-kimi-k2-5',
153
- 'openrouter-kimi-k2-thinking',
154
- // MiniMax
155
- 'openrouter-minimax-m2-1',
156
- // ZhipuAI
157
- 'openrouter-zhipuai-glm-4-7',
158
- 'openrouter-zhipuai-glm-4-7-flash',
77
+ 'deepseek-r1',
159
78
  ]
160
79
  }
161
80
  };
@@ -163,6 +82,10 @@ const MODELS = {
163
82
  // Model aliases: some CLI tools send short names that Grazie doesn't recognise yet.
164
83
  // Map them to the Grazie-accepted equivalents so the proxy can rewrite on the fly.
165
84
  const MODEL_ALIASES = {
85
+ // Temporary compatibility mapping: staging currently exposes GPT-5.4 as spark preview.
86
+ // Keep user-facing defaults on gpt-5.4 and rewrite at the proxy edge.
87
+ 'gpt-5.4': 'gpt-5.3-codex-spark-preview',
88
+ 'openai-gpt-5-4': 'gpt-5.3-codex-spark-preview',
166
89
  };
167
90
 
168
91
  // All models for tools that support multiple providers (OpenCode, Codex)
package/lib/model-list.js CHANGED
@@ -56,7 +56,7 @@ function getGroupsForTool(tool) {
56
56
  defaultModel: config.MODELS.openai.default,
57
57
  },
58
58
  {
59
- title: 'Grazie Native Chat (Google, DeepSeek, Mistral, xAI, Qwen, etc.)',
59
+ title: 'Grazie Native (xAI, DeepSeek)',
60
60
  models: config.MODELS.grazie.available,
61
61
  defaultModel: config.MODELS.grazie.default,
62
62
  },
@@ -85,7 +85,7 @@ function getGroupsForTool(tool) {
85
85
  defaultModel: config.MODELS.gemini.default,
86
86
  },
87
87
  {
88
- title: 'Grazie Native Chat (Google, DeepSeek, Mistral, xAI, Qwen, etc.)',
88
+ title: 'Grazie Native (xAI, DeepSeek)',
89
89
  models: config.MODELS.grazie.available,
90
90
  defaultModel: config.MODELS.grazie.default,
91
91
  },
@@ -1,30 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
3
  const config = require('./config');
6
4
 
7
- // Fix node-pty spawn-helper permissions (macOS/Linux)
8
- // The prebuilt binary sometimes loses execute permissions during npm install
9
- try {
10
- const platform = process.platform === 'darwin' ? 'darwin' : process.platform;
11
- const arch = process.arch;
12
- const spawnHelperPath = path.join(
13
- __dirname,
14
- '..',
15
- 'node_modules',
16
- 'node-pty',
17
- 'prebuilds',
18
- `${platform}-${arch}`,
19
- 'spawn-helper'
20
- );
21
- if (fs.existsSync(spawnHelperPath)) {
22
- fs.chmodSync(spawnHelperPath, 0o755);
23
- }
24
- } catch {
25
- // Ignore errors - this is a best-effort fix
26
- }
27
-
28
5
  console.log(`
29
6
  ╔══════════════════════════════════════════════════════════════╗
30
7
  ║ jbai-cli installed! ║
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jbai-cli",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "CLI wrappers to use AI coding tools (Claude Code, Codex, Gemini CLI, OpenCode, Goose, Continue) with JetBrains AI Platform",
5
5
  "keywords": [
6
6
  "jetbrains",
@@ -35,6 +35,7 @@
35
35
  "jbai-codex": "bin/jbai-codex.js",
36
36
  "jbai-codex-5.2": "bin/jbai-codex-5.2.js",
37
37
  "jbai-codex-5.3": "bin/jbai-codex-5.3.js",
38
+ "jbai-codex-5.4": "bin/jbai-codex-5.4.js",
38
39
  "jbai-codex-rockhopper": "bin/jbai-codex-rockhopper.js",
39
40
  "jbai-gemini": "bin/jbai-gemini.js",
40
41
  "jbai-gemini-3.1": "bin/jbai-gemini-3.1.js",
@@ -55,9 +56,7 @@
55
56
  "engines": {
56
57
  "node": ">=18.0.0"
57
58
  },
58
- "dependencies": {
59
- "node-pty": "^1.1.0"
60
- },
59
+ "dependencies": {},
61
60
  "scripts": {
62
61
  "postinstall": "node lib/postinstall.js",
63
62
  "test": "node bin/jbai.js test"
package/lib/handoff.js DELETED
@@ -1,152 +0,0 @@
1
- const { execSync } = require('child_process');
2
- const config = require('./config');
3
-
4
- function getGitOutput(command, cwd = process.cwd()) {
5
- try {
6
- return execSync(command, { stdio: ['ignore', 'pipe', 'ignore'], cwd }).toString().trim();
7
- } catch {
8
- return '';
9
- }
10
- }
11
-
12
- function getGitRepoUrl(cwd = process.cwd()) {
13
- return getGitOutput('git remote get-url origin', cwd);
14
- }
15
-
16
- function getGitRef(cwd = process.cwd()) {
17
- const branch = getGitOutput('git rev-parse --abbrev-ref HEAD', cwd);
18
- if (branch && branch !== 'HEAD') {
19
- return branch;
20
- }
21
- return getGitOutput('git rev-parse HEAD', cwd);
22
- }
23
-
24
- function openUrl(url) {
25
- const escaped = url.replace(/"/g, '\\"');
26
- try {
27
- if (process.platform === 'darwin') {
28
- execSync(`open "${escaped}"`, { stdio: 'ignore' });
29
- return true;
30
- }
31
- if (process.platform === 'win32') {
32
- execSync(`start "" "${escaped}"`, { stdio: 'ignore' });
33
- return true;
34
- }
35
- execSync(`xdg-open "${escaped}"`, { stdio: 'ignore' });
36
- return true;
37
- } catch {
38
- return false;
39
- }
40
- }
41
-
42
- function normalizeGrazieEnvironment(env) {
43
- if (!env) {
44
- return config.getEnvironment() === 'production' ? 'PRODUCTION' : 'STAGING';
45
- }
46
- return env.toString().toUpperCase();
47
- }
48
-
49
- function getDefaultModel() {
50
- return config.MODELS.claude.default;
51
- }
52
-
53
- function parseBool(value, fallback) {
54
- if (value === undefined || value === null) {
55
- return fallback;
56
- }
57
- const normalized = value.toString().toLowerCase();
58
- if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
59
- if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
60
- return fallback;
61
- }
62
-
63
- async function createHandoff({
64
- task,
65
- repoUrl,
66
- ref,
67
- branchName,
68
- grazieToken,
69
- grazieEnvironment,
70
- grazieModel,
71
- gitToken,
72
- facadeToken,
73
- orcaUrl,
74
- source,
75
- autoStart,
76
- shouldOpen,
77
- cwd,
78
- }) {
79
- const finalTask = task && task.trim()
80
- ? task.trim()
81
- : 'Continue the current task from the CLI session.';
82
-
83
- const finalRepoUrl = repoUrl && repoUrl.trim() ? repoUrl.trim() : getGitRepoUrl(cwd);
84
- if (!finalRepoUrl) {
85
- throw new Error('Could not determine git repo. Use --repo or set JBAI_HANDOFF_REPO.');
86
- }
87
-
88
- const finalGrazieToken = grazieToken || config.getToken();
89
- if (!finalGrazieToken) {
90
- throw new Error('No Grazie token found. Run: jbai token set');
91
- }
92
- if (config.isTokenExpired(finalGrazieToken)) {
93
- throw new Error('Grazie token expired. Run: jbai token refresh');
94
- }
95
-
96
- const finalRef = ref || getGitRef(cwd);
97
- const finalOrcaUrl = (orcaUrl || process.env.ORCA_LAB_URL || 'http://localhost:3000')
98
- .replace(/\/$/, '');
99
- const finalFacadeToken = facadeToken || process.env.FACADE_JWT_TOKEN || '';
100
- const finalGitToken = gitToken || process.env.GITHUB_TOKEN || process.env.GH_TOKEN || '';
101
- const finalGrazieEnv = normalizeGrazieEnvironment(
102
- grazieEnvironment || process.env.JBAI_HANDOFF_ENV
103
- );
104
- const finalModel = grazieModel || process.env.JBAI_HANDOFF_MODEL || getDefaultModel();
105
- const finalAutoStart = parseBool(autoStart ?? process.env.JBAI_HANDOFF_AUTO_START, true);
106
- const openBrowser = parseBool(shouldOpen ?? process.env.JBAI_HANDOFF_OPEN, true);
107
-
108
- const payload = {
109
- task: finalTask,
110
- repoUrl: finalRepoUrl,
111
- ref: finalRef || undefined,
112
- branchName: branchName || process.env.JBAI_HANDOFF_BRANCH || undefined,
113
- gitToken: finalGitToken || undefined,
114
- grazieToken: finalGrazieToken,
115
- grazieEnvironment: finalGrazieEnv,
116
- grazieModel: finalModel,
117
- source: source || 'jbai-cli',
118
- autoStart: finalAutoStart,
119
- };
120
-
121
- const headers = {
122
- 'Content-Type': 'application/json',
123
- ...(finalFacadeToken ? { Authorization: `Bearer ${finalFacadeToken}` } : {}),
124
- };
125
-
126
- const response = await fetch(`${finalOrcaUrl}/api/handoff`, {
127
- method: 'POST',
128
- headers,
129
- body: JSON.stringify(payload),
130
- });
131
-
132
- if (!response.ok) {
133
- const errorText = await response.text().catch(() => '');
134
- const detail = errorText ? ` ${errorText}` : '';
135
- throw new Error(`Orca Lab handoff failed (${response.status}).${detail}`);
136
- }
137
-
138
- const result = await response.json();
139
-
140
- if (openBrowser && result.environmentUrl) {
141
- openUrl(result.environmentUrl);
142
- }
143
-
144
- return result;
145
- }
146
-
147
- module.exports = {
148
- createHandoff,
149
- getGitRepoUrl,
150
- getGitRef,
151
- openUrl,
152
- };
@@ -1,220 +0,0 @@
1
- const os = require('os');
2
- const { spawn } = require('child_process');
3
- const pty = require('node-pty');
4
- const { createHandoff } = require('./handoff');
5
-
6
- const DEFAULT_TRIGGER = '\x1d'; // Ctrl+]
7
- const DEFAULT_TRIGGER_LABEL = 'Ctrl+]';
8
-
9
- function stripHandoffFlag(args) {
10
- const cleaned = [];
11
- let disabled = false;
12
- for (const arg of args) {
13
- if (arg === '--no-handoff') {
14
- disabled = true;
15
- continue;
16
- }
17
- cleaned.push(arg);
18
- }
19
- return { args: cleaned, disabled };
20
- }
21
-
22
- function isPrintableChar(char) {
23
- const code = char.charCodeAt(0);
24
- return code >= 32 && code !== 127;
25
- }
26
-
27
- function shouldIgnoreLine(line) {
28
- const trimmed = line.trim();
29
- if (!trimmed) return true;
30
- if (trimmed.startsWith('/')) return true;
31
- return false;
32
- }
33
-
34
- function buildTrigger() {
35
- const trigger = process.env.JBAI_HANDOFF_TRIGGER || DEFAULT_TRIGGER;
36
- const label = process.env.JBAI_HANDOFF_TRIGGER_LABEL || DEFAULT_TRIGGER_LABEL;
37
- return { trigger, label };
38
- }
39
-
40
- function runWithHandoff({
41
- command,
42
- args,
43
- env,
44
- toolName,
45
- handoffDefaults = {},
46
- }) {
47
- const { trigger, label } = buildTrigger();
48
- const canUsePty = process.stdin.isTTY && process.stdout.isTTY;
49
-
50
- if (!canUsePty || handoffDefaults.enabled === false) {
51
- const child = spawn(command, args, { stdio: 'inherit', env });
52
- return child;
53
- }
54
-
55
- process.stderr.write(`ℹ️ Handoff trigger: ${label}\n`);
56
-
57
- let ptyProcess;
58
- try {
59
- ptyProcess = pty.spawn(command, args, {
60
- name: 'xterm-256color',
61
- cols: process.stdout.columns || 80,
62
- rows: process.stdout.rows || 24,
63
- cwd: process.cwd(),
64
- env,
65
- });
66
- } catch (err) {
67
- // node-pty throws synchronous errors (e.g., posix_spawnp failed) when executable not found
68
- const error = new Error(err.message || 'Failed to spawn process');
69
- // Check if this is likely a "command not found" error
70
- const isNotFound = err.message && (
71
- err.message.includes('posix_spawnp failed') ||
72
- err.message.includes('ENOENT') ||
73
- err.message.includes('not found')
74
- );
75
- error.code = isNotFound ? 'ENOENT' : 'SPAWN_ERROR';
76
- error.command = command;
77
- error.originalError = err;
78
- // Return a minimal event emitter that immediately emits the error
79
- const { EventEmitter } = require('events');
80
- const fakeChild = new EventEmitter();
81
- setImmediate(() => fakeChild.emit('error', error));
82
- return fakeChild;
83
- }
84
-
85
- let lineBuffer = '';
86
- let lastPrompt = '';
87
- let inEscape = false;
88
- let handoffInProgress = false;
89
-
90
- const updateLineBuffer = (text) => {
91
- for (const char of text) {
92
- if (char === '\u001b') {
93
- inEscape = true;
94
- continue;
95
- }
96
- if (inEscape) {
97
- if (/[a-zA-Z~]/.test(char)) {
98
- inEscape = false;
99
- }
100
- continue;
101
- }
102
- if (char === '\r' || char === '\n') {
103
- if (!shouldIgnoreLine(lineBuffer)) {
104
- lastPrompt = lineBuffer.trim();
105
- }
106
- lineBuffer = '';
107
- continue;
108
- }
109
- if (char === '\u007f' || char === '\b') {
110
- if (lineBuffer.length > 0) {
111
- lineBuffer = lineBuffer.slice(0, -1);
112
- }
113
- continue;
114
- }
115
- if (!isPrintableChar(char)) {
116
- continue;
117
- }
118
- lineBuffer += char;
119
- if (lineBuffer.length > 4000) {
120
- lineBuffer = lineBuffer.slice(-4000);
121
- }
122
- }
123
- };
124
-
125
- const triggerHandoff = async () => {
126
- if (handoffInProgress) return;
127
- handoffInProgress = true;
128
- try {
129
- const fallbackTask = process.env.JBAI_HANDOFF_TASK;
130
- const task = (lastPrompt && lastPrompt.trim()) || fallbackTask || '';
131
- if (!task) {
132
- process.stderr.write('⚠️ No recent prompt detected; set JBAI_HANDOFF_TASK.\n');
133
- return;
134
- }
135
-
136
- process.stderr.write('🚀 Creating Orca Lab handoff...\n');
137
- const result = await createHandoff({
138
- task,
139
- repoUrl: process.env.JBAI_HANDOFF_REPO || handoffDefaults.repoUrl,
140
- ref: process.env.JBAI_HANDOFF_REF || handoffDefaults.ref,
141
- branchName: process.env.JBAI_HANDOFF_BRANCH || handoffDefaults.branchName,
142
- gitToken: process.env.GITHUB_TOKEN || process.env.GH_TOKEN || handoffDefaults.gitToken,
143
- facadeToken: process.env.FACADE_JWT_TOKEN || handoffDefaults.facadeToken,
144
- orcaUrl: process.env.ORCA_LAB_URL || handoffDefaults.orcaUrl,
145
- grazieToken: handoffDefaults.grazieToken,
146
- grazieEnvironment: handoffDefaults.grazieEnvironment,
147
- grazieModel: handoffDefaults.grazieModel,
148
- source: toolName || 'jbai-cli',
149
- autoStart: true,
150
- shouldOpen: true,
151
- cwd: handoffDefaults.cwd,
152
- });
153
-
154
- process.stderr.write(`✅ Handoff ready: ${result.environmentUrl}\n`);
155
- } catch (error) {
156
- const message = error instanceof Error ? error.message : 'Handoff failed';
157
- process.stderr.write(`❌ ${message}\n`);
158
- } finally {
159
- handoffInProgress = false;
160
- }
161
- };
162
-
163
- ptyProcess.onData((data) => process.stdout.write(data));
164
-
165
- const onStdinData = (data) => {
166
- const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
167
- const hasTrigger = buffer.includes(Buffer.from(trigger));
168
-
169
- const sanitized = hasTrigger
170
- ? Buffer.from(buffer.toString('utf8').split(trigger).join(''), 'utf8')
171
- : buffer;
172
-
173
- if (hasTrigger) {
174
- triggerHandoff();
175
- }
176
-
177
- if (sanitized.length > 0) {
178
- updateLineBuffer(sanitized.toString('utf8'));
179
- ptyProcess.write(sanitized);
180
- }
181
- };
182
-
183
- const onResize = () => {
184
- try {
185
- ptyProcess.resize(process.stdout.columns || 80, process.stdout.rows || 24);
186
- } catch {
187
- // Ignore resize errors
188
- }
189
- };
190
-
191
- process.stdin.setRawMode(true);
192
- process.stdin.resume();
193
- process.stdin.on('data', onStdinData);
194
-
195
- if (process.stdout.isTTY) {
196
- process.stdout.on('resize', onResize);
197
- }
198
-
199
- const cleanup = () => {
200
- if (process.stdin.isTTY) {
201
- process.stdin.setRawMode(false);
202
- }
203
- process.stdin.off('data', onStdinData);
204
- if (process.stdout.isTTY) {
205
- process.stdout.off('resize', onResize);
206
- }
207
- };
208
-
209
- ptyProcess.onExit(({ exitCode }) => {
210
- cleanup();
211
- process.exit(exitCode || 0);
212
- });
213
-
214
- return ptyProcess;
215
- }
216
-
217
- module.exports = {
218
- runWithHandoff,
219
- stripHandoffFlag,
220
- };