@kikkimo/claude-launcher 1.0.0 → 2.1.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/CHANGELOG.md +146 -0
- package/README.md +103 -41
- package/claude-launcher +1071 -576
- package/docs/README-zh.md +107 -45
- package/lib/api-manager.js +449 -0
- package/lib/auth/password-input.js +158 -0
- package/lib/auth/password-strength.js +154 -0
- package/lib/auth/password-validator.js +255 -0
- package/lib/crypto.js +85 -0
- package/lib/i18n/formatter.js +62 -0
- package/lib/i18n/index.js +218 -0
- package/lib/i18n/language-manager.js +160 -0
- package/lib/i18n/locales/de.js +538 -0
- package/lib/i18n/locales/en.js +539 -0
- package/lib/i18n/locales/es.js +538 -0
- package/lib/i18n/locales/fr.js +538 -0
- package/lib/i18n/locales/it.js +539 -0
- package/lib/i18n/locales/ja.js +538 -0
- package/lib/i18n/locales/ko.js +538 -0
- package/lib/i18n/locales/pt.js +539 -0
- package/lib/i18n/locales/ru.js +539 -0
- package/lib/i18n/locales/zh-TW.js +538 -0
- package/lib/i18n/locales/zh.js +538 -0
- package/lib/launcher.js +359 -0
- package/lib/presets/providers.js +148 -0
- package/lib/ui/colors.js +32 -0
- package/lib/ui/interactive-table.js +338 -0
- package/lib/ui/menu.js +383 -0
- package/lib/ui/prompts.js +571 -0
- package/lib/utils/stdin-manager.js +715 -0
- package/lib/utils/string-width.js +180 -0
- package/lib/utils/version-checker.js +240 -0
- package/lib/validators.js +130 -0
- package/package.json +2 -2
package/lib/launcher.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launcher Module - Handles Claude Code launching with various configurations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const colors = require('./ui/colors');
|
|
7
|
+
const i18n = require('./i18n');
|
|
8
|
+
const { getProvider } = require('./presets/providers');
|
|
9
|
+
const stdinManager = require('./utils/stdin-manager');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Launch Claude Code with specified environment variables
|
|
13
|
+
*/
|
|
14
|
+
function launchClaude(command, envVars = {}, disableAuthTokens = false) {
|
|
15
|
+
// Disable Ctrl+C monitoring before launching Claude Code
|
|
16
|
+
// This allows Ctrl+C to be handled exclusively by Claude Code process
|
|
17
|
+
stdinManager.disableCtrlC();
|
|
18
|
+
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(colors.yellow + '[*] ' + i18n.tSync('launch.starting') + colors.reset);
|
|
21
|
+
console.log(colors.gray + i18n.tSync('launch.command', command) + colors.reset);
|
|
22
|
+
|
|
23
|
+
if (Object.keys(envVars).length > 0) {
|
|
24
|
+
console.log(colors.gray + i18n.tSync('launch.environment_variables') + colors.reset);
|
|
25
|
+
// Mask sensitive environment variables based on key name patterns
|
|
26
|
+
const secretKeyRe = /(token|key|secret|pass|auth|credential)/i;
|
|
27
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
28
|
+
const masked = secretKeyRe.test(key) ? '***' : String(value);
|
|
29
|
+
console.log(colors.gray + ' ' + key + '=' + masked + colors.reset);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(colors.green + '[>] ' + i18n.tSync('launch.run_in_terminal') + colors.reset);
|
|
35
|
+
console.log(colors.gray + ' ' + i18n.tSync('launch.launcher_exit') + colors.reset);
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
// Prepare clean environment
|
|
39
|
+
const env = { ...process.env, ...envVars };
|
|
40
|
+
|
|
41
|
+
// Disable conflicting auth tokens when using third-party API
|
|
42
|
+
if (disableAuthTokens) {
|
|
43
|
+
// Only delete CLAUDE_CODE_OAUTH_TOKEN - keep ANTHROPIC_AUTH_TOKEN that we just set
|
|
44
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
45
|
+
console.log(colors.gray + ' Disabled: CLAUDE_CODE_OAUTH_TOKEN' + colors.reset);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse command and arguments
|
|
49
|
+
const args = command.split(' ');
|
|
50
|
+
const cmd = args.shift();
|
|
51
|
+
|
|
52
|
+
let consoleRelinquished = false;
|
|
53
|
+
const relinquishConsoleToChild = () => {
|
|
54
|
+
if (consoleRelinquished) return;
|
|
55
|
+
consoleRelinquished = true;
|
|
56
|
+
// Do the minimal changes: switch to cooked mode and detach current scope, but do not pause stdin nor swallow signals
|
|
57
|
+
try {
|
|
58
|
+
if (process.stdin.isTTY) {
|
|
59
|
+
process.stdin.setRawMode(false);
|
|
60
|
+
}
|
|
61
|
+
} catch (_) {}
|
|
62
|
+
|
|
63
|
+
// Detach only current scope listeners to avoid affecting other modules
|
|
64
|
+
if (stdinManager.activeScope && typeof stdinManager.activeScope.detach === 'function') {
|
|
65
|
+
stdinManager.activeScope.detach();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Suspend stdin manager so no new listeners are attached while Claude is running
|
|
69
|
+
if (typeof stdinManager.suspend === 'function') {
|
|
70
|
+
stdinManager.suspend();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const restoreConsoleForLauncher = () => {
|
|
75
|
+
if (!consoleRelinquished) return;
|
|
76
|
+
consoleRelinquished = false;
|
|
77
|
+
if (typeof stdinManager.resume === 'function') {
|
|
78
|
+
stdinManager.resume();
|
|
79
|
+
}
|
|
80
|
+
stdinManager.enableCtrlC();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleLaunchFailure = (message, opts = {}) => {
|
|
84
|
+
if (opts.afterHandover) {
|
|
85
|
+
restoreConsoleForLauncher();
|
|
86
|
+
} else {
|
|
87
|
+
stdinManager.enableCtrlC();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(colors.red + '[x] ' + message + colors.reset);
|
|
91
|
+
console.log(colors.gray + i18n.tSync('ui.general.press_key_return_menu') + colors.reset);
|
|
92
|
+
|
|
93
|
+
if (process.stdin.isTTY) {
|
|
94
|
+
try {
|
|
95
|
+
process.stdin.setRawMode(true);
|
|
96
|
+
process.stdin.resume();
|
|
97
|
+
} catch (_) {
|
|
98
|
+
// Ignore setup failures
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Set timeout to prevent infinite hanging
|
|
102
|
+
const timeoutId = setTimeout(() => {
|
|
103
|
+
try {
|
|
104
|
+
process.stdin.setRawMode(false);
|
|
105
|
+
} catch (_) {
|
|
106
|
+
// Ignore cleanup failures
|
|
107
|
+
}
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}, 60000); // 60 second timeout
|
|
110
|
+
|
|
111
|
+
process.stdin.once('data', () => {
|
|
112
|
+
clearTimeout(timeoutId);
|
|
113
|
+
try {
|
|
114
|
+
process.stdin.setRawMode(false);
|
|
115
|
+
} catch (_) {
|
|
116
|
+
// Ignore cleanup failures
|
|
117
|
+
}
|
|
118
|
+
// Exit after user acknowledges the error
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
// For non-TTY environments, exit immediately
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
// Clean up terminal state before launching Claude
|
|
129
|
+
if (process.stdin.isTTY) {
|
|
130
|
+
try {
|
|
131
|
+
process.stdin.setRawMode(false);
|
|
132
|
+
process.stdin.pause();
|
|
133
|
+
} catch (_) {
|
|
134
|
+
// Ignore cleanup failures
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Note: stdin listener management is handled by relinquishConsoleToChild()
|
|
139
|
+
// using stdinManager.activeScope.detach() and stdinManager.suspend().
|
|
140
|
+
// This ensures only the current scope's listeners are detached while
|
|
141
|
+
// preserving any listeners from other modules.
|
|
142
|
+
//
|
|
143
|
+
// Note: Do NOT remove global SIGINT/SIGTERM handlers here.
|
|
144
|
+
// The existing handlers already check stdinManager.isSuspended() and
|
|
145
|
+
// will properly ignore signals during child process execution.
|
|
146
|
+
// Removing all handlers would break other modules and degrade reliability.
|
|
147
|
+
|
|
148
|
+
// Launch Claude in current terminal, inherit stdio
|
|
149
|
+
const child = spawn(cmd, args, {
|
|
150
|
+
stdio: 'inherit',
|
|
151
|
+
env: env,
|
|
152
|
+
cwd: process.cwd(),
|
|
153
|
+
shell: true
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
relinquishConsoleToChild();
|
|
157
|
+
|
|
158
|
+
child.on('close', (code) => {
|
|
159
|
+
restoreConsoleForLauncher();
|
|
160
|
+
process.exit(code || 0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
child.on('error', (error) => {
|
|
164
|
+
handleLaunchFailure('Error running Claude: ' + error.message, { afterHandover: true });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
} catch (error) {
|
|
168
|
+
handleLaunchFailure('Error launching Claude Code: ' + error.message);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Launch Claude with default settings
|
|
174
|
+
*/
|
|
175
|
+
function launchClaudeDefault() {
|
|
176
|
+
launchClaude('claude');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Launch Claude with skip permissions flag
|
|
181
|
+
*/
|
|
182
|
+
function launchClaudeSkipPermissions() {
|
|
183
|
+
launchClaude('claude --dangerously-skip-permissions');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get environment variables based on provider type
|
|
188
|
+
*/
|
|
189
|
+
function getProviderEnvVars(api) {
|
|
190
|
+
// Decrypt the auth token (all tokens are stored encrypted)
|
|
191
|
+
const { decrypt } = require('./crypto');
|
|
192
|
+
const decrypted = decrypt(api.authToken);
|
|
193
|
+
|
|
194
|
+
if (!decrypted.success) {
|
|
195
|
+
console.error('Failed to decrypt auth token:', decrypted.error);
|
|
196
|
+
throw new Error('Failed to decrypt API auth token. Please check your configuration.');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const authToken = decrypted.value;
|
|
200
|
+
|
|
201
|
+
const baseEnvVars = {
|
|
202
|
+
ANTHROPIC_BASE_URL: api.baseUrl,
|
|
203
|
+
ANTHROPIC_AUTH_TOKEN: authToken,
|
|
204
|
+
ANTHROPIC_MODEL: api.model,
|
|
205
|
+
ANTHROPIC_SMALL_FAST_MODEL: api.smallFastModel || api.model
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Get provider configuration and merge provider-specific environment variables
|
|
209
|
+
const providerConfig = getProvider(api.provider);
|
|
210
|
+
|
|
211
|
+
if (providerConfig && providerConfig.envVars) {
|
|
212
|
+
// Merge base env vars with provider-specific env vars
|
|
213
|
+
return {
|
|
214
|
+
...baseEnvVars,
|
|
215
|
+
...providerConfig.envVars
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return baseEnvVars;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Launch Claude with third-party API configuration
|
|
224
|
+
*/
|
|
225
|
+
function launchClaudeWithApi(api, skipPermissions = false) {
|
|
226
|
+
const command = skipPermissions
|
|
227
|
+
? 'claude --dangerously-skip-permissions'
|
|
228
|
+
: 'claude';
|
|
229
|
+
|
|
230
|
+
const envVars = getProviderEnvVars(api);
|
|
231
|
+
|
|
232
|
+
console.log('');
|
|
233
|
+
console.log(colors.bright + colors.orange + '🔗 ' + i18n.tSync('launch.using_third_party_api') + colors.reset);
|
|
234
|
+
|
|
235
|
+
// Get provider configuration for display
|
|
236
|
+
const providerConfig = getProvider(api.provider);
|
|
237
|
+
const providerName = providerConfig ? providerConfig.name : (api.provider || 'Custom');
|
|
238
|
+
|
|
239
|
+
console.log(colors.gray + ` Provider: ${providerName}` + colors.reset);
|
|
240
|
+
console.log(colors.gray + ` API: ${api.name}` + colors.reset);
|
|
241
|
+
console.log(colors.gray + ` Base URL: ${api.baseUrl}` + colors.reset);
|
|
242
|
+
console.log(colors.gray + ` Model: ${api.model}` + colors.reset);
|
|
243
|
+
|
|
244
|
+
// Show provider-specific optimizations if envVars are defined
|
|
245
|
+
if (providerConfig && providerConfig.envVars && Object.keys(providerConfig.envVars).length > 0) {
|
|
246
|
+
console.log(colors.yellow + ' ⚡ ' + i18n.tSync('launch.provider_optimizations_applied') + colors.reset);
|
|
247
|
+
|
|
248
|
+
// Display specific optimizations based on envVars
|
|
249
|
+
const msRaw = providerConfig.envVars.API_TIMEOUT_MS;
|
|
250
|
+
const ms = Number(msRaw);
|
|
251
|
+
if (Number.isFinite(ms) && ms > 0) {
|
|
252
|
+
const timeoutSec = Math.floor(ms / 1000);
|
|
253
|
+
const timeoutMin = Math.floor(timeoutSec / 60);
|
|
254
|
+
// Use singular or plural form based on timeoutMin value
|
|
255
|
+
const key = timeoutMin === 1 ? 'launch.extended_timeout_format_singular' : 'launch.extended_timeout_format';
|
|
256
|
+
console.log(colors.gray + ' • ' + i18n.tSync(key, timeoutSec, timeoutMin) + colors.reset);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (providerConfig.envVars.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC) {
|
|
260
|
+
console.log(colors.gray + ' • ' + i18n.tSync('launch.non_essential_traffic_disabled') + colors.reset);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Display any other custom env vars (excluding the ones already shown)
|
|
264
|
+
// Apply same masking logic as in launchClaude to protect sensitive values
|
|
265
|
+
const secretKeyRe = /(token|key|secret|pass|auth|credential)/i;
|
|
266
|
+
for (const [key, value] of Object.entries(providerConfig.envVars)) {
|
|
267
|
+
if (key === 'API_TIMEOUT_MS' || key === 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC') continue;
|
|
268
|
+
const masked = secretKeyRe.test(key) ? '***' : String(value);
|
|
269
|
+
console.log(colors.gray + ' • ' + i18n.tSync('launch.custom_env_var', key, masked) + colors.reset);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log('');
|
|
274
|
+
|
|
275
|
+
launchClaude(command, envVars, true);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Test API connection
|
|
280
|
+
*/
|
|
281
|
+
async function testApiConnection(api) {
|
|
282
|
+
console.log(colors.yellow + '🔍 Testing API connection...' + colors.reset);
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
// Handle both plaintext (during testing) and encrypted tokens (from stored APIs)
|
|
286
|
+
let authToken = api.authToken;
|
|
287
|
+
|
|
288
|
+
// Try to decrypt if it looks like an encrypted token
|
|
289
|
+
if (typeof authToken === 'string' && authToken.includes(':')) {
|
|
290
|
+
const { decrypt } = require('./crypto');
|
|
291
|
+
const decrypted = decrypt(authToken);
|
|
292
|
+
|
|
293
|
+
if (decrypted.success) {
|
|
294
|
+
authToken = decrypted.value;
|
|
295
|
+
}
|
|
296
|
+
// If decryption fails but token contains ':', it might be encrypted but corrupted
|
|
297
|
+
else if (authToken.split(':').length === 3) {
|
|
298
|
+
console.error('Failed to decrypt auth token for testing:', decrypted.error);
|
|
299
|
+
return { success: false, error: 'Failed to decrypt auth token' };
|
|
300
|
+
}
|
|
301
|
+
// Otherwise, treat as plaintext token
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Try to make a simple request to test the connection
|
|
305
|
+
const https = require('https');
|
|
306
|
+
const url = new URL(api.baseUrl);
|
|
307
|
+
|
|
308
|
+
return new Promise((resolve) => {
|
|
309
|
+
const options = {
|
|
310
|
+
hostname: url.hostname,
|
|
311
|
+
port: url.port || 443,
|
|
312
|
+
path: url.pathname,
|
|
313
|
+
method: 'GET',
|
|
314
|
+
timeout: 5000,
|
|
315
|
+
headers: {
|
|
316
|
+
'Authorization': `Bearer ${authToken}`
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const req = https.request(options, (res) => {
|
|
321
|
+
if (res.statusCode === 401) {
|
|
322
|
+
console.log(colors.yellow + '⚠️ API returned 401 - Check your auth token' + colors.reset);
|
|
323
|
+
resolve({ success: false, error: 'Authentication failed' });
|
|
324
|
+
} else if (res.statusCode >= 200 && res.statusCode < 500) {
|
|
325
|
+
console.log(colors.green + '✓ API is reachable' + colors.reset);
|
|
326
|
+
resolve({ success: true });
|
|
327
|
+
} else {
|
|
328
|
+
console.log(colors.red + `❌ API returned status ${res.statusCode}` + colors.reset);
|
|
329
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}` });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
req.on('error', (error) => {
|
|
334
|
+
console.log(colors.red + `❌ Connection failed: ${error.message}` + colors.reset);
|
|
335
|
+
resolve({ success: false, error: error.message });
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
req.on('timeout', () => {
|
|
339
|
+
console.log(colors.red + '❌ Connection timeout' + colors.reset);
|
|
340
|
+
req.destroy();
|
|
341
|
+
resolve({ success: false, error: 'Timeout' });
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
req.end();
|
|
345
|
+
});
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.log(colors.red + `❌ Test failed: ${error.message}` + colors.reset);
|
|
348
|
+
return { success: false, error: error.message };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
module.exports = {
|
|
353
|
+
launchClaude,
|
|
354
|
+
launchClaudeDefault,
|
|
355
|
+
launchClaudeSkipPermissions,
|
|
356
|
+
launchClaudeWithApi,
|
|
357
|
+
getProviderEnvVars,
|
|
358
|
+
testApiConnection
|
|
359
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Providers Presets - Claude Code compatible API providers
|
|
3
|
+
*
|
|
4
|
+
* Note: Only includes APIs that are compatible with Claude Code's Anthropic API format
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const providers = {
|
|
8
|
+
anthropic: {
|
|
9
|
+
name: 'Anthropic (Official)',
|
|
10
|
+
baseUrl: 'https://api.anthropic.com',
|
|
11
|
+
models: [
|
|
12
|
+
'claude-3-5-haiku-20241022',
|
|
13
|
+
'claude-3.7-sonnet',
|
|
14
|
+
'claude-sonnet-4',
|
|
15
|
+
'claude-opus-4',
|
|
16
|
+
'claude-opus-4.1'
|
|
17
|
+
],
|
|
18
|
+
authTokenFormat: 'sk-ant-api03-...',
|
|
19
|
+
description: 'Official Anthropic API - Fully compatible',
|
|
20
|
+
requiresToken: true,
|
|
21
|
+
compatibility: 'native'
|
|
22
|
+
},
|
|
23
|
+
moonshot: {
|
|
24
|
+
name: 'Moonshot AI (Kimi-K2)',
|
|
25
|
+
baseUrl: 'https://api.moonshot.cn/anthropic',
|
|
26
|
+
models: [
|
|
27
|
+
'kimi-k2-0711-preview',
|
|
28
|
+
'kimi-k2-0905-preview',
|
|
29
|
+
'kimi-k2-turbo-preview'
|
|
30
|
+
],
|
|
31
|
+
authTokenFormat: 'sk-...',
|
|
32
|
+
description: 'Moonshot AI - Provides Anthropic-compatible API',
|
|
33
|
+
requiresToken: true,
|
|
34
|
+
compatibility: 'anthropic-compatible',
|
|
35
|
+
envVars: {
|
|
36
|
+
API_TIMEOUT_MS: '3000000',
|
|
37
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1'
|
|
38
|
+
},
|
|
39
|
+
note: 'Requires extended timeout for large responses'
|
|
40
|
+
},
|
|
41
|
+
deepseek: {
|
|
42
|
+
name: 'DeepSeek (DeepSeek V3/V3.1)',
|
|
43
|
+
baseUrl: 'https://api.deepseek.com/anthropic',
|
|
44
|
+
models: [
|
|
45
|
+
'deepseek-chat'
|
|
46
|
+
],
|
|
47
|
+
authTokenFormat: 'sk-...',
|
|
48
|
+
description: 'DeepSeek AI - Anthropic-compatible endpoint',
|
|
49
|
+
requiresToken: true,
|
|
50
|
+
compatibility: 'anthropic-compatible',
|
|
51
|
+
envVars: {
|
|
52
|
+
API_TIMEOUT_MS: '600000',
|
|
53
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1'
|
|
54
|
+
},
|
|
55
|
+
note: 'Requires extended timeout for complex reasoning tasks'
|
|
56
|
+
},
|
|
57
|
+
zhipu: {
|
|
58
|
+
name: 'ZhiPu AI (GLM-4.5/4.6) - 智谱清言',
|
|
59
|
+
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
60
|
+
models: [
|
|
61
|
+
'glm-4.5',
|
|
62
|
+
'glm-4.6'
|
|
63
|
+
],
|
|
64
|
+
authTokenFormat: 'sk-...',
|
|
65
|
+
description: 'ZhiPu AI (智谱清言) - Anthropic-compatible API for mainland China',
|
|
66
|
+
requiresToken: true,
|
|
67
|
+
compatibility: 'anthropic-compatible',
|
|
68
|
+
envVars: {
|
|
69
|
+
API_TIMEOUT_MS: '3000000',
|
|
70
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1'
|
|
71
|
+
},
|
|
72
|
+
note: 'Requires extended timeout for large responses'
|
|
73
|
+
},
|
|
74
|
+
zai: {
|
|
75
|
+
name: 'Z.ai (GLM-4.5/4.6) - ZhiPu Global',
|
|
76
|
+
baseUrl: 'https://api.z.ai/api/anthropic',
|
|
77
|
+
models: [
|
|
78
|
+
'glm-4.5',
|
|
79
|
+
'glm-4.6'
|
|
80
|
+
],
|
|
81
|
+
authTokenFormat: 'sk-...',
|
|
82
|
+
description: 'Z.ai (ZhiPu AI Global) - Anthropic-compatible API for international users',
|
|
83
|
+
requiresToken: true,
|
|
84
|
+
compatibility: 'anthropic-compatible',
|
|
85
|
+
envVars: {
|
|
86
|
+
API_TIMEOUT_MS: '3000000',
|
|
87
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1'
|
|
88
|
+
},
|
|
89
|
+
note: 'Requires extended timeout for large responses'
|
|
90
|
+
},
|
|
91
|
+
custom: {
|
|
92
|
+
name: 'Custom Anthropic-Compatible API',
|
|
93
|
+
baseUrl: 'https://your-api-server.com/v1/anthropic',
|
|
94
|
+
models: [
|
|
95
|
+
'your-model-name'
|
|
96
|
+
],
|
|
97
|
+
authTokenFormat: 'Bearer token or API key',
|
|
98
|
+
description: 'Custom server with Anthropic-compatible API',
|
|
99
|
+
requiresToken: true,
|
|
100
|
+
compatibility: 'anthropic-compatible',
|
|
101
|
+
note: 'Replace URL and model with your actual server details'
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get all available providers
|
|
107
|
+
*/
|
|
108
|
+
function getAllProviders() {
|
|
109
|
+
return Object.keys(providers).map(key => ({
|
|
110
|
+
id: key,
|
|
111
|
+
...providers[key]
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get a specific provider by ID
|
|
117
|
+
*/
|
|
118
|
+
function getProvider(providerId) {
|
|
119
|
+
return providers[providerId] || null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get suggested models for a provider
|
|
124
|
+
*/
|
|
125
|
+
function getSuggestedModels(providerId) {
|
|
126
|
+
const provider = providers[providerId];
|
|
127
|
+
return provider ? provider.models : [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Validate if a URL matches a known provider
|
|
132
|
+
*/
|
|
133
|
+
function detectProvider(baseUrl) {
|
|
134
|
+
for (const [key, provider] of Object.entries(providers)) {
|
|
135
|
+
if (baseUrl.includes(provider.baseUrl.replace('https://', '').replace('http://', '').split('/')[0])) {
|
|
136
|
+
return key;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return 'custom';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
providers,
|
|
144
|
+
getAllProviders,
|
|
145
|
+
getProvider,
|
|
146
|
+
getSuggestedModels,
|
|
147
|
+
detectProvider
|
|
148
|
+
};
|
package/lib/ui/colors.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colors Module - ANSI color codes for Claude-style theming
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const colors = {
|
|
6
|
+
reset: '\x1b[0m',
|
|
7
|
+
bright: '\x1b[1m',
|
|
8
|
+
dim: '\x1b[2m',
|
|
9
|
+
|
|
10
|
+
// Claude theme colors
|
|
11
|
+
orange: '\x1b[38;5;208m', // Claude brand orange
|
|
12
|
+
amber: '\x1b[38;5;214m', // Amber/yellow-orange
|
|
13
|
+
|
|
14
|
+
// Standard colors
|
|
15
|
+
white: '\x1b[37m',
|
|
16
|
+
gray: '\x1b[90m',
|
|
17
|
+
green: '\x1b[32m',
|
|
18
|
+
red: '\x1b[31m',
|
|
19
|
+
yellow: '\x1b[33m',
|
|
20
|
+
blue: '\x1b[34m',
|
|
21
|
+
cyan: '\x1b[36m',
|
|
22
|
+
black: '\x1b[30m',
|
|
23
|
+
|
|
24
|
+
// Background colors
|
|
25
|
+
bgOrange: '\x1b[48;5;208m', // Background orange
|
|
26
|
+
bgAmber: '\x1b[48;5;214m', // Background amber
|
|
27
|
+
bgRed: '\x1b[41m',
|
|
28
|
+
bgGreen: '\x1b[42m',
|
|
29
|
+
bgBlue: '\x1b[44m'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
module.exports = colors;
|