claudmax 3.0.0 → 3.0.4

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.
Files changed (2) hide show
  1. package/index.js +389 -177
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -9,13 +9,30 @@ const os = require('os');
9
9
  const https = require('https');
10
10
  const { execSync } = require('child_process');
11
11
 
12
- // ── Constants ─────────────────────────────────────────────────────────────────
13
- const MCP_PKG = 'claudmax-mcp-server';
12
+ // ── Constants ──────────────────────────────────────────────────────────────
13
+ const MCP_PKG = 'claudmax-mcp';
14
14
  const API_BASE = 'https://api.claudmax.pro';
15
-
16
15
  const HOME = os.homedir();
17
16
 
18
- // ── Color helpers ─────────────────────────────────────────────────────────────
17
+ // ── CLI args ──────────────────────────────────────────────────────────────
18
+ const args = process.argv.slice(2);
19
+
20
+ // --version / -v — must be FIRST, before anything interactive
21
+ if (args.includes('--version') || args.includes('-v')) {
22
+ const pkg = require('./package.json');
23
+ console.log(pkg.version);
24
+ process.exit(0);
25
+ }
26
+
27
+ const flags = {};
28
+ for (let i = 0; i < args.length; i++) {
29
+ if (args[i].startsWith('--')) {
30
+ const key = args[i].slice(2);
31
+ flags[key] = args[i + 1] !== undefined && !args[i + 1].startsWith('--') ? args[i + 1] : true;
32
+ }
33
+ }
34
+
35
+ // ── Color helpers ─────────────────────────────────────────────────────────
19
36
  const C = {
20
37
  reset: '\x1b[0m',
21
38
  bold: (s) => `\x1b[1m${s}\x1b[0m`,
@@ -32,8 +49,15 @@ const CHECK = C.green('\u2713');
32
49
  const CROSS = C.red('\u2717');
33
50
  const WARN = C.yellow('\u26A0');
34
51
  const INFO = C.cyan('\u2139');
52
+ const ARROW = C.cyan('\u25b6');
53
+
54
+ // --help / -h — before any interactive prompts
55
+ if (args.includes('--help') || args.includes('-h')) {
56
+ printHelp();
57
+ process.exit(0);
58
+ }
35
59
 
36
- // ── Readline helper ────────────────────────────────────────────────────────────
60
+ // ── Readline helper ────────────────────────────────────────────────────────
37
61
  function createRL() {
38
62
  return readline.createInterface({ input: process.stdin, output: process.stdout });
39
63
  }
@@ -42,7 +66,7 @@ function ask(rl, question) {
42
66
  return new Promise((resolve) => rl.question(question, resolve));
43
67
  }
44
68
 
45
- // ── File helpers ──────────────────────────────────────────────────────────────
69
+ // ── File helpers ─────────────────────────────────────────────────────────
46
70
  function readJson(filePath) {
47
71
  try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
48
72
  catch { return null; }
@@ -54,289 +78,477 @@ function writeJson(filePath, data) {
54
78
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
55
79
  }
56
80
 
57
- function deepMerge(target, source) {
58
- for (const key of Object.keys(source)) {
59
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
60
- if (!target[key] || typeof target[key] !== 'object') target[key] = {};
61
- deepMerge(target[key], source[key]);
62
- } else {
63
- target[key] = source[key];
64
- }
81
+ function ensureDir(dir) {
82
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
83
+ }
84
+
85
+ function fileExists(filePath) {
86
+ try { return fs.existsSync(filePath); } catch { return false; }
87
+ }
88
+
89
+ // ── Platform-aware path helpers ──────────────────────────────────────────
90
+ function getVSCodeSettingsPath() {
91
+ if (process.platform === 'win32') {
92
+ return path.join(process.env.APPDATA || '', 'Code', 'User', 'settings.json');
93
+ } else if (process.platform === 'darwin') {
94
+ return path.join(HOME, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
95
+ } else {
96
+ return path.join(HOME, '.config', 'Code', 'User', 'settings.json');
65
97
  }
66
- return target;
67
98
  }
68
99
 
69
- function ensureDir(dir) {
70
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
100
+ function getCursorSettingsPath() {
101
+ if (process.platform === 'win32') {
102
+ return path.join(process.env.APPDATA || '', 'Cursor', 'User', 'settings.json');
103
+ } else if (process.platform === 'darwin') {
104
+ return path.join(HOME, 'Library', 'Application Support', 'Cursor', 'User', 'settings.json');
105
+ } else {
106
+ return path.join(HOME, '.config', 'Cursor', 'User', 'settings.json');
107
+ }
108
+ }
109
+
110
+ function getWindsurfSettingsPath() {
111
+ if (process.platform === 'win32') {
112
+ return path.join(process.env.APPDATA || '', 'Windsurf', 'User', 'settings.json');
113
+ } else if (process.platform === 'darwin') {
114
+ return path.join(HOME, 'Library', 'Application Support', 'Windsurf', 'User', 'settings.json');
115
+ } else {
116
+ return path.join(HOME, '.config', 'Windsurf', 'User', 'settings.json');
117
+ }
118
+ }
119
+
120
+ function getVSCodeExtensionsPath() {
121
+ if (process.platform === 'win32') {
122
+ return path.join(process.env.USERPROFILE || HOME, '.vscode', 'extensions');
123
+ } else if (process.platform === 'darwin') {
124
+ return path.join(HOME, '.vscode', 'extensions');
125
+ } else {
126
+ return path.join(HOME, '.vscode', 'extensions');
127
+ }
128
+ }
129
+
130
+ // ── IDE auto-detector ─────────────────────────────────────────────────────
131
+ function detectIDESilent() {
132
+ const detected = [];
133
+
134
+ // Claude Code CLI
135
+ if (fileExists(path.join(HOME, '.claude', 'settings.json')) ||
136
+ fileExists(path.join(HOME, '.claude.json'))) {
137
+ detected.push('claude-code');
138
+ }
139
+
140
+ // VS Code (Claude extension)
141
+ if (fileExists(getVSCodeSettingsPath())) {
142
+ detected.push('vscode');
143
+ }
144
+
145
+ // Cursor
146
+ if (fileExists(path.join(HOME, '.cursor', 'mcp.json')) ||
147
+ fileExists(path.join(HOME, 'Library', 'Application Support', 'Cursor'))) {
148
+ detected.push('cursor');
149
+ }
150
+
151
+ // Windsurf
152
+ if (fileExists(path.join(HOME, '.windsurf', 'mcp.json')) ||
153
+ fileExists(path.join(HOME, '.config', 'Windsurf'))) {
154
+ detected.push('windsurf');
155
+ }
156
+
157
+ // Cline — check VS Code extensions folder
158
+ const vscodeExtPath = getVSCodeExtensionsPath();
159
+ if (fileExists(path.join(vscodeExtPath, 'saoudrizwan.claude-dev'))) {
160
+ detected.push('cline');
161
+ }
162
+
163
+ // Roo Code — check VS Code extensions folder
164
+ if (fileExists(path.join(vscodeExtPath, 'RooVeterinaryInc.roo-cline'))) {
165
+ detected.push('roo');
166
+ }
167
+
168
+ // Antigravity
169
+ if (fileExists(path.join(HOME, '.config', 'antigravity', 'config.json'))) {
170
+ detected.push('antigravity');
171
+ }
172
+
173
+ return [...new Set(detected)];
71
174
  }
72
175
 
73
- // ── API verification ────────────────────────────────────────────────────────
176
+ // ── API verification ──────────────────────────────────────────────────────
74
177
  function verifyConnection(apiKey) {
75
178
  return new Promise((resolve) => {
179
+ const url = new URL(`${API_BASE}/v1/models`);
76
180
  const options = {
77
- hostname: API_BASE.replace('https://', ''),
181
+ hostname: url.hostname,
78
182
  port: 443,
79
- path: '/api/v1/key-check',
80
- method: 'POST',
183
+ path: url.pathname,
184
+ method: 'GET',
81
185
  headers: {
82
- 'Content-Type': 'application/json',
83
- 'User-Agent': 'ClaudMax-CLI/2.0.0',
186
+ 'x-api-key': apiKey,
187
+ 'User-Agent': 'ClaudMax-CLI/3.0.2',
84
188
  },
85
189
  timeout: 15000,
86
190
  };
87
191
 
88
192
  const req = https.request(options, (res) => {
89
193
  let body = '';
90
- res.on('data', (chunk) => { body += chunk; });
194
+ res.on('data', (c) => { body += c; });
91
195
  res.on('end', () => {
92
- try {
93
- // key-check uses POST with { apiKey } field
94
- const data = JSON.parse(body);
95
- resolve({ status: res.statusCode, data, ok: true });
96
- } catch {
97
- resolve({ status: res.statusCode, error: body, ok: false });
98
- }
196
+ if (res.statusCode === 200) resolve({ ok: true, status: res.statusCode });
197
+ else if (res.statusCode === 401) resolve({ ok: false, status: res.statusCode, error: 'invalid_key' });
198
+ else resolve({ ok: false, status: res.statusCode, error: body });
99
199
  });
100
200
  });
101
201
 
102
- req.on('error', (err) => resolve({ status: 0, error: err.message, ok: false }));
103
- req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout', ok: false }); });
104
-
105
- // key-check endpoint expects { apiKey } in body
106
- req.write(JSON.stringify({ apiKey }));
202
+ req.on('error', (err) => resolve({ ok: false, status: 0, error: err.message }));
203
+ req.on('timeout', () => { req.destroy(); resolve({ ok: false, status: 0, error: 'timeout' }); });
107
204
  req.end();
108
205
  });
109
206
  }
110
207
 
111
- // ── IDE configurators ────────────────────────────────────────────────────────
112
- function configureClaudeCLI(apiKey) {
113
- process.stdout.write(` Configuring Claude Code (CLI)... `);
208
+ // ── IDE configurators ─────────────────────────────────────────────────────
114
209
 
115
- const settingsPath = path.join(HOME, '.claude', 'settings.json');
116
- const dotClaudePath = path.join(HOME, '.claude.json');
210
+ // 1. Claude Code CLI
211
+ function configureClaudeCode(apiKey) {
212
+ process.stdout.write(` ${ARROW} Claude Code CLI...`);
117
213
 
214
+ // Write billing bypass + API key to ~/.claude/settings.json
215
+ const settingsPath = path.join(HOME, '.claude', 'settings.json');
118
216
  ensureDir(path.dirname(settingsPath));
119
-
120
- // settings.json — Claude Code reads ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL from here
121
217
  const settings = readJson(settingsPath) || {};
122
- deepMerge(settings, {
123
- env: {
124
- ANTHROPIC_AUTH_TOKEN: apiKey,
125
- ANTHROPIC_BASE_URL: API_BASE + '/v1',
126
- ANTHROPIC_MODEL: 'claude-opus-4-6',
127
- ANTHROPIC_SMALL_FAST_MODEL: 'claude-haiku-4-5',
128
- ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6',
129
- ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-6',
130
- ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5',
131
- CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
132
- },
133
- hasCompletedOnboarding: true,
134
- });
218
+ settings.env = {
219
+ ...(settings.env || {}),
220
+ ANTHROPIC_API_KEY: apiKey,
221
+ ANTHROPIC_BASE_URL: API_BASE,
222
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
223
+ };
224
+ settings.telemetryEnabled = false;
225
+ settings.autoUpdates = false;
226
+ settings.disableTelemetry = true;
135
227
  writeJson(settingsPath, settings);
136
- process.stdout.write(`${CHECK} `);
137
- process.stdout.write(`${C.dim(settingsPath + '\n')}`);
138
228
 
139
- // .claude.json — MCP servers
229
+ // Write MCP server to ~/.claude.json
230
+ const dotClaudePath = path.join(HOME, '.claude.json');
140
231
  const dotClaude = readJson(dotClaudePath) || {};
141
232
  if (!dotClaude.mcpServers) dotClaude.mcpServers = {};
142
233
  dotClaude.mcpServers['ClaudMax'] = {
143
234
  command: 'npx',
144
235
  args: ['-y', MCP_PKG],
145
236
  env: {
146
- CLAUDMAX_API_KEY: apiKey,
147
- CLAUDMAX_URL: API_BASE,
237
+ ANTHROPIC_API_KEY: apiKey,
238
+ ANTHROPIC_BASE_URL: API_BASE,
148
239
  },
149
240
  };
150
241
  writeJson(dotClaudePath, dotClaude);
151
- process.stdout.write(` ${CHECK} ${C.dim(dotClaudePath + '\n')}`);
242
+
243
+ process.stdout.write(`${CHECK}\n`);
244
+ process.stdout.write(` ${C.dim(settingsPath)}\n`);
245
+ process.stdout.write(` ${C.dim(dotClaudePath)}\n`);
152
246
  }
153
247
 
248
+ // 2. VS Code Claude Extension
249
+ function configureVSCodeClaude(apiKey) {
250
+ process.stdout.write(` ${ARROW} VS Code Claude Extension...`);
251
+
252
+ const vsSettingsPath = getVSCodeSettingsPath();
253
+ ensureDir(path.dirname(vsSettingsPath));
254
+ const vsSettings = readJson(vsSettingsPath) || {};
255
+ vsSettings['claude.apiBaseUrl'] = API_BASE;
256
+ vsSettings['claude.apiKey'] = apiKey;
257
+ vsSettings['claude.telemetry.enabled'] = false;
258
+ vsSettings['workbench.enableExperiments'] = false;
259
+ writeJson(vsSettingsPath, vsSettings);
260
+
261
+ process.stdout.write(`${CHECK}\n`);
262
+ process.stdout.write(` ${C.dim(vsSettingsPath)}\n`);
263
+ }
264
+
265
+ // 3. Cursor
154
266
  function configureCursor(apiKey) {
155
- process.stdout.write(` Configuring Cursor... `);
267
+ process.stdout.write(` ${ARROW} Cursor...`);
268
+
269
+ // Write MCP server to ~/.cursor/mcp.json
156
270
  const mcpPath = path.join(HOME, '.cursor', 'mcp.json');
157
271
  ensureDir(path.dirname(mcpPath));
158
- const existing = readJson(mcpPath) || {};
159
- if (!existing.mcpServers) existing.mcpServers = {};
160
- existing.mcpServers['ClaudMax'] = {
272
+ const mcp = readJson(mcpPath) || {};
273
+ if (!mcp.mcpServers) mcp.mcpServers = {};
274
+ mcp.mcpServers['claudmax'] = {
161
275
  command: 'npx',
162
276
  args: ['-y', MCP_PKG],
163
277
  env: {
164
- CLAUDMAX_API_KEY: apiKey,
165
- CLAUDMAX_URL: API_BASE,
278
+ ANTHROPIC_BASE_URL: API_BASE,
279
+ ANTHROPIC_API_KEY: apiKey,
166
280
  },
167
281
  };
168
- writeJson(mcpPath, existing);
169
- console.log(`${CHECK} ${C.dim(mcpPath)}`);
282
+ writeJson(mcpPath, mcp);
283
+
284
+ // Merge Cursor settings.json
285
+ const settingsPath = getCursorSettingsPath();
286
+ ensureDir(path.dirname(settingsPath));
287
+ const settings = readJson(settingsPath) || {};
288
+ settings['cursor.general.apiBaseUrl'] = API_BASE;
289
+ settings['cursor.general.apiKey'] = apiKey;
290
+ settings['cursor.telemetry.enabled'] = false;
291
+ writeJson(settingsPath, settings);
292
+
293
+ process.stdout.write(`${CHECK}\n`);
294
+ process.stdout.write(` ${C.dim(mcpPath)}\n`);
295
+ process.stdout.write(` ${C.dim(settingsPath)}\n`);
170
296
  }
171
297
 
298
+ // 4. Windsurf
172
299
  function configureWindsurf(apiKey) {
173
- process.stdout.write(` Configuring Windsurf... `);
300
+ process.stdout.write(` ${ARROW} Windsurf...`);
301
+
302
+ // Write MCP server to ~/.windsurf/mcp.json
174
303
  const mcpPath = path.join(HOME, '.windsurf', 'mcp.json');
175
304
  ensureDir(path.dirname(mcpPath));
176
- const existing = readJson(mcpPath) || {};
177
- if (!existing.mcpServers) existing.mcpServers = {};
178
- existing.mcpServers['ClaudMax'] = {
305
+ const mcp = readJson(mcpPath) || {};
306
+ if (!mcp.mcpServers) mcp.mcpServers = {};
307
+ mcp.mcpServers['claudmax'] = {
179
308
  command: 'npx',
180
309
  args: ['-y', MCP_PKG],
181
310
  env: {
182
- CLAUDMAX_API_KEY: apiKey,
183
- CLAUDMAX_URL: API_BASE,
311
+ ANTHROPIC_BASE_URL: API_BASE,
312
+ ANTHROPIC_API_KEY: apiKey,
184
313
  },
185
314
  };
186
- writeJson(mcpPath, existing);
187
- console.log(`${CHECK} ${C.dim(mcpPath)}`);
315
+ writeJson(mcpPath, mcp);
316
+
317
+ // Merge Windsurf settings.json
318
+ const settingsPath = getWindsurfSettingsPath();
319
+ ensureDir(path.dirname(settingsPath));
320
+ const settings = readJson(settingsPath) || {};
321
+ settings['windsurf.apiBaseUrl'] = API_BASE;
322
+ settings['windsurf.apiKey'] = apiKey;
323
+ settings['windsurf.telemetry.enabled'] = false;
324
+ writeJson(settingsPath, settings);
325
+
326
+ process.stdout.write(`${CHECK}\n`);
327
+ process.stdout.write(` ${C.dim(mcpPath)}\n`);
328
+ process.stdout.write(` ${C.dim(settingsPath)}\n`);
188
329
  }
189
330
 
331
+ // 5. Cline (VS Code extension — reads VS Code settings.json)
190
332
  function configureCline(apiKey) {
191
- process.stdout.write(` Configuring Cline... `);
192
- const settingsPath = path.join(HOME, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
333
+ process.stdout.write(` ${ARROW} Cline...`);
334
+
335
+ const settingsPath = getVSCodeSettingsPath();
193
336
  ensureDir(path.dirname(settingsPath));
194
- const existing = readJson(settingsPath) || {};
195
- deepMerge(existing, {
196
- 'cline.apiProvider': 'anthropic',
197
- 'cline.anthropicBaseUrl': API_BASE + '/v1',
198
- 'cline.apiKey': apiKey,
199
- });
200
- writeJson(settingsPath, existing);
201
- console.log(`${CHECK} ${C.dim(settingsPath)}`);
337
+ const settings = readJson(settingsPath) || {};
338
+ settings['cline.apiProvider'] = 'anthropic';
339
+ settings['cline.apiBaseUrl'] = API_BASE;
340
+ settings['cline.apiKey'] = apiKey;
341
+ settings['cline.telemetry.enabled'] = false;
342
+ writeJson(settingsPath, settings);
343
+
344
+ process.stdout.write(`${CHECK}\n`);
345
+ process.stdout.write(` ${C.dim(settingsPath)}\n`);
202
346
  }
203
347
 
348
+ // 6. Roo Code (VS Code extension — reads VS Code settings.json)
204
349
  function configureRooCode(apiKey) {
205
- process.stdout.write(` Configuring Roo Code... `);
206
- const settingsPath = path.join(HOME, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
350
+ process.stdout.write(` ${ARROW} Roo Code...`);
351
+
352
+ const settingsPath = getVSCodeSettingsPath();
207
353
  ensureDir(path.dirname(settingsPath));
208
- const existing = readJson(settingsPath) || {};
209
- deepMerge(existing, {
210
- 'roo-cline.apiProvider': 'anthropic',
211
- 'roo-cline.anthropicBaseUrl': API_BASE + '/v1',
212
- 'roo-cline.apiKey': apiKey,
354
+ const settings = readJson(settingsPath) || {};
355
+ settings['roo-cline.apiProvider'] = 'anthropic';
356
+ settings['roo-cline.apiBaseUrl'] = API_BASE;
357
+ settings['roo-cline.apiKey'] = apiKey;
358
+ settings['roo-cline.telemetry.enabled'] = false;
359
+ writeJson(settingsPath, settings);
360
+
361
+ process.stdout.write(`${CHECK}\n`);
362
+ process.stdout.write(` ${C.dim(settingsPath)}\n`);
363
+ }
364
+
365
+ // 7. Antigravity
366
+ function configureAntigravity(apiKey) {
367
+ process.stdout.write(` ${ARROW} Antigravity...`);
368
+
369
+ const configDir = path.join(HOME, '.config', 'antigravity');
370
+ ensureDir(configDir);
371
+ const configPath = path.join(configDir, 'config.json');
372
+ writeJson(configPath, {
373
+ apiBaseUrl: API_BASE,
374
+ apiKey: apiKey,
375
+ provider: 'anthropic',
376
+ telemetry: false,
213
377
  });
214
- writeJson(settingsPath, existing);
215
- console.log(`${CHECK} ${C.dim(settingsPath)}`);
378
+
379
+ process.stdout.write(`${CHECK}\n`);
380
+ process.stdout.write(` ${C.dim(configPath)}\n`);
216
381
  }
217
382
 
218
- function configureVSCodeClaude(apiKey) {
219
- configureClaudeCLI(apiKey);
220
- console.log(` ${INFO} Claude extension auto-detects settings. Restart VS Code after setup.`);
383
+ // ── IDE registry ────────────────────────────────────────────────────────────
384
+ const IDES = [
385
+ { id: 'claude-code', name: 'Claude Code (CLI)', configure: configureClaudeCode },
386
+ { id: 'vscode', name: 'VS Code (Claude Extension)', configure: configureVSCodeClaude },
387
+ { id: 'cursor', name: 'Cursor', configure: configureCursor },
388
+ { id: 'windsurf', name: 'Windsurf', configure: configureWindsurf },
389
+ { id: 'cline', name: 'Cline (VS Code Extension)', configure: configureCline },
390
+ { id: 'roo', name: 'Roo Code (VS Code Extension)', configure: configureRooCode },
391
+ { id: 'antigravity', name: 'Antigravity (VS Code Extension)', configure: configureAntigravity },
392
+ ];
393
+
394
+ // ── Banner ────────────────────────────────────────────────────────────────
395
+ function printBanner() {
396
+ console.log('');
397
+ console.log(C.magenta(' \u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e'));
398
+ console.log(C.magenta(' \u2502') + C.bold(' \u2726 ClaudMax Setup ') + C.magenta('\u2502'));
399
+ console.log(C.magenta(' \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510'));
400
+ console.log('');
221
401
  }
222
402
 
223
- // ── MCP server install ────────────────────────────────────────────────────────
224
- function installMCPServer() {
225
- process.stdout.write(` Installing ${MCP_PKG} globally... `);
403
+ function printHelp() {
404
+ console.log(`
405
+ ${C.bold('Usage:')} npx claudmax [options]
406
+
407
+ ${C.bold('Options:')}
408
+ --api-key <key> Your ClaudMax API key (required in non-interactive mode)
409
+ --ide <ides> Comma-separated IDEs: claude-code,vscode,cursor,windsurf,cline,roo,antigravity
410
+ Or "all" to configure every supported IDE
411
+ Or "auto" to auto-detect installed IDEs (default)
412
+ --skip-mcp Skip MCP server installation
413
+ --verify Verify API key after configuration
414
+ --help, -h Show this help message
415
+
416
+ ${C.bold('Examples:')}
417
+ npx claudmax Interactive mode
418
+ npx claudmax --api-key sk-ant-... Configure all detected IDEs
419
+ npx claudmax --api-key sk-ant-... --ide all Configure all IDEs
420
+ npx claudmax --api-key sk-ant-... --ide claude-code,cursor
421
+ npx claudmax --api-key sk-ant-... --ide all --verify
422
+
423
+ ${C.bold('Supported IDEs:')}
424
+ claude-code - Claude Code CLI
425
+ vscode - VS Code with Claude extension
426
+ cursor - Cursor AI editor
427
+ windsrf - Windsurf AI editor
428
+ cline - Cline VS Code extension
429
+ roo - Roo Code VS Code extension
430
+ antigravity - Antigravity VS Code extension
431
+ `);
432
+ }
433
+
434
+ // ── MCP install ──────────────────────────────────────────────────────────
435
+ function installMCP() {
436
+ if (flags['skip-mcp']) return;
437
+ process.stdout.write(` ${ARROW} Installing ClaudMax MCP server...`);
226
438
  try {
227
- execSync('npm install -g ' + MCP_PKG, { encoding: 'utf8', timeout: 60000 });
228
- console.log(`${CHECK}`);
229
- return true;
439
+ execSync('npm install -g ' + MCP_PKG, { encoding: 'utf8', timeout: 60000, stdio: 'pipe' });
440
+ process.stdout.write(`${CHECK}\n`);
230
441
  } catch (err) {
231
- const msg = err.message || '';
232
- // Check if already installed
233
- if (msg.includes('EACCES') || msg.includes('permission')) {
234
- console.log(`${WARN} Permission denied — try: sudo npm install -g ${MCP_PKG}`);
442
+ const msg = (err.stderr || err.message || '').toLowerCase();
443
+ if (msg.includes('eacces') || msg.includes('permission')) {
444
+ process.stdout.write(`${WARN} Permission denied — run: ${C.bold('sudo npm install -g ' + MCP_PKG)}\n`);
235
445
  } else {
236
- console.log(`${WARN} npm install failed. Run manually: ${C.bold('npm install -g ' + MCP_PKG)}`);
446
+ process.stdout.write(`${WARN} npm install failed. Run manually: ${C.bold('npm install -g ' + MCP_PKG)}\n`);
237
447
  }
238
- return false;
239
448
  }
240
449
  }
241
450
 
242
- // ── Banner ────────────────────────────────────────────────────────────────────
243
- function printBanner() {
244
- console.log('');
245
- console.log(C.magenta(' ╔════════════════════════════════════════════════════════════╗'));
246
- console.log(C.magenta(' ║') + C.bold(' ⚡ ClaudMax Setup ⚡ ') + C.magenta('║'));
247
- console.log(C.magenta(' ╚════════════════════════════════════════════════════════════╝'));
248
- console.log('');
249
- }
250
-
251
- // ── Main ──────────────────────────────────────────────────────────────────────
451
+ // ── Main ──────────────────────────────────────────────────────────────────
252
452
  async function main() {
253
- const rl = createRL();
254
453
  printBanner();
255
454
 
256
- // ── API key ───────────────────────────────────────────────────────────────
257
- let apiKey = await ask(rl, ` ${C.bold('Enter your ClaudMax API key')}: `);
258
- apiKey = apiKey.trim();
455
+ const rl = createRL();
456
+ const detected = detectIDESilent();
259
457
 
260
- if (!apiKey) {
261
- console.log(`\n ${CROSS} ${C.red('No API key provided. Run')} ${C.bold('npx claudmax')} ${C.red('to try again.')}`);
262
- rl.close();
263
- process.exit(1);
264
- }
458
+ // ── Parse --ide ──────────────────────────────────────────────────────
459
+ let targetIDEStr = flags.ide || 'auto';
460
+ let selectedIds = [];
265
461
 
266
- // ── IDE selection ────────────────────────────────────────────────────────
267
- const IDES = [
268
- { id: 1, name: 'Claude Code (CLI)', shortName: 'Claude Code', configure: configureClaudeCLI },
269
- { id: 2, name: 'VS Code (Claude Extension)', shortName: 'VS Code', configure: configureVSCodeClaude },
270
- { id: 3, name: 'Cursor', shortName: 'Cursor', configure: configureCursor },
271
- { id: 4, name: 'Windsurf', shortName: 'Windsurf', configure: configureWindsurf },
272
- { id: 5, name: 'Cline (VS Code Extension)', shortName: 'Cline', configure: configureCline },
273
- { id: 6, name: 'Roo Code (VS Code Extension)', shortName: 'Roo Code', configure: configureRooCode },
274
- ];
462
+ if (targetIDEStr === 'auto') {
463
+ selectedIds = detected;
464
+ if (selectedIds.length > 0) {
465
+ console.log(` ${INFO} Auto-detected IDEs: ${selectedIds.join(', ')}`);
466
+ } else {
467
+ console.log(` ${WARN} No IDEs auto-detected. Use ${C.bold('--ide all')} to configure all.`);
468
+ targetIDEStr = 'all';
469
+ }
470
+ }
275
471
 
276
- console.log('');
277
- for (const ide of IDES) {
278
- console.log(` ${C.magenta('[' + ide.id + ']')} ${ide.name}`);
472
+ if (targetIDEStr === 'all') {
473
+ selectedIds = IDES.map((i) => i.id);
474
+ } else if (targetIDEStr !== 'auto') {
475
+ selectedIds = targetIDEStr.split(',').map((s) => s.trim()).filter(Boolean);
279
476
  }
280
- console.log('');
281
- console.log(` ${C.dim("Enter numbers separated by spaces, or 'a' for all:")}`);
282
477
 
283
- const choice = await ask(rl, ` ${C.bold('Your choice')}: `);
284
- let selectedIds = [];
478
+ selectedIds = [...new Set(selectedIds)];
285
479
 
286
- if (choice.trim().toLowerCase() === 'a') {
287
- selectedIds = IDES.map((ide) => ide.id);
288
- } else if (choice.trim()) {
289
- selectedIds = choice.trim().split(/[\s,]+/).map(Number).filter((n) => n >= 1 && n <= IDES.length);
480
+ // ── API key ──────────────────────────────────────────────────────────
481
+ let apiKey = (flags['api-key'] || flags.apiKey || '').trim();
482
+
483
+ if (!apiKey) {
484
+ if (!process.stdin.isTTY) {
485
+ console.log(` ${CROSS} ${C.red('API key required. Use:')} ${C.bold('--api-key sk-ant-...')}\n`);
486
+ rl.close();
487
+ process.exit(1);
488
+ }
489
+ process.stdout.write(` ${C.bold('Enter your ClaudMax API key')}\n`);
490
+ process.stdout.write(` ${C.dim('Get your key at:')} ${C.cyan('https://claudmax.pro/check-usage')}\n\n`);
491
+ while (!apiKey.trim()) {
492
+ apiKey = await ask(rl, ` ${C.bold('API Key:')} `);
493
+ if (!apiKey.trim()) console.log(` ${CROSS} API key cannot be empty.\n`);
494
+ }
290
495
  }
291
496
 
292
- console.log('');
497
+ apiKey = apiKey.trim();
498
+ process.stdout.write(` ${CHECK} API key set: ${C.dim(apiKey.slice(0, 10) + '...' + apiKey.slice(-4))}\n\n`);
293
499
 
294
- // ── Configure IDEs ───────────────────────────────────────────────────────
500
+ // ── Configure IDEs ──────────────────────────────────────────────────
295
501
  if (selectedIds.length > 0) {
502
+ process.stdout.write(` ${C.bold('Configuring:')} ${selectedIds.map(id => {
503
+ const ide = IDES.find(i => i.id === id);
504
+ return ide ? ide.name : id;
505
+ }).join(', ')}\n\n`);
506
+
296
507
  const selectedIDEs = IDES.filter((ide) => selectedIds.includes(ide.id));
297
508
  for (const ide of selectedIDEs) {
298
509
  try {
299
510
  ide.configure(apiKey);
300
511
  } catch (err) {
301
- console.log(` ${CROSS} ${C.red('Failed to configure')} ${ide.name}: ${err.message}`);
512
+ process.stdout.write(` ${CROSS} Failed: ${err.message}\n`);
302
513
  }
303
514
  }
304
- console.log('');
305
515
  } else {
306
- console.log(` ${WARN} ${C.yellow('No IDEs selected — skipping configuration.')}`);
307
- console.log('');
516
+ console.log(` ${WARN} No IDEs selected.\n`);
308
517
  }
309
518
 
310
- // ── Install MCP server ───────────────────────────────────────────────────
311
- installMCPServer();
312
519
  console.log('');
313
520
 
314
- // ── Verify API key ───────────────────────────────────────────────────────
315
- process.stdout.write(` Verifying connection to ClaudMax API... `);
316
- const result = await verifyConnection(apiKey);
521
+ // ── Install MCP ──────────────────────────────────────────────────────
522
+ installMCP();
523
+ console.log('');
317
524
 
318
- if (result.ok && result.status === 200) {
319
- console.log(`${CHECK} ${C.green('API key is valid.')}`);
320
- } else if (result.ok && result.status === 401) {
321
- console.log(`${CROSS} ${C.red('Invalid API key.')}`);
322
- rl.close();
323
- process.exit(1);
324
- } else {
325
- console.log(`${WARN} ${C.yellow('Could not verify — check your network.')}`);
525
+ // ── Verify ──────────────────────────────────────────────────────────
526
+ if (flags.verify || flags.v) {
527
+ process.stdout.write(` ${ARROW} Verifying connection...`);
528
+ const result = await verifyConnection(apiKey);
529
+ if (result.ok) {
530
+ process.stdout.write(`\r ${CHECK} Connected — API key is valid.\n`);
531
+ } else if (result.status === 401) {
532
+ process.stdout.write(`\r ${CROSS} Invalid API key.\n`);
533
+ rl.close();
534
+ process.exit(1);
535
+ } else {
536
+ process.stdout.write(`\r ${WARN} HTTP ${result.status || '?'} — could not verify.\n`);
537
+ }
538
+ console.log('');
326
539
  }
327
540
 
328
- // ── Summary ──────────────────────────────────────────────────────────────
329
- console.log('');
330
- console.log(C.magenta(' ╔════════════════════════════════════════════════════════════╗'));
331
- console.log(C.magenta('') + C.green(' ✓ Setup complete! ') + C.magenta(''));
332
- console.log(C.magenta(' ║') + C.dim(' Restart your IDE(s) to apply. ') + C.magenta(''));
333
- console.log(C.magenta(' ╚════════════════════════════════════════════════════════════╝'));
541
+ // ── Summary ───────────────────────────────────────────────────────────
542
+ console.log(` ${C.magenta('\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510')}`);
543
+ console.log(` ${C.magenta('\u2502')} ${C.green('\u2713')} Setup complete! ${C.magenta('\u2502')}`);
544
+ console.log(` ${C.magenta('\u2502')} Restart your IDE(s) to apply changes. ${C.magenta('\u2502')}`);
545
+ console.log(` ${C.magenta('\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518')}`);
334
546
  console.log('');
335
547
 
336
548
  rl.close();
337
549
  }
338
550
 
339
551
  main().catch((err) => {
340
- console.error('\n' + C.red('\u2717 Fatal error: ' + (err && err.message ? err.message : err)));
552
+ console.error(`\n${C.red('\u2717 Fatal error:')} ${err.message}\n`);
341
553
  process.exit(1);
342
554
  });
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "claudmax",
3
- "version": "3.0.0",
3
+ "version": "3.0.4",
4
4
  "description": "ClaudMax CLI — Configure Claude Code, Cursor, Windsurf, Cline, and Roo Code to use ClaudMax API gateway with one command",
5
5
  "main": "index.js",
6
6
  "bin": {
7
- "claudmax": "./index.js"
7
+ "claudmax": "index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node ./index.js"