@kikkimo/claude-launcher 2.5.0 → 3.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 +70 -0
- package/README.md +23 -13
- package/claude-launcher +1244 -432
- package/docs/README-zh.md +23 -13
- package/lib/api-manager.js +629 -70
- package/lib/auth/password-input.js +8 -4
- package/lib/auth/password-validator.js +83 -48
- package/lib/i18n/index.js +4 -3
- package/lib/i18n/language-manager.js +4 -3
- package/lib/i18n/locales/de.js +229 -13
- package/lib/i18n/locales/en.js +235 -13
- package/lib/i18n/locales/es.js +229 -13
- package/lib/i18n/locales/fr.js +229 -13
- package/lib/i18n/locales/it.js +229 -13
- package/lib/i18n/locales/ja.js +229 -13
- package/lib/i18n/locales/ko.js +229 -13
- package/lib/i18n/locales/pt.js +229 -13
- package/lib/i18n/locales/ru.js +229 -13
- package/lib/i18n/locales/zh-TW.js +229 -13
- package/lib/i18n/locales/zh.js +235 -13
- package/lib/launcher.js +167 -110
- package/lib/presets/providers.js +143 -39
- package/lib/ui/api-editor.js +668 -0
- package/lib/ui/i18n-labels.js +16 -0
- package/lib/ui/interactive-table.js +216 -99
- package/lib/ui/menu.js +79 -62
- package/lib/ui/prompts.js +168 -139
- package/lib/ui/screen.js +125 -0
- package/lib/utils/stdin-manager.js +11 -9
- package/lib/utils/version-checker.js +65 -4
- package/lib/validators.js +102 -1
- package/package.json +2 -2
- package/docs/superpowers/plans/2026-03-31-update-models-and-auto-mode.md +0 -1414
- package/docs/superpowers/specs/2026-03-31-update-models-and-auto-mode-design.md +0 -187
package/claude-launcher
CHANGED
|
@@ -25,7 +25,7 @@ function forceStdinCleanup() {
|
|
|
25
25
|
} catch (error) {
|
|
26
26
|
// Ignore cleanup errors but log for debugging
|
|
27
27
|
if (process.env.DEBUG_STDIN) {
|
|
28
|
-
|
|
28
|
+
screen.debug('[DEBUG] forceStdinCleanup error: ' + error.message);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -36,12 +36,14 @@ const Menu = require('./lib/ui/menu');
|
|
|
36
36
|
const colors = require('./lib/ui/colors');
|
|
37
37
|
const { checkForUpdates, forceCheckForUpdates } = require('./lib/utils/version-checker');
|
|
38
38
|
const {
|
|
39
|
+
simpleInput,
|
|
39
40
|
waitForKey,
|
|
40
41
|
promptForThirdPartyApi,
|
|
41
42
|
confirmAction,
|
|
42
43
|
showSuccess,
|
|
43
44
|
showError,
|
|
44
|
-
showInfo
|
|
45
|
+
showInfo,
|
|
46
|
+
selectProvider,
|
|
45
47
|
} = require('./lib/ui/prompts');
|
|
46
48
|
const {
|
|
47
49
|
launchClaudeDefault,
|
|
@@ -50,10 +52,13 @@ const {
|
|
|
50
52
|
launchClaudeWithApi
|
|
51
53
|
} = require('./lib/launcher');
|
|
52
54
|
const { getPasswordInput } = require('./lib/auth/password-input');
|
|
53
|
-
const {
|
|
55
|
+
const { passwordGuard, verifyCurrentPassword, setupNewPassword, changePassword: changePasswordModule } = require('./lib/auth/password-validator');
|
|
54
56
|
const { maskApiToken } = require('./lib/validators');
|
|
55
57
|
const { showApiSelectionTable, confirmDeletion } = require('./lib/ui/interactive-table');
|
|
58
|
+
const { editApi, editApiEnvVarsById, resolveProviderName } = require('./lib/ui/api-editor');
|
|
59
|
+
const { i18nLabel } = require('./lib/ui/i18n-labels');
|
|
56
60
|
const i18n = require('./lib/i18n');
|
|
61
|
+
const screen = require('./lib/ui/screen');
|
|
57
62
|
const fs = require('fs');
|
|
58
63
|
const path = require('path');
|
|
59
64
|
const os = require('os');
|
|
@@ -66,6 +71,7 @@ const apiManager = new ApiManager();
|
|
|
66
71
|
let globalMainMenu = null;
|
|
67
72
|
let globalConfirmMenu = null;
|
|
68
73
|
let globalApiManagementMenu = null;
|
|
74
|
+
let globalConfigMenu = null;
|
|
69
75
|
|
|
70
76
|
/**
|
|
71
77
|
* Initialize global menu objects to prevent recreation and screen flickering
|
|
@@ -80,6 +86,9 @@ function initializeGlobalMenus() {
|
|
|
80
86
|
if (!globalApiManagementMenu) {
|
|
81
87
|
globalApiManagementMenu = new Menu();
|
|
82
88
|
}
|
|
89
|
+
if (!globalConfigMenu) {
|
|
90
|
+
globalConfigMenu = new Menu();
|
|
91
|
+
}
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
/**
|
|
@@ -132,7 +141,7 @@ function openFileWithDefault(filePath) {
|
|
|
132
141
|
|
|
133
142
|
exec(command, (error) => {
|
|
134
143
|
if (error) {
|
|
135
|
-
|
|
144
|
+
screen.write(colors.yellow + `Could not open file automatically: ${error.message}` + colors.reset + '\n');
|
|
136
145
|
}
|
|
137
146
|
});
|
|
138
147
|
}
|
|
@@ -215,71 +224,653 @@ function validateImportFile(filePath) {
|
|
|
215
224
|
// Main menu options - will be populated dynamically with i18n
|
|
216
225
|
let menuOptions = [];
|
|
217
226
|
|
|
227
|
+
// --- Add API flow helpers ---
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Input helper for add-api steps. Supports Esc / "back" to go back,
|
|
231
|
+
* "exit"/"quit" to cancel, empty input uses defaultValue if provided.
|
|
232
|
+
* @param {string} promptText
|
|
233
|
+
* @param {Object} opts
|
|
234
|
+
* @param {string} opts.defaultValue - fallback when input is empty
|
|
235
|
+
* @param {boolean} opts.allowEmpty - if true, empty input returns ""
|
|
236
|
+
* @param {string} opts.stepLabel - step header e.g. "Step 2/6"
|
|
237
|
+
* @param {string[]} opts.hintLines - i18n keys for hint lines (default: exit + empty_restore)
|
|
238
|
+
* @param {Function} opts.validate - (value) => { valid, error? }
|
|
239
|
+
* @returns {string|null} - input value, or null to go back
|
|
240
|
+
*/
|
|
241
|
+
async function inputStepValue(promptText, { defaultValue, allowEmpty, stepLabel, hintLines, validate } = {}) {
|
|
242
|
+
const lines = [''];
|
|
243
|
+
if (stepLabel) {
|
|
244
|
+
lines.push(colors.bright + stepLabel + colors.reset);
|
|
245
|
+
}
|
|
246
|
+
lines.push(colors.cyan + promptText + colors.reset);
|
|
247
|
+
lines.push('');
|
|
248
|
+
const hints = hintLines || ['prompt.exit_to_cancel', 'prompt.empty_to_restore'];
|
|
249
|
+
for (const h of hints) {
|
|
250
|
+
lines.push(colors.gray + i18n.tSync(h) + colors.reset);
|
|
251
|
+
}
|
|
252
|
+
lines.push('');
|
|
253
|
+
screen.render(lines);
|
|
254
|
+
|
|
255
|
+
while (true) {
|
|
256
|
+
const input = await simpleInput(colors.green + '> ' + colors.reset);
|
|
257
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
258
|
+
const cancelledMsg = await i18n.t('errors.general.cancelled_by_user');
|
|
259
|
+
throw new Error(cancelledMsg);
|
|
260
|
+
}
|
|
261
|
+
if (input.toLowerCase() === 'back') {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
if (input === '') {
|
|
265
|
+
if (allowEmpty) return '';
|
|
266
|
+
if (defaultValue !== undefined) return defaultValue;
|
|
267
|
+
}
|
|
268
|
+
if (validate) {
|
|
269
|
+
const result = validate(input);
|
|
270
|
+
if (!result.valid) {
|
|
271
|
+
screen.write(colors.red + (result.error || 'Invalid input') + colors.reset + '\n');
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return input;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Rebuild draft after user goes back and re-enters connection info.
|
|
281
|
+
* Preserves manual overrides from oldDraft for model/runtime/custom sections.
|
|
282
|
+
*/
|
|
283
|
+
function rebuildDraftPreservingOverrides(oldDraft, providerId, baseUrl, authToken, model, name) {
|
|
284
|
+
const draft = ApiManager.buildApiDraft(providerId, baseUrl, authToken, model, name);
|
|
285
|
+
if (!oldDraft) return draft;
|
|
286
|
+
|
|
287
|
+
// Preserve model overrides: if user set a value different from auto, keep it
|
|
288
|
+
const oldAuto = oldDraft._autoModelEnvVars || {};
|
|
289
|
+
for (const k of Object.keys(oldDraft.modelEnvVars || {})) {
|
|
290
|
+
if (oldDraft.modelEnvVars[k] !== oldAuto[k]) {
|
|
291
|
+
draft.modelEnvVars[k] = oldDraft.modelEnvVars[k];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Preserve runtime manual overrides
|
|
295
|
+
const oldRtSources = oldDraft._runtimeEnvSources || {};
|
|
296
|
+
for (const k of Object.keys(oldRtSources)) {
|
|
297
|
+
if (oldRtSources[k] === 'manual') {
|
|
298
|
+
draft.runtimeEnvVars[k] = oldDraft.runtimeEnvVars[k];
|
|
299
|
+
draft._runtimeEnvSources[k] = 'manual';
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Preserve custom vars
|
|
303
|
+
draft.customEnvVars = { ...(oldDraft.customEnvVars || {}) };
|
|
304
|
+
return draft;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Show duplicate API branch: enter existing config or go back.
|
|
309
|
+
* @returns {'enter_existing'|'go_back'}
|
|
310
|
+
*/
|
|
311
|
+
async function showDuplicateBranch(existingApi) {
|
|
312
|
+
const menu = new Menu();
|
|
313
|
+
screen.render([
|
|
314
|
+
'', colors.yellow + i18n.tSync('add_api.duplicate_title') + colors.reset, '',
|
|
315
|
+
i18n.tSync('api.edit.field_name') + ': ' + existingApi.name,
|
|
316
|
+
i18n.tSync('api.details.provider') + ': ' + resolveProviderName(existingApi.provider),
|
|
317
|
+
i18n.tSync('api.details.url') + ': ' + existingApi.baseUrl,
|
|
318
|
+
i18n.tSync('api.details.model') + ': ' + existingApi.model,
|
|
319
|
+
'', colors.gray + i18n.tSync('add_api.duplicate_draft_discarded') + colors.reset, '',
|
|
320
|
+
i18n.tSync('action.please_choose'), '',
|
|
321
|
+
]);
|
|
322
|
+
menu.setOptions([
|
|
323
|
+
i18n.tSync('add_api.duplicate_enter_config'),
|
|
324
|
+
i18n.tSync('add_api.duplicate_back'),
|
|
325
|
+
]);
|
|
326
|
+
const choice = await menu.navigate();
|
|
327
|
+
if (choice === 0) return 'enter_existing';
|
|
328
|
+
return 'go_back';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// --- Draft editing sub-pages (no persistence, operate on draft object) ---
|
|
332
|
+
|
|
333
|
+
async function editDraftModelConfig(draft) {
|
|
334
|
+
const menu = new Menu();
|
|
335
|
+
const { PREDEFINED_MODEL_ENV_KEYS } = require('./lib/validators');
|
|
336
|
+
const { MODEL_CONFIG_LABELS } = require('./lib/api-manager');
|
|
337
|
+
const getLabel = (key) => i18nLabel("model", key, MODEL_CONFIG_LABELS);
|
|
338
|
+
|
|
339
|
+
while (true) {
|
|
340
|
+
const rows = PREDEFINED_MODEL_ENV_KEYS.map((key) => {
|
|
341
|
+
const currentVal = draft.modelEnvVars[key] || '';
|
|
342
|
+
const autoVal = draft._autoModelEnvVars[key] || '';
|
|
343
|
+
const displayVal = currentVal || autoVal || i18n.tSync('status.not_set');
|
|
344
|
+
const isOverridden = autoVal !== '' && currentVal !== autoVal;
|
|
345
|
+
const mark = isOverridden ? ' ' + colors.cyan + i18n.tSync('status.overridden') + colors.reset : '';
|
|
346
|
+
return { label: ' ' + getLabel(key), displayVal, mark };
|
|
347
|
+
});
|
|
348
|
+
const { getStringWidth, padStringToWidth } = require('./lib/utils/string-width');
|
|
349
|
+
const maxLabelW = Math.max(...rows.map(r => getStringWidth(r.label)));
|
|
350
|
+
const maxValueW = Math.max(...rows.map(r => getStringWidth(r.displayVal)));
|
|
351
|
+
const options = rows.map(r => {
|
|
352
|
+
return colors.reset + padStringToWidth(r.label, maxLabelW + 2) + ' ' + padStringToWidth(r.displayVal, maxValueW) + r.mark;
|
|
353
|
+
});
|
|
354
|
+
options.push(i18n.tSync('api.edit.back'));
|
|
355
|
+
menu.setOptions(options);
|
|
356
|
+
const modelHintFn = (idx) => {
|
|
357
|
+
if (idx >= PREDEFINED_MODEL_ENV_KEYS.length) return null;
|
|
358
|
+
const k = PREDEFINED_MODEL_ENV_KEYS[idx];
|
|
359
|
+
const dk = {
|
|
360
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: 'hints.model.sonnet_detail',
|
|
361
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: 'hints.model.opus_detail',
|
|
362
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'hints.model.haiku_detail',
|
|
363
|
+
CLAUDE_CODE_SUBAGENT_MODEL: 'hints.model.subagent_detail',
|
|
364
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION: 'hints.model.custom_option_detail',
|
|
365
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: 'hints.model.custom_name_detail',
|
|
366
|
+
};
|
|
367
|
+
return dk[k] ? i18n.tSync(dk[k]) : null;
|
|
368
|
+
};
|
|
369
|
+
const choice = await menu.navigate(null, modelHintFn, 'navigation.enter_to_edit');
|
|
370
|
+
|
|
371
|
+
if (choice === -1 || choice === PREDEFINED_MODEL_ENV_KEYS.length) return;
|
|
372
|
+
|
|
373
|
+
const key = PREDEFINED_MODEL_ENV_KEYS[choice];
|
|
374
|
+
const autoVal = draft._autoModelEnvVars[key] || '';
|
|
375
|
+
const label = getLabel(key);
|
|
376
|
+
const subMenu = new Menu();
|
|
377
|
+
let inSub = true;
|
|
378
|
+
while (inSub) {
|
|
379
|
+
const currentVal = draft.modelEnvVars[key] || '';
|
|
380
|
+
screen.render([
|
|
381
|
+
'', colors.cyan + label + colors.reset, '',
|
|
382
|
+
i18n.tSync('status.current_value') + ': ' + (currentVal || i18n.tSync('status.not_set')),
|
|
383
|
+
i18n.tSync('status.recommended_value') + ': ' + (autoVal || i18n.tSync('status.not_set')),
|
|
384
|
+
'', i18n.tSync('action.please_choose'), '',
|
|
385
|
+
]);
|
|
386
|
+
subMenu.setOptions([
|
|
387
|
+
i18n.tSync('action.follow_recommended'),
|
|
388
|
+
i18n.tSync('action.custom_input'),
|
|
389
|
+
i18n.tSync('api.edit.back'),
|
|
390
|
+
]);
|
|
391
|
+
const subChoice = await subMenu.navigate(null, null);
|
|
392
|
+
if (subChoice === -1 || subChoice === 2) { inSub = false; break; }
|
|
393
|
+
if (subChoice === 0) {
|
|
394
|
+
draft.modelEnvVars[key] = autoVal;
|
|
395
|
+
inSub = false;
|
|
396
|
+
} else if (subChoice === 1) {
|
|
397
|
+
const input = await inputStepValue(
|
|
398
|
+
i18n.tSync('api.edit.current_value', currentVal),
|
|
399
|
+
{ allowEmpty: true }
|
|
400
|
+
);
|
|
401
|
+
if (input === null) continue; // "back"
|
|
402
|
+
draft.modelEnvVars[key] = (input === '') ? autoVal : input;
|
|
403
|
+
inSub = false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function editDraftRuntimeConfig(draft) {
|
|
410
|
+
const menu = new Menu();
|
|
411
|
+
const { PREDEFINED_RUNTIME_KEYS, TYPE_A_FIELDS, TYPE_B_FIELDS } = require('./lib/validators');
|
|
412
|
+
const { RUNTIME_CONFIG_LABELS } = require('./lib/api-manager');
|
|
413
|
+
const { getProvider } = require('./lib/presets/providers');
|
|
414
|
+
const getLabel = (key) => i18nLabel("runtime", key, RUNTIME_CONFIG_LABELS);
|
|
415
|
+
const providerConfig = getProvider(draft.provider);
|
|
416
|
+
const providerDefaults = providerConfig ? providerConfig.envVars || {} : {};
|
|
417
|
+
|
|
418
|
+
while (true) {
|
|
419
|
+
const runtimeKeys = [...PREDEFINED_RUNTIME_KEYS];
|
|
420
|
+
const rows = runtimeKeys.map(key => {
|
|
421
|
+
const val = draft.runtimeEnvVars[key] || '';
|
|
422
|
+
const providerVal = providerDefaults[key];
|
|
423
|
+
let display;
|
|
424
|
+
if (val === '') {
|
|
425
|
+
if (providerVal !== undefined) {
|
|
426
|
+
if (TYPE_A_FIELDS.includes(key) && providerVal === '1') {
|
|
427
|
+
display = i18n.tSync('status.enabled');
|
|
428
|
+
} else {
|
|
429
|
+
display = providerVal;
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
display = i18n.tSync('status.auto');
|
|
433
|
+
}
|
|
434
|
+
} else if (TYPE_A_FIELDS.includes(key) && val === 'off') {
|
|
435
|
+
display = i18n.tSync('status.disabled');
|
|
436
|
+
} else if (TYPE_A_FIELDS.includes(key) && val === '1') {
|
|
437
|
+
display = i18n.tSync('status.enabled');
|
|
438
|
+
} else {
|
|
439
|
+
display = val;
|
|
440
|
+
}
|
|
441
|
+
const isManual = (draft._runtimeEnvSources || {})[key] === 'manual';
|
|
442
|
+
const mark = isManual ? ' ' + colors.cyan + i18n.tSync('status.overridden') + colors.reset : '';
|
|
443
|
+
return { label: ' ' + getLabel(key), display, mark };
|
|
444
|
+
});
|
|
445
|
+
const { getStringWidth, padStringToWidth } = require('./lib/utils/string-width');
|
|
446
|
+
const maxLabelW = Math.max(...rows.map(r => getStringWidth(r.label)));
|
|
447
|
+
const maxValueW = Math.max(...rows.map(r => getStringWidth(r.display)));
|
|
448
|
+
const options = rows.map(r => {
|
|
449
|
+
return colors.reset + padStringToWidth(r.label, maxLabelW + 2) + ' ' + padStringToWidth(r.display, maxValueW) + r.mark;
|
|
450
|
+
});
|
|
451
|
+
options.push(i18n.tSync('api.edit.back'));
|
|
452
|
+
menu.setOptions(options);
|
|
453
|
+
const runtimeHintFn = (idx) => {
|
|
454
|
+
if (idx >= runtimeKeys.length) return null;
|
|
455
|
+
const k = runtimeKeys[idx];
|
|
456
|
+
const dk = {
|
|
457
|
+
API_TIMEOUT_MS: 'hints.runtime.timeout_detail',
|
|
458
|
+
CLAUDE_CODE_ATTRIBUTION_HEADER: 'hints.runtime.attribution_detail',
|
|
459
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 'hints.runtime.nonessential_detail',
|
|
460
|
+
CLAUDE_CODE_EFFORT_LEVEL: 'hints.runtime.effort_detail',
|
|
461
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: 'hints.runtime.experimental_detail',
|
|
462
|
+
CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK: 'hints.runtime.nonstreaming_detail',
|
|
463
|
+
};
|
|
464
|
+
if (!dk[k]) return null;
|
|
465
|
+
const val = draft.runtimeEnvVars[k] || '';
|
|
466
|
+
const isManual = (draft._runtimeEnvSources || {})[k] === 'manual';
|
|
467
|
+
let sourceText;
|
|
468
|
+
if (isManual && val !== '') {
|
|
469
|
+
sourceText = i18n.tSync('hints.runtime.source_manual');
|
|
470
|
+
} else if (providerDefaults[k] !== undefined) {
|
|
471
|
+
sourceText = i18n.tSync('hints.runtime.source_provider');
|
|
472
|
+
} else {
|
|
473
|
+
sourceText = i18n.tSync('hints.runtime.source_default');
|
|
474
|
+
}
|
|
475
|
+
return i18n.tSync(dk[k]) + '\n' + sourceText;
|
|
476
|
+
};
|
|
477
|
+
const choice = await menu.navigate(null, runtimeHintFn, 'navigation.enter_to_edit');
|
|
478
|
+
|
|
479
|
+
if (choice === -1 || choice === runtimeKeys.length) return;
|
|
480
|
+
|
|
481
|
+
const key = runtimeKeys[choice];
|
|
482
|
+
if (TYPE_A_FIELDS.includes(key) || TYPE_B_FIELDS.includes(key)) {
|
|
483
|
+
const values = TYPE_A_FIELDS.includes(key) ? ['', '1', 'off'] : ['', '1', '0'];
|
|
484
|
+
try {
|
|
485
|
+
const v = draft.runtimeEnvVars[key] || '';
|
|
486
|
+
const idx = values.indexOf(v);
|
|
487
|
+
const next = values[(idx + 1) % values.length];
|
|
488
|
+
ApiManager.applyDraftEnvChange(draft, 'runtime', key, next);
|
|
489
|
+
} catch (e) {
|
|
490
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
491
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
const isEffortLevel = (key === 'CLAUDE_CODE_EFFORT_LEVEL');
|
|
495
|
+
const extraHints = isEffortLevel ? ['hints.runtime.effort_values'] : [];
|
|
496
|
+
const draftVal = draft.runtimeEnvVars[key] || '';
|
|
497
|
+
const draftProvVal = providerDefaults[key];
|
|
498
|
+
const draftDisplayVal = draftVal || draftProvVal || i18n.tSync('status.auto');
|
|
499
|
+
const input = await inputStepValue(
|
|
500
|
+
i18n.tSync('api.edit.current_value', draftDisplayVal),
|
|
501
|
+
{ allowEmpty: true, hintLines: ['prompt.exit_to_cancel', ...extraHints] }
|
|
502
|
+
);
|
|
503
|
+
if (input === null) continue; // "back"
|
|
504
|
+
try {
|
|
505
|
+
ApiManager.applyDraftEnvChange(draft, 'runtime', key, input === '' ? '' : input);
|
|
506
|
+
} catch (e) {
|
|
507
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
508
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function editDraftCustomVars(draft) {
|
|
515
|
+
const menu = new Menu();
|
|
516
|
+
while (true) {
|
|
517
|
+
const keys = Object.keys(draft.customEnvVars || {});
|
|
518
|
+
const options = keys.length > 0
|
|
519
|
+
? keys.map(k => ' ' + k + ' = ' + draft.customEnvVars[k])
|
|
520
|
+
: [colors.gray + i18n.tSync('api.edit.no_custom_vars') + colors.reset];
|
|
521
|
+
options.push(i18n.tSync('action.add_variable'));
|
|
522
|
+
options.push(i18n.tSync('api.edit.back'));
|
|
523
|
+
menu.setOptions(options);
|
|
524
|
+
const choice = await menu.navigate(null, null, 'navigation.enter_to_edit');
|
|
525
|
+
|
|
526
|
+
if (choice === -1 || choice === options.length - 1) return;
|
|
527
|
+
if (choice === options.length - 2) {
|
|
528
|
+
const keyInput = await inputStepValue(i18n.tSync('api.edit.enter_custom_key'), { allowEmpty: false });
|
|
529
|
+
if (!keyInput || keyInput === null) continue;
|
|
530
|
+
const valInput = await inputStepValue(i18n.tSync('api.edit.enter_custom_value'), { allowEmpty: true });
|
|
531
|
+
if (valInput === null) continue;
|
|
532
|
+
try {
|
|
533
|
+
ApiManager.applyDraftEnvChange(draft, 'custom', keyInput, valInput || '');
|
|
534
|
+
} catch (e) {
|
|
535
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
536
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
537
|
+
}
|
|
538
|
+
} else if (choice < keys.length) {
|
|
539
|
+
const key = keys[choice];
|
|
540
|
+
screen.render(['', colors.yellow + i18n.tSync('confirm.delete_variable') + colors.reset]);
|
|
541
|
+
const confirm = await simpleInput('> ');
|
|
542
|
+
if (confirm.toLowerCase() === 'y') {
|
|
543
|
+
ApiManager.deleteDraftCustomEnvVar(draft, key);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function editDraftSection(draft, section) {
|
|
550
|
+
if (section === 'modify_model') await editDraftModelConfig(draft);
|
|
551
|
+
else if (section === 'modify_runtime') await editDraftRuntimeConfig(draft);
|
|
552
|
+
else if (section === 'modify_custom') await editDraftCustomVars(draft);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Show config confirmation page (step 6).
|
|
557
|
+
* @returns {'finish'|'back'|'modify_model'|'modify_runtime'|'modify_custom'}
|
|
558
|
+
*/
|
|
559
|
+
async function showConfigConfirmation(draft) {
|
|
560
|
+
const menu = new Menu();
|
|
561
|
+
const { PREDEFINED_MODEL_ENV_KEYS, PREDEFINED_RUNTIME_KEYS } = require('./lib/validators');
|
|
562
|
+
const modelOverridden = PREDEFINED_MODEL_ENV_KEYS.filter(k => {
|
|
563
|
+
const v = draft.modelEnvVars[k] || '';
|
|
564
|
+
const a = draft._autoModelEnvVars[k];
|
|
565
|
+
return a !== undefined && v !== a;
|
|
566
|
+
}).length;
|
|
567
|
+
const runtimeOverridden = PREDEFINED_RUNTIME_KEYS.filter(k => {
|
|
568
|
+
const val = (draft.runtimeEnvVars || {})[k] || '';
|
|
569
|
+
return val !== '';
|
|
570
|
+
}).length;
|
|
571
|
+
const customCount = Object.keys(draft.customEnvVars).length;
|
|
572
|
+
|
|
573
|
+
const { getStringWidth, padStringToWidth } = require('./lib/utils/string-width');
|
|
574
|
+
const fieldLabels = [
|
|
575
|
+
i18n.tSync('api.edit.field_name'), i18n.tSync('api.details.provider'),
|
|
576
|
+
i18n.tSync('api.details.url'), i18n.tSync('api.details.model'),
|
|
577
|
+
];
|
|
578
|
+
const labelW = Math.max(...fieldLabels.map(l => getStringWidth(l)));
|
|
579
|
+
const fieldValues = [
|
|
580
|
+
draft.name || i18n.tSync('status.not_set'), resolveProviderName(draft.provider),
|
|
581
|
+
draft.baseUrl, draft.model,
|
|
582
|
+
];
|
|
583
|
+
function fieldLine(label, value) {
|
|
584
|
+
return ' ' + colors.gray + padStringToWidth(label, labelW) + colors.reset + ' ' + colors.cyan + value + colors.reset + '\n';
|
|
585
|
+
}
|
|
586
|
+
const overriddenMark = ' ' + colors.cyan + i18n.tSync('status.overridden') + colors.reset;
|
|
587
|
+
const versionInfo = colors.bright + colors.cyan + i18n.tSync('add_api.step_n_of_m', '6', '6') + ' · ' + i18n.tSync('add_api.confirm_config') + colors.reset + '\n'
|
|
588
|
+
+ '\n'
|
|
589
|
+
+ fieldLine(fieldLabels[0], fieldValues[0])
|
|
590
|
+
+ fieldLine(fieldLabels[1], fieldValues[1])
|
|
591
|
+
+ fieldLine(fieldLabels[2], fieldValues[2])
|
|
592
|
+
+ fieldLine(fieldLabels[3], fieldValues[3])
|
|
593
|
+
+ '\n'
|
|
594
|
+
+ ' ' + colors.gray + i18n.tSync('add_api.confirm_page_prompt') + colors.reset + '\n'
|
|
595
|
+
+ '\n'
|
|
596
|
+
+ ' ' + i18n.tSync('page.model_config') + ' ' + colors.cyan + modelOverridden + '/' + PREDEFINED_MODEL_ENV_KEYS.length + colors.reset + ' ' + overriddenMark + '\n'
|
|
597
|
+
+ ' ' + i18n.tSync('page.runtime_config') + ' ' + colors.cyan + runtimeOverridden + '/' + PREDEFINED_RUNTIME_KEYS.length + colors.reset + ' ' + overriddenMark + '\n'
|
|
598
|
+
+ ' ' + i18n.tSync('page.custom_vars') + ' ' + colors.cyan + i18n.tSync('summary.x_items', String(customCount)) + colors.reset;
|
|
599
|
+
|
|
600
|
+
menu.setOptions([
|
|
601
|
+
i18n.tSync('action.finish_create'),
|
|
602
|
+
i18n.tSync('page.model_config'),
|
|
603
|
+
i18n.tSync('page.runtime_config'),
|
|
604
|
+
i18n.tSync('page.custom_vars'),
|
|
605
|
+
i18n.tSync('action.cancel_config'),
|
|
606
|
+
]);
|
|
607
|
+
const hintFn = (idx) => {
|
|
608
|
+
if (idx === 0) return i18n.tSync('add_api.finish_hint');
|
|
609
|
+
if (idx === 1) return i18n.tSync('hints.model.desc');
|
|
610
|
+
if (idx === 2) return i18n.tSync('hints.runtime.desc');
|
|
611
|
+
if (idx === 3) return i18n.tSync('hints.custom.desc');
|
|
612
|
+
return null;
|
|
613
|
+
};
|
|
614
|
+
const choice = await menu.navigate(versionInfo, hintFn);
|
|
615
|
+
if (choice === -1 || choice === 4) return 'cancel';
|
|
616
|
+
if (choice === 0) return 'finish';
|
|
617
|
+
if (choice === 1) return 'modify_model';
|
|
618
|
+
if (choice === 2) return 'modify_runtime';
|
|
619
|
+
if (choice === 3) return 'modify_custom';
|
|
620
|
+
}
|
|
621
|
+
|
|
218
622
|
/**
|
|
219
|
-
*
|
|
623
|
+
* Replay draft edits onto a newly created API.
|
|
624
|
+
* @returns {{ ok: string[], failed: string[] }}
|
|
625
|
+
*/
|
|
626
|
+
function replayDraftToApi(apiManager, draft, newApi) {
|
|
627
|
+
const ok = [];
|
|
628
|
+
const failed = [];
|
|
629
|
+
|
|
630
|
+
// Model env vars: only write overridden values
|
|
631
|
+
for (const k of Object.keys(draft.modelEnvVars || {})) {
|
|
632
|
+
const v = draft.modelEnvVars[k];
|
|
633
|
+
const autoV = draft._autoModelEnvVars[k] || '';
|
|
634
|
+
if (v !== autoV) {
|
|
635
|
+
try {
|
|
636
|
+
apiManager.updateModelEnvVar(newApi.id, k, v);
|
|
637
|
+
ok.push('model/' + k);
|
|
638
|
+
} catch (e) {
|
|
639
|
+
failed.push('model/' + k + ': ' + e.message);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Runtime env vars: only write manual values
|
|
644
|
+
for (const k of Object.keys(draft.runtimeEnvVars || {})) {
|
|
645
|
+
if ((draft._runtimeEnvSources || {})[k] === 'manual') {
|
|
646
|
+
try {
|
|
647
|
+
apiManager.updateRuntimeEnvVar(newApi.id, k, draft.runtimeEnvVars[k]);
|
|
648
|
+
ok.push('runtime/' + k);
|
|
649
|
+
} catch (e) {
|
|
650
|
+
failed.push('runtime/' + k + ': ' + e.message);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Custom env vars
|
|
655
|
+
for (const [k, v] of Object.entries(draft.customEnvVars || {})) {
|
|
656
|
+
try {
|
|
657
|
+
apiManager.setCustomEnvVar(newApi.id, k, v);
|
|
658
|
+
ok.push('custom/' + k);
|
|
659
|
+
} catch (e) {
|
|
660
|
+
failed.push('custom/' + k + ': ' + e.message);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return { ok, failed };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Add new third-party API — 6-step wizard with state machine.
|
|
668
|
+
* Step 1: Provider → 2: Base URL → 3: Auth Token → 4: Model → 5: Name → 6: Confirm
|
|
220
669
|
*/
|
|
221
670
|
async function addNewThirdPartyApi() {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const hasExportPassword = apiManager.hasExportPassword();
|
|
228
|
-
|
|
229
|
-
const newApi = apiManager.addApi(
|
|
230
|
-
apiData.baseUrl,
|
|
231
|
-
apiData.authToken,
|
|
232
|
-
apiData.model,
|
|
233
|
-
apiData.name,
|
|
234
|
-
apiData.provider
|
|
235
|
-
);
|
|
671
|
+
const { getProvider, getSuggestedModels } = require('./lib/presets/providers');
|
|
672
|
+
let step = 1;
|
|
673
|
+
let providerId = null, providerMeta = null;
|
|
674
|
+
let baseUrl = null, authToken = null, model = null, name = '';
|
|
675
|
+
let draft = null;
|
|
236
676
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
677
|
+
try {
|
|
678
|
+
while (step >= 1 && step <= 6) {
|
|
679
|
+
switch (step) {
|
|
680
|
+
case 1: { // Provider
|
|
681
|
+
const result = await selectProvider({
|
|
682
|
+
title: i18n.tSync('add_api.step_n_of_m', '1', '6'),
|
|
683
|
+
showNote: true,
|
|
684
|
+
});
|
|
685
|
+
if (!result) { step = 0; break; } // Esc = cancel
|
|
686
|
+
providerId = result.id;
|
|
687
|
+
providerMeta = getProvider(providerId);
|
|
688
|
+
baseUrl = null; authToken = null; model = null; name = '';
|
|
689
|
+
step = 2;
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
case 2: { // Base URL
|
|
693
|
+
const defaultUrl = providerMeta ? providerMeta.baseUrl || '' : '';
|
|
694
|
+
const input = await inputStepValue(
|
|
695
|
+
i18n.tSync('api.edit.field_base_url') + (defaultUrl ? ' [' + defaultUrl + ']' : ''),
|
|
696
|
+
{ defaultValue: defaultUrl, stepLabel: i18n.tSync('add_api.step_n_of_m', '2', '6') }
|
|
697
|
+
);
|
|
698
|
+
if (input === null) { step = 1; break; }
|
|
699
|
+
baseUrl = input;
|
|
700
|
+
step = 3;
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
case 3: { // Auth Token
|
|
704
|
+
const input = await inputStepValue(
|
|
705
|
+
i18n.tSync('api.details.token'),
|
|
706
|
+
{
|
|
707
|
+
stepLabel: i18n.tSync('add_api.step_n_of_m', '3', '6'),
|
|
708
|
+
hintLines: ['prompt.exit_to_cancel'],
|
|
709
|
+
validate: (v) => {
|
|
710
|
+
if (!v || v.trim().length < 10) {
|
|
711
|
+
return { valid: false, error: i18n.tSync('errors.api.invalid_token', 'at least 10 characters required') };
|
|
712
|
+
}
|
|
713
|
+
return { valid: true };
|
|
714
|
+
},
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
if (input === null) { step = 2; break; }
|
|
718
|
+
authToken = input;
|
|
719
|
+
step = 4;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
case 4: { // Model
|
|
723
|
+
const suggested = getSuggestedModels(providerId);
|
|
724
|
+
const hintLines = suggested.length > 0
|
|
725
|
+
? ['', colors.gray + ' ' + i18n.tSync('add_api.recommended_models') + ': ' + suggested.join(', ') + colors.reset, '']
|
|
726
|
+
: ['', ''];
|
|
727
|
+
const fullPrompt = i18n.tSync('api.details.model');
|
|
728
|
+
const lines = [''];
|
|
729
|
+
lines.push(colors.bright + i18n.tSync('add_api.step_n_of_m', '4', '6') + colors.reset);
|
|
730
|
+
lines.push(colors.cyan + fullPrompt + colors.reset);
|
|
731
|
+
lines.push(...hintLines);
|
|
732
|
+
lines.push(colors.gray + i18n.tSync('prompt.exit_to_cancel') + colors.reset);
|
|
733
|
+
lines.push(colors.gray + i18n.tSync('prompt.empty_to_restore') + colors.reset);
|
|
734
|
+
lines.push('');
|
|
735
|
+
screen.render(lines);
|
|
736
|
+
const input = await simpleInput(colors.green + '> ' + colors.reset);
|
|
737
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
738
|
+
throw new Error(await i18n.t('errors.general.cancelled_by_user'));
|
|
739
|
+
}
|
|
740
|
+
if (input.toLowerCase() === 'back') { step = 3; break; }
|
|
741
|
+
if (input === '' && suggested.length > 0) {
|
|
742
|
+
model = suggested[0];
|
|
743
|
+
} else {
|
|
744
|
+
model = input;
|
|
745
|
+
}
|
|
243
746
|
|
|
244
|
-
|
|
245
|
-
|
|
747
|
+
// Duplicate check
|
|
748
|
+
const dup = apiManager.checkDuplicate(baseUrl, authToken, model);
|
|
749
|
+
if (dup.isDuplicate) {
|
|
750
|
+
const action = await showDuplicateBranch(dup.existing);
|
|
751
|
+
if (action === 'enter_existing') {
|
|
752
|
+
await editApiEnvVarsById(apiManager, { apiId: dup.existing.id });
|
|
753
|
+
return; // done
|
|
754
|
+
}
|
|
755
|
+
step = 2; break; // go back to modify connection info
|
|
756
|
+
}
|
|
757
|
+
step = 5;
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
case 5: { // Name
|
|
761
|
+
const fullName = resolveProviderName(providerId);
|
|
762
|
+
const providerShortName = fullName.split(' (')[0].split(' -')[0];
|
|
763
|
+
// Count APIs sharing the same short-name prefix (handles moonshot / kimi_for_coding)
|
|
764
|
+
const sameNameCount = apiManager.config.apis.filter(a => {
|
|
765
|
+
const aFull = resolveProviderName(a.provider);
|
|
766
|
+
return aFull.split(' (')[0].split(' -')[0] === providerShortName;
|
|
767
|
+
}).length;
|
|
768
|
+
const autoName = providerShortName + ' #' + (sameNameCount + 1);
|
|
769
|
+
const input = await inputStepValue(
|
|
770
|
+
i18n.tSync('api.edit.field_name'),
|
|
771
|
+
{ defaultValue: autoName, stepLabel: i18n.tSync('add_api.step_n_of_m', '5', '6') }
|
|
772
|
+
);
|
|
773
|
+
if (input === null) { step = 4; break; }
|
|
774
|
+
name = input;
|
|
775
|
+
// Build/rebuild draft preserving manual edits if returning from step 6
|
|
776
|
+
draft = rebuildDraftPreservingOverrides(draft, providerId, baseUrl, authToken, model, name);
|
|
777
|
+
step = 6;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
case 6: { // Config confirmation
|
|
781
|
+
const action = await showConfigConfirmation(draft);
|
|
782
|
+
if (action === 'finish') {
|
|
783
|
+
try {
|
|
784
|
+
const newApi = apiManager.addApi(baseUrl, authToken, model, name, providerId);
|
|
785
|
+
const replayResult = replayDraftToApi(apiManager, draft, newApi);
|
|
786
|
+
if (replayResult.failed.length > 0) {
|
|
787
|
+
screen.write(colors.yellow + i18n.tSync('add_api.partial_failure') + colors.reset + '\n');
|
|
788
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
789
|
+
await editApiEnvVarsById(apiManager, { apiId: newApi.id });
|
|
790
|
+
} else {
|
|
791
|
+
const isFirstApi = apiManager.getApis().length === 1;
|
|
792
|
+
showSuccess(await i18n.t('messages.success.api_added'), [
|
|
793
|
+
`Name: ${newApi.name}`,
|
|
794
|
+
`${await i18n.t('api.details.provider')}: ${resolveProviderName(newApi.provider)}`,
|
|
795
|
+
`${await i18n.t('api.details.url')}: ${newApi.baseUrl}`,
|
|
796
|
+
`${await i18n.t('api.details.model')}: ${newApi.model}`
|
|
797
|
+
]);
|
|
798
|
+
if (isFirstApi) {
|
|
799
|
+
showInfo(await i18n.t('messages.info.first_time_usage'));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} catch (error) {
|
|
803
|
+
forceStdinCleanup();
|
|
804
|
+
if (error.code === 'DUPLICATE_API') {
|
|
805
|
+
const existing = apiManager.config.apis.find(a => a.id === error.existingApiId);
|
|
806
|
+
if (existing) {
|
|
807
|
+
screen.write(colors.yellow + i18n.tSync('add_api.duplicate_race_lost') + colors.reset + '\n');
|
|
808
|
+
const action = await showDuplicateBranch(existing);
|
|
809
|
+
if (action === 'enter_existing') {
|
|
810
|
+
await editApiEnvVarsById(apiManager, { apiId: existing.id });
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const cancelledMessage = await i18n.t('errors.general.cancelled_by_user');
|
|
816
|
+
if (error.message === cancelledMessage) {
|
|
817
|
+
screen.write(colors.yellow + await i18n.t('messages.info.operation_cancelled') + colors.reset + '\n');
|
|
818
|
+
} else {
|
|
819
|
+
showError(await i18n.t('errors.api.failed_add', error.message));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return; // Exit loop on all paths
|
|
823
|
+
} else if (action === 'cancel') {
|
|
824
|
+
return; // back to main menu
|
|
825
|
+
} else {
|
|
826
|
+
// modify_model, modify_runtime, modify_custom
|
|
827
|
+
await editDraftSection(draft, action);
|
|
828
|
+
// Loop back to step 6 (confirmation re-renders)
|
|
829
|
+
}
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
246
832
|
}
|
|
833
|
+
}
|
|
247
834
|
|
|
835
|
+
if (step === 0) {
|
|
836
|
+
screen.write(colors.yellow + i18n.tSync('messages.info.operation_cancelled') + colors.reset + '\n');
|
|
837
|
+
}
|
|
248
838
|
} catch (error) {
|
|
249
|
-
// Force cleanup stdin state to prevent navigation issues
|
|
250
839
|
forceStdinCleanup();
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (error.
|
|
255
|
-
|
|
256
|
-
|
|
840
|
+
const cancelledMsg = i18n.tSync('errors.general.cancelled_by_user');
|
|
841
|
+
if (error.message === cancelledMsg) {
|
|
842
|
+
screen.write(colors.yellow + i18n.tSync('messages.info.operation_cancelled') + colors.reset + '\n');
|
|
843
|
+
} else if (error.code === 'DUPLICATE_API') {
|
|
844
|
+
const existing = apiManager.config.apis.find(a => a.id === error.existingApiId);
|
|
845
|
+
if (existing) {
|
|
846
|
+
screen.write(colors.yellow + i18n.tSync('add_api.duplicate_race_lost') + colors.reset + '\n');
|
|
847
|
+
}
|
|
257
848
|
} else {
|
|
258
|
-
|
|
259
|
-
showError(await i18n.t('errors.api.failed_add', error.message));
|
|
849
|
+
screen.write(colors.red + error.message + colors.reset + '\n');
|
|
260
850
|
}
|
|
261
851
|
}
|
|
262
|
-
|
|
263
|
-
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
264
852
|
}
|
|
265
853
|
|
|
266
854
|
/**
|
|
267
855
|
* Remove third-party API menu with submenu
|
|
268
856
|
*/
|
|
269
857
|
async function removeThirdPartyApi() {
|
|
270
|
-
console.clear();
|
|
271
|
-
console.log('');
|
|
272
|
-
console.log(colors.bright + colors.orange + '🗑️ ' + await i18n.t('menu.remove_api.title') + colors.reset);
|
|
273
|
-
console.log('');
|
|
274
|
-
|
|
275
858
|
const apis = apiManager.getApis();
|
|
276
859
|
|
|
860
|
+
const lines = [
|
|
861
|
+
'',
|
|
862
|
+
colors.bright + colors.orange + '🗑️ ' + await i18n.t('menu.remove_api.title') + colors.reset,
|
|
863
|
+
'',
|
|
864
|
+
];
|
|
865
|
+
|
|
277
866
|
// Show current API count
|
|
278
867
|
if (apis.length > 0) {
|
|
279
|
-
|
|
280
|
-
|
|
868
|
+
lines.push(colors.cyan + ' ' + await i18n.t('messages.info.current_api_count', apis.length) + colors.reset);
|
|
869
|
+
lines.push('');
|
|
281
870
|
}
|
|
282
871
|
|
|
872
|
+
screen.render(lines);
|
|
873
|
+
|
|
283
874
|
const menuOptions = [
|
|
284
875
|
await i18n.t('menu.remove_api.delete_single'),
|
|
285
876
|
await i18n.t('menu.remove_api.clear_all'),
|
|
@@ -337,7 +928,6 @@ async function deleteSingleApi() {
|
|
|
337
928
|
const selectedIndex = apis.findIndex(api => api.id === selectedApi.id);
|
|
338
929
|
apiManager.removeApi(selectedIndex);
|
|
339
930
|
|
|
340
|
-
console.clear();
|
|
341
931
|
showSuccess(await i18n.t('messages.success.api_removed'), [
|
|
342
932
|
`${await i18n.t('api.actions.removed_info', selectedApi.name)}`,
|
|
343
933
|
`${await i18n.t('api.details.provider')}: ${selectedApi.provider}`
|
|
@@ -376,24 +966,23 @@ async function clearAllApis() {
|
|
|
376
966
|
const count = apis.length;
|
|
377
967
|
|
|
378
968
|
if (count === 0) {
|
|
379
|
-
console.clear();
|
|
380
969
|
showInfo(await i18n.t('messages.info.no_apis'));
|
|
381
970
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
382
971
|
return;
|
|
383
972
|
}
|
|
384
973
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
974
|
+
screen.render([
|
|
975
|
+
'',
|
|
976
|
+
colors.bright + colors.red + '⚠️ ' + await i18n.t('menu.remove_api.clear_all') + colors.reset,
|
|
977
|
+
'',
|
|
978
|
+
colors.yellow + ' ' + await i18n.t('messages.prompts.confirm_clear_all', count) + colors.reset,
|
|
979
|
+
'',
|
|
980
|
+
]);
|
|
391
981
|
|
|
392
982
|
const input = await simpleInput(colors.cyan + ' ' + await i18n.t('messages.prompts.confirm_clear_all_input') + colors.reset);
|
|
393
983
|
|
|
394
984
|
if (input === 'CLEAR') {
|
|
395
985
|
const clearedCount = apiManager.clearAllApis();
|
|
396
|
-
console.clear();
|
|
397
986
|
showSuccess(await i18n.t('messages.info.all_apis_cleared', clearedCount));
|
|
398
987
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
399
988
|
} else {
|
|
@@ -455,10 +1044,11 @@ function formatRelativeTime(timestamp) {
|
|
|
455
1044
|
}
|
|
456
1045
|
|
|
457
1046
|
async function viewStatistics() {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
1047
|
+
screen.render([
|
|
1048
|
+
'',
|
|
1049
|
+
colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset,
|
|
1050
|
+
'',
|
|
1051
|
+
]);
|
|
462
1052
|
|
|
463
1053
|
const menuOptions = [
|
|
464
1054
|
await i18n.t('statistics.menu_view'),
|
|
@@ -480,7 +1070,7 @@ async function viewStatistics() {
|
|
|
480
1070
|
const confirm = await simpleInput(colors.yellow + ' ' + await i18n.t('statistics.reset_confirm') + ' ' + colors.reset);
|
|
481
1071
|
if (confirm.toLowerCase() === 'y') {
|
|
482
1072
|
apiManager.resetStatistics();
|
|
483
|
-
|
|
1073
|
+
screen.write(colors.green + ' ✓ ' + await i18n.t('statistics.reset_success') + colors.reset + '\n');
|
|
484
1074
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
485
1075
|
}
|
|
486
1076
|
return viewStatistics();
|
|
@@ -498,38 +1088,38 @@ async function viewStatistics() {
|
|
|
498
1088
|
async function showStatisticsDetails() {
|
|
499
1089
|
const { padStringToWidth } = require('./lib/utils/string-width');
|
|
500
1090
|
|
|
501
|
-
console.clear();
|
|
502
|
-
console.log('');
|
|
503
|
-
console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
|
|
504
|
-
console.log('');
|
|
505
|
-
|
|
506
1091
|
const stats = apiManager.getEnhancedStatistics();
|
|
507
1092
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1093
|
+
const lines = [
|
|
1094
|
+
'',
|
|
1095
|
+
colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset,
|
|
1096
|
+
'',
|
|
1097
|
+
// Summary section
|
|
1098
|
+
colors.cyan + ' ' + i18n.tSync('ui.general.summary') + ':' + colors.reset,
|
|
1099
|
+
colors.gray + ` ${await i18n.t('statistics.total_apis', stats.totalApis)}` + colors.reset,
|
|
1100
|
+
colors.gray + ` ${await i18n.t('statistics.active_api', stats.activeApiName)}` + colors.reset,
|
|
1101
|
+
colors.gray + ` ${await i18n.t('statistics.most_used', stats.mostUsedApi)}` + colors.reset,
|
|
1102
|
+
colors.gray + ` ${await i18n.t('statistics.total_usage', stats.totalUsage)}` + colors.reset,
|
|
1103
|
+
colors.gray + ` ${await i18n.t('statistics.success_rate', stats.successRate)}` + colors.reset,
|
|
1104
|
+
'',
|
|
1105
|
+
];
|
|
516
1106
|
|
|
517
1107
|
if (stats.apiStats.length > 0) {
|
|
518
|
-
|
|
519
|
-
|
|
1108
|
+
lines.push(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
|
|
1109
|
+
lines.push('');
|
|
520
1110
|
|
|
521
1111
|
// Table header
|
|
522
|
-
|
|
1112
|
+
lines.push(colors.dim + ' ' +
|
|
523
1113
|
padStringToWidth(await i18n.t('statistics.header_name'), 20) +
|
|
524
1114
|
padStringToWidth(await i18n.t('statistics.header_usage'), 10) +
|
|
525
1115
|
padStringToWidth(await i18n.t('statistics.header_success'), 10) +
|
|
526
1116
|
await i18n.t('statistics.header_last_used') +
|
|
527
1117
|
colors.reset);
|
|
528
|
-
|
|
1118
|
+
lines.push(colors.dim + ' ' + '─'.repeat(60) + colors.reset);
|
|
529
1119
|
|
|
530
1120
|
for (const api of stats.apiStats) {
|
|
531
1121
|
const lastUsedText = formatRelativeTime(api.lastUsed);
|
|
532
|
-
|
|
1122
|
+
lines.push(colors.gray + ' ' +
|
|
533
1123
|
padStringToWidth(api.name, 20) +
|
|
534
1124
|
padStringToWidth(String(api.usageCount), 10) +
|
|
535
1125
|
padStringToWidth(api.successRate, 10) +
|
|
@@ -537,10 +1127,11 @@ async function showStatisticsDetails() {
|
|
|
537
1127
|
colors.reset);
|
|
538
1128
|
}
|
|
539
1129
|
} else {
|
|
540
|
-
|
|
1130
|
+
lines.push(colors.gray + ' ' + await i18n.t('statistics.no_usage') + colors.reset);
|
|
541
1131
|
}
|
|
542
1132
|
|
|
543
|
-
|
|
1133
|
+
lines.push('');
|
|
1134
|
+
screen.render(lines);
|
|
544
1135
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
545
1136
|
}
|
|
546
1137
|
|
|
@@ -551,36 +1142,36 @@ async function showStatisticsDetails() {
|
|
|
551
1142
|
async function handleFirstTimePasswordSetup() {
|
|
552
1143
|
while (true) {
|
|
553
1144
|
// Clear screen and show header
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1145
|
+
const lines = [
|
|
1146
|
+
'',
|
|
1147
|
+
colors.bright + colors.yellow + '🔐 ' + i18n.tSync('password.setup.first_time_title') + colors.reset,
|
|
1148
|
+
'',
|
|
1149
|
+
// Show information
|
|
1150
|
+
colors.cyan + i18n.tSync('password.setup.why_needed') + colors.reset,
|
|
1151
|
+
];
|
|
561
1152
|
const whyNeededItems = i18n.tSync('password.setup.why_needed_items');
|
|
562
1153
|
if (Array.isArray(whyNeededItems)) {
|
|
563
1154
|
whyNeededItems.forEach(item => {
|
|
564
|
-
|
|
1155
|
+
lines.push(colors.gray + '• ' + item + colors.reset);
|
|
565
1156
|
});
|
|
566
1157
|
}
|
|
567
|
-
|
|
568
|
-
|
|
1158
|
+
lines.push('');
|
|
1159
|
+
lines.push(colors.cyan + '🔒 ' + i18n.tSync('password.setup.new_security_title') + colors.reset);
|
|
569
1160
|
const securityItems = i18n.tSync('password.setup.security_items');
|
|
570
1161
|
if (Array.isArray(securityItems)) {
|
|
571
1162
|
securityItems.forEach(item => {
|
|
572
|
-
|
|
1163
|
+
lines.push(colors.gray + '• ' + item + colors.reset);
|
|
573
1164
|
});
|
|
574
1165
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1166
|
+
lines.push('');
|
|
1167
|
+
lines.push(colors.yellow + i18n.tSync('password.setup.options_title') + colors.reset);
|
|
1168
|
+
lines.push(colors.gray + '• ' + i18n.tSync('password.setup.option_set') + colors.reset);
|
|
1169
|
+
lines.push(colors.gray + '• ' + i18n.tSync('password.setup.option_skip') + colors.reset);
|
|
1170
|
+
lines.push('');
|
|
1171
|
+
lines.push(colors.red + i18n.tSync('password.setup.warning_skip') + colors.reset);
|
|
1172
|
+
lines.push('');
|
|
1173
|
+
lines.push(colors.gray + '按任意键继续...' + colors.reset);
|
|
1174
|
+
screen.render(lines);
|
|
584
1175
|
|
|
585
1176
|
// Wait for user to read the information
|
|
586
1177
|
await new Promise((resolve) => {
|
|
@@ -656,14 +1247,16 @@ async function promptForPasswordSetup() {
|
|
|
656
1247
|
* Confirm skip password setup
|
|
657
1248
|
*/
|
|
658
1249
|
async function confirmSkipPassword() {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1250
|
+
screen.render([
|
|
1251
|
+
'',
|
|
1252
|
+
colors.bright + colors.red + '⚠️ ' + i18n.tSync('errors.password.confirm_skip_title') + colors.reset,
|
|
1253
|
+
'',
|
|
1254
|
+
colors.gray + i18n.tSync('ui.general.after_skipping_password_setup') + colors.reset,
|
|
1255
|
+
colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[0] + colors.reset,
|
|
1256
|
+
colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[1] + colors.reset,
|
|
1257
|
+
colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[2] + colors.reset,
|
|
1258
|
+
'',
|
|
1259
|
+
]);
|
|
667
1260
|
|
|
668
1261
|
// Ensure global menus are initialized
|
|
669
1262
|
initializeGlobalMenus();
|
|
@@ -678,12 +1271,12 @@ async function confirmSkipPassword() {
|
|
|
678
1271
|
if (choice === 0) {
|
|
679
1272
|
try {
|
|
680
1273
|
apiManager.skipPasswordSetup();
|
|
681
|
-
|
|
1274
|
+
screen.write(colors.yellow + i18n.tSync('errors.password.setup_skipped') + colors.reset + '\n');
|
|
682
1275
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
683
1276
|
return true;
|
|
684
1277
|
} catch (error) {
|
|
685
1278
|
forceStdinCleanup();
|
|
686
|
-
|
|
1279
|
+
screen.write(colors.red + `Operation failed: ${error.message}` + colors.reset + '\n');
|
|
687
1280
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
688
1281
|
return false;
|
|
689
1282
|
}
|
|
@@ -699,147 +1292,114 @@ async function showApiManagementMenu() {
|
|
|
699
1292
|
// Force cleanup stdin state before showing API management menu
|
|
700
1293
|
forceStdinCleanup();
|
|
701
1294
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1295
|
+
screen.render([
|
|
1296
|
+
'',
|
|
1297
|
+
colors.bright + colors.orange + '📋 ' + await i18n.t('menu.api_management.title') + colors.reset,
|
|
1298
|
+
'',
|
|
1299
|
+
]);
|
|
706
1300
|
|
|
707
1301
|
// Check if this is first time usage and prompt for password setup
|
|
708
1302
|
if (apiManager.isFirstTimeUsage()) {
|
|
709
1303
|
const passwordChoice = await handleFirstTimePasswordSetup();
|
|
710
1304
|
if (!passwordChoice) {
|
|
711
|
-
// User chose to skip or canceled, return to main menu
|
|
712
1305
|
return showMenu();
|
|
713
1306
|
}
|
|
714
1307
|
}
|
|
715
1308
|
|
|
716
1309
|
// Build menu options based on password setup status
|
|
717
1310
|
const menuOptions = [
|
|
718
|
-
await i18n.t('menu.api_management.add_new'),
|
|
719
|
-
await i18n.t('menu.api_management.
|
|
720
|
-
await i18n.t('menu.api_management.
|
|
721
|
-
await i18n.t('menu.api_management.
|
|
1311
|
+
await i18n.t('menu.api_management.add_new'), // 0
|
|
1312
|
+
await i18n.t('menu.api_management.edit'), // 1
|
|
1313
|
+
await i18n.t('menu.api_management.remove'), // 2
|
|
1314
|
+
await i18n.t('menu.api_management.switch'), // 3
|
|
1315
|
+
await i18n.t('menu.api_management.statistics'), // 4
|
|
1316
|
+
await i18n.t('menu.api_management.manual_upgrade') // 5
|
|
722
1317
|
];
|
|
723
1318
|
|
|
724
1319
|
// Add import/export options only if password is set
|
|
725
1320
|
if (apiManager.canUseImportExport()) {
|
|
726
|
-
menuOptions.push(await i18n.t('menu.api_management.export')); //
|
|
727
|
-
menuOptions.push(await i18n.t('menu.api_management.import')); //
|
|
728
|
-
menuOptions.push(await i18n.t('menu.api_management.change_password')); //
|
|
1321
|
+
menuOptions.push(await i18n.t('menu.api_management.export')); // 6
|
|
1322
|
+
menuOptions.push(await i18n.t('menu.api_management.import')); // 7
|
|
1323
|
+
menuOptions.push(await i18n.t('menu.api_management.change_password')); // 8
|
|
729
1324
|
}
|
|
730
1325
|
|
|
731
|
-
|
|
732
|
-
menuOptions.push(await i18n.t('model_upgrade.settings_title')); // 4 or 7 (depending on import/export)
|
|
733
|
-
|
|
734
|
-
menuOptions.push(await i18n.t('menu.api_management.back')); // 5 or 8
|
|
1326
|
+
menuOptions.push(await i18n.t('menu.api_management.back')); // last
|
|
735
1327
|
|
|
736
1328
|
// Ensure global menus are initialized
|
|
737
1329
|
initializeGlobalMenus();
|
|
738
1330
|
|
|
739
1331
|
globalApiManagementMenu.setOptions(menuOptions);
|
|
740
1332
|
|
|
741
|
-
const
|
|
1333
|
+
const hintCallback = (index) => {
|
|
1334
|
+
if (!apiManager.hasExportPassword()) return null;
|
|
1335
|
+
const passwordHints = {};
|
|
1336
|
+
passwordHints[1] = i18n.tSync('hints.edit_password_required');
|
|
1337
|
+
passwordHints[2] = i18n.tSync('hints.remove_password_required');
|
|
1338
|
+
if (apiManager.canUseImportExport()) {
|
|
1339
|
+
passwordHints[6] = i18n.tSync('hints.export_password_required');
|
|
1340
|
+
passwordHints[7] = i18n.tSync('hints.import_password_required');
|
|
1341
|
+
}
|
|
1342
|
+
return passwordHints[index] || null;
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
const choice = await globalApiManagementMenu.navigate(null, hintCallback);
|
|
742
1346
|
|
|
743
1347
|
// Handle menu choices based on current menu options
|
|
744
1348
|
if (choice === 0) { // Add New API
|
|
745
1349
|
await addNewThirdPartyApi();
|
|
746
1350
|
return showMenu();
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (choice === 1) { // Edit API
|
|
1354
|
+
if (await passwordGuard(apiManager, 'edit')) {
|
|
1355
|
+
await editApi(apiManager);
|
|
1356
|
+
}
|
|
1357
|
+
return showApiManagementMenu();
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
if (choice === 2) { // Remove API
|
|
1361
|
+
if (await passwordGuard(apiManager, 'delete')) {
|
|
1362
|
+
await removeThirdPartyApi();
|
|
1363
|
+
}
|
|
1364
|
+
return showApiManagementMenu();
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
if (choice === 3) { // Switch Active API
|
|
751
1368
|
await switchThirdPartyApi();
|
|
752
1369
|
return showMenu();
|
|
753
|
-
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (choice === 4) { // View API Statistics
|
|
754
1373
|
await viewStatistics();
|
|
755
1374
|
return showMenu();
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (choice === 5) { // Manual Model Upgrade
|
|
1378
|
+
await performManualUpgrade();
|
|
1379
|
+
return showApiManagementMenu();
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if (apiManager.canUseImportExport()) {
|
|
1383
|
+
if (choice === 6) { // Export Configuration
|
|
759
1384
|
await exportConfiguration();
|
|
760
|
-
return
|
|
761
|
-
}
|
|
1385
|
+
return showApiManagementMenu();
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
if (choice === 7) { // Import Configuration
|
|
762
1389
|
await importConfiguration();
|
|
763
|
-
return
|
|
764
|
-
} else if (choice === 6) { // Change Password
|
|
765
|
-
await changePassword();
|
|
766
|
-
return showMenu();
|
|
767
|
-
} else if (choice === 7) { // Model Upgrade Settings (NEW)
|
|
768
|
-
return await showModelUpgradeSettings();
|
|
769
|
-
} else if (choice === 8) { // Back to Main Menu
|
|
770
|
-
return showMenu();
|
|
1390
|
+
return showApiManagementMenu();
|
|
771
1391
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
return await showModelUpgradeSettings();
|
|
776
|
-
} else if (choice === 5) { // Back to Main Menu
|
|
1392
|
+
|
|
1393
|
+
if (choice === 8) { // Change Password
|
|
1394
|
+
await changePassword();
|
|
777
1395
|
return showMenu();
|
|
778
1396
|
}
|
|
779
1397
|
}
|
|
780
1398
|
|
|
781
|
-
//
|
|
1399
|
+
// Back to Main Menu (last item) or default fallback
|
|
782
1400
|
return showMenu();
|
|
783
1401
|
}
|
|
784
1402
|
|
|
785
|
-
/**
|
|
786
|
-
* Show model upgrade settings menu
|
|
787
|
-
*/
|
|
788
|
-
async function showModelUpgradeSettings() {
|
|
789
|
-
const versionChecker = require('./lib/utils/version-checker');
|
|
790
|
-
const upgradeChecker = require('./lib/utils/model-upgrade-checker');
|
|
791
|
-
|
|
792
|
-
console.clear();
|
|
793
|
-
console.log('');
|
|
794
|
-
console.log(colors.bright + colors.orange + '⚙️ ' + await i18n.t('model_upgrade.settings_title') + colors.reset);
|
|
795
|
-
console.log('');
|
|
796
|
-
|
|
797
|
-
const config = await versionChecker.loadConfig();
|
|
798
|
-
const isAutoOn = config.autoModelUpgrade === true;
|
|
799
|
-
|
|
800
|
-
console.log(colors.cyan + ' ' + await i18n.t('model_upgrade.current_config') + ':' + colors.reset);
|
|
801
|
-
console.log(colors.gray + ' ' + await i18n.t('model_upgrade.auto_upgrade_label') + ': ' +
|
|
802
|
-
(isAutoOn
|
|
803
|
-
? colors.green + await i18n.t('model_upgrade.auto_upgrade_on')
|
|
804
|
-
: colors.dim + await i18n.t('model_upgrade.auto_upgrade_off')
|
|
805
|
-
) + colors.reset);
|
|
806
|
-
console.log('');
|
|
807
|
-
|
|
808
|
-
const menuOptions = [
|
|
809
|
-
isAutoOn
|
|
810
|
-
? await i18n.t('model_upgrade.menu_toggle_auto_on')
|
|
811
|
-
: await i18n.t('model_upgrade.menu_toggle_auto_off'),
|
|
812
|
-
await i18n.t('model_upgrade.menu_manual_upgrade'),
|
|
813
|
-
await i18n.t('model_upgrade.menu_back')
|
|
814
|
-
];
|
|
815
|
-
|
|
816
|
-
initializeGlobalMenus();
|
|
817
|
-
globalApiManagementMenu.setOptions(menuOptions);
|
|
818
|
-
const choice = await globalApiManagementMenu.navigate();
|
|
819
|
-
|
|
820
|
-
switch (choice) {
|
|
821
|
-
case 0: // Toggle auto upgrade
|
|
822
|
-
await versionChecker.setAutoModelUpgrade(!isAutoOn);
|
|
823
|
-
console.log('');
|
|
824
|
-
console.log(colors.green + '✓ ' + await i18n.t('model_upgrade.auto_upgrade_label') + ': ' +
|
|
825
|
-
(!isAutoOn
|
|
826
|
-
? await i18n.t('model_upgrade.auto_upgrade_on')
|
|
827
|
-
: await i18n.t('model_upgrade.auto_upgrade_off')
|
|
828
|
-
) + colors.reset);
|
|
829
|
-
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
830
|
-
return showModelUpgradeSettings();
|
|
831
|
-
|
|
832
|
-
case 1: // Manual upgrade
|
|
833
|
-
await performManualUpgrade();
|
|
834
|
-
return showModelUpgradeSettings();
|
|
835
|
-
|
|
836
|
-
case 2: // Back
|
|
837
|
-
case -1:
|
|
838
|
-
default:
|
|
839
|
-
return showApiManagementMenu();
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
1403
|
/**
|
|
844
1404
|
* Perform manual upgrade for all APIs with interactive confirmation
|
|
845
1405
|
*/
|
|
@@ -847,21 +1407,26 @@ async function performManualUpgrade() {
|
|
|
847
1407
|
const { getLatestModel, getProvider } = require('./lib/presets/providers');
|
|
848
1408
|
const { simpleInput } = require('./lib/ui/prompts');
|
|
849
1409
|
|
|
850
|
-
console.clear();
|
|
851
|
-
console.log('');
|
|
852
|
-
console.log(colors.bright + colors.orange + '🔄 ' + await i18n.t('model_upgrade.manual_title') + colors.reset);
|
|
853
|
-
console.log('');
|
|
854
|
-
|
|
855
1410
|
const apis = apiManager.getApis();
|
|
856
1411
|
|
|
857
1412
|
if (apis.length === 0) {
|
|
858
|
-
|
|
1413
|
+
screen.render([
|
|
1414
|
+
'',
|
|
1415
|
+
colors.bright + colors.orange + '🔄 ' + await i18n.t('model_upgrade.manual_title') + colors.reset,
|
|
1416
|
+
'',
|
|
1417
|
+
colors.yellow + ' ' + await i18n.t('messages.info.no_apis') + colors.reset,
|
|
1418
|
+
]);
|
|
859
1419
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
860
1420
|
return;
|
|
861
1421
|
}
|
|
862
1422
|
|
|
863
|
-
|
|
864
|
-
|
|
1423
|
+
screen.render([
|
|
1424
|
+
'',
|
|
1425
|
+
colors.bright + colors.orange + '🔄 ' + await i18n.t('model_upgrade.manual_title') + colors.reset,
|
|
1426
|
+
'',
|
|
1427
|
+
colors.gray + ' ' + await i18n.t('model_upgrade.manual_checking', apis.length) + colors.reset,
|
|
1428
|
+
'',
|
|
1429
|
+
]);
|
|
865
1430
|
|
|
866
1431
|
let upgradedCount = 0;
|
|
867
1432
|
let skippedUpToDate = 0;
|
|
@@ -872,87 +1437,164 @@ async function performManualUpgrade() {
|
|
|
872
1437
|
const api = apis[i];
|
|
873
1438
|
const latestModel = getLatestModel(api.model, api.provider);
|
|
874
1439
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1440
|
+
screen.write(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset + '\n');
|
|
1441
|
+
screen.write(colors.bright + ` ${i + 1}/${apis.length} ${api.name}` + colors.reset + '\n');
|
|
1442
|
+
screen.write(colors.gray + ' ' + await i18n.t('model_upgrade.manual_api_current', api.model) + colors.reset + '\n');
|
|
878
1443
|
|
|
879
1444
|
if (latestModel) {
|
|
880
|
-
|
|
881
|
-
|
|
1445
|
+
screen.write(colors.green + ' ' + await i18n.t('model_upgrade.manual_api_latest', latestModel) + colors.reset + '\n');
|
|
1446
|
+
screen.write('\n');
|
|
882
1447
|
|
|
883
1448
|
// Ask for confirmation
|
|
884
1449
|
const answer = await simpleInput(colors.yellow + ' ' + await i18n.t('model_upgrade.manual_confirm') + ' ' + colors.reset);
|
|
885
1450
|
|
|
886
1451
|
if (answer.toLowerCase() === 'y') {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1452
|
+
try {
|
|
1453
|
+
apiManager.updateApiModel(api.id, latestModel);
|
|
1454
|
+
screen.write(colors.green + ' ✓ ' + await i18n.t('model_upgrade.manual_upgraded', api.model, latestModel) + colors.reset + '\n');
|
|
1455
|
+
upgradedCount++;
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
screen.write(colors.yellow + ' ⚠️ Skipped: ' + error.message + colors.reset + '\n');
|
|
1458
|
+
skippedByUser++;
|
|
1459
|
+
}
|
|
890
1460
|
} else {
|
|
891
|
-
|
|
1461
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_skipped') + colors.reset + '\n');
|
|
892
1462
|
skippedByUser++;
|
|
893
1463
|
}
|
|
894
1464
|
} else {
|
|
895
1465
|
// No upgrade info available - check if model exists in provider
|
|
896
1466
|
const provider = getProvider(api.provider);
|
|
897
1467
|
if (provider && provider.models && provider.models.includes(api.model)) {
|
|
898
|
-
|
|
899
|
-
console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_uptodate') + colors.reset);
|
|
1468
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_uptodate') + colors.reset + '\n');
|
|
900
1469
|
skippedUpToDate++;
|
|
901
1470
|
} else {
|
|
902
|
-
|
|
1471
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_no_info') + colors.reset + '\n');
|
|
903
1472
|
skippedNoInfo++;
|
|
904
1473
|
}
|
|
905
1474
|
}
|
|
906
1475
|
|
|
907
|
-
|
|
1476
|
+
screen.write('\n');
|
|
908
1477
|
}
|
|
909
1478
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1479
|
+
screen.write(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset + '\n');
|
|
1480
|
+
screen.write('\n');
|
|
1481
|
+
screen.write(colors.green + ' ' + await i18n.t('model_upgrade.manual_complete') + colors.reset + '\n');
|
|
1482
|
+
screen.write(colors.gray + ' ' + await i18n.t('model_upgrade.manual_stats_upgraded', upgradedCount) + colors.reset + '\n');
|
|
1483
|
+
screen.write(colors.gray + ' ' + await i18n.t('model_upgrade.manual_stats_skipped',
|
|
915
1484
|
skippedUpToDate + skippedNoInfo + skippedByUser,
|
|
916
1485
|
skippedUpToDate,
|
|
917
|
-
skippedNoInfo) + colors.reset);
|
|
918
|
-
|
|
1486
|
+
skippedNoInfo) + colors.reset + '\n');
|
|
1487
|
+
screen.write('\n');
|
|
919
1488
|
|
|
920
1489
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
921
1490
|
}
|
|
922
1491
|
|
|
923
1492
|
/**
|
|
924
|
-
*
|
|
1493
|
+
* Show API selection menu for select launch mode
|
|
925
1494
|
*/
|
|
926
|
-
async function
|
|
927
|
-
|
|
928
|
-
|
|
1495
|
+
async function showApiSelectMenu(skipPermissions = false) {
|
|
1496
|
+
const { padStringToWidth, getStringWidth } = require('./lib/utils/string-width');
|
|
1497
|
+
const { getProvider } = require('./lib/presets/providers');
|
|
929
1498
|
|
|
930
|
-
|
|
931
|
-
console.clear();
|
|
932
|
-
showInfo(i18n.tSync('launch.no_active_api'), [
|
|
933
|
-
i18n.tSync('launch.no_active_api_desc'),
|
|
934
|
-
i18n.tSync('launch.add_configure_first')
|
|
935
|
-
]);
|
|
1499
|
+
const apis = apiManager.getApis();
|
|
936
1500
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1501
|
+
if (apis.length === 0) {
|
|
1502
|
+
showInfo(i18n.tSync('hints.no_api_configured'), []);
|
|
1503
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
1504
|
+
return showMenu();
|
|
1505
|
+
}
|
|
940
1506
|
|
|
941
|
-
|
|
942
|
-
apiManager.recordSuccessfulLaunch();
|
|
1507
|
+
const activeApi = apiManager.getActiveApi();
|
|
943
1508
|
|
|
944
|
-
|
|
1509
|
+
// Build menu items with two-column alignment
|
|
1510
|
+
// Compute max name width (including ● prefix) for consistent column alignment
|
|
1511
|
+
let maxNameWidth = 0;
|
|
1512
|
+
apis.forEach(api => {
|
|
1513
|
+
const w = getStringWidth('● ' + api.name);
|
|
1514
|
+
if (w > maxNameWidth) maxNameWidth = w;
|
|
1515
|
+
});
|
|
945
1516
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1517
|
+
const menuItems = apis.map((api) => {
|
|
1518
|
+
const prefix = (activeApi && activeApi.id === api.id) ? '● ' : ' ';
|
|
1519
|
+
const nameCol = prefix + api.name;
|
|
1520
|
+
return padStringToWidth(nameCol, maxNameWidth + 4) + api.model;
|
|
1521
|
+
});
|
|
1522
|
+
menuItems.push(i18n.tSync('menu.api_select.back'));
|
|
1523
|
+
|
|
1524
|
+
// Hint callback for each API item
|
|
1525
|
+
const hintCallback = (selectedIndex) => {
|
|
1526
|
+
if (selectedIndex >= apis.length) return null; // "Back" item
|
|
1527
|
+
const api = apis[selectedIndex];
|
|
1528
|
+
const providerConfig = getProvider(api.provider);
|
|
1529
|
+
const providerName = providerConfig ? providerConfig.name : (api.provider || 'Custom');
|
|
1530
|
+
const lastUsed = formatRelativeTime(api.lastUsed || null);
|
|
1531
|
+
const usageCount = api.usageCount || 0;
|
|
1532
|
+
return i18n.tSync('hints.api_select.info', api.name) + '\n' +
|
|
1533
|
+
i18n.tSync('hints.api_select.detail', providerName, api.model) + '\n' +
|
|
1534
|
+
i18n.tSync('hints.api_select.usage', usageCount, lastUsed);
|
|
1535
|
+
};
|
|
949
1536
|
|
|
950
|
-
|
|
1537
|
+
screen.render([
|
|
1538
|
+
'',
|
|
1539
|
+
colors.bright + colors.orange + '🔗 ' + i18n.tSync('menu.api_select.title') + colors.reset,
|
|
1540
|
+
'',
|
|
1541
|
+
]);
|
|
951
1542
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1543
|
+
const selectMenu = new Menu();
|
|
1544
|
+
selectMenu.setOptions(menuItems);
|
|
1545
|
+
const choice = await selectMenu.navigate(null, hintCallback);
|
|
1546
|
+
|
|
1547
|
+
if (choice === -1 || choice === apis.length) {
|
|
1548
|
+
// Back or Esc
|
|
1549
|
+
return showMenu();
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// User selected an API
|
|
1553
|
+
const selectedApi = apis[choice];
|
|
1554
|
+
apiManager.setActiveApi(choice);
|
|
1555
|
+
apiManager.recordLaunchAttempt();
|
|
1556
|
+
screen.exitForHandoff();
|
|
1557
|
+
launchClaudeWithApi(selectedApi, skipPermissions, {
|
|
1558
|
+
rollbackFn: (errorMessage) => apiManager.rollbackLaunchAttempt(errorMessage)
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* Handle third-party API launch
|
|
1564
|
+
*/
|
|
1565
|
+
async function handleThirdPartyApiLaunch(skipPermissions = false) {
|
|
1566
|
+
const { loadConfigSync } = require('./lib/utils/version-checker');
|
|
1567
|
+
const config = loadConfigSync();
|
|
1568
|
+
|
|
1569
|
+
if (config.apiLaunchMode === 'select') {
|
|
1570
|
+
return showApiSelectMenu(skipPermissions);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// Direct mode
|
|
1574
|
+
const apis = apiManager.getApis();
|
|
1575
|
+
|
|
1576
|
+
if (apis.length === 0) {
|
|
1577
|
+
showInfo(i18n.tSync('hints.no_api_configured'), []);
|
|
1578
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
1579
|
+
return showMenu();
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
const activeApi = apiManager.getActiveApi();
|
|
1583
|
+
|
|
1584
|
+
if (activeApi === null) {
|
|
1585
|
+
showInfo(i18n.tSync('launch.no_active_api'), [
|
|
1586
|
+
i18n.tSync('launch.no_active_api_desc'),
|
|
1587
|
+
i18n.tSync('launch.add_configure_first')
|
|
1588
|
+
]);
|
|
1589
|
+
await waitForKey(i18n.tSync('launch.press_key_return'));
|
|
1590
|
+
return showMenu();
|
|
955
1591
|
}
|
|
1592
|
+
|
|
1593
|
+
apiManager.recordLaunchAttempt();
|
|
1594
|
+
screen.exitForHandoff();
|
|
1595
|
+
launchClaudeWithApi(activeApi, skipPermissions, {
|
|
1596
|
+
rollbackFn: (errorMessage) => apiManager.rollbackLaunchAttempt(errorMessage)
|
|
1597
|
+
});
|
|
956
1598
|
}
|
|
957
1599
|
|
|
958
1600
|
/**
|
|
@@ -961,14 +1603,17 @@ async function handleThirdPartyApiLaunch(skipPermissions = false) {
|
|
|
961
1603
|
async function executeSelection(selectedIndex) {
|
|
962
1604
|
switch (selectedIndex) {
|
|
963
1605
|
case 0: // Launch Claude Code
|
|
1606
|
+
screen.exitForHandoff();
|
|
964
1607
|
launchClaudeDefault();
|
|
965
1608
|
break;
|
|
966
1609
|
|
|
967
1610
|
case 1: // Launch Claude Code (Skip Permissions)
|
|
1611
|
+
screen.exitForHandoff();
|
|
968
1612
|
launchClaudeSkipPermissions();
|
|
969
1613
|
break;
|
|
970
1614
|
|
|
971
1615
|
case 2: // Launch Claude Code (Enable Auto Mode)
|
|
1616
|
+
screen.exitForHandoff();
|
|
972
1617
|
launchClaudeAutoMode();
|
|
973
1618
|
break;
|
|
974
1619
|
|
|
@@ -983,15 +1628,16 @@ async function executeSelection(selectedIndex) {
|
|
|
983
1628
|
case 5: // 3rd-party API Management
|
|
984
1629
|
return await showApiManagementMenu();
|
|
985
1630
|
|
|
986
|
-
case 6: //
|
|
987
|
-
return await
|
|
1631
|
+
case 6: // Configuration Management
|
|
1632
|
+
return await showConfigManagement();
|
|
988
1633
|
|
|
989
1634
|
case 7: // Version Update Check
|
|
990
1635
|
return await showVersionUpdateCheck();
|
|
991
1636
|
|
|
992
1637
|
case 8: // Exit
|
|
993
|
-
|
|
994
|
-
|
|
1638
|
+
screen.write('\n');
|
|
1639
|
+
screen.write(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset + '\n');
|
|
1640
|
+
screen.exit();
|
|
995
1641
|
process.exit(0);
|
|
996
1642
|
break;
|
|
997
1643
|
|
|
@@ -1001,6 +1647,114 @@ async function executeSelection(selectedIndex) {
|
|
|
1001
1647
|
}
|
|
1002
1648
|
}
|
|
1003
1649
|
|
|
1650
|
+
/**
|
|
1651
|
+
* Show configuration management submenu
|
|
1652
|
+
*/
|
|
1653
|
+
async function showConfigManagement(restoreIndex = 0) {
|
|
1654
|
+
const versionChecker = require('./lib/utils/version-checker');
|
|
1655
|
+
const { getStringWidth, padStringToWidth } = require('./lib/utils/string-width');
|
|
1656
|
+
|
|
1657
|
+
const config = await versionChecker.loadConfig();
|
|
1658
|
+
|
|
1659
|
+
// Build labels with padding to align current values
|
|
1660
|
+
const labelWidth = 40;
|
|
1661
|
+
|
|
1662
|
+
const langName = i18n.getCurrentLanguageName();
|
|
1663
|
+
const autoUpgradeVal = config.autoModelUpgrade
|
|
1664
|
+
? i18n.tSync('config.values.on')
|
|
1665
|
+
: i18n.tSync('config.values.off');
|
|
1666
|
+
const upgradeNotifVal = config.showModelUpgradeNotification !== false
|
|
1667
|
+
? i18n.tSync('config.values.on')
|
|
1668
|
+
: i18n.tSync('config.values.off');
|
|
1669
|
+
const telemetryVal = config.disableTelemetry !== false
|
|
1670
|
+
? i18n.tSync('config.values.recommended_off')
|
|
1671
|
+
: i18n.tSync('config.values.on');
|
|
1672
|
+
const launchModeVal = config.apiLaunchMode === 'select'
|
|
1673
|
+
? i18n.tSync('config.values.select_mode')
|
|
1674
|
+
: i18n.tSync('config.values.direct_mode');
|
|
1675
|
+
const noFlickerVal = config.noFlicker !== false
|
|
1676
|
+
? i18n.tSync('config.values.recommended_on')
|
|
1677
|
+
: i18n.tSync('config.values.off');
|
|
1678
|
+
|
|
1679
|
+
const menuOptions = [
|
|
1680
|
+
padStringToWidth(i18n.tSync('menu.config.language'), labelWidth) + langName,
|
|
1681
|
+
padStringToWidth(i18n.tSync('menu.config.auto_model_upgrade'), labelWidth) + autoUpgradeVal,
|
|
1682
|
+
padStringToWidth(i18n.tSync('menu.config.model_upgrade_notification'), labelWidth) + upgradeNotifVal,
|
|
1683
|
+
padStringToWidth(i18n.tSync('menu.config.telemetry'), labelWidth) + telemetryVal,
|
|
1684
|
+
padStringToWidth(i18n.tSync('menu.config.api_launch_mode'), labelWidth) + launchModeVal,
|
|
1685
|
+
padStringToWidth(i18n.tSync('menu.config.no_flicker'), labelWidth) + noFlickerVal,
|
|
1686
|
+
i18n.tSync('menu.config.back')
|
|
1687
|
+
];
|
|
1688
|
+
|
|
1689
|
+
// Synchronous hint callback for config items
|
|
1690
|
+
const hintCallback = (selectedIndex) => {
|
|
1691
|
+
switch (selectedIndex) {
|
|
1692
|
+
case 0: return i18n.tSync('hints.config.language', langName);
|
|
1693
|
+
case 1: return i18n.tSync('hints.config.auto_upgrade');
|
|
1694
|
+
case 2: return i18n.tSync('hints.config.upgrade_notification');
|
|
1695
|
+
case 3: return i18n.tSync('hints.config.telemetry');
|
|
1696
|
+
case 4: return i18n.tSync('hints.config.launch_mode');
|
|
1697
|
+
case 5: return i18n.tSync('hints.config.no_flicker');
|
|
1698
|
+
default: return null;
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
screen.render([
|
|
1703
|
+
'',
|
|
1704
|
+
colors.bright + colors.orange + '⚙️ ' + i18n.tSync('menu.config.title') + colors.reset,
|
|
1705
|
+
'',
|
|
1706
|
+
]);
|
|
1707
|
+
|
|
1708
|
+
initializeGlobalMenus();
|
|
1709
|
+
globalConfigMenu.setOptions(menuOptions);
|
|
1710
|
+
globalConfigMenu.selectedIndex = restoreIndex;
|
|
1711
|
+
const choice = await globalConfigMenu.navigate(null, hintCallback);
|
|
1712
|
+
|
|
1713
|
+
switch (choice) {
|
|
1714
|
+
case 0: // Language Settings
|
|
1715
|
+
return await showLanguageSettings();
|
|
1716
|
+
|
|
1717
|
+
case 1: { // Auto Model Upgrade toggle
|
|
1718
|
+
const newVal = !config.autoModelUpgrade;
|
|
1719
|
+
config.autoModelUpgrade = newVal;
|
|
1720
|
+
await versionChecker.saveConfig(config);
|
|
1721
|
+
return showConfigManagement(1);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
case 2: { // Model Upgrade Notification toggle
|
|
1725
|
+
const newVal = config.showModelUpgradeNotification === false ? true : false;
|
|
1726
|
+
config.showModelUpgradeNotification = newVal;
|
|
1727
|
+
await versionChecker.saveConfig(config);
|
|
1728
|
+
return showConfigManagement(2);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
case 3: { // Telemetry toggle
|
|
1732
|
+
const newVal = config.disableTelemetry === false ? true : false;
|
|
1733
|
+
config.disableTelemetry = newVal;
|
|
1734
|
+
await versionChecker.saveConfig(config);
|
|
1735
|
+
return showConfigManagement(3);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
case 4: { // API Launch Mode toggle
|
|
1739
|
+
const newVal = config.apiLaunchMode === 'select' ? 'direct' : 'select';
|
|
1740
|
+
config.apiLaunchMode = newVal;
|
|
1741
|
+
await versionChecker.saveConfig(config);
|
|
1742
|
+
return showConfigManagement(4);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
case 5: { // noFlicker toggle
|
|
1746
|
+
config.noFlicker = !config.noFlicker;
|
|
1747
|
+
await versionChecker.saveConfig(config);
|
|
1748
|
+
return showConfigManagement(5);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
case 6: // Back
|
|
1752
|
+
case -1:
|
|
1753
|
+
default:
|
|
1754
|
+
return showMenu();
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1004
1758
|
/**
|
|
1005
1759
|
* Show main menu
|
|
1006
1760
|
*/
|
|
@@ -1047,6 +1801,9 @@ async function showMenu() {
|
|
|
1047
1801
|
try {
|
|
1048
1802
|
const upgradeChecker = require('./lib/utils/model-upgrade-checker');
|
|
1049
1803
|
const autoUpgrade = await upgradeChecker.isAutoUpgradeEnabled();
|
|
1804
|
+
const versionChecker = require('./lib/utils/version-checker');
|
|
1805
|
+
const launcherConfig = await versionChecker.loadConfig();
|
|
1806
|
+
const showUpgradeNotif = launcherConfig.showModelUpgradeNotification !== false;
|
|
1050
1807
|
|
|
1051
1808
|
if (autoUpgrade) {
|
|
1052
1809
|
// Auto upgrade enabled: always check and upgrade (bypass cache)
|
|
@@ -1062,8 +1819,8 @@ async function showMenu() {
|
|
|
1062
1819
|
}
|
|
1063
1820
|
}
|
|
1064
1821
|
}
|
|
1065
|
-
} else {
|
|
1066
|
-
// Auto upgrade disabled: use cache for notification
|
|
1822
|
+
} else if (showUpgradeNotif) {
|
|
1823
|
+
// Auto upgrade disabled but notification enabled: use cache for notification
|
|
1067
1824
|
const result = await upgradeChecker.checkForModelUpgrades(apiManager);
|
|
1068
1825
|
if (result.needsCheck && result.upgrades.length > 0) {
|
|
1069
1826
|
const first = result.upgrades[0];
|
|
@@ -1102,40 +1859,109 @@ async function showMenu() {
|
|
|
1102
1859
|
await i18n.t('menu.main.launch_api'),
|
|
1103
1860
|
await i18n.t('menu.main.launch_api_skip'),
|
|
1104
1861
|
await i18n.t('menu.main.api_management'),
|
|
1105
|
-
await i18n.t('menu.main.
|
|
1862
|
+
await i18n.t('menu.main.config_management'),
|
|
1106
1863
|
await i18n.t('menu.main.version_check'),
|
|
1107
1864
|
await i18n.t('menu.main.exit')
|
|
1108
1865
|
];
|
|
1109
1866
|
|
|
1110
|
-
// Pre-compute hint
|
|
1867
|
+
// Pre-compute all hint data synchronously for menu callback
|
|
1111
1868
|
const hintAutoMode = i18n.tSync('hints.auto_mode_info');
|
|
1869
|
+
|
|
1112
1870
|
const activeApi = apiManager.getActiveApi();
|
|
1113
|
-
|
|
1871
|
+
const apis = apiManager.getApis();
|
|
1872
|
+
const apiCount = apis.length;
|
|
1873
|
+
const { getProvider } = require('./lib/presets/providers');
|
|
1874
|
+
|
|
1875
|
+
// Pre-compute config for hints
|
|
1876
|
+
const hintConfig = require('./lib/utils/version-checker').loadConfigSync();
|
|
1877
|
+
const hintApiLaunchMode = hintConfig.apiLaunchMode || 'direct';
|
|
1878
|
+
|
|
1879
|
+
// Pre-compute active API details
|
|
1880
|
+
let activeApiName = i18n.tSync('hints.select_mode_active_none');
|
|
1881
|
+
let activeProviderName = '';
|
|
1882
|
+
let activeModel = '';
|
|
1883
|
+
let activeLastUsed = '';
|
|
1114
1884
|
if (activeApi) {
|
|
1115
|
-
const { getProvider } = require('./lib/presets/providers');
|
|
1116
1885
|
const providerConfig = getProvider(activeApi.provider);
|
|
1117
|
-
|
|
1118
|
-
|
|
1886
|
+
activeProviderName = providerConfig ? providerConfig.name : (activeApi.provider || 'Custom');
|
|
1887
|
+
activeModel = activeApi.model;
|
|
1888
|
+
activeApiName = activeApi.name;
|
|
1889
|
+
activeLastUsed = formatRelativeTime(activeApi.lastUsed || null);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// Pre-compute API hints for indices 3, 4
|
|
1893
|
+
let hintApiLines = null;
|
|
1894
|
+
if (apiCount === 0) {
|
|
1895
|
+
hintApiLines = i18n.tSync('hints.no_api_configured');
|
|
1896
|
+
} else if (hintApiLaunchMode === 'direct') {
|
|
1897
|
+
if (activeApi) {
|
|
1898
|
+
hintApiLines =
|
|
1899
|
+
i18n.tSync('hints.direct_mode_desc') + '\n' +
|
|
1900
|
+
i18n.tSync('hints.direct_mode_api_info', activeApiName, activeProviderName) + '\n' +
|
|
1901
|
+
i18n.tSync('hints.direct_mode_api_detail', activeModel, activeLastUsed) + '\n' +
|
|
1902
|
+
i18n.tSync('hints.direct_mode_change');
|
|
1903
|
+
} else {
|
|
1904
|
+
hintApiLines =
|
|
1905
|
+
i18n.tSync('hints.direct_mode_no_active') + '\n' +
|
|
1906
|
+
i18n.tSync('hints.direct_mode_no_active_detail', apiCount) + '\n' +
|
|
1907
|
+
'' + '\n' +
|
|
1908
|
+
i18n.tSync('hints.direct_mode_change');
|
|
1909
|
+
}
|
|
1119
1910
|
} else {
|
|
1120
|
-
|
|
1911
|
+
// select mode
|
|
1912
|
+
hintApiLines =
|
|
1913
|
+
i18n.tSync('hints.select_mode_desc') + '\n' +
|
|
1914
|
+
i18n.tSync('hints.select_mode_change') + '\n' +
|
|
1915
|
+
'' + '\n' +
|
|
1916
|
+
i18n.tSync('hints.select_mode_api_count', apiCount, activeApiName);
|
|
1121
1917
|
}
|
|
1122
1918
|
|
|
1919
|
+
// Pre-compute API management hint (index 5)
|
|
1920
|
+
const hintApiMgmt = i18n.tSync('hints.api_management_info', apiCount, activeApiName);
|
|
1921
|
+
|
|
1922
|
+
// Pre-compute config summary hint (index 6)
|
|
1923
|
+
const cfgLangName = i18n.getCurrentLanguageName();
|
|
1924
|
+
const cfgLaunchMode = hintApiLaunchMode === 'select'
|
|
1925
|
+
? i18n.tSync('config.values.select_mode')
|
|
1926
|
+
: i18n.tSync('config.values.direct_mode');
|
|
1927
|
+
const cfgTelemetry = hintConfig.disableTelemetry !== false
|
|
1928
|
+
? i18n.tSync('config.values.off')
|
|
1929
|
+
: i18n.tSync('config.values.on');
|
|
1930
|
+
const cfgNoFlicker = hintConfig.noFlicker !== false
|
|
1931
|
+
? i18n.tSync('config.values.on')
|
|
1932
|
+
: i18n.tSync('config.values.off');
|
|
1933
|
+
const hintConfigSummary = i18n.tSync('hints.config_summary', cfgLangName, cfgLaunchMode, cfgTelemetry, cfgNoFlicker);
|
|
1934
|
+
|
|
1123
1935
|
// Synchronous hint callback — must not use await
|
|
1124
1936
|
const hintCallback = (selectedIndex) => {
|
|
1125
1937
|
switch (selectedIndex) {
|
|
1126
|
-
case
|
|
1127
|
-
case
|
|
1128
|
-
|
|
1129
|
-
|
|
1938
|
+
case 0:
|
|
1939
|
+
case 1:
|
|
1940
|
+
return null;
|
|
1941
|
+
case 2:
|
|
1942
|
+
return hintAutoMode;
|
|
1943
|
+
case 3:
|
|
1944
|
+
case 4:
|
|
1945
|
+
return hintApiLines;
|
|
1946
|
+
case 5:
|
|
1947
|
+
return hintApiMgmt;
|
|
1948
|
+
case 6:
|
|
1949
|
+
return hintConfigSummary;
|
|
1950
|
+
case 7:
|
|
1951
|
+
case 8:
|
|
1952
|
+
return null;
|
|
1953
|
+
default:
|
|
1954
|
+
return null;
|
|
1130
1955
|
}
|
|
1131
1956
|
};
|
|
1132
1957
|
|
|
1133
1958
|
globalMainMenu.setOptions(menuOptions);
|
|
1134
|
-
const selection = await globalMainMenu.navigate(
|
|
1959
|
+
const selection = await globalMainMenu.navigate(displayInfo || null, hintCallback);
|
|
1135
1960
|
|
|
1136
1961
|
if (selection === -1) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1962
|
+
screen.write('\n');
|
|
1963
|
+
screen.write(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset + '\n');
|
|
1964
|
+
screen.exit();
|
|
1139
1965
|
process.exit(0);
|
|
1140
1966
|
} else {
|
|
1141
1967
|
await executeSelection(selection);
|
|
@@ -1150,26 +1976,22 @@ async function showMenu() {
|
|
|
1150
1976
|
* Export configuration with password encryption
|
|
1151
1977
|
*/
|
|
1152
1978
|
async function exportConfiguration() {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1979
|
+
screen.render([
|
|
1980
|
+
'',
|
|
1981
|
+
colors.bright + colors.orange + '💾 ' + await i18n.t('import_export.export.title') + colors.reset,
|
|
1982
|
+
'',
|
|
1983
|
+
// Export function description
|
|
1984
|
+
colors.cyan + '📄 ' + i18n.tSync('import_export.export.description_title') + colors.reset,
|
|
1985
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset,
|
|
1986
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[1] + colors.reset,
|
|
1987
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[2] + colors.reset,
|
|
1988
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[3] + colors.reset,
|
|
1989
|
+
'',
|
|
1990
|
+
]);
|
|
1165
1991
|
|
|
1166
1992
|
// Verify password before export
|
|
1167
|
-
const verified = await
|
|
1168
|
-
if (!verified)
|
|
1169
|
-
console.log('');
|
|
1170
|
-
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1993
|
+
const verified = await passwordGuard(apiManager, 'export');
|
|
1994
|
+
if (!verified) return;
|
|
1173
1995
|
|
|
1174
1996
|
try {
|
|
1175
1997
|
// Get export data
|
|
@@ -1183,62 +2005,59 @@ async function exportConfiguration() {
|
|
|
1183
2005
|
// Write JSON file
|
|
1184
2006
|
fs.writeFileSync(filePath, exportData, 'utf8');
|
|
1185
2007
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
2008
|
+
screen.write(colors.green + '✓ ' + i18n.tSync('import_export.export.success_title') + colors.reset + '\n');
|
|
2009
|
+
screen.write('\n');
|
|
2010
|
+
screen.write(colors.cyan + '📁 ' + i18n.tSync('import_export.export.details_title') + colors.reset + '\n');
|
|
2011
|
+
screen.write(colors.gray + ` • ` + i18n.tSync('import_export.export.details_file_saved', filePath) + colors.reset + '\n');
|
|
2012
|
+
screen.write(colors.gray + ` • ` + i18n.tSync('import_export.export.details_export_dir', exportDir) + colors.reset + '\n');
|
|
2013
|
+
screen.write(colors.gray + ` • ` + i18n.tSync('import_export.export.details_filename', filename) + colors.reset + '\n');
|
|
2014
|
+
screen.write('\n');
|
|
1193
2015
|
|
|
1194
2016
|
// Open file with default application
|
|
1195
|
-
|
|
2017
|
+
screen.write(colors.yellow + '🔍 ' + i18n.tSync('import_export.export.opening_file') + colors.reset + '\n');
|
|
1196
2018
|
openFileWithDefault(filePath);
|
|
1197
2019
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
2020
|
+
screen.write('\n');
|
|
2021
|
+
screen.write(colors.cyan + '💡 ' + i18n.tSync('import_export.export.tips_title') + colors.reset + '\n');
|
|
2022
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[0] + colors.reset + '\n');
|
|
2023
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[1] + colors.reset + '\n');
|
|
1202
2024
|
|
|
1203
2025
|
} catch (error) {
|
|
1204
2026
|
forceStdinCleanup();
|
|
1205
|
-
|
|
2027
|
+
screen.write(colors.red + `❌ Export failed: ${error.message}` + colors.reset + '\n');
|
|
1206
2028
|
}
|
|
1207
2029
|
|
|
1208
|
-
|
|
1209
|
-
await waitForKey(i18n.tSync('
|
|
2030
|
+
screen.write('\n');
|
|
2031
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
1210
2032
|
}
|
|
1211
2033
|
|
|
1212
2034
|
/**
|
|
1213
2035
|
* Import configuration from plaintext JSON
|
|
1214
2036
|
*/
|
|
1215
2037
|
async function importConfiguration() {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
2038
|
+
screen.render([
|
|
2039
|
+
'',
|
|
2040
|
+
colors.bright + colors.orange + '📥 ' + await i18n.t('import_export.import.title') + colors.reset,
|
|
2041
|
+
'',
|
|
2042
|
+
// Import function description
|
|
2043
|
+
colors.cyan + '📄 ' + i18n.tSync('ui.general.import_function_description') + colors.reset,
|
|
2044
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset,
|
|
2045
|
+
colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[0] + colors.reset,
|
|
2046
|
+
colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[1] + colors.reset,
|
|
2047
|
+
colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[2] + colors.reset,
|
|
2048
|
+
'',
|
|
2049
|
+
]);
|
|
1228
2050
|
|
|
1229
2051
|
// Verify password identity
|
|
1230
|
-
const passwordVerified = await
|
|
1231
|
-
if (!passwordVerified)
|
|
1232
|
-
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
2052
|
+
const passwordVerified = await passwordGuard(apiManager, 'import');
|
|
2053
|
+
if (!passwordVerified) return;
|
|
1235
2054
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
2055
|
+
screen.write('\n');
|
|
2056
|
+
screen.write(colors.cyan + '📁 ' + i18n.tSync('ui.general.file_input_required') + colors.reset + '\n');
|
|
2057
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[0] + colors.reset + '\n');
|
|
2058
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[1] + colors.reset + '\n');
|
|
2059
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[2] + colors.reset + '\n');
|
|
2060
|
+
screen.write('\n');
|
|
1242
2061
|
|
|
1243
2062
|
const { simpleInput } = require('./lib/ui/prompts');
|
|
1244
2063
|
|
|
@@ -1253,85 +2072,86 @@ async function importConfiguration() {
|
|
|
1253
2072
|
const filePath = await simpleInput(colors.green + filePrompt + colors.reset);
|
|
1254
2073
|
|
|
1255
2074
|
if (!filePath) {
|
|
1256
|
-
|
|
2075
|
+
screen.write(colors.red + i18n.tSync('ui.general.file_path_empty') + colors.reset + '\n');
|
|
1257
2076
|
if (attempts < maxAttempts) {
|
|
1258
|
-
|
|
2077
|
+
screen.write('\n');
|
|
1259
2078
|
continue;
|
|
1260
2079
|
} else {
|
|
1261
|
-
|
|
2080
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset + '\n');
|
|
1262
2081
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1263
2082
|
return;
|
|
1264
2083
|
}
|
|
1265
2084
|
}
|
|
1266
2085
|
|
|
1267
2086
|
// Validate file
|
|
1268
|
-
|
|
1269
|
-
|
|
2087
|
+
screen.write('\n');
|
|
2088
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.validating_file') + colors.reset + '\n');
|
|
1270
2089
|
const validation = validateImportFile(filePath);
|
|
1271
2090
|
|
|
1272
2091
|
if (!validation.valid) {
|
|
1273
|
-
|
|
2092
|
+
screen.write(colors.red + '❌ ' + i18n.tSync('ui.general.file_validation_failed', validation.error) + colors.reset + '\n');
|
|
1274
2093
|
if (attempts < maxAttempts) {
|
|
1275
|
-
|
|
1276
|
-
|
|
2094
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.check_file_path_json') + colors.reset + '\n');
|
|
2095
|
+
screen.write('\n');
|
|
1277
2096
|
continue;
|
|
1278
2097
|
} else {
|
|
1279
|
-
|
|
2098
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset + '\n');
|
|
1280
2099
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1281
2100
|
return;
|
|
1282
2101
|
}
|
|
1283
2102
|
}
|
|
1284
2103
|
|
|
1285
2104
|
// File is valid, proceed with import
|
|
1286
|
-
|
|
1287
|
-
|
|
2105
|
+
screen.write(colors.green + i18n.tSync('ui.general.file_validation_successful') + colors.reset + '\n');
|
|
2106
|
+
screen.write('\n');
|
|
1288
2107
|
|
|
1289
2108
|
try {
|
|
1290
2109
|
// Import the validated configuration data
|
|
1291
2110
|
const result = apiManager.importConfigAuthenticated(validation.data);
|
|
1292
2111
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
2112
|
+
screen.write(colors.green + i18n.tSync('ui.general.import_successful') + colors.reset + '\n');
|
|
2113
|
+
screen.write('\n');
|
|
2114
|
+
screen.write(colors.cyan + i18n.tSync('ui.general.import_statistics') + colors.reset + '\n');
|
|
1296
2115
|
const importItems = i18n.tSync('ui.general.import_stats_items');
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
2116
|
+
screen.write(colors.gray + ` • ` + importItems[0].replace('{0}', result.imported) + colors.reset + '\n');
|
|
2117
|
+
screen.write(colors.gray + ` • ` + importItems[1].replace('{1}', result.skipped) + colors.reset + '\n');
|
|
2118
|
+
screen.write(colors.gray + ` • ` + importItems[2] + colors.reset + '\n');
|
|
2119
|
+
screen.write(colors.gray + ` • ` + importItems[3].replace('{0}', path.resolve(filePath)) + colors.reset + '\n');
|
|
1301
2120
|
|
|
1302
2121
|
break; // Success, exit the loop
|
|
1303
2122
|
|
|
1304
2123
|
} catch (error) {
|
|
1305
2124
|
forceStdinCleanup();
|
|
1306
|
-
|
|
2125
|
+
screen.write(colors.red + `❌ Import failed: ${error.message}` + colors.reset + '\n');
|
|
1307
2126
|
if (attempts < maxAttempts) {
|
|
1308
|
-
|
|
1309
|
-
|
|
2127
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.import_tips')[0] + colors.reset + '\n');
|
|
2128
|
+
screen.write('\n');
|
|
1310
2129
|
continue;
|
|
1311
2130
|
} else {
|
|
1312
|
-
|
|
2131
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_failed') + colors.reset + '\n');
|
|
1313
2132
|
}
|
|
1314
2133
|
}
|
|
1315
2134
|
}
|
|
1316
2135
|
|
|
1317
|
-
|
|
1318
|
-
await waitForKey(i18n.tSync('
|
|
2136
|
+
screen.write('\n');
|
|
2137
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
1319
2138
|
}
|
|
1320
2139
|
|
|
1321
2140
|
/**
|
|
1322
2141
|
* Change password
|
|
1323
2142
|
*/
|
|
1324
2143
|
async function changePassword() {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
2144
|
+
screen.render([
|
|
2145
|
+
'',
|
|
2146
|
+
colors.bright + colors.orange + '🔑 ' + i18n.tSync('errors.password.change_password_title') + colors.reset,
|
|
2147
|
+
'',
|
|
2148
|
+
]);
|
|
1329
2149
|
|
|
1330
2150
|
// Use unified password change module
|
|
1331
2151
|
const success = await changePasswordModule(apiManager);
|
|
1332
2152
|
|
|
1333
2153
|
if (success) {
|
|
1334
|
-
|
|
2154
|
+
screen.write('\n');
|
|
1335
2155
|
}
|
|
1336
2156
|
|
|
1337
2157
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
@@ -1346,16 +2166,16 @@ async function showLanguageSettings() {
|
|
|
1346
2166
|
const currentLanguage = i18n.getCurrentLanguage();
|
|
1347
2167
|
const currentLanguageName = i18n.getCurrentLanguageName();
|
|
1348
2168
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
2169
|
+
screen.render([
|
|
2170
|
+
'',
|
|
2171
|
+
colors.bright + colors.orange + '🌍 ' + await i18n.t('menu.language.title') + colors.reset,
|
|
2172
|
+
'',
|
|
2173
|
+
// Show current language
|
|
2174
|
+
colors.cyan + await i18n.t('menu.language.current', currentLanguageName) + colors.reset,
|
|
2175
|
+
'',
|
|
2176
|
+
colors.yellow + await i18n.t('menu.language.select_prompt') + colors.reset,
|
|
2177
|
+
'',
|
|
2178
|
+
]);
|
|
1359
2179
|
|
|
1360
2180
|
// Create menu options
|
|
1361
2181
|
const languageOptions = [];
|
|
@@ -1388,14 +2208,14 @@ async function showLanguageSettings() {
|
|
|
1388
2208
|
}
|
|
1389
2209
|
|
|
1390
2210
|
// Switch language
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
2211
|
+
screen.render([
|
|
2212
|
+
'',
|
|
2213
|
+
colors.yellow + await i18n.t('status.switching_language') + colors.reset,
|
|
2214
|
+
]);
|
|
1394
2215
|
|
|
1395
2216
|
try {
|
|
1396
2217
|
await i18n.setLanguage(selectedLangCode);
|
|
1397
2218
|
|
|
1398
|
-
console.clear();
|
|
1399
2219
|
const newLanguageName = i18n.getCurrentLanguageName();
|
|
1400
2220
|
showSuccess(await i18n.t('messages.success.language_changed'), [
|
|
1401
2221
|
await i18n.t('menu.language.changed_success', newLanguageName)
|
|
@@ -1424,18 +2244,18 @@ async function showLanguageSettings() {
|
|
|
1424
2244
|
*/
|
|
1425
2245
|
async function showVersionUpdateCheck() {
|
|
1426
2246
|
try {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
2247
|
+
screen.render([
|
|
2248
|
+
'',
|
|
2249
|
+
colors.bright + colors.orange + '🔄 ' + await i18n.t('version_check.title') + colors.reset,
|
|
2250
|
+
'',
|
|
2251
|
+
colors.cyan + await i18n.t('version_check.checking') + colors.reset,
|
|
2252
|
+
colors.gray + await i18n.t('version_check.please_wait') + colors.reset,
|
|
2253
|
+
'',
|
|
2254
|
+
]);
|
|
1435
2255
|
|
|
1436
2256
|
// Show progress indicator
|
|
1437
2257
|
const progressInterval = setInterval(() => {
|
|
1438
|
-
|
|
2258
|
+
screen.write('.');
|
|
1439
2259
|
}, 500);
|
|
1440
2260
|
|
|
1441
2261
|
try {
|
|
@@ -1444,46 +2264,46 @@ async function showVersionUpdateCheck() {
|
|
|
1444
2264
|
|
|
1445
2265
|
// Stop progress indicator
|
|
1446
2266
|
clearInterval(progressInterval);
|
|
1447
|
-
|
|
2267
|
+
screen.write('\n\n');
|
|
1448
2268
|
|
|
1449
2269
|
if (result.error) {
|
|
1450
2270
|
// Handle errors (timeout, network, etc.)
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
2271
|
+
screen.write(colors.red + '❌ ' + await i18n.t('version_check.error') + colors.reset + '\n');
|
|
2272
|
+
screen.write(colors.red + ' ' + result.error + colors.reset + '\n');
|
|
2273
|
+
screen.write('\n');
|
|
2274
|
+
screen.write(colors.gray + await i18n.t('version_check.error_tips') + colors.reset + '\n');
|
|
1455
2275
|
} else if (result.available) {
|
|
1456
2276
|
// Update available
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
2277
|
+
screen.write(colors.yellow + '🎉 ' + await i18n.t('version_check.update_available') + colors.reset + '\n');
|
|
2278
|
+
screen.write('\n');
|
|
2279
|
+
screen.write(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset + '\n');
|
|
2280
|
+
screen.write(colors.green + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset + '\n');
|
|
2281
|
+
screen.write('\n');
|
|
2282
|
+
screen.write(colors.yellow + '💡 ' + await i18n.t('version_check.update_command') + colors.reset + '\n');
|
|
2283
|
+
screen.write(colors.yellow + ' npm update -g @kikkimo/claude-launcher' + colors.reset + '\n');
|
|
1464
2284
|
} else {
|
|
1465
2285
|
// Already up to date
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
2286
|
+
screen.write(colors.green + '✅ ' + await i18n.t('version_check.up_to_date') + colors.reset + '\n');
|
|
2287
|
+
screen.write('\n');
|
|
2288
|
+
screen.write(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset + '\n');
|
|
2289
|
+
screen.write(colors.cyan + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset + '\n');
|
|
1470
2290
|
}
|
|
1471
2291
|
|
|
1472
2292
|
} catch (error) {
|
|
1473
2293
|
// Stop progress indicator
|
|
1474
2294
|
clearInterval(progressInterval);
|
|
1475
|
-
|
|
2295
|
+
screen.write('\n\n');
|
|
1476
2296
|
|
|
1477
|
-
|
|
1478
|
-
|
|
2297
|
+
screen.write(colors.red + '❌ ' + await i18n.t('version_check.unexpected_error') + colors.reset + '\n');
|
|
2298
|
+
screen.write(colors.red + ' ' + error.message + colors.reset + '\n');
|
|
1479
2299
|
}
|
|
1480
2300
|
|
|
1481
|
-
|
|
2301
|
+
screen.write('\n');
|
|
1482
2302
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
1483
2303
|
return showMenu();
|
|
1484
2304
|
|
|
1485
2305
|
} catch (error) {
|
|
1486
|
-
|
|
2306
|
+
screen.write(colors.red + '❌ Failed to check version: ' + error.message + colors.reset + '\n');
|
|
1487
2307
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1488
2308
|
return showMenu();
|
|
1489
2309
|
}
|
|
@@ -1495,8 +2315,9 @@ async function showVersionUpdateCheck() {
|
|
|
1495
2315
|
* Graceful shutdown handlers
|
|
1496
2316
|
*/
|
|
1497
2317
|
process.on('SIGTERM', () => {
|
|
1498
|
-
|
|
1499
|
-
|
|
2318
|
+
screen.write('\n');
|
|
2319
|
+
screen.write(colors.green + i18n.tSync('ui.general.goodbye') + colors.reset + '\n');
|
|
2320
|
+
screen.exit();
|
|
1500
2321
|
process.exit(0);
|
|
1501
2322
|
});
|
|
1502
2323
|
|
|
@@ -1509,43 +2330,34 @@ const stdinManager = require('./lib/utils/stdin-manager');
|
|
|
1509
2330
|
let exiting = false;
|
|
1510
2331
|
|
|
1511
2332
|
process.on('SIGINT', () => {
|
|
1512
|
-
// During Claude run, ignore in launcher so child handles it
|
|
1513
|
-
// Check this BEFORE setting exiting flag to avoid breaking reentrancy protection
|
|
1514
2333
|
if (stdinManager.isSuspended && stdinManager.isSuspended()) {
|
|
1515
2334
|
return;
|
|
1516
2335
|
}
|
|
1517
|
-
|
|
1518
|
-
// Prevent re-entrance - ensure cleanup runs only once
|
|
1519
|
-
if (exiting) {
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
2336
|
+
if (exiting) return;
|
|
1522
2337
|
exiting = true;
|
|
1523
|
-
|
|
1524
|
-
// Try to reset stdin state before handling
|
|
1525
2338
|
try {
|
|
1526
2339
|
if (process.stdin.isTTY) {
|
|
1527
2340
|
process.stdin.setRawMode(false);
|
|
1528
2341
|
process.stdin.pause();
|
|
1529
2342
|
}
|
|
1530
|
-
} catch (_) {
|
|
1531
|
-
// Ignore errors during emergency cleanup
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// Use unified Ctrl+C handler from StdinManager (synchronous)
|
|
2343
|
+
} catch (_) {}
|
|
1535
2344
|
try {
|
|
1536
|
-
stdinManager.handleCtrlC();
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
// this line won't be reached, which is expected behavior
|
|
2345
|
+
const shouldExit = stdinManager.handleCtrlC();
|
|
2346
|
+
if (shouldExit === false) {
|
|
2347
|
+
exiting = false;
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
} catch (_) {}
|
|
2351
|
+
screen.exit();
|
|
1544
2352
|
process.exit(130);
|
|
1545
2353
|
});
|
|
1546
2354
|
|
|
2355
|
+
process.on('uncaughtException', (err) => { screen.exit(); console.error(err); process.exit(1); });
|
|
2356
|
+
process.on('unhandledRejection', (err) => { screen.exit(); console.error(err); process.exit(1); });
|
|
2357
|
+
|
|
1547
2358
|
// Initialize global menus and start the application
|
|
1548
2359
|
initializeGlobalMenus();
|
|
1549
2360
|
|
|
1550
2361
|
// Start the application
|
|
2362
|
+
screen.enter();
|
|
1551
2363
|
showMenu();
|