@kikkimo/claude-launcher 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +100 -41
- package/claude-launcher +1017 -576
- package/docs/README-zh.md +104 -45
- package/lib/api-manager.js +449 -0
- package/lib/auth/password-input.js +144 -0
- package/lib/auth/password-strength.js +154 -0
- package/lib/auth/password-validator.js +255 -0
- package/lib/crypto.js +85 -0
- package/lib/i18n/formatter.js +62 -0
- package/lib/i18n/index.js +218 -0
- package/lib/i18n/language-manager.js +160 -0
- package/lib/i18n/locales/de.js +523 -0
- package/lib/i18n/locales/en.js +524 -0
- package/lib/i18n/locales/es.js +523 -0
- package/lib/i18n/locales/fr.js +523 -0
- package/lib/i18n/locales/it.js +523 -0
- package/lib/i18n/locales/ja.js +523 -0
- package/lib/i18n/locales/ko.js +523 -0
- package/lib/i18n/locales/pt.js +523 -0
- package/lib/i18n/locales/ru.js +523 -0
- package/lib/i18n/locales/zh-TW.js +523 -0
- package/lib/i18n/locales/zh.js +523 -0
- package/lib/launcher.js +253 -0
- package/lib/presets/providers.js +104 -0
- package/lib/ui/colors.js +32 -0
- package/lib/ui/interactive-table.js +260 -0
- package/lib/ui/menu.js +314 -0
- package/lib/ui/prompts.js +540 -0
- package/lib/utils/string-width.js +180 -0
- package/lib/utils/version-checker.js +240 -0
- package/lib/validators.js +130 -0
- package/package.json +2 -2
package/claude-launcher
CHANGED
|
@@ -1,679 +1,1120 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Claude Launcher - Refactored modular version
|
|
5
|
+
* A launcher for Claude Code with third-party API support
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Force complete stdin reset to prevent navigation issues
|
|
10
|
+
*/
|
|
11
|
+
function forceStdinCleanup() {
|
|
12
|
+
try {
|
|
13
|
+
if (process.stdin.isTTY) {
|
|
14
|
+
process.stdin.setRawMode(false);
|
|
15
|
+
process.stdin.removeAllListeners('data');
|
|
16
|
+
process.stdin.pause();
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// Ignore cleanup errors
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const ApiManager = require('./lib/api-manager');
|
|
25
|
+
const Menu = require('./lib/ui/menu');
|
|
26
|
+
const colors = require('./lib/ui/colors');
|
|
27
|
+
const { checkForUpdates, forceCheckForUpdates } = require('./lib/utils/version-checker');
|
|
28
|
+
const {
|
|
29
|
+
waitForKey,
|
|
30
|
+
promptForThirdPartyApi,
|
|
31
|
+
confirmAction,
|
|
32
|
+
showSuccess,
|
|
33
|
+
showError,
|
|
34
|
+
showInfo
|
|
35
|
+
} = require('./lib/ui/prompts');
|
|
36
|
+
const {
|
|
37
|
+
launchClaudeDefault,
|
|
38
|
+
launchClaudeSkipPermissions,
|
|
39
|
+
launchClaudeWithApi
|
|
40
|
+
} = require('./lib/launcher');
|
|
41
|
+
const { getPasswordInput } = require('./lib/auth/password-input');
|
|
42
|
+
const { verifyExportPassword, setupNewPassword, changePassword: changePasswordModule } = require('./lib/auth/password-validator');
|
|
43
|
+
const { maskApiToken } = require('./lib/validators');
|
|
44
|
+
const { showApiSelectionTable, confirmDeletion } = require('./lib/ui/interactive-table');
|
|
45
|
+
const i18n = require('./lib/i18n');
|
|
4
46
|
const fs = require('fs');
|
|
5
47
|
const path = require('path');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
48
|
+
const os = require('os');
|
|
49
|
+
const { exec } = require('child_process');
|
|
50
|
+
|
|
51
|
+
// Initialize components
|
|
52
|
+
const apiManager = new ApiManager();
|
|
53
|
+
|
|
54
|
+
// Global menu objects to prevent screen flickering during navigation
|
|
55
|
+
let globalMainMenu = null;
|
|
56
|
+
let globalConfirmMenu = null;
|
|
57
|
+
let globalApiManagementMenu = null;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize global menu objects to prevent recreation and screen flickering
|
|
61
|
+
*/
|
|
62
|
+
function initializeGlobalMenus() {
|
|
63
|
+
if (!globalMainMenu) {
|
|
64
|
+
globalMainMenu = new Menu();
|
|
65
|
+
}
|
|
66
|
+
if (!globalConfirmMenu) {
|
|
67
|
+
globalConfirmMenu = new Menu();
|
|
68
|
+
}
|
|
69
|
+
if (!globalApiManagementMenu) {
|
|
70
|
+
globalApiManagementMenu = new Menu();
|
|
25
71
|
}
|
|
26
|
-
|
|
27
|
-
// Default to home directory if no config exists
|
|
28
|
-
return path.join(require('os').homedir(), '.claude-launcher.env');
|
|
29
72
|
}
|
|
30
73
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
bgOrange: '\x1b[48;5;208m', // Background orange
|
|
46
|
-
bgAmber: '\x1b[48;5;214m' // Background amber
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// Generate encryption key from machine-specific data
|
|
50
|
-
function getEncryptionKey() {
|
|
51
|
-
const os = require('os');
|
|
52
|
-
// Use a combination of hostname and user info to create a machine-specific key
|
|
53
|
-
const machineId = os.hostname() + os.userInfo().username + os.platform();
|
|
54
|
-
// Derive a 32-byte key using PBKDF2
|
|
55
|
-
return crypto.pbkdf2Sync(machineId, 'claude-launcher-salt', 10000, 32, 'sha256');
|
|
74
|
+
/**
|
|
75
|
+
* Get export directory path and create if not exists
|
|
76
|
+
* @returns {string} - Export directory path
|
|
77
|
+
*/
|
|
78
|
+
function getExportDirectory() {
|
|
79
|
+
const userHome = os.homedir();
|
|
80
|
+
const exportDir = path.join(userHome, 'claude-launcher');
|
|
81
|
+
|
|
82
|
+
// Create directory if it doesn't exist
|
|
83
|
+
if (!fs.existsSync(exportDir)) {
|
|
84
|
+
fs.mkdirSync(exportDir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return exportDir;
|
|
56
88
|
}
|
|
57
89
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Generate timestamp-based filename for export
|
|
92
|
+
* @returns {string} - Filename with timestamp
|
|
93
|
+
*/
|
|
94
|
+
function generateExportFilename() {
|
|
95
|
+
const now = new Date();
|
|
96
|
+
const timestamp = now.getFullYear().toString() +
|
|
97
|
+
(now.getMonth() + 1).toString().padStart(2, '0') +
|
|
98
|
+
now.getDate().toString().padStart(2, '0') +
|
|
99
|
+
now.getHours().toString().padStart(2, '0') +
|
|
100
|
+
now.getMinutes().toString().padStart(2, '0') +
|
|
101
|
+
now.getSeconds().toString().padStart(2, '0');
|
|
102
|
+
|
|
103
|
+
return `claude-launcher-export-${timestamp}.json`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Open file with system default application
|
|
108
|
+
* @param {string} filePath - Path to the file to open
|
|
109
|
+
*/
|
|
110
|
+
function openFileWithDefault(filePath) {
|
|
111
|
+
const platform = process.platform;
|
|
112
|
+
let command;
|
|
113
|
+
|
|
114
|
+
if (platform === 'win32') {
|
|
115
|
+
command = `start "" "${filePath}"`;
|
|
116
|
+
} else if (platform === 'darwin') {
|
|
117
|
+
command = `open "${filePath}"`;
|
|
118
|
+
} else {
|
|
119
|
+
command = `xdg-open "${filePath}"`;
|
|
82
120
|
}
|
|
121
|
+
|
|
122
|
+
exec(command, (error) => {
|
|
123
|
+
if (error) {
|
|
124
|
+
console.log(colors.yellow + `Could not open file automatically: ${error.message}` + colors.reset);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
83
127
|
}
|
|
84
128
|
|
|
85
|
-
|
|
86
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Validate import file path and JSON format
|
|
131
|
+
* @param {string} filePath - Path to the JSON file
|
|
132
|
+
* @returns {Object} - Validation result with success status and data/error
|
|
133
|
+
*/
|
|
134
|
+
function validateImportFile(filePath) {
|
|
135
|
+
const result = {
|
|
136
|
+
valid: false,
|
|
137
|
+
data: null,
|
|
138
|
+
error: null
|
|
139
|
+
};
|
|
140
|
+
|
|
87
141
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
142
|
+
// Check if file path is provided
|
|
143
|
+
if (!filePath || filePath.trim() === '') {
|
|
144
|
+
result.error = 'File path cannot be empty';
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Normalize and resolve path
|
|
149
|
+
const normalizedPath = path.resolve(filePath.trim());
|
|
150
|
+
|
|
151
|
+
// Check if file exists
|
|
152
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
153
|
+
result.error = i18n.tSync('errors.file.file_not_found', normalizedPath);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check if it's a file (not directory)
|
|
158
|
+
const stats = fs.statSync(normalizedPath);
|
|
159
|
+
if (!stats.isFile()) {
|
|
160
|
+
result.error = `Path is not a file: ${normalizedPath}`;
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check file extension
|
|
165
|
+
if (path.extname(normalizedPath).toLowerCase() !== '.json') {
|
|
166
|
+
result.error = `File must have .json extension: ${normalizedPath}`;
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Read and parse JSON file
|
|
171
|
+
const fileContent = fs.readFileSync(normalizedPath, 'utf8');
|
|
172
|
+
|
|
173
|
+
// Validate JSON format
|
|
174
|
+
let jsonData;
|
|
175
|
+
try {
|
|
176
|
+
jsonData = JSON.parse(fileContent);
|
|
177
|
+
} catch (parseError) {
|
|
178
|
+
result.error = `Invalid JSON format: ${parseError.message}`;
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Basic structure validation for claude-launcher config
|
|
183
|
+
if (!jsonData || typeof jsonData !== 'object') {
|
|
184
|
+
result.error = 'JSON file must contain a valid configuration object';
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check for required fields (basic validation)
|
|
189
|
+
if (!jsonData.hasOwnProperty('apis') || !Array.isArray(jsonData.apis)) {
|
|
190
|
+
result.error = i18n.tSync('errors.file.invalid_format', 'JSON file must contain an "apis" array');
|
|
191
|
+
return result;
|
|
94
192
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
102
|
-
decrypted += decipher.final('utf8');
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
success: true,
|
|
106
|
-
value: decrypted,
|
|
107
|
-
error: null
|
|
108
|
-
};
|
|
193
|
+
|
|
194
|
+
result.valid = true;
|
|
195
|
+
result.data = fileContent; // Return raw JSON string for compatibility
|
|
196
|
+
return result;
|
|
197
|
+
|
|
109
198
|
} catch (error) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
value: null,
|
|
113
|
-
error: error.message
|
|
114
|
-
};
|
|
199
|
+
result.error = `File validation error: ${error.message}`;
|
|
200
|
+
return result;
|
|
115
201
|
}
|
|
116
202
|
}
|
|
117
203
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
204
|
+
// Main menu options - will be populated dynamically with i18n
|
|
205
|
+
let menuOptions = [];
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Add new third-party API
|
|
209
|
+
*/
|
|
210
|
+
async function addNewThirdPartyApi() {
|
|
211
|
+
try {
|
|
212
|
+
const apiData = await promptForThirdPartyApi();
|
|
213
|
+
|
|
214
|
+
// Check if this is the first API
|
|
215
|
+
const isFirstApi = apiManager.getApis().length === 0;
|
|
216
|
+
const hasExportPassword = apiManager.hasExportPassword();
|
|
217
|
+
|
|
218
|
+
const newApi = apiManager.addApi(
|
|
219
|
+
apiData.baseUrl,
|
|
220
|
+
apiData.authToken,
|
|
221
|
+
apiData.model,
|
|
222
|
+
apiData.name,
|
|
223
|
+
apiData.provider
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
showSuccess(await i18n.t('messages.success.api_added'), [
|
|
227
|
+
`Name: ${newApi.name}`,
|
|
228
|
+
`${await i18n.t('api.details.provider')}: ${newApi.provider}`,
|
|
229
|
+
`${await i18n.t('api.details.url')}: ${newApi.baseUrl}`,
|
|
230
|
+
`${await i18n.t('api.details.model')}: ${newApi.model}`
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
if (isFirstApi) {
|
|
234
|
+
showInfo(await i18n.t('messages.info.first_time_usage'));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// Force cleanup stdin state to prevent navigation issues
|
|
239
|
+
forceStdinCleanup();
|
|
240
|
+
|
|
241
|
+
// Check if user cancelled the operation
|
|
242
|
+
const cancelledMessage = await i18n.t('errors.general.cancelled_by_user');
|
|
243
|
+
if (error.message === cancelledMessage) {
|
|
244
|
+
// User cancelled - show neutral message instead of error
|
|
245
|
+
console.log(colors.yellow + await i18n.t('messages.info.operation_cancelled') + colors.reset);
|
|
246
|
+
} else {
|
|
247
|
+
// Actual error occurred
|
|
248
|
+
showError(await i18n.t('errors.api.failed_add', error.message));
|
|
249
|
+
}
|
|
139
250
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
valid: true,
|
|
143
|
-
error: null,
|
|
144
|
-
value: apiKey
|
|
145
|
-
};
|
|
251
|
+
|
|
252
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
146
253
|
}
|
|
147
254
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Remove third-party API
|
|
257
|
+
*/
|
|
258
|
+
async function removeThirdPartyApi() {
|
|
152
259
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
console.log(colors.gray + `Creating default config at: ${CONFIG_FILE}` + colors.reset);
|
|
175
|
-
|
|
176
|
-
// Try to read template file, fallback to hardcoded defaults
|
|
177
|
-
let defaultConfig = `KIMI_API_KEY=Your_Double_Base64_Encoded_Api_Key
|
|
178
|
-
KIMI_BASE_URL=https://api.moonshot.cn/anthropic/`;
|
|
179
|
-
|
|
180
|
-
const templatePath = path.join(__dirname, 'claude-launcher-template.env');
|
|
260
|
+
// Get API list
|
|
261
|
+
const apis = apiManager.getApis();
|
|
262
|
+
const activeApi = apiManager.getActiveApi();
|
|
263
|
+
const activeIndex = activeApi ? apis.findIndex(api => api.id === activeApi.id) : -1;
|
|
264
|
+
|
|
265
|
+
// Show API selection table (handles no APIs case internally)
|
|
266
|
+
const selectedApi = await showApiSelectionTable(
|
|
267
|
+
apis,
|
|
268
|
+
i18n.tSync('ui.general.select_api_remove'),
|
|
269
|
+
'remove',
|
|
270
|
+
activeIndex
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (!selectedApi) {
|
|
274
|
+
return showMenu();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Show confirmation dialog
|
|
278
|
+
const confirmed = await confirmDeletion(selectedApi);
|
|
279
|
+
|
|
280
|
+
if (confirmed) {
|
|
181
281
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
282
|
+
// Find the index of the selected API in the current list
|
|
283
|
+
const selectedIndex = apis.findIndex(api => api.id === selectedApi.id);
|
|
284
|
+
apiManager.removeApi(selectedIndex);
|
|
285
|
+
|
|
286
|
+
console.clear();
|
|
287
|
+
showSuccess(await i18n.t('messages.success.api_removed'), [
|
|
288
|
+
`${await i18n.t('api.actions.removed_info', selectedApi.name)}`,
|
|
289
|
+
`${await i18n.t('api.details.provider')}: ${selectedApi.provider}`
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
// Show success message and return to main menu
|
|
293
|
+
const remainingApis = apiManager.getApis();
|
|
294
|
+
if (remainingApis.length === 0) {
|
|
295
|
+
showInfo(await i18n.t('messages.info.all_apis_removed'));
|
|
185
296
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(colors.gray + 'Please edit the file to add your actual API credentials.' + colors.reset);
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.log(colors.red + 'Failed to create config file: ' + error.message + colors.reset);
|
|
297
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
298
|
+
return showMenu();
|
|
299
|
+
|
|
300
|
+
} catch (removeError) {
|
|
301
|
+
showError(await i18n.t('errors.api.failed_remove', removeError.message));
|
|
302
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
303
|
+
return showMenu();
|
|
196
304
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
305
|
+
} else {
|
|
306
|
+
showInfo(await i18n.t('messages.info.removal_cancelled'));
|
|
307
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
308
|
+
return showMenu();
|
|
200
309
|
}
|
|
310
|
+
|
|
201
311
|
} catch (error) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
312
|
+
forceStdinCleanup();
|
|
313
|
+
showError('Failed to remove API', [error.message]);
|
|
314
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
205
315
|
}
|
|
206
|
-
|
|
207
|
-
return config;
|
|
208
316
|
}
|
|
209
317
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
console.log(colors.gray + `Command: ${command}` + colors.reset);
|
|
215
|
-
|
|
216
|
-
if (Object.keys(envVars).length > 0) {
|
|
217
|
-
console.log(colors.gray + 'Environment variables:' + colors.reset);
|
|
218
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
219
|
-
if (key === 'ANTHROPIC_API_KEY') {
|
|
220
|
-
console.log(colors.gray + ` ${key}=***` + colors.reset);
|
|
221
|
-
} else {
|
|
222
|
-
console.log(colors.gray + ` ${key}=${value}` + colors.reset);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
console.log('');
|
|
228
|
-
console.log(colors.green + '[+] Claude will run in current terminal.' + colors.reset);
|
|
229
|
-
console.log(colors.gray + ' Launcher will exit to transfer control to Claude.' + colors.reset);
|
|
230
|
-
console.log('');
|
|
231
|
-
|
|
232
|
-
// Prepare clean environment
|
|
233
|
-
const env = { ...process.env, ...envVars };
|
|
234
|
-
|
|
235
|
-
// Disable conflicting auth tokens when using Kimi API
|
|
236
|
-
if (disableAuthTokens) {
|
|
237
|
-
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
238
|
-
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
239
|
-
console.log(colors.gray + ' Disabled: ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN' + colors.reset);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Parse command and arguments
|
|
243
|
-
const args = command.split(' ');
|
|
244
|
-
const cmd = args.shift();
|
|
245
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Switch active third-party API
|
|
320
|
+
*/
|
|
321
|
+
async function switchThirdPartyApi() {
|
|
246
322
|
try {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
cwd: process.cwd(),
|
|
264
|
-
shell: true // Use shell to find the command
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Don't exit immediately, wait for Claude to exit then exit launcher
|
|
268
|
-
child.on('close', (code) => {
|
|
269
|
-
process.exit(code || 0);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
child.on('error', (error) => {
|
|
273
|
-
console.log(colors.red + 'Error running Claude: ' + error.message + colors.reset);
|
|
274
|
-
process.exit(1);
|
|
275
|
-
});
|
|
276
|
-
|
|
323
|
+
const apis = apiManager.getApis();
|
|
324
|
+
const activeApi = apiManager.getActiveApi();
|
|
325
|
+
const activeIndex = activeApi ? apis.findIndex(api => api.id === activeApi.id) : -1;
|
|
326
|
+
|
|
327
|
+
// 现在表格函数内部处理整个切换流程
|
|
328
|
+
const selectedApi = await showApiSelectionTable(
|
|
329
|
+
apis,
|
|
330
|
+
i18n.tSync('api.actions.select_to_switch'),
|
|
331
|
+
'switch',
|
|
332
|
+
activeIndex,
|
|
333
|
+
apiManager // 传递 apiManager 让表格函数处理切换逻辑
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// 表格函数已经处理了所有显示和切换逻辑,直接返回主菜单
|
|
337
|
+
return showMenu();
|
|
338
|
+
|
|
277
339
|
} catch (error) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
process.stdin.once('data', () => {
|
|
283
|
-
process.stdin.setRawMode(false);
|
|
284
|
-
showMenu();
|
|
285
|
-
});
|
|
340
|
+
forceStdinCleanup();
|
|
341
|
+
showError(await i18n.t('errors.api.failed_switch', error.message));
|
|
342
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
343
|
+
return showMenu();
|
|
286
344
|
}
|
|
287
345
|
}
|
|
288
346
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
347
|
+
/**
|
|
348
|
+
* View API statistics
|
|
349
|
+
*/
|
|
350
|
+
async function viewStatistics() {
|
|
351
|
+
console.clear();
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
|
|
354
|
+
console.log('');
|
|
355
|
+
|
|
356
|
+
const stats = apiManager.getStatistics();
|
|
357
|
+
const apis = apiManager.getApis();
|
|
358
|
+
|
|
359
|
+
console.log(colors.cyan + ' ' + i18n.tSync('ui.general.summary') + colors.reset);
|
|
360
|
+
console.log(colors.gray + ` ${await i18n.t('statistics.total_apis', stats.totalApis)}` + colors.reset);
|
|
361
|
+
console.log(colors.gray + ` ${await i18n.t('statistics.active_api', stats.activeApiName)}` + colors.reset);
|
|
362
|
+
console.log(colors.gray + ` ${await i18n.t('statistics.most_used', stats.mostUsedApi)}` + colors.reset);
|
|
363
|
+
console.log(colors.gray + ` ${await i18n.t('statistics.total_usage', stats.totalUsage)}` + colors.reset);
|
|
364
|
+
console.log('');
|
|
365
|
+
|
|
366
|
+
if (apis.length > 0) {
|
|
367
|
+
console.log(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + colors.reset);
|
|
368
|
+
|
|
369
|
+
// Pre-fetch translations to avoid await in forEach
|
|
370
|
+
const currentlyActiveText = await i18n.t('api.details.currently_active');
|
|
371
|
+
const providerText = await i18n.t('api.details.provider');
|
|
372
|
+
const usageText = await i18n.t('api.details.usage');
|
|
373
|
+
const timesSuffixText = await i18n.t('api.details.times_suffix');
|
|
374
|
+
const createdAtText = await i18n.t('api.details.created_at');
|
|
375
|
+
|
|
376
|
+
apis.forEach((api, index) => {
|
|
377
|
+
const isActive = apiManager.getActiveApi()?.id === api.id;
|
|
378
|
+
const activeText = isActive ? ` (${currentlyActiveText})` : '';
|
|
379
|
+
console.log(colors.gray + ` ${index + 1}. ${api.name}${activeText}` + colors.reset);
|
|
380
|
+
console.log(colors.dim + ` ${providerText}: ${api.provider}` + colors.reset);
|
|
381
|
+
console.log(colors.dim + ` ${usageText}: ${api.usageCount || 0} ${timesSuffixText}` + colors.reset);
|
|
382
|
+
console.log(colors.dim + ` ${createdAtText}: ${api.createdAt}` + colors.reset);
|
|
300
383
|
});
|
|
301
|
-
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.log('');
|
|
387
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
302
388
|
}
|
|
303
389
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Handle first time password setup
|
|
393
|
+
*/
|
|
394
|
+
async function handleFirstTimePasswordSetup() {
|
|
395
|
+
while (true) {
|
|
396
|
+
// Clear screen and show header
|
|
307
397
|
console.clear();
|
|
308
398
|
console.log('');
|
|
309
|
-
console.log(colors.bright + colors.
|
|
399
|
+
console.log(colors.bright + colors.yellow + '🔐 ' + i18n.tSync('password.setup.first_time_title') + colors.reset);
|
|
310
400
|
console.log('');
|
|
311
|
-
|
|
312
|
-
|
|
401
|
+
|
|
402
|
+
// Show information
|
|
403
|
+
console.log(colors.cyan + i18n.tSync('password.setup.why_needed') + colors.reset);
|
|
404
|
+
const whyNeededItems = i18n.tSync('password.setup.why_needed_items');
|
|
405
|
+
if (Array.isArray(whyNeededItems)) {
|
|
406
|
+
whyNeededItems.forEach(item => {
|
|
407
|
+
console.log(colors.gray + '• ' + item + colors.reset);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
313
410
|
console.log('');
|
|
314
|
-
console.log(colors.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
411
|
+
console.log(colors.cyan + '🔒 ' + i18n.tSync('password.setup.new_security_title') + colors.reset);
|
|
412
|
+
const securityItems = i18n.tSync('password.setup.security_items');
|
|
413
|
+
if (Array.isArray(securityItems)) {
|
|
414
|
+
securityItems.forEach(item => {
|
|
415
|
+
console.log(colors.gray + '• ' + item + colors.reset);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
319
418
|
console.log('');
|
|
320
|
-
console.log(colors.yellow + '
|
|
321
|
-
console.log(colors.gray + '
|
|
322
|
-
console.log(colors.gray + '
|
|
323
|
-
console.log(colors.gray + ' • Key cannot be decrypted on other machines' + colors.reset);
|
|
419
|
+
console.log(colors.yellow + i18n.tSync('password.setup.options_title') + colors.reset);
|
|
420
|
+
console.log(colors.gray + '• ' + i18n.tSync('password.setup.option_set') + colors.reset);
|
|
421
|
+
console.log(colors.gray + '• ' + i18n.tSync('password.setup.option_skip') + colors.reset);
|
|
324
422
|
console.log('');
|
|
325
|
-
|
|
326
|
-
// Show input box (fixed spacing)
|
|
327
|
-
console.log(colors.orange + '┌─────────────────────────────────────────────────────────┐' + colors.reset);
|
|
328
|
-
console.log(colors.orange + '│' + colors.reset + ' ' + colors.bright + 'Enter your Kimi API key' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
329
|
-
console.log(colors.orange + '├─────────────────────────────────────────────────────────┤' + colors.reset);
|
|
330
|
-
console.log(colors.orange + '│' + colors.reset + ' ' + colors.gray + 'Format: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
331
|
-
console.log(colors.orange + '│' + colors.reset + ' ' + colors.gray + 'You can copy and paste your API key here' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
332
|
-
console.log(colors.orange + '│' + colors.reset + ' ' + colors.bright + colors.yellow + 'After entering, press ENTER to continue' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
333
|
-
console.log(colors.orange + '│' + colors.reset + ' ' + colors.gray + 'Type "exit" or "quit" to return to menu' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
334
|
-
console.log(colors.orange + '└─────────────────────────────────────────────────────────┘' + colors.reset);
|
|
423
|
+
console.log(colors.red + i18n.tSync('password.setup.warning_skip') + colors.reset);
|
|
335
424
|
console.log('');
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (!validation.valid) {
|
|
356
|
-
console.log(colors.red + '[X] Invalid API key: ' + validation.error + colors.reset);
|
|
357
|
-
console.log(colors.yellow + '[!] Please try again with a valid API key.' + colors.reset);
|
|
358
|
-
console.log('');
|
|
359
|
-
continue; // Ask again
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Encrypt and save the API key
|
|
363
|
-
const encrypted = encryptApiKey(apiKey);
|
|
364
|
-
|
|
365
|
-
if (!encrypted.success) {
|
|
366
|
-
console.log(colors.red + '[X] Failed to encrypt API key: ' + encrypted.error + colors.reset);
|
|
367
|
-
console.log(colors.yellow + '[!] Please try again.' + colors.reset);
|
|
368
|
-
console.log('');
|
|
369
|
-
continue; // Ask again
|
|
425
|
+
|
|
426
|
+
console.log(colors.gray + '按任意键继续...' + colors.reset);
|
|
427
|
+
|
|
428
|
+
// Wait for user to read the information
|
|
429
|
+
await new Promise((resolve) => {
|
|
430
|
+
if (process.stdin.isTTY) {
|
|
431
|
+
process.stdin.setRawMode(true);
|
|
432
|
+
process.stdin.resume();
|
|
433
|
+
process.stdin.once('data', () => {
|
|
434
|
+
try {
|
|
435
|
+
process.stdin.setRawMode(false);
|
|
436
|
+
process.stdin.pause();
|
|
437
|
+
} catch (error) {
|
|
438
|
+
// Ignore cleanup errors
|
|
439
|
+
}
|
|
440
|
+
resolve();
|
|
441
|
+
});
|
|
442
|
+
} else {
|
|
443
|
+
resolve();
|
|
370
444
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Now show the menu for selection
|
|
448
|
+
const menuOptions = [
|
|
449
|
+
i18n.tSync('password.setup.menu_set_password'),
|
|
450
|
+
i18n.tSync('password.setup.menu_skip_setup'),
|
|
451
|
+
i18n.tSync('menu.api_management.back')
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
// Ensure global menus are initialized
|
|
455
|
+
initializeGlobalMenus();
|
|
456
|
+
globalConfirmMenu.setOptions(menuOptions);
|
|
457
|
+
|
|
458
|
+
const choice = await globalConfirmMenu.navigate();
|
|
459
|
+
|
|
460
|
+
switch (choice) {
|
|
461
|
+
case 0: // Set password
|
|
462
|
+
const passwordResult = await promptForPasswordSetup();
|
|
463
|
+
if (passwordResult) {
|
|
464
|
+
return true; // Password set successfully
|
|
465
|
+
}
|
|
466
|
+
// If password setup failed, continue loop to show menu again
|
|
467
|
+
break;
|
|
468
|
+
|
|
469
|
+
case 1: // Skip setup
|
|
470
|
+
const skipResult = await confirmSkipPassword();
|
|
471
|
+
if (skipResult === true) {
|
|
472
|
+
return true; // Skip confirmed
|
|
473
|
+
} else if (skipResult === 'reconsider') {
|
|
474
|
+
continue; // Return to main setup menu
|
|
475
|
+
}
|
|
476
|
+
// If skip was canceled, continue loop
|
|
477
|
+
break;
|
|
478
|
+
|
|
479
|
+
case 2: // Back to main menu
|
|
480
|
+
case -1: // ESC pressed
|
|
481
|
+
default:
|
|
482
|
+
return false; // Exit setup
|
|
381
483
|
}
|
|
382
|
-
|
|
383
|
-
} catch (error) {
|
|
384
|
-
console.log(colors.red + '[X] Setup failed: ' + error.message + colors.reset);
|
|
385
|
-
process.exit(1);
|
|
386
484
|
}
|
|
387
485
|
}
|
|
388
486
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
397
|
-
configContent = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
398
|
-
const lines = configContent.split('\n');
|
|
399
|
-
|
|
400
|
-
// Update existing key or add new one
|
|
401
|
-
for (let i = 0; i < lines.length; i++) {
|
|
402
|
-
const match = lines[i].match(/^([^=]+)=(.*)$/);
|
|
403
|
-
if (match && match[1] === key) {
|
|
404
|
-
lines[i] = `${key}=${value}`;
|
|
405
|
-
keyExists = true;
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (keyExists) {
|
|
411
|
-
configContent = lines.join('\n');
|
|
412
|
-
} else {
|
|
413
|
-
// Add new key at the end
|
|
414
|
-
configContent += `\n${key}=${value}`;
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
// Create new config file
|
|
418
|
-
configContent = `# Claude Launcher Configuration\n# Generated automatically\n\n${key}=${value}\nKIMI_BASE_URL=https://api.moonshot.cn/anthropic/\n`;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
fs.writeFileSync(CONFIG_FILE, configContent);
|
|
422
|
-
} catch (error) {
|
|
423
|
-
console.log(colors.red + 'Error updating config file: ' + error.message + colors.reset);
|
|
424
|
-
throw error;
|
|
487
|
+
/**
|
|
488
|
+
* Prompt user to set password
|
|
489
|
+
*/
|
|
490
|
+
async function promptForPasswordSetup() {
|
|
491
|
+
const result = await setupNewPassword(apiManager, true);
|
|
492
|
+
if (result) {
|
|
493
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
425
494
|
}
|
|
495
|
+
return result;
|
|
426
496
|
}
|
|
427
497
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return
|
|
462
|
-
ANTHROPIC_BASE_URL: baseUrl,
|
|
463
|
-
ANTHROPIC_API_KEY: apiKey
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Validate decrypted API key
|
|
468
|
-
const validation = validateApiKey(decrypted.value);
|
|
469
|
-
|
|
470
|
-
if (!validation.valid) {
|
|
471
|
-
console.log(colors.red + '[X] Stored API key is invalid: ' + validation.error + colors.reset);
|
|
472
|
-
console.log('');
|
|
473
|
-
|
|
474
|
-
const apiKey = await promptForApiKey();
|
|
475
|
-
return {
|
|
476
|
-
ANTHROPIC_BASE_URL: baseUrl,
|
|
477
|
-
ANTHROPIC_API_KEY: apiKey
|
|
478
|
-
};
|
|
498
|
+
/**
|
|
499
|
+
* Confirm skip password setup
|
|
500
|
+
*/
|
|
501
|
+
async function confirmSkipPassword() {
|
|
502
|
+
console.log('');
|
|
503
|
+
console.log(colors.bright + colors.red + '⚠️ ' + i18n.tSync('errors.password.confirm_skip_title') + colors.reset);
|
|
504
|
+
console.log('');
|
|
505
|
+
console.log(colors.gray + i18n.tSync('ui.general.after_skipping_password_setup') + colors.reset);
|
|
506
|
+
console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[0] + colors.reset);
|
|
507
|
+
console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[1] + colors.reset);
|
|
508
|
+
console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[2] + colors.reset);
|
|
509
|
+
console.log('');
|
|
510
|
+
|
|
511
|
+
// Ensure global menus are initialized
|
|
512
|
+
initializeGlobalMenus();
|
|
513
|
+
|
|
514
|
+
globalConfirmMenu.setOptions([
|
|
515
|
+
i18n.tSync('ui.general.confirm_skip_option'),
|
|
516
|
+
i18n.tSync('ui.general.reconsider_option')
|
|
517
|
+
]);
|
|
518
|
+
|
|
519
|
+
const choice = await globalConfirmMenu.navigate();
|
|
520
|
+
|
|
521
|
+
if (choice === 0) {
|
|
522
|
+
try {
|
|
523
|
+
apiManager.skipPasswordSetup();
|
|
524
|
+
console.log(colors.yellow + i18n.tSync('errors.password.setup_skipped') + colors.reset);
|
|
525
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
526
|
+
return true;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
forceStdinCleanup();
|
|
529
|
+
console.log(colors.red + `Operation failed: ${error.message}` + colors.reset);
|
|
530
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
531
|
+
return false;
|
|
479
532
|
}
|
|
480
|
-
|
|
481
|
-
// Success case
|
|
482
|
-
console.log(colors.green + '[+] API Key validation successful!' + colors.reset);
|
|
483
|
-
console.log(colors.gray + ` Decrypted key starts with: ${validation.value.substring(0, 8)}...` + colors.reset);
|
|
484
|
-
console.log(colors.gray + ` Base URL: ${baseUrl}` + colors.reset);
|
|
485
|
-
console.log('');
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
ANTHROPIC_BASE_URL: baseUrl,
|
|
489
|
-
ANTHROPIC_API_KEY: validation.value
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
} catch (error) {
|
|
493
|
-
console.log(colors.red + '[X] Configuration error: ' + error.message + colors.reset);
|
|
494
|
-
throw error;
|
|
495
533
|
}
|
|
534
|
+
|
|
535
|
+
return 'reconsider'; // Return to password setup main menu
|
|
496
536
|
}
|
|
497
537
|
|
|
498
|
-
|
|
499
|
-
|
|
538
|
+
/**
|
|
539
|
+
* Show API Management Menu
|
|
540
|
+
*/
|
|
541
|
+
async function showApiManagementMenu() {
|
|
542
|
+
// Force cleanup stdin state before showing API management menu
|
|
543
|
+
forceStdinCleanup();
|
|
544
|
+
|
|
500
545
|
console.clear();
|
|
501
546
|
console.log('');
|
|
502
|
-
|
|
503
|
-
// Claude-style orange/amber border with Unicode box drawing characters
|
|
504
|
-
const border = colors.orange;
|
|
505
|
-
const title = colors.white + colors.bright;
|
|
506
|
-
|
|
507
|
-
console.log(border + ' ┌────────────────────────────────────────┐' + colors.reset);
|
|
508
|
-
console.log(border + ' │' + title + ' Claude Code Launcher ' + border + '│' + colors.reset);
|
|
509
|
-
console.log(border + ' └────────────────────────────────────────┘' + colors.reset);
|
|
510
|
-
console.log('');
|
|
511
|
-
console.log(colors.gray + ' Use ↑↓ arrow keys to navigate, Enter to select' + colors.reset);
|
|
547
|
+
console.log(colors.bright + colors.orange + '📋 ' + await i18n.t('menu.api_management.title') + colors.reset);
|
|
512
548
|
console.log('');
|
|
513
|
-
}
|
|
514
549
|
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
'Exit'
|
|
522
|
-
];
|
|
523
|
-
|
|
524
|
-
let selectedIndex = 0;
|
|
525
|
-
|
|
526
|
-
// Display menu with current selection
|
|
527
|
-
function displayMenu() {
|
|
528
|
-
displayHeader();
|
|
529
|
-
|
|
530
|
-
menuOptions.forEach((option, index) => {
|
|
531
|
-
if (index === selectedIndex) {
|
|
532
|
-
// Selected item with Claude-style highlighting
|
|
533
|
-
console.log(colors.orange + ' → ' + colors.black + colors.bgAmber + option + colors.reset);
|
|
534
|
-
} else {
|
|
535
|
-
// Normal item
|
|
536
|
-
console.log(colors.gray + ' ' + option + colors.reset);
|
|
550
|
+
// Check if this is first time usage and prompt for password setup
|
|
551
|
+
if (apiManager.isFirstTimeUsage()) {
|
|
552
|
+
const passwordChoice = await handleFirstTimePasswordSetup();
|
|
553
|
+
if (!passwordChoice) {
|
|
554
|
+
// User chose to skip or canceled, return to main menu
|
|
555
|
+
return showMenu();
|
|
537
556
|
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
console.log('');
|
|
541
|
-
}
|
|
557
|
+
}
|
|
542
558
|
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
case '\r': // Enter
|
|
557
|
-
executeSelection();
|
|
558
|
-
break;
|
|
559
|
-
|
|
560
|
-
case '\u001b': // Escape
|
|
561
|
-
case 'q':
|
|
562
|
-
case 'Q':
|
|
563
|
-
console.log('');
|
|
564
|
-
console.log(colors.green + 'Goodbye!' + colors.reset);
|
|
565
|
-
process.exit(0);
|
|
566
|
-
break;
|
|
559
|
+
// Build menu options based on password setup status
|
|
560
|
+
const menuOptions = [
|
|
561
|
+
await i18n.t('menu.api_management.add_new'),
|
|
562
|
+
await i18n.t('menu.api_management.remove'),
|
|
563
|
+
await i18n.t('menu.api_management.switch'),
|
|
564
|
+
await i18n.t('menu.api_management.statistics')
|
|
565
|
+
];
|
|
566
|
+
|
|
567
|
+
// Add import/export options only if password is set
|
|
568
|
+
if (apiManager.canUseImportExport()) {
|
|
569
|
+
menuOptions.push(await i18n.t('menu.api_management.export'));
|
|
570
|
+
menuOptions.push(await i18n.t('menu.api_management.import'));
|
|
571
|
+
menuOptions.push(await i18n.t('menu.api_management.change_password'));
|
|
567
572
|
}
|
|
573
|
+
|
|
574
|
+
menuOptions.push(await i18n.t('menu.api_management.back'));
|
|
575
|
+
|
|
576
|
+
// Ensure global menus are initialized
|
|
577
|
+
initializeGlobalMenus();
|
|
578
|
+
|
|
579
|
+
globalApiManagementMenu.setOptions(menuOptions);
|
|
580
|
+
|
|
581
|
+
const choice = await globalApiManagementMenu.navigate();
|
|
582
|
+
|
|
583
|
+
// Handle menu choices based on current menu options
|
|
584
|
+
if (choice === 0) { // Add New API
|
|
585
|
+
await addNewThirdPartyApi();
|
|
586
|
+
return showMenu();
|
|
587
|
+
} else if (choice === 1) { // Remove API
|
|
588
|
+
await removeThirdPartyApi();
|
|
589
|
+
return showMenu();
|
|
590
|
+
} else if (choice === 2) { // Switch Active API
|
|
591
|
+
await switchThirdPartyApi();
|
|
592
|
+
return showMenu();
|
|
593
|
+
} else if (choice === 3) { // View API Statistics
|
|
594
|
+
await viewStatistics();
|
|
595
|
+
return showMenu();
|
|
596
|
+
} else if (apiManager.canUseImportExport()) {
|
|
597
|
+
// If import/export is available, handle those options
|
|
598
|
+
if (choice === 4) { // Export Configuration
|
|
599
|
+
await exportConfiguration();
|
|
600
|
+
return showMenu();
|
|
601
|
+
} else if (choice === 5) { // Import Configuration
|
|
602
|
+
await importConfiguration();
|
|
603
|
+
return showMenu();
|
|
604
|
+
} else if (choice === 6) { // Change Password
|
|
605
|
+
await changePassword();
|
|
606
|
+
return showMenu();
|
|
607
|
+
} else if (choice === 7) { // Back to Main Menu
|
|
608
|
+
return showMenu();
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
// If import/export is not available, only Back to Main Menu
|
|
612
|
+
if (choice === 4) { // Back to Main Menu
|
|
613
|
+
return showMenu();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Default fallback to main menu
|
|
618
|
+
return showMenu();
|
|
568
619
|
}
|
|
569
620
|
|
|
570
|
-
|
|
571
|
-
|
|
621
|
+
/**
|
|
622
|
+
* Handle third-party API launch
|
|
623
|
+
*/
|
|
624
|
+
async function handleThirdPartyApiLaunch(skipPermissions = false) {
|
|
572
625
|
try {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
626
|
+
const activeApi = apiManager.getActiveApi();
|
|
627
|
+
|
|
628
|
+
if (!activeApi) {
|
|
629
|
+
console.clear();
|
|
630
|
+
showInfo(i18n.tSync('launch.no_active_api'), [
|
|
631
|
+
i18n.tSync('launch.no_active_api_desc'),
|
|
632
|
+
i18n.tSync('launch.add_configure_first')
|
|
633
|
+
]);
|
|
634
|
+
|
|
635
|
+
await waitForKey(i18n.tSync('launch.press_key_return'));
|
|
636
|
+
return showMenu();
|
|
583
637
|
}
|
|
638
|
+
|
|
639
|
+
// Increment usage count for the active API since we're actually using it
|
|
640
|
+
apiManager.incrementActiveApiUsage();
|
|
641
|
+
|
|
642
|
+
launchClaudeWithApi(activeApi, skipPermissions);
|
|
643
|
+
|
|
584
644
|
} catch (error) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
console.log(colors.gray + 'Returning to menu...' + colors.reset);
|
|
588
|
-
console.log('');
|
|
589
|
-
|
|
590
|
-
// Simple delay before returning to menu
|
|
645
|
+
showError('Failed to launch with third-party API', [error.message]);
|
|
646
|
+
|
|
591
647
|
setTimeout(() => {
|
|
592
648
|
showMenu();
|
|
593
649
|
}, 2000);
|
|
594
650
|
}
|
|
595
651
|
}
|
|
596
652
|
|
|
597
|
-
|
|
598
|
-
|
|
653
|
+
/**
|
|
654
|
+
* Execute selected menu item
|
|
655
|
+
*/
|
|
656
|
+
async function executeSelection(selectedIndex) {
|
|
599
657
|
switch (selectedIndex) {
|
|
600
658
|
case 0: // Launch Claude Code
|
|
601
|
-
|
|
659
|
+
launchClaudeDefault();
|
|
602
660
|
break;
|
|
603
|
-
|
|
661
|
+
|
|
604
662
|
case 1: // Launch Claude Code (Skip Permissions)
|
|
605
|
-
|
|
663
|
+
launchClaudeSkipPermissions();
|
|
606
664
|
break;
|
|
607
|
-
|
|
608
|
-
case 2: // Launch Claude Code with
|
|
609
|
-
|
|
665
|
+
|
|
666
|
+
case 2: // Launch Claude Code with 3rd-party API
|
|
667
|
+
await handleThirdPartyApiLaunch(false);
|
|
610
668
|
break;
|
|
611
|
-
|
|
612
|
-
case 3: // Launch Claude Code with
|
|
613
|
-
|
|
669
|
+
|
|
670
|
+
case 3: // Launch Claude Code with 3rd-party API (Skip Permissions)
|
|
671
|
+
await handleThirdPartyApiLaunch(true);
|
|
614
672
|
break;
|
|
615
|
-
|
|
616
|
-
case 4: //
|
|
673
|
+
|
|
674
|
+
case 4: // 3rd-party API Management
|
|
675
|
+
return await showApiManagementMenu();
|
|
676
|
+
|
|
677
|
+
case 5: // Language Settings
|
|
678
|
+
return await showLanguageSettings();
|
|
679
|
+
|
|
680
|
+
case 6: // Version Update Check
|
|
681
|
+
return await showVersionUpdateCheck();
|
|
682
|
+
|
|
683
|
+
case 7: // Exit
|
|
617
684
|
console.log('');
|
|
618
|
-
console.log(colors.green + '
|
|
685
|
+
console.log(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset);
|
|
619
686
|
process.exit(0);
|
|
620
687
|
break;
|
|
688
|
+
|
|
689
|
+
default:
|
|
690
|
+
showMenu();
|
|
691
|
+
break;
|
|
621
692
|
}
|
|
622
693
|
}
|
|
623
694
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
695
|
+
/**
|
|
696
|
+
* Show main menu
|
|
697
|
+
*/
|
|
698
|
+
async function showMenu() {
|
|
699
|
+
// Force cleanup stdin state before showing main menu
|
|
700
|
+
forceStdinCleanup();
|
|
701
|
+
|
|
702
|
+
// Ensure global menus are initialized
|
|
703
|
+
initializeGlobalMenus();
|
|
704
|
+
|
|
705
|
+
// ========================================
|
|
706
|
+
// Version check configuration (hardcoded, modify as needed)
|
|
707
|
+
// ========================================
|
|
708
|
+
const ALWAYS_SHOW_VERSION = false; // Version display toggle: true=always show, false=only show updates
|
|
709
|
+
const VERSION_CHECK_INTERVAL_HOURS = 12; // Version check interval (hours): modify this to adjust check frequency
|
|
710
|
+
|
|
711
|
+
// Check for updates and prepare version info string
|
|
712
|
+
let versionInfo = null;
|
|
713
|
+
try {
|
|
714
|
+
const updateInfo = await checkForUpdates(false, VERSION_CHECK_INTERVAL_HOURS);
|
|
715
|
+
|
|
716
|
+
// Use hardcoded switch instead of config file settings
|
|
717
|
+
const shouldShowVersion = ALWAYS_SHOW_VERSION || updateInfo.available;
|
|
718
|
+
|
|
719
|
+
if (shouldShowVersion) {
|
|
720
|
+
if (updateInfo.available) {
|
|
721
|
+
// Display update notification (Claude Code style)
|
|
722
|
+
versionInfo = colors.yellow + ' ⚠️ ' + i18n.tSync('version.update_available', updateInfo.latestVersion, updateInfo.currentVersion) + colors.reset + '\n' +
|
|
723
|
+
colors.yellow + ' ' + i18n.tSync('version.install_command') + colors.reset;
|
|
724
|
+
} else if (ALWAYS_SHOW_VERSION) {
|
|
725
|
+
// Display current version info (always show mode)
|
|
726
|
+
versionInfo = colors.cyan + ' ℹ️ ' + i18n.tSync('version.current_version_info', updateInfo.currentVersion, updateInfo.latestVersion) + colors.reset + '\n' +
|
|
727
|
+
colors.yellow + ' ' + i18n.tSync('version.install_command') + colors.reset;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
} catch (error) {
|
|
731
|
+
// Silently ignore update check errors
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Populate menu options dynamically with i18n translations
|
|
735
|
+
menuOptions = [
|
|
736
|
+
await i18n.t('menu.main.launch_default'),
|
|
737
|
+
await i18n.t('menu.main.launch_skip'),
|
|
738
|
+
await i18n.t('menu.main.launch_api'),
|
|
739
|
+
await i18n.t('menu.main.launch_api_skip'),
|
|
740
|
+
await i18n.t('menu.main.api_management'),
|
|
741
|
+
await i18n.t('menu.main.language_settings'),
|
|
742
|
+
await i18n.t('menu.main.version_check'),
|
|
743
|
+
await i18n.t('menu.main.exit')
|
|
744
|
+
];
|
|
745
|
+
|
|
746
|
+
globalMainMenu.setOptions(menuOptions);
|
|
747
|
+
const selection = await globalMainMenu.navigate(false, versionInfo); // Pass version info to display between banner and nav
|
|
748
|
+
|
|
749
|
+
if (selection === -1) {
|
|
750
|
+
console.log('');
|
|
751
|
+
console.log(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset);
|
|
752
|
+
process.exit(0);
|
|
638
753
|
} else {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
754
|
+
await executeSelection(selection);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Export configuration with password encryption
|
|
764
|
+
*/
|
|
765
|
+
async function exportConfiguration() {
|
|
766
|
+
console.clear();
|
|
767
|
+
console.log('');
|
|
768
|
+
console.log(colors.bright + colors.orange + '💾 ' + await i18n.t('import_export.export.title') + colors.reset);
|
|
769
|
+
console.log('');
|
|
770
|
+
|
|
771
|
+
// Add export function description
|
|
772
|
+
console.log(colors.cyan + '📄 ' + i18n.tSync('import_export.export.description_title') + colors.reset);
|
|
773
|
+
console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset);
|
|
774
|
+
console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[1] + colors.reset);
|
|
775
|
+
console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[2] + colors.reset);
|
|
776
|
+
console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[3] + colors.reset);
|
|
777
|
+
console.log('');
|
|
778
|
+
|
|
779
|
+
// Verify password before export
|
|
780
|
+
const verified = await verifyExportPassword(apiManager, 'export');
|
|
781
|
+
if (!verified) {
|
|
782
|
+
console.log('');
|
|
783
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
try {
|
|
788
|
+
// Get export data
|
|
789
|
+
const exportData = apiManager.exportConfigAuthenticated();
|
|
790
|
+
|
|
791
|
+
// Prepare file path
|
|
792
|
+
const exportDir = getExportDirectory();
|
|
793
|
+
const filename = generateExportFilename();
|
|
794
|
+
const filePath = path.join(exportDir, filename);
|
|
795
|
+
|
|
796
|
+
// Write JSON file
|
|
797
|
+
fs.writeFileSync(filePath, exportData, 'utf8');
|
|
798
|
+
|
|
799
|
+
console.log(colors.green + '✓ ' + i18n.tSync('import_export.export.success_title') + colors.reset);
|
|
800
|
+
console.log('');
|
|
801
|
+
console.log(colors.cyan + '📁 ' + i18n.tSync('import_export.export.details_title') + colors.reset);
|
|
802
|
+
console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_file_saved', filePath) + colors.reset);
|
|
803
|
+
console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_export_dir', exportDir) + colors.reset);
|
|
804
|
+
console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_filename', filename) + colors.reset);
|
|
805
|
+
console.log('');
|
|
806
|
+
|
|
807
|
+
// Open file with default application
|
|
808
|
+
console.log(colors.yellow + '🔍 ' + i18n.tSync('import_export.export.opening_file') + colors.reset);
|
|
809
|
+
openFileWithDefault(filePath);
|
|
810
|
+
|
|
811
|
+
console.log('');
|
|
812
|
+
console.log(colors.cyan + '💡 ' + i18n.tSync('import_export.export.tips_title') + colors.reset);
|
|
813
|
+
console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[0] + colors.reset);
|
|
814
|
+
console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[1] + colors.reset);
|
|
815
|
+
|
|
816
|
+
} catch (error) {
|
|
817
|
+
forceStdinCleanup();
|
|
818
|
+
console.log(colors.red + `❌ Export failed: ${error.message}` + colors.reset);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
console.log('');
|
|
822
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Import configuration from plaintext JSON
|
|
827
|
+
*/
|
|
828
|
+
async function importConfiguration() {
|
|
829
|
+
console.clear();
|
|
830
|
+
console.log('');
|
|
831
|
+
console.log(colors.bright + colors.orange + '📥 ' + await i18n.t('import_export.import.title') + colors.reset);
|
|
832
|
+
console.log('');
|
|
833
|
+
|
|
834
|
+
// Add import function description
|
|
835
|
+
console.log(colors.cyan + '📄 ' + i18n.tSync('ui.general.import_function_description') + colors.reset);
|
|
836
|
+
console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset);
|
|
837
|
+
console.log(colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[0] + colors.reset);
|
|
838
|
+
console.log(colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[1] + colors.reset);
|
|
839
|
+
console.log(colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[2] + colors.reset);
|
|
840
|
+
console.log('');
|
|
841
|
+
|
|
842
|
+
// Verify password identity
|
|
843
|
+
const passwordVerified = await verifyExportPassword(apiManager, 'import');
|
|
844
|
+
if (!passwordVerified) {
|
|
845
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
console.log('');
|
|
850
|
+
console.log(colors.cyan + '📁 ' + i18n.tSync('ui.general.file_input_required') + colors.reset);
|
|
851
|
+
console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[0] + colors.reset);
|
|
852
|
+
console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[1] + colors.reset);
|
|
853
|
+
console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[2] + colors.reset);
|
|
854
|
+
console.log('');
|
|
855
|
+
|
|
856
|
+
const { simpleInput } = require('./lib/ui/prompts');
|
|
857
|
+
|
|
858
|
+
let attempts = 0;
|
|
859
|
+
const maxAttempts = 3;
|
|
860
|
+
|
|
861
|
+
while (attempts < maxAttempts) {
|
|
862
|
+
attempts++;
|
|
863
|
+
|
|
864
|
+
// Get file path from user
|
|
865
|
+
const filePrompt = i18n.tSync('ui.general.enter_json_file_path_attempt', attempts, maxAttempts);
|
|
866
|
+
const filePath = await simpleInput(colors.green + filePrompt + colors.reset);
|
|
867
|
+
|
|
868
|
+
if (!filePath) {
|
|
869
|
+
console.log(colors.red + i18n.tSync('ui.general.file_path_empty') + colors.reset);
|
|
870
|
+
if (attempts < maxAttempts) {
|
|
655
871
|
console.log('');
|
|
656
|
-
|
|
657
|
-
process.exit(0);
|
|
872
|
+
continue;
|
|
658
873
|
} else {
|
|
659
|
-
console.log(colors.red + '
|
|
874
|
+
console.log(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset);
|
|
875
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
876
|
+
return;
|
|
660
877
|
}
|
|
661
|
-
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Validate file
|
|
881
|
+
console.log('');
|
|
882
|
+
console.log(colors.yellow + i18n.tSync('ui.general.validating_file') + colors.reset);
|
|
883
|
+
const validation = validateImportFile(filePath);
|
|
884
|
+
|
|
885
|
+
if (!validation.valid) {
|
|
886
|
+
console.log(colors.red + '❌ ' + i18n.tSync('ui.general.file_validation_failed', validation.error) + colors.reset);
|
|
887
|
+
if (attempts < maxAttempts) {
|
|
888
|
+
console.log(colors.yellow + i18n.tSync('ui.general.check_file_path_json') + colors.reset);
|
|
889
|
+
console.log('');
|
|
890
|
+
continue;
|
|
891
|
+
} else {
|
|
892
|
+
console.log(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset);
|
|
893
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// File is valid, proceed with import
|
|
899
|
+
console.log(colors.green + i18n.tSync('ui.general.file_validation_successful') + colors.reset);
|
|
900
|
+
console.log('');
|
|
901
|
+
|
|
902
|
+
try {
|
|
903
|
+
// Import the validated configuration data
|
|
904
|
+
const result = apiManager.importConfigAuthenticated(validation.data);
|
|
905
|
+
|
|
906
|
+
console.log(colors.green + i18n.tSync('ui.general.import_successful') + colors.reset);
|
|
907
|
+
console.log('');
|
|
908
|
+
console.log(colors.cyan + i18n.tSync('ui.general.import_statistics') + colors.reset);
|
|
909
|
+
const importItems = i18n.tSync('ui.general.import_stats_items');
|
|
910
|
+
console.log(colors.gray + ` • ` + importItems[0].replace('{0}', result.imported) + colors.reset);
|
|
911
|
+
console.log(colors.gray + ` • ` + importItems[1].replace('{1}', result.skipped) + colors.reset);
|
|
912
|
+
console.log(colors.gray + ` • ` + importItems[2] + colors.reset);
|
|
913
|
+
console.log(colors.gray + ` • ` + importItems[3].replace('{0}', path.resolve(filePath)) + colors.reset);
|
|
914
|
+
|
|
915
|
+
break; // Success, exit the loop
|
|
916
|
+
|
|
917
|
+
} catch (error) {
|
|
918
|
+
forceStdinCleanup();
|
|
919
|
+
console.log(colors.red + `❌ Import failed: ${error.message}` + colors.reset);
|
|
920
|
+
if (attempts < maxAttempts) {
|
|
921
|
+
console.log(colors.yellow + i18n.tSync('ui.general.import_tips')[0] + colors.reset);
|
|
922
|
+
console.log('');
|
|
923
|
+
continue;
|
|
924
|
+
} else {
|
|
925
|
+
console.log(colors.red + i18n.tSync('ui.general.max_attempts_import_failed') + colors.reset);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
662
928
|
}
|
|
929
|
+
|
|
930
|
+
console.log('');
|
|
931
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
663
932
|
}
|
|
664
933
|
|
|
665
|
-
|
|
666
|
-
|
|
934
|
+
/**
|
|
935
|
+
* Change password
|
|
936
|
+
*/
|
|
937
|
+
async function changePassword() {
|
|
938
|
+
console.clear();
|
|
667
939
|
console.log('');
|
|
668
|
-
console.log(colors.
|
|
669
|
-
|
|
670
|
-
|
|
940
|
+
console.log(colors.bright + colors.orange + '🔑 ' + i18n.tSync('errors.password.change_password_title') + colors.reset);
|
|
941
|
+
console.log('');
|
|
942
|
+
|
|
943
|
+
// Use unified password change module
|
|
944
|
+
const success = await changePasswordModule(apiManager);
|
|
945
|
+
|
|
946
|
+
if (success) {
|
|
947
|
+
console.log('');
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Show language settings menu
|
|
955
|
+
*/
|
|
956
|
+
async function showLanguageSettings() {
|
|
957
|
+
try {
|
|
958
|
+
const supportedLanguages = i18n.getSupportedLanguages();
|
|
959
|
+
const currentLanguage = i18n.getCurrentLanguage();
|
|
960
|
+
const currentLanguageName = i18n.getCurrentLanguageName();
|
|
961
|
+
|
|
962
|
+
console.clear();
|
|
963
|
+
console.log('');
|
|
964
|
+
console.log(colors.bright + colors.orange + '🌍 ' + await i18n.t('menu.language.title') + colors.reset);
|
|
965
|
+
console.log('');
|
|
966
|
+
|
|
967
|
+
// Show current language
|
|
968
|
+
console.log(colors.cyan + await i18n.t('menu.language.current', currentLanguageName) + colors.reset);
|
|
969
|
+
console.log('');
|
|
970
|
+
console.log(colors.yellow + await i18n.t('menu.language.select_prompt') + colors.reset);
|
|
971
|
+
console.log('');
|
|
972
|
+
|
|
973
|
+
// Create menu options
|
|
974
|
+
const languageOptions = [];
|
|
975
|
+
const languageCodes = Object.keys(supportedLanguages);
|
|
976
|
+
|
|
977
|
+
languageCodes.forEach(langCode => {
|
|
978
|
+
const isActive = langCode === currentLanguage;
|
|
979
|
+
const displayName = supportedLanguages[langCode];
|
|
980
|
+
languageOptions.push(isActive ? `● ${displayName}` : ` ${displayName}`);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
languageOptions.push(await i18n.t('menu.language.back'));
|
|
984
|
+
|
|
985
|
+
// Ensure global menus are initialized
|
|
986
|
+
initializeGlobalMenus();
|
|
987
|
+
|
|
988
|
+
globalApiManagementMenu.setOptions(languageOptions);
|
|
989
|
+
const choice = await globalApiManagementMenu.navigate();
|
|
671
990
|
|
|
991
|
+
if (choice === -1 || choice === languageOptions.length - 1) {
|
|
992
|
+
// Back to main menu
|
|
993
|
+
return showMenu();
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Check if user selected current language
|
|
997
|
+
const selectedLangCode = languageCodes[choice];
|
|
998
|
+
if (selectedLangCode === currentLanguage) {
|
|
999
|
+
// Already current language, return to main menu
|
|
1000
|
+
return showMenu();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Switch language
|
|
1004
|
+
console.clear();
|
|
1005
|
+
console.log('');
|
|
1006
|
+
console.log(colors.yellow + await i18n.t('status.switching_language') + colors.reset);
|
|
1007
|
+
|
|
1008
|
+
try {
|
|
1009
|
+
await i18n.setLanguage(selectedLangCode);
|
|
1010
|
+
|
|
1011
|
+
console.clear();
|
|
1012
|
+
const newLanguageName = i18n.getCurrentLanguageName();
|
|
1013
|
+
showSuccess(await i18n.t('messages.success.language_changed'), [
|
|
1014
|
+
await i18n.t('menu.language.changed_success', newLanguageName)
|
|
1015
|
+
]);
|
|
1016
|
+
|
|
1017
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
1018
|
+
|
|
1019
|
+
// Return to main menu after successful language change
|
|
1020
|
+
return showMenu();
|
|
1021
|
+
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
showError(await i18n.t('errors.general.operation_failed', error.message));
|
|
1024
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
1025
|
+
return showMenu();
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
showError('Failed to show language settings', [error.message]);
|
|
1030
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1031
|
+
return showMenu();
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Show version update check screen
|
|
1037
|
+
*/
|
|
1038
|
+
async function showVersionUpdateCheck() {
|
|
1039
|
+
try {
|
|
1040
|
+
console.clear();
|
|
1041
|
+
console.log('');
|
|
1042
|
+
console.log(colors.bright + colors.orange + '🔄 ' + await i18n.t('version_check.title') + colors.reset);
|
|
1043
|
+
console.log('');
|
|
1044
|
+
|
|
1045
|
+
console.log(colors.cyan + await i18n.t('version_check.checking') + colors.reset);
|
|
1046
|
+
console.log(colors.gray + await i18n.t('version_check.please_wait') + colors.reset);
|
|
1047
|
+
console.log('');
|
|
1048
|
+
|
|
1049
|
+
// Show progress indicator
|
|
1050
|
+
const progressInterval = setInterval(() => {
|
|
1051
|
+
process.stdout.write('.');
|
|
1052
|
+
}, 500);
|
|
1053
|
+
|
|
1054
|
+
try {
|
|
1055
|
+
// Force check with 15 second timeout
|
|
1056
|
+
const result = await forceCheckForUpdates(15000);
|
|
1057
|
+
|
|
1058
|
+
// Stop progress indicator
|
|
1059
|
+
clearInterval(progressInterval);
|
|
1060
|
+
console.log('\n');
|
|
1061
|
+
|
|
1062
|
+
if (result.error) {
|
|
1063
|
+
// Handle errors (timeout, network, etc.)
|
|
1064
|
+
console.log(colors.red + '❌ ' + await i18n.t('version_check.error') + colors.reset);
|
|
1065
|
+
console.log(colors.red + ' ' + result.error + colors.reset);
|
|
1066
|
+
console.log('');
|
|
1067
|
+
console.log(colors.gray + await i18n.t('version_check.error_tips') + colors.reset);
|
|
1068
|
+
} else if (result.available) {
|
|
1069
|
+
// Update available
|
|
1070
|
+
console.log(colors.yellow + '🎉 ' + await i18n.t('version_check.update_available') + colors.reset);
|
|
1071
|
+
console.log('');
|
|
1072
|
+
console.log(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset);
|
|
1073
|
+
console.log(colors.green + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset);
|
|
1074
|
+
console.log('');
|
|
1075
|
+
console.log(colors.yellow + '💡 ' + await i18n.t('version_check.update_command') + colors.reset);
|
|
1076
|
+
console.log(colors.yellow + ' npm update -g @kikkimo/claude-launcher' + colors.reset);
|
|
1077
|
+
} else {
|
|
1078
|
+
// Already up to date
|
|
1079
|
+
console.log(colors.green + '✅ ' + await i18n.t('version_check.up_to_date') + colors.reset);
|
|
1080
|
+
console.log('');
|
|
1081
|
+
console.log(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset);
|
|
1082
|
+
console.log(colors.cyan + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
// Stop progress indicator
|
|
1087
|
+
clearInterval(progressInterval);
|
|
1088
|
+
console.log('\n');
|
|
1089
|
+
|
|
1090
|
+
console.log(colors.red + '❌ ' + await i18n.t('version_check.unexpected_error') + colors.reset);
|
|
1091
|
+
console.log(colors.red + ' ' + error.message + colors.reset);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
console.log('');
|
|
1095
|
+
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
1096
|
+
return showMenu();
|
|
1097
|
+
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.log(colors.red + '❌ Failed to check version: ' + error.message + colors.reset);
|
|
1100
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1101
|
+
return showMenu();
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// End of API Management functions
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Graceful shutdown handlers
|
|
1109
|
+
*/
|
|
672
1110
|
process.on('SIGTERM', () => {
|
|
673
1111
|
console.log('');
|
|
674
|
-
console.log(colors.green + '
|
|
1112
|
+
console.log(colors.green + i18n.tSync('ui.general.goodbye') + colors.reset);
|
|
675
1113
|
process.exit(0);
|
|
676
1114
|
});
|
|
677
1115
|
|
|
1116
|
+
// Initialize global menus and start the application
|
|
1117
|
+
initializeGlobalMenus();
|
|
1118
|
+
|
|
678
1119
|
// Start the application
|
|
679
|
-
showMenu();
|
|
1120
|
+
showMenu();
|