jbai-cli 1.0.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 +211 -0
- package/bin/jbai-aider.js +47 -0
- package/bin/jbai-claude.js +46 -0
- package/bin/jbai-codex.js +80 -0
- package/bin/jbai-gemini.js +136 -0
- package/bin/jbai-opencode.js +47 -0
- package/bin/jbai.js +249 -0
- package/lib/config.js +141 -0
- package/lib/postinstall.js +25 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# jbai-cli
|
|
2
|
+
|
|
3
|
+
**Use AI coding tools with your JetBrains AI subscription** — no separate API keys needed.
|
|
4
|
+
|
|
5
|
+
One token, all tools: Claude Code, Codex, Aider, Gemini, OpenCode.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g jbai-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup (2 minutes)
|
|
14
|
+
|
|
15
|
+
### Step 1: Get your token
|
|
16
|
+
|
|
17
|
+
1. Go to [platform.jetbrains.ai](https://platform.jetbrains.ai/) (or [staging](https://platform.stgn.jetbrains.ai/))
|
|
18
|
+
2. Click your **Profile** icon (top right)
|
|
19
|
+
3. Click **"Copy Developer Token"**
|
|
20
|
+
|
|
21
|
+
### Step 2: Save your token
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
jbai token set
|
|
25
|
+
# Paste your token when prompted
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Step 3: Verify it works
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
jbai test
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Expected output:
|
|
35
|
+
```
|
|
36
|
+
Testing JetBrains AI Platform (staging)
|
|
37
|
+
|
|
38
|
+
1. OpenAI Proxy (GPT): ✅ Working
|
|
39
|
+
2. Anthropic Proxy (Claude): ✅ Working
|
|
40
|
+
3. Google Proxy (Gemini): ✅ Working
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Claude Code
|
|
46
|
+
```bash
|
|
47
|
+
jbai-claude
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Codex CLI
|
|
51
|
+
```bash
|
|
52
|
+
# Interactive mode
|
|
53
|
+
jbai-codex
|
|
54
|
+
|
|
55
|
+
# One-shot task
|
|
56
|
+
jbai-codex exec "explain this codebase"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Aider
|
|
60
|
+
```bash
|
|
61
|
+
jbai-aider
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Gemini
|
|
65
|
+
```bash
|
|
66
|
+
# Interactive chat
|
|
67
|
+
jbai-gemini
|
|
68
|
+
|
|
69
|
+
# One-shot question
|
|
70
|
+
jbai-gemini "What is Kubernetes?"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### OpenCode
|
|
74
|
+
```bash
|
|
75
|
+
jbai-opencode
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Using Different Models
|
|
79
|
+
|
|
80
|
+
Each tool has a sensible default, but you can specify any available model:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Claude with Opus
|
|
84
|
+
jbai-claude --model claude-opus-4-1-20250805
|
|
85
|
+
|
|
86
|
+
# Codex with GPT-5
|
|
87
|
+
jbai-codex --model gpt-5-2025-08-07
|
|
88
|
+
|
|
89
|
+
# Gemini with Pro
|
|
90
|
+
jbai-gemini --model gemini-2.5-pro "Your question"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Available Models
|
|
94
|
+
|
|
95
|
+
**Claude (Anthropic)**
|
|
96
|
+
| Model | Notes |
|
|
97
|
+
|-------|-------|
|
|
98
|
+
| `claude-sonnet-4-5-20250929` | Default, recommended |
|
|
99
|
+
| `claude-opus-4-1-20250805` | Most capable |
|
|
100
|
+
| `claude-sonnet-4-20250514` | |
|
|
101
|
+
| `claude-3-7-sonnet-20250219` | |
|
|
102
|
+
| `claude-3-5-haiku-20241022` | Fast |
|
|
103
|
+
|
|
104
|
+
**GPT (OpenAI)**
|
|
105
|
+
| Model | Notes |
|
|
106
|
+
|-------|-------|
|
|
107
|
+
| `gpt-4o-2024-11-20` | Default |
|
|
108
|
+
| `gpt-5-2025-08-07` | Latest |
|
|
109
|
+
| `gpt-5.1-2025-11-13` | |
|
|
110
|
+
| `gpt-5-mini-2025-08-07` | Fast |
|
|
111
|
+
| `o3-2025-04-16` | Reasoning |
|
|
112
|
+
| `o3-mini-2025-01-31` | |
|
|
113
|
+
|
|
114
|
+
**Gemini (Google)**
|
|
115
|
+
| Model | Notes |
|
|
116
|
+
|-------|-------|
|
|
117
|
+
| `gemini-2.5-flash` | Default, fast |
|
|
118
|
+
| `gemini-2.5-pro` | More capable |
|
|
119
|
+
| `gemini-3-pro-preview` | Preview |
|
|
120
|
+
| `gemini-3-flash-preview` | Preview |
|
|
121
|
+
|
|
122
|
+
## Commands Reference
|
|
123
|
+
|
|
124
|
+
| Command | Description |
|
|
125
|
+
|---------|-------------|
|
|
126
|
+
| `jbai help` | Show help |
|
|
127
|
+
| `jbai token` | Show token status |
|
|
128
|
+
| `jbai token set` | Set/update token |
|
|
129
|
+
| `jbai test` | Test API connections |
|
|
130
|
+
| `jbai models` | List all models |
|
|
131
|
+
| `jbai env staging` | Use staging environment |
|
|
132
|
+
| `jbai env production` | Use production environment |
|
|
133
|
+
|
|
134
|
+
## Prerequisites
|
|
135
|
+
|
|
136
|
+
Install the underlying tools you want to use:
|
|
137
|
+
|
|
138
|
+
| Tool | Install Command |
|
|
139
|
+
|------|-----------------|
|
|
140
|
+
| Claude Code | `npm i -g @anthropic-ai/claude-code` |
|
|
141
|
+
| Codex | `npm i -g @openai/codex` |
|
|
142
|
+
| Aider | `pip install aider-chat` |
|
|
143
|
+
| OpenCode | `go install github.com/opencode-ai/opencode@latest` |
|
|
144
|
+
| Gemini | Built-in, no install needed |
|
|
145
|
+
|
|
146
|
+
## Token Management
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Check token status (shows expiry date)
|
|
150
|
+
jbai token
|
|
151
|
+
|
|
152
|
+
# Update expired token
|
|
153
|
+
jbai token set
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Tokens are stored securely at `~/.jbai/token`
|
|
157
|
+
|
|
158
|
+
## Switching Environments
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Staging (default) - for testing
|
|
162
|
+
jbai env staging
|
|
163
|
+
|
|
164
|
+
# Production - for real work
|
|
165
|
+
jbai env production
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> **Note**: Staging and production use different tokens. Get the right one from the corresponding platform URL.
|
|
169
|
+
|
|
170
|
+
## How It Works
|
|
171
|
+
|
|
172
|
+
jbai-cli uses JetBrains AI Platform's **Guarded Proxy**, which provides API-compatible endpoints:
|
|
173
|
+
|
|
174
|
+
- OpenAI API → `api.jetbrains.ai/user/v5/llm/openai/v1`
|
|
175
|
+
- Anthropic API → `api.jetbrains.ai/user/v5/llm/anthropic/v1`
|
|
176
|
+
- Google Vertex → `api.jetbrains.ai/user/v5/llm/google/v1/vertex`
|
|
177
|
+
|
|
178
|
+
Your JetBrains AI token authenticates all requests via the `Grazie-Authenticate-JWT` header.
|
|
179
|
+
|
|
180
|
+
## Troubleshooting
|
|
181
|
+
|
|
182
|
+
### "Token expired"
|
|
183
|
+
```bash
|
|
184
|
+
jbai token set
|
|
185
|
+
# Get fresh token from platform.jetbrains.ai
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### "Claude Code not found"
|
|
189
|
+
```bash
|
|
190
|
+
npm install -g @anthropic-ai/claude-code
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### "Connection failed"
|
|
194
|
+
```bash
|
|
195
|
+
# Test which endpoints work
|
|
196
|
+
jbai test
|
|
197
|
+
|
|
198
|
+
# Check your environment
|
|
199
|
+
jbai token
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Wrong environment
|
|
203
|
+
```bash
|
|
204
|
+
# Staging token won't work with production
|
|
205
|
+
jbai env staging # if using staging token
|
|
206
|
+
jbai env production # if using production token
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const config = require('../lib/config');
|
|
5
|
+
|
|
6
|
+
const token = config.getToken();
|
|
7
|
+
if (!token) {
|
|
8
|
+
console.error('❌ No token found. Run: jbai token set');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (config.isTokenExpired(token)) {
|
|
13
|
+
console.error('⚠️ Token expired. Run: jbai token refresh');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const endpoints = config.getEndpoints();
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
// Build aider arguments
|
|
21
|
+
const hasModel = args.includes('--model');
|
|
22
|
+
const aiderArgs = [
|
|
23
|
+
'--openai-api-base', endpoints.openai,
|
|
24
|
+
'--openai-api-key', 'placeholder',
|
|
25
|
+
'--extra-headers', JSON.stringify({ 'Grazie-Authenticate-JWT': token })
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
if (!hasModel) {
|
|
29
|
+
aiderArgs.push('--model', config.MODELS.openai.default);
|
|
30
|
+
}
|
|
31
|
+
aiderArgs.push(...args);
|
|
32
|
+
|
|
33
|
+
const child = spawn('aider', aiderArgs, {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
env: process.env
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
child.on('error', (err) => {
|
|
39
|
+
if (err.code === 'ENOENT') {
|
|
40
|
+
console.error('❌ Aider not found. Install: pip install aider-chat');
|
|
41
|
+
} else {
|
|
42
|
+
console.error(`Error: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const config = require('../lib/config');
|
|
5
|
+
|
|
6
|
+
const token = config.getToken();
|
|
7
|
+
if (!token) {
|
|
8
|
+
console.error('❌ No token found. Run: jbai token set');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (config.isTokenExpired(token)) {
|
|
13
|
+
console.error('⚠️ Token expired. Run: jbai token refresh');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const endpoints = config.getEndpoints();
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
// Check if model specified
|
|
21
|
+
const hasModel = args.includes('--model') || args.includes('-m');
|
|
22
|
+
const finalArgs = hasModel ? args : ['--model', config.MODELS.claude.default, ...args];
|
|
23
|
+
|
|
24
|
+
// Set environment for Claude Code
|
|
25
|
+
const env = {
|
|
26
|
+
...process.env,
|
|
27
|
+
ANTHROPIC_BASE_URL: endpoints.anthropic,
|
|
28
|
+
ANTHROPIC_API_KEY: token,
|
|
29
|
+
ANTHROPIC_AUTH_TOKEN: token
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const child = spawn('claude', finalArgs, {
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
env
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
child.on('error', (err) => {
|
|
38
|
+
if (err.code === 'ENOENT') {
|
|
39
|
+
console.error('❌ Claude Code not found. Install: npm install -g @anthropic-ai/claude-code');
|
|
40
|
+
} else {
|
|
41
|
+
console.error(`Error: ${err.message}`);
|
|
42
|
+
}
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
@@ -0,0 +1,80 @@
|
|
|
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 config = require('../lib/config');
|
|
8
|
+
|
|
9
|
+
const token = config.getToken();
|
|
10
|
+
if (!token) {
|
|
11
|
+
console.error('❌ No token found. Run: jbai token set');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (config.isTokenExpired(token)) {
|
|
16
|
+
console.error('⚠️ Token expired. Run: jbai token refresh');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const endpoints = config.getEndpoints();
|
|
21
|
+
const env = config.getEnvironment();
|
|
22
|
+
const providerName = env === 'staging' ? 'jbai-staging' : 'jbai';
|
|
23
|
+
const envVarName = env === 'staging' ? 'GRAZIE_STAGING_TOKEN' : 'GRAZIE_API_TOKEN';
|
|
24
|
+
|
|
25
|
+
// Ensure Codex config exists
|
|
26
|
+
const codexDir = path.join(os.homedir(), '.codex');
|
|
27
|
+
const codexConfig = path.join(codexDir, 'config.toml');
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(codexDir)) {
|
|
30
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check if our provider is configured
|
|
34
|
+
let configContent = '';
|
|
35
|
+
if (fs.existsSync(codexConfig)) {
|
|
36
|
+
configContent = fs.readFileSync(codexConfig, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!configContent.includes(`[model_providers.${providerName}]`)) {
|
|
40
|
+
const providerConfig = `
|
|
41
|
+
# JetBrains AI (${env})
|
|
42
|
+
[model_providers.${providerName}]
|
|
43
|
+
name = "JetBrains AI (${env})"
|
|
44
|
+
base_url = "${endpoints.openai}"
|
|
45
|
+
env_http_headers = { "Grazie-Authenticate-JWT" = "${envVarName}" }
|
|
46
|
+
wire_api = "responses"
|
|
47
|
+
`;
|
|
48
|
+
fs.appendFileSync(codexConfig, providerConfig);
|
|
49
|
+
console.log(`✅ Added ${providerName} provider to Codex config`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
const hasModel = args.includes('--model');
|
|
54
|
+
const finalArgs = ['-c', `model_provider=${providerName}`];
|
|
55
|
+
|
|
56
|
+
if (!hasModel) {
|
|
57
|
+
finalArgs.push('--model', config.MODELS.openai.default);
|
|
58
|
+
}
|
|
59
|
+
finalArgs.push(...args);
|
|
60
|
+
|
|
61
|
+
const childEnv = {
|
|
62
|
+
...process.env,
|
|
63
|
+
[envVarName]: token
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const child = spawn('codex', finalArgs, {
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
env: childEnv
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
child.on('error', (err) => {
|
|
72
|
+
if (err.code === 'ENOENT') {
|
|
73
|
+
console.error('❌ Codex not found. Install: npm install -g @openai/codex');
|
|
74
|
+
} else {
|
|
75
|
+
console.error(`Error: ${err.message}`);
|
|
76
|
+
}
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const config = require('../lib/config');
|
|
5
|
+
|
|
6
|
+
const token = config.getToken();
|
|
7
|
+
if (!token) {
|
|
8
|
+
console.error('❌ No token found. Run: jbai token set');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (config.isTokenExpired(token)) {
|
|
13
|
+
console.error('⚠️ Token expired. Run: jbai token refresh');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const endpoints = config.getEndpoints();
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
// Check if model specified
|
|
21
|
+
const hasModel = args.includes('--model') || args.includes('-m');
|
|
22
|
+
const finalArgs = hasModel ? args : ['--model', config.MODELS.claude.default, ...args];
|
|
23
|
+
|
|
24
|
+
// Set environment for OpenCode
|
|
25
|
+
const env = {
|
|
26
|
+
...process.env,
|
|
27
|
+
ANTHROPIC_BASE_URL: endpoints.anthropic,
|
|
28
|
+
ANTHROPIC_API_KEY: token,
|
|
29
|
+
OPENAI_API_BASE: endpoints.openai,
|
|
30
|
+
OPENAI_API_KEY: token
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const child = spawn('opencode', finalArgs, {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
env
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
child.on('error', (err) => {
|
|
39
|
+
if (err.code === 'ENOENT') {
|
|
40
|
+
console.error('❌ OpenCode not found. Install: go install github.com/opencode-ai/opencode@latest');
|
|
41
|
+
} else {
|
|
42
|
+
console.error(`Error: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on('exit', (code) => process.exit(code || 0));
|
package/bin/jbai.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const config = require('../lib/config');
|
|
7
|
+
|
|
8
|
+
const VERSION = require('../package.json').version;
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
jbai-cli v${VERSION} - JetBrains AI Platform CLI Tools
|
|
12
|
+
|
|
13
|
+
COMMANDS:
|
|
14
|
+
jbai token Show token status
|
|
15
|
+
jbai token set Set token interactively
|
|
16
|
+
jbai token refresh Refresh expired token
|
|
17
|
+
jbai test Test all API endpoints
|
|
18
|
+
jbai env [staging|production] Switch environment
|
|
19
|
+
jbai models List available models
|
|
20
|
+
jbai help Show this help
|
|
21
|
+
|
|
22
|
+
TOOL WRAPPERS:
|
|
23
|
+
jbai-claude Launch Claude Code with JetBrains AI
|
|
24
|
+
jbai-codex Launch Codex CLI with JetBrains AI
|
|
25
|
+
jbai-aider Launch Aider with JetBrains AI
|
|
26
|
+
jbai-gemini Launch Gemini with JetBrains AI
|
|
27
|
+
jbai-opencode Launch OpenCode with JetBrains AI
|
|
28
|
+
|
|
29
|
+
EXAMPLES:
|
|
30
|
+
jbai token set # Set your token
|
|
31
|
+
jbai-claude # Start Claude Code
|
|
32
|
+
jbai-codex exec "explain code" # Run Codex task
|
|
33
|
+
jbai-aider # Start Aider
|
|
34
|
+
|
|
35
|
+
TOKEN:
|
|
36
|
+
Get token: ${config.getEndpoints().tokenUrl}
|
|
37
|
+
Stored at: ${config.TOKEN_FILE}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
async function showTokenStatus() {
|
|
41
|
+
const token = config.getToken();
|
|
42
|
+
|
|
43
|
+
if (!token) {
|
|
44
|
+
console.log('❌ No token found');
|
|
45
|
+
console.log(` Run: jbai token set`);
|
|
46
|
+
console.log(` Get token: ${config.getEndpoints().tokenUrl}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`Token file: ${config.TOKEN_FILE}`);
|
|
51
|
+
console.log(`Token length: ${token.length} chars`);
|
|
52
|
+
console.log(`Environment: ${config.getEnvironment()}`);
|
|
53
|
+
|
|
54
|
+
const expiry = config.getTokenExpiry(token);
|
|
55
|
+
if (expiry) {
|
|
56
|
+
const now = new Date();
|
|
57
|
+
const daysLeft = Math.floor((expiry - now) / (1000 * 60 * 60 * 24));
|
|
58
|
+
|
|
59
|
+
if (config.isTokenExpired(token)) {
|
|
60
|
+
console.log(`⚠️ Token EXPIRED: ${expiry.toLocaleString()}`);
|
|
61
|
+
console.log(` Run: jbai token refresh`);
|
|
62
|
+
} else {
|
|
63
|
+
console.log(`✅ Expires: ${expiry.toLocaleString()} (${daysLeft} days left)`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function setToken() {
|
|
69
|
+
const endpoints = config.getEndpoints();
|
|
70
|
+
console.log(`Get token from: ${endpoints.tokenUrl}`);
|
|
71
|
+
console.log(`Click Profile → 'Copy Developer Token'\n`);
|
|
72
|
+
|
|
73
|
+
const rl = readline.createInterface({
|
|
74
|
+
input: process.stdin,
|
|
75
|
+
output: process.stdout
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
rl.question('Paste your token: ', (token) => {
|
|
80
|
+
rl.close();
|
|
81
|
+
|
|
82
|
+
if (!token || !token.includes('.')) {
|
|
83
|
+
console.log('❌ Invalid token format');
|
|
84
|
+
resolve(false);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
config.setToken(token);
|
|
89
|
+
console.log('\n✅ Token saved!');
|
|
90
|
+
showTokenStatus();
|
|
91
|
+
resolve(true);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function testEndpoints() {
|
|
97
|
+
const token = config.getToken();
|
|
98
|
+
if (!token) {
|
|
99
|
+
console.log('❌ No token found. Run: jbai token set');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const endpoints = config.getEndpoints();
|
|
104
|
+
console.log(`Testing JetBrains AI Platform (${config.getEnvironment()})\n`);
|
|
105
|
+
|
|
106
|
+
// Test OpenAI
|
|
107
|
+
process.stdout.write('1. OpenAI Proxy (GPT): ');
|
|
108
|
+
try {
|
|
109
|
+
const result = await httpPost(
|
|
110
|
+
`${endpoints.openai}/chat/completions`,
|
|
111
|
+
{ model: 'gpt-4o-2024-11-20', messages: [{ role: 'user', content: 'Say OK' }], max_tokens: 5 },
|
|
112
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
113
|
+
);
|
|
114
|
+
console.log(result.choices ? '✅ Working' : '❌ Failed');
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.log(`❌ ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Test Anthropic
|
|
120
|
+
process.stdout.write('2. Anthropic Proxy (Claude): ');
|
|
121
|
+
try {
|
|
122
|
+
const result = await httpPost(
|
|
123
|
+
`${endpoints.anthropic}/messages`,
|
|
124
|
+
{ model: 'claude-sonnet-4-5-20250929', messages: [{ role: 'user', content: 'Say OK' }], max_tokens: 10 },
|
|
125
|
+
{ 'Grazie-Authenticate-JWT': token, 'anthropic-version': '2023-06-01' }
|
|
126
|
+
);
|
|
127
|
+
console.log(result.content ? '✅ Working' : '❌ Failed');
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.log(`❌ ${e.message}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Test Google
|
|
133
|
+
process.stdout.write('3. Google Proxy (Gemini): ');
|
|
134
|
+
try {
|
|
135
|
+
const result = await httpPost(
|
|
136
|
+
`${endpoints.google}/v1/projects/default/locations/default/publishers/google/models/gemini-2.5-flash:generateContent`,
|
|
137
|
+
{ contents: [{ role: 'user', parts: [{ text: 'Say OK' }] }] },
|
|
138
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
139
|
+
);
|
|
140
|
+
console.log(result.candidates ? '✅ Working' : '❌ Failed');
|
|
141
|
+
} catch (e) {
|
|
142
|
+
console.log(`❌ ${e.message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function httpPost(url, body, headers) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const urlObj = new URL(url);
|
|
149
|
+
const data = JSON.stringify(body);
|
|
150
|
+
|
|
151
|
+
const req = https.request({
|
|
152
|
+
hostname: urlObj.hostname,
|
|
153
|
+
port: 443,
|
|
154
|
+
path: urlObj.pathname,
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: {
|
|
157
|
+
'Content-Type': 'application/json',
|
|
158
|
+
'Content-Length': Buffer.byteLength(data),
|
|
159
|
+
...headers
|
|
160
|
+
}
|
|
161
|
+
}, (res) => {
|
|
162
|
+
let body = '';
|
|
163
|
+
res.on('data', chunk => body += chunk);
|
|
164
|
+
res.on('end', () => {
|
|
165
|
+
try {
|
|
166
|
+
resolve(JSON.parse(body));
|
|
167
|
+
} catch {
|
|
168
|
+
reject(new Error('Invalid response'));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
req.on('error', reject);
|
|
174
|
+
req.write(data);
|
|
175
|
+
req.end();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function showModels() {
|
|
180
|
+
console.log('Available Models:\n');
|
|
181
|
+
|
|
182
|
+
console.log('Claude (Anthropic):');
|
|
183
|
+
config.MODELS.claude.available.forEach((m, i) => {
|
|
184
|
+
const def = m === config.MODELS.claude.default ? ' (default)' : '';
|
|
185
|
+
console.log(` - ${m}${def}`);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
console.log('\nGPT (OpenAI):');
|
|
189
|
+
config.MODELS.openai.available.forEach((m, i) => {
|
|
190
|
+
const def = m === config.MODELS.openai.default ? ' (default)' : '';
|
|
191
|
+
console.log(` - ${m}${def}`);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
console.log('\nGemini (Google):');
|
|
195
|
+
config.MODELS.gemini.available.forEach((m, i) => {
|
|
196
|
+
const def = m === config.MODELS.gemini.default ? ' (default)' : '';
|
|
197
|
+
console.log(` - ${m}${def}`);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function setEnvironment(env) {
|
|
202
|
+
if (!['staging', 'production'].includes(env)) {
|
|
203
|
+
console.log('Usage: jbai env [staging|production]');
|
|
204
|
+
console.log(`Current: ${config.getEnvironment()}`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const cfg = config.getConfig();
|
|
209
|
+
cfg.environment = env;
|
|
210
|
+
config.setConfig(cfg);
|
|
211
|
+
console.log(`✅ Switched to ${env}`);
|
|
212
|
+
console.log(` Token URL: ${config.ENDPOINTS[env].tokenUrl}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Main
|
|
216
|
+
const [,, command, ...args] = process.argv;
|
|
217
|
+
|
|
218
|
+
switch (command) {
|
|
219
|
+
case 'token':
|
|
220
|
+
if (args[0] === 'set' || args[0] === 'refresh') {
|
|
221
|
+
setToken();
|
|
222
|
+
} else {
|
|
223
|
+
showTokenStatus();
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
case 'test':
|
|
227
|
+
testEndpoints();
|
|
228
|
+
break;
|
|
229
|
+
case 'models':
|
|
230
|
+
showModels();
|
|
231
|
+
break;
|
|
232
|
+
case 'env':
|
|
233
|
+
setEnvironment(args[0]);
|
|
234
|
+
break;
|
|
235
|
+
case 'help':
|
|
236
|
+
case '--help':
|
|
237
|
+
case '-h':
|
|
238
|
+
case undefined:
|
|
239
|
+
console.log(HELP);
|
|
240
|
+
break;
|
|
241
|
+
case 'version':
|
|
242
|
+
case '--version':
|
|
243
|
+
case '-v':
|
|
244
|
+
console.log(`jbai-cli v${VERSION}`);
|
|
245
|
+
break;
|
|
246
|
+
default:
|
|
247
|
+
console.log(`Unknown command: ${command}`);
|
|
248
|
+
console.log('Run: jbai help');
|
|
249
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.jbai');
|
|
6
|
+
const TOKEN_FILE = path.join(CONFIG_DIR, 'token');
|
|
7
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
8
|
+
|
|
9
|
+
const ENDPOINTS = {
|
|
10
|
+
staging: {
|
|
11
|
+
base: 'https://api.stgn.jetbrains.ai',
|
|
12
|
+
openai: 'https://api.stgn.jetbrains.ai/user/v5/llm/openai/v1',
|
|
13
|
+
anthropic: 'https://api.stgn.jetbrains.ai/user/v5/llm/anthropic/v1',
|
|
14
|
+
google: 'https://api.stgn.jetbrains.ai/user/v5/llm/google/v1/vertex',
|
|
15
|
+
tokenUrl: 'https://platform.stgn.jetbrains.ai/'
|
|
16
|
+
},
|
|
17
|
+
production: {
|
|
18
|
+
base: 'https://api.jetbrains.ai',
|
|
19
|
+
openai: 'https://api.jetbrains.ai/user/v5/llm/openai/v1',
|
|
20
|
+
anthropic: 'https://api.jetbrains.ai/user/v5/llm/anthropic/v1',
|
|
21
|
+
google: 'https://api.jetbrains.ai/user/v5/llm/google/v1/vertex',
|
|
22
|
+
tokenUrl: 'https://platform.jetbrains.ai/'
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const MODELS = {
|
|
27
|
+
claude: {
|
|
28
|
+
default: 'claude-sonnet-4-5-20250929',
|
|
29
|
+
available: [
|
|
30
|
+
'claude-sonnet-4-5-20250929',
|
|
31
|
+
'claude-opus-4-1-20250805',
|
|
32
|
+
'claude-sonnet-4-20250514',
|
|
33
|
+
'claude-3-7-sonnet-20250219',
|
|
34
|
+
'claude-3-5-haiku-20241022'
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
openai: {
|
|
38
|
+
default: 'gpt-4o-2024-11-20',
|
|
39
|
+
available: [
|
|
40
|
+
'gpt-4o-2024-11-20',
|
|
41
|
+
'gpt-5-2025-08-07',
|
|
42
|
+
'gpt-5.1-2025-11-13',
|
|
43
|
+
'gpt-5-mini-2025-08-07',
|
|
44
|
+
'o3-2025-04-16',
|
|
45
|
+
'o3-mini-2025-01-31'
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
gemini: {
|
|
49
|
+
default: 'gemini-2.5-flash',
|
|
50
|
+
available: [
|
|
51
|
+
'gemini-2.5-flash',
|
|
52
|
+
'gemini-2.5-pro',
|
|
53
|
+
'gemini-3-pro-preview',
|
|
54
|
+
'gemini-3-flash-preview'
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function ensureConfigDir() {
|
|
60
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
61
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getToken() {
|
|
66
|
+
if (!fs.existsSync(TOKEN_FILE)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return fs.readFileSync(TOKEN_FILE, 'utf-8').trim();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function setToken(token) {
|
|
73
|
+
ensureConfigDir();
|
|
74
|
+
fs.writeFileSync(TOKEN_FILE, token.trim(), { mode: 0o600 });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getConfig() {
|
|
78
|
+
ensureConfigDir();
|
|
79
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
80
|
+
return { environment: 'staging' };
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
84
|
+
} catch {
|
|
85
|
+
return { environment: 'staging' };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function setConfig(config) {
|
|
90
|
+
ensureConfigDir();
|
|
91
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getEnvironment() {
|
|
95
|
+
const config = getConfig();
|
|
96
|
+
return config.environment || 'staging';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getEndpoints() {
|
|
100
|
+
return ENDPOINTS[getEnvironment()];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseJWT(token) {
|
|
104
|
+
try {
|
|
105
|
+
const payload = token.split('.')[1];
|
|
106
|
+
const decoded = Buffer.from(payload, 'base64url').toString('utf-8');
|
|
107
|
+
return JSON.parse(decoded);
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getTokenExpiry(token) {
|
|
114
|
+
const payload = parseJWT(token);
|
|
115
|
+
if (!payload || !payload.exp) return null;
|
|
116
|
+
return new Date(payload.exp * 1000);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isTokenExpired(token) {
|
|
120
|
+
const expiry = getTokenExpiry(token);
|
|
121
|
+
if (!expiry) return true;
|
|
122
|
+
return expiry < new Date();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
CONFIG_DIR,
|
|
127
|
+
TOKEN_FILE,
|
|
128
|
+
CONFIG_FILE,
|
|
129
|
+
ENDPOINTS,
|
|
130
|
+
MODELS,
|
|
131
|
+
ensureConfigDir,
|
|
132
|
+
getToken,
|
|
133
|
+
setToken,
|
|
134
|
+
getConfig,
|
|
135
|
+
setConfig,
|
|
136
|
+
getEnvironment,
|
|
137
|
+
getEndpoints,
|
|
138
|
+
parseJWT,
|
|
139
|
+
getTokenExpiry,
|
|
140
|
+
isTokenExpired
|
|
141
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const config = require('./config');
|
|
4
|
+
|
|
5
|
+
console.log(`
|
|
6
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
7
|
+
║ jbai-cli installed! ║
|
|
8
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
9
|
+
|
|
10
|
+
Next steps:
|
|
11
|
+
|
|
12
|
+
1. Get your token from: ${config.ENDPOINTS.staging.tokenUrl}
|
|
13
|
+
(Click Profile → 'Copy Developer Token')
|
|
14
|
+
|
|
15
|
+
2. Set your token:
|
|
16
|
+
$ jbai token set
|
|
17
|
+
|
|
18
|
+
3. Start using AI tools:
|
|
19
|
+
$ jbai-claude # Claude Code
|
|
20
|
+
$ jbai-codex # OpenAI Codex
|
|
21
|
+
$ jbai-aider # Aider
|
|
22
|
+
$ jbai-gemini # Gemini
|
|
23
|
+
|
|
24
|
+
Run 'jbai help' for more options.
|
|
25
|
+
`);
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jbai-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI wrappers to use AI coding tools (Claude Code, Codex, Aider, Gemini) with JetBrains AI Platform",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jetbrains",
|
|
7
|
+
"ai",
|
|
8
|
+
"claude",
|
|
9
|
+
"codex",
|
|
10
|
+
"aider",
|
|
11
|
+
"gemini",
|
|
12
|
+
"cli",
|
|
13
|
+
"openai",
|
|
14
|
+
"anthropic"
|
|
15
|
+
],
|
|
16
|
+
"author": "Andrii Shchupliak",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/ashchupliak/jbai-cli"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/ashchupliak/jbai-cli/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/ashchupliak/jbai-cli#readme",
|
|
26
|
+
"bin": {
|
|
27
|
+
"jbai": "./bin/jbai.js",
|
|
28
|
+
"jbai-claude": "./bin/jbai-claude.js",
|
|
29
|
+
"jbai-codex": "./bin/jbai-codex.js",
|
|
30
|
+
"jbai-aider": "./bin/jbai-aider.js",
|
|
31
|
+
"jbai-gemini": "./bin/jbai-gemini.js",
|
|
32
|
+
"jbai-opencode": "./bin/jbai-opencode.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"bin/",
|
|
36
|
+
"lib/",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"postinstall": "node lib/postinstall.js",
|
|
44
|
+
"test": "node bin/jbai.js test"
|
|
45
|
+
}
|
|
46
|
+
}
|