@kikkimo/claude-launcher 1.0.0 → 2.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/CHANGELOG.md +55 -0
- package/README.md +100 -41
- package/claude-launcher +1017 -576
- package/docs/README-zh.md +104 -45
- package/lib/api-manager.js +449 -0
- package/lib/auth/password-input.js +144 -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 +523 -0
- package/lib/i18n/locales/en.js +524 -0
- package/lib/i18n/locales/es.js +523 -0
- package/lib/i18n/locales/fr.js +523 -0
- package/lib/i18n/locales/it.js +523 -0
- package/lib/i18n/locales/ja.js +523 -0
- package/lib/i18n/locales/ko.js +523 -0
- package/lib/i18n/locales/pt.js +523 -0
- package/lib/i18n/locales/ru.js +523 -0
- package/lib/i18n/locales/zh-TW.js +523 -0
- package/lib/i18n/locales/zh.js +523 -0
- package/lib/launcher.js +253 -0
- package/lib/presets/providers.js +104 -0
- package/lib/ui/colors.js +32 -0
- package/lib/ui/interactive-table.js +260 -0
- package/lib/ui/menu.js +314 -0
- package/lib/ui/prompts.js +540 -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
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts Module - User input prompts and interactions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const colors = require('./colors');
|
|
7
|
+
const { getAllProviders } = require('../presets/providers');
|
|
8
|
+
const { validateBaseUrl, validateAuthToken, validateModel } = require('../validators');
|
|
9
|
+
const i18n = require('../i18n');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Simple input using readline
|
|
13
|
+
*/
|
|
14
|
+
async function simpleInput(prompt) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const rl = readline.createInterface({
|
|
17
|
+
input: process.stdin,
|
|
18
|
+
output: process.stdout
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
rl.question(prompt, (answer) => {
|
|
22
|
+
rl.close();
|
|
23
|
+
resolve(answer.trim());
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get provider choice with ESC key support
|
|
30
|
+
*/
|
|
31
|
+
async function getProviderChoice(prompt) {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
if (process.stdin.isTTY) {
|
|
34
|
+
// Use raw mode to capture ESC key - this is necessary for interactive input
|
|
35
|
+
process.stdout.write(colors.green + prompt + colors.reset);
|
|
36
|
+
|
|
37
|
+
let input = '';
|
|
38
|
+
|
|
39
|
+
// Save original state
|
|
40
|
+
const originalRawMode = process.stdin.isRaw;
|
|
41
|
+
const originalPaused = process.stdin.isPaused();
|
|
42
|
+
|
|
43
|
+
process.stdin.setRawMode(true);
|
|
44
|
+
process.stdin.resume();
|
|
45
|
+
process.stdin.setEncoding('utf8');
|
|
46
|
+
|
|
47
|
+
const cleanup = () => {
|
|
48
|
+
try {
|
|
49
|
+
process.stdin.setRawMode(false);
|
|
50
|
+
if (originalPaused) {
|
|
51
|
+
process.stdin.pause();
|
|
52
|
+
}
|
|
53
|
+
process.stdin.removeAllListeners('data');
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Ignore cleanup errors
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleKeyPress = (key) => {
|
|
60
|
+
const keyCode = key.charCodeAt(0);
|
|
61
|
+
|
|
62
|
+
switch (keyCode) {
|
|
63
|
+
case 27: // ESC key
|
|
64
|
+
cleanup();
|
|
65
|
+
process.stdout.write('\n');
|
|
66
|
+
resolve(null);
|
|
67
|
+
return;
|
|
68
|
+
|
|
69
|
+
case 13: // Enter key
|
|
70
|
+
cleanup();
|
|
71
|
+
process.stdout.write('\n');
|
|
72
|
+
resolve(input);
|
|
73
|
+
return;
|
|
74
|
+
|
|
75
|
+
case 127: // Backspace
|
|
76
|
+
case 8: // Backspace (some terminals)
|
|
77
|
+
if (input.length > 0) {
|
|
78
|
+
input = input.slice(0, -1);
|
|
79
|
+
process.stdout.write('\b \b');
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
|
|
83
|
+
case 3: // Ctrl+C
|
|
84
|
+
cleanup();
|
|
85
|
+
process.stdout.write('\n');
|
|
86
|
+
resolve(null);
|
|
87
|
+
return;
|
|
88
|
+
|
|
89
|
+
default:
|
|
90
|
+
// Only accept printable characters
|
|
91
|
+
if (keyCode >= 32 && keyCode < 127) {
|
|
92
|
+
input += key;
|
|
93
|
+
process.stdout.write(key);
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
process.stdin.on('data', handleKeyPress);
|
|
100
|
+
} else {
|
|
101
|
+
// Fallback for non-TTY environments
|
|
102
|
+
const rl = readline.createInterface({
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
output: process.stdout
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
rl.question(colors.green + prompt + colors.reset, (answer) => {
|
|
108
|
+
rl.close();
|
|
109
|
+
resolve(answer.trim());
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Wait for any key press
|
|
118
|
+
*/
|
|
119
|
+
async function waitForKey(message = 'Press any key to continue...') {
|
|
120
|
+
console.log(colors.gray + message + colors.reset);
|
|
121
|
+
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
if (process.stdin.isTTY) {
|
|
124
|
+
// Force complete cleanup before setting up new listeners
|
|
125
|
+
try {
|
|
126
|
+
process.stdin.removeAllListeners('data');
|
|
127
|
+
process.stdin.removeAllListeners('keypress');
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// Ignore cleanup errors
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
process.stdin.setRawMode(true);
|
|
133
|
+
process.stdin.resume();
|
|
134
|
+
process.stdin.once('data', () => {
|
|
135
|
+
// Complete cleanup after key press
|
|
136
|
+
try {
|
|
137
|
+
process.stdin.setRawMode(false);
|
|
138
|
+
process.stdin.removeAllListeners('data');
|
|
139
|
+
process.stdin.removeAllListeners('keypress');
|
|
140
|
+
process.stdin.pause();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// Ignore cleanup errors
|
|
143
|
+
}
|
|
144
|
+
resolve();
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
const rl = readline.createInterface({
|
|
148
|
+
input: process.stdin,
|
|
149
|
+
output: process.stdout
|
|
150
|
+
});
|
|
151
|
+
rl.question('', () => {
|
|
152
|
+
rl.close();
|
|
153
|
+
resolve();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Prompt for third-party API configuration with enhanced guidance
|
|
161
|
+
*/
|
|
162
|
+
async function promptForThirdPartyApi() {
|
|
163
|
+
try {
|
|
164
|
+
// Step 1: Show information and wait for acknowledgment
|
|
165
|
+
console.clear();
|
|
166
|
+
console.log('');
|
|
167
|
+
console.log(colors.bright + colors.orange + i18n.tSync('ui.general.add_new_api_title') + colors.reset);
|
|
168
|
+
console.log('');
|
|
169
|
+
|
|
170
|
+
// Security and privacy information
|
|
171
|
+
console.log(colors.yellow + i18n.tSync('ui.general.security_privacy_info') + colors.reset);
|
|
172
|
+
const securityItems = i18n.tSync('ui.general.security_items');
|
|
173
|
+
securityItems.forEach(item => {
|
|
174
|
+
console.log(colors.bright + colors.green + ' • ' + item + colors.reset);
|
|
175
|
+
});
|
|
176
|
+
console.log('');
|
|
177
|
+
|
|
178
|
+
console.log(colors.yellow + i18n.tSync('ui.general.configuration_tips') + colors.reset);
|
|
179
|
+
const configTips = i18n.tSync('ui.general.config_tip_items');
|
|
180
|
+
configTips.forEach(tip => {
|
|
181
|
+
console.log(colors.gray + ' • ' + tip + colors.reset);
|
|
182
|
+
});
|
|
183
|
+
console.log(colors.gray + ' • ' + i18n.tSync('ui.general.type_exit_cancel') + colors.reset);
|
|
184
|
+
console.log('');
|
|
185
|
+
|
|
186
|
+
console.log(colors.yellow + i18n.tSync('ui.general.all_providers_compatible') + colors.reset);
|
|
187
|
+
console.log('');
|
|
188
|
+
|
|
189
|
+
await waitForKey(i18n.tSync('ui.general.press_continue_provider_selection'));
|
|
190
|
+
|
|
191
|
+
// Step 2: Show provider selection menu
|
|
192
|
+
console.clear();
|
|
193
|
+
console.log('');
|
|
194
|
+
console.log(colors.bright + colors.orange + i18n.tSync('ui.general.add_new_api_title') + colors.reset);
|
|
195
|
+
console.log('');
|
|
196
|
+
|
|
197
|
+
// Show available providers
|
|
198
|
+
console.log(colors.cyan + i18n.tSync('ui.general.compatible_providers_title') + colors.reset);
|
|
199
|
+
console.log('');
|
|
200
|
+
const providers = getAllProviders();
|
|
201
|
+
providers.forEach((provider, index) => {
|
|
202
|
+
const compatIcon = provider.compatibility === 'native' ? '🎯' : '✅';
|
|
203
|
+
console.log(colors.gray + ` ${index + 1}. ${compatIcon} ${provider.name}` + colors.reset);
|
|
204
|
+
console.log(colors.dim + ` ${provider.description}` + colors.reset);
|
|
205
|
+
});
|
|
206
|
+
console.log('');
|
|
207
|
+
|
|
208
|
+
// Select provider or custom with validation
|
|
209
|
+
let selectedProvider = null;
|
|
210
|
+
let baseUrl = '';
|
|
211
|
+
let suggestedModels = [];
|
|
212
|
+
|
|
213
|
+
while (true) {
|
|
214
|
+
const selectPrompt = i18n.tSync('ui.general.select_provider_prompt').replace('{0}', providers.length);
|
|
215
|
+
const providerChoice = await getProviderChoice(selectPrompt);
|
|
216
|
+
|
|
217
|
+
if (providerChoice === null) {
|
|
218
|
+
// User cancelled with ESC
|
|
219
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (providerChoice.toLowerCase() === 'exit' || providerChoice.toLowerCase() === 'quit') {
|
|
223
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Require non-empty input
|
|
227
|
+
if (!providerChoice || providerChoice.trim() === '') {
|
|
228
|
+
console.log(colors.red + i18n.tSync('ui.general.provider_selection_required', providers.length) + colors.reset);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Validate numeric input
|
|
233
|
+
if (isNaN(providerChoice)) {
|
|
234
|
+
console.log(colors.red + i18n.tSync('ui.general.invalid_provider_selection').replace('{0}', providers.length) + colors.reset);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const index = parseInt(providerChoice) - 1;
|
|
239
|
+
if (index < 0 || index >= providers.length) {
|
|
240
|
+
console.log(colors.red + i18n.tSync('ui.general.invalid_provider_number').replace('{0}', providers.length) + colors.reset);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Valid selection
|
|
245
|
+
selectedProvider = providers[index];
|
|
246
|
+
baseUrl = selectedProvider.baseUrl;
|
|
247
|
+
suggestedModels = selectedProvider.models || [];
|
|
248
|
+
|
|
249
|
+
console.log('');
|
|
250
|
+
console.log(colors.green + i18n.tSync('ui.general.selected_provider', selectedProvider.name) + colors.reset);
|
|
251
|
+
if (selectedProvider.note) {
|
|
252
|
+
if (selectedProvider.id === 'custom') {
|
|
253
|
+
console.log(colors.yellow + ' ' + i18n.tSync('ui.general.replace_url_model_note') + colors.reset);
|
|
254
|
+
} else {
|
|
255
|
+
console.log(colors.yellow + ` Note: ${selectedProvider.note}` + colors.reset);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
console.log('');
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Input base URL - different handling for custom vs specific providers
|
|
263
|
+
if (selectedProvider && selectedProvider.id === 'custom') {
|
|
264
|
+
// Custom provider - show reference URL and require manual input
|
|
265
|
+
console.log(colors.gray + ` ` + i18n.tSync('ui.general.reference_base_url', baseUrl) + colors.reset);
|
|
266
|
+
console.log('');
|
|
267
|
+
|
|
268
|
+
while (true) {
|
|
269
|
+
const inputUrl = await simpleInput(colors.green + i18n.tSync('ui.general.api_base_url_prompt') + colors.reset);
|
|
270
|
+
|
|
271
|
+
if (inputUrl.toLowerCase() === 'exit' || inputUrl.toLowerCase() === 'quit') {
|
|
272
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!inputUrl || inputUrl.trim() === '') {
|
|
276
|
+
console.log(colors.red + i18n.tSync('ui.general.base_url_required') + colors.reset);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const validation = validateBaseUrl(inputUrl);
|
|
281
|
+
if (!validation.valid) {
|
|
282
|
+
console.log(colors.red + `❌ ${validation.error}` + colors.reset);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
baseUrl = validation.value;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
} else if (selectedProvider && !baseUrl.includes('{')) {
|
|
289
|
+
// Specific providers - show recommended URL with option to use default
|
|
290
|
+
console.log(colors.gray + ` ` + i18n.tSync('ui.general.recommended_base_url', baseUrl) + colors.reset);
|
|
291
|
+
|
|
292
|
+
// For all known providers, show the recommended URL in the prompt
|
|
293
|
+
let prompt;
|
|
294
|
+
if (selectedProvider.id === 'anthropic' || selectedProvider.id === 'deepseek' || selectedProvider.id === 'moonshot') {
|
|
295
|
+
prompt = colors.green + i18n.tSync('ui.general.press_enter_default_url') + `${colors.yellow}${baseUrl}${colors.green}` + colors.reset;
|
|
296
|
+
console.log(colors.gray + ' ' + i18n.tSync('ui.general.edit_url_hint') + colors.reset);
|
|
297
|
+
} else {
|
|
298
|
+
prompt = colors.green + i18n.tSync('ui.general.press_enter_default_url') + colors.reset;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const customUrl = await simpleInput(prompt);
|
|
302
|
+
if (customUrl) {
|
|
303
|
+
if (customUrl.toLowerCase() === 'exit' || customUrl.toLowerCase() === 'quit') {
|
|
304
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
305
|
+
}
|
|
306
|
+
baseUrl = customUrl;
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
// Fallback case
|
|
310
|
+
while (true) {
|
|
311
|
+
const inputUrl = await simpleInput(colors.green + i18n.tSync('ui.general.api_base_url_prompt') + colors.reset);
|
|
312
|
+
|
|
313
|
+
if (inputUrl.toLowerCase() === 'exit' || inputUrl.toLowerCase() === 'quit') {
|
|
314
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const validation = validateBaseUrl(inputUrl);
|
|
318
|
+
if (!validation.valid) {
|
|
319
|
+
console.log(colors.red + `❌ ${validation.error}` + colors.reset);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
baseUrl = validation.value;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Input auth token
|
|
328
|
+
let authToken;
|
|
329
|
+
console.log('');
|
|
330
|
+
|
|
331
|
+
// Simplified API token input
|
|
332
|
+
if (selectedProvider) {
|
|
333
|
+
console.log(colors.gray + ` ` + i18n.tSync('ui.general.expected_format', selectedProvider.authTokenFormat) + colors.reset);
|
|
334
|
+
}
|
|
335
|
+
console.log(colors.gray + ' ' + i18n.tSync('ui.general.type_exit_cancel_setup') + colors.reset);
|
|
336
|
+
console.log('');
|
|
337
|
+
|
|
338
|
+
while (true) {
|
|
339
|
+
const token = await simpleInput(colors.green + i18n.tSync('ui.general.auth_token_prompt') + colors.reset);
|
|
340
|
+
|
|
341
|
+
if (token.toLowerCase() === 'exit' || token.toLowerCase() === 'quit') {
|
|
342
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const validation = validateAuthToken(token);
|
|
346
|
+
if (!validation.valid) {
|
|
347
|
+
console.log(colors.red + `❌ ${validation.error}` + colors.reset);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
authToken = validation.value;
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Input model - different handling for custom vs specific providers
|
|
355
|
+
let model;
|
|
356
|
+
console.log('');
|
|
357
|
+
|
|
358
|
+
if (selectedProvider && selectedProvider.id === 'custom') {
|
|
359
|
+
// Custom provider - always require manual input, no suggested models
|
|
360
|
+
while (true) {
|
|
361
|
+
const inputModel = await simpleInput(colors.green + i18n.tSync('ui.general.model_name_prompt') + colors.reset);
|
|
362
|
+
|
|
363
|
+
if (inputModel.toLowerCase() === 'exit' || inputModel.toLowerCase() === 'quit') {
|
|
364
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const validation = validateModel(inputModel);
|
|
368
|
+
if (!validation.valid) {
|
|
369
|
+
console.log(colors.red + `❌ ${validation.error}` + colors.reset);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
model = validation.value;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
} else if (suggestedModels.length > 0) {
|
|
376
|
+
// Specific providers - show suggested models
|
|
377
|
+
console.log(colors.cyan + ' ' + i18n.tSync('ui.general.suggested_models') + colors.reset);
|
|
378
|
+
suggestedModels.forEach((m, i) => {
|
|
379
|
+
console.log(colors.gray + ` ${i + 1}. ${m}` + colors.reset);
|
|
380
|
+
});
|
|
381
|
+
console.log('');
|
|
382
|
+
|
|
383
|
+
while (true) {
|
|
384
|
+
const modelPrompt = i18n.tSync('ui.general.select_model_prompt').replace('{0}', suggestedModels.length);
|
|
385
|
+
const modelChoice = await simpleInput(colors.green + modelPrompt + colors.reset);
|
|
386
|
+
|
|
387
|
+
if (modelChoice.toLowerCase() === 'exit' || modelChoice.toLowerCase() === 'quit') {
|
|
388
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check if it's a number selection
|
|
392
|
+
if (!isNaN(modelChoice) && modelChoice.trim() !== '') {
|
|
393
|
+
const index = parseInt(modelChoice) - 1;
|
|
394
|
+
if (index >= 0 && index < suggestedModels.length) {
|
|
395
|
+
model = suggestedModels[index];
|
|
396
|
+
break;
|
|
397
|
+
} else {
|
|
398
|
+
console.log(colors.red + i18n.tSync('ui.general.invalid_model_selection').replace('{0}', suggestedModels.length) + colors.reset);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// If not a number, validate as custom model name
|
|
404
|
+
const validation = validateModel(modelChoice);
|
|
405
|
+
if (!validation.valid) {
|
|
406
|
+
console.log(colors.red + `❌ ${validation.error}` + colors.reset);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
model = validation.value;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
// Fallback - manual input
|
|
414
|
+
while (true) {
|
|
415
|
+
const inputModel = await simpleInput(colors.green + i18n.tSync('ui.general.model_name_prompt') + colors.reset);
|
|
416
|
+
|
|
417
|
+
if (inputModel.toLowerCase() === 'exit' || inputModel.toLowerCase() === 'quit') {
|
|
418
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const validation = validateModel(inputModel);
|
|
422
|
+
if (!validation.valid) {
|
|
423
|
+
console.log(colors.red + `❌ ${validation.error}` + colors.reset);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
model = validation.value;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Input name
|
|
432
|
+
const name = await simpleInput(colors.green + i18n.tSync('ui.general.api_name_prompt') + colors.reset);
|
|
433
|
+
if (name.toLowerCase() === 'exit' || name.toLowerCase() === 'quit') {
|
|
434
|
+
throw new Error(i18n.tSync('errors.general.cancelled_by_user'));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
baseUrl,
|
|
439
|
+
authToken,
|
|
440
|
+
model,
|
|
441
|
+
name: name || undefined,
|
|
442
|
+
provider: selectedProvider?.id || 'custom'
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
} catch (error) {
|
|
446
|
+
// Let the upper layer handle error display to avoid duplicate messages
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Confirm action prompt
|
|
453
|
+
*/
|
|
454
|
+
async function confirmAction(message) {
|
|
455
|
+
console.log(colors.yellow + message + colors.reset);
|
|
456
|
+
console.log(colors.gray + i18n.tSync('ui.general.press_y_confirm') + colors.reset);
|
|
457
|
+
|
|
458
|
+
return new Promise((resolve) => {
|
|
459
|
+
if (process.stdin.isTTY) {
|
|
460
|
+
// Force complete cleanup before setting up new listeners
|
|
461
|
+
try {
|
|
462
|
+
process.stdin.removeAllListeners('data');
|
|
463
|
+
process.stdin.removeAllListeners('keypress');
|
|
464
|
+
} catch (error) {
|
|
465
|
+
// Ignore cleanup errors
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
process.stdin.setRawMode(true);
|
|
469
|
+
process.stdin.resume();
|
|
470
|
+
process.stdin.once('data', (key) => {
|
|
471
|
+
// Complete cleanup after key press
|
|
472
|
+
try {
|
|
473
|
+
process.stdin.setRawMode(false);
|
|
474
|
+
process.stdin.removeAllListeners('data');
|
|
475
|
+
process.stdin.removeAllListeners('keypress');
|
|
476
|
+
process.stdin.pause();
|
|
477
|
+
} catch (error) {
|
|
478
|
+
// Ignore cleanup errors
|
|
479
|
+
}
|
|
480
|
+
resolve(key.toString().toLowerCase() === 'y');
|
|
481
|
+
});
|
|
482
|
+
} else {
|
|
483
|
+
const rl = readline.createInterface({
|
|
484
|
+
input: process.stdin,
|
|
485
|
+
output: process.stdout
|
|
486
|
+
});
|
|
487
|
+
rl.question('', (answer) => {
|
|
488
|
+
rl.close();
|
|
489
|
+
resolve(answer.toLowerCase() === 'y');
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Display success message
|
|
497
|
+
*/
|
|
498
|
+
function showSuccess(title, details = []) {
|
|
499
|
+
console.log('');
|
|
500
|
+
console.log(colors.bright + colors.green + `✓ ${title}` + colors.reset);
|
|
501
|
+
details.forEach(detail => {
|
|
502
|
+
console.log(colors.gray + ` ${detail}` + colors.reset);
|
|
503
|
+
});
|
|
504
|
+
console.log('');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Display error message
|
|
509
|
+
*/
|
|
510
|
+
function showError(title, details = []) {
|
|
511
|
+
console.log('');
|
|
512
|
+
console.log(colors.bright + colors.red + `❌ ${title}` + colors.reset);
|
|
513
|
+
details.forEach(detail => {
|
|
514
|
+
console.log(colors.gray + ` ${detail}` + colors.reset);
|
|
515
|
+
});
|
|
516
|
+
console.log('');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Display info message
|
|
521
|
+
*/
|
|
522
|
+
function showInfo(title, details = []) {
|
|
523
|
+
console.log('');
|
|
524
|
+
console.log(colors.bright + colors.cyan + `ℹ️ ${title}` + colors.reset);
|
|
525
|
+
details.forEach(detail => {
|
|
526
|
+
console.log(colors.gray + ` ${detail}` + colors.reset);
|
|
527
|
+
});
|
|
528
|
+
console.log('');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
module.exports = {
|
|
533
|
+
simpleInput,
|
|
534
|
+
waitForKey,
|
|
535
|
+
promptForThirdPartyApi,
|
|
536
|
+
confirmAction,
|
|
537
|
+
showSuccess,
|
|
538
|
+
showError,
|
|
539
|
+
showInfo
|
|
540
|
+
};
|