@kikkimo/claude-launcher 3.0.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 +28 -0
- package/README.md +7 -4
- package/claude-launcher +634 -38
- package/docs/README-zh.md +7 -4
- package/lib/api-manager.js +501 -67
- package/lib/i18n/locales/de.js +144 -6
- package/lib/i18n/locales/en.js +150 -6
- package/lib/i18n/locales/es.js +144 -6
- package/lib/i18n/locales/fr.js +144 -6
- package/lib/i18n/locales/it.js +144 -6
- package/lib/i18n/locales/ja.js +144 -6
- package/lib/i18n/locales/ko.js +144 -6
- package/lib/i18n/locales/pt.js +144 -6
- package/lib/i18n/locales/ru.js +144 -6
- package/lib/i18n/locales/zh-TW.js +144 -6
- package/lib/i18n/locales/zh.js +150 -6
- package/lib/launcher.js +46 -17
- package/lib/presets/providers.js +143 -39
- package/lib/ui/api-editor.js +668 -210
- package/lib/ui/i18n-labels.js +16 -0
- package/lib/ui/menu.js +19 -13
- package/lib/ui/screen.js +125 -125
- package/lib/utils/version-checker.js +6 -5
- package/lib/validators.js +102 -1
- package/package.json +2 -2
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared i18n label resolver for config field display names.
|
|
3
|
+
*/
|
|
4
|
+
const i18n = require('../i18n');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a config label via i18n first, fall back to constant map.
|
|
8
|
+
*/
|
|
9
|
+
function i18nLabel(section, key, fallbackMap) {
|
|
10
|
+
const i18nKey = 'config_labels.' + section + '.' + key;
|
|
11
|
+
const val = i18n.tSync(i18nKey);
|
|
12
|
+
if (val !== i18nKey) return val;
|
|
13
|
+
return fallbackMap[key] || key;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { i18nLabel };
|
package/lib/ui/menu.js
CHANGED
|
@@ -61,8 +61,11 @@ class Menu {
|
|
|
61
61
|
* Display menu with current selection
|
|
62
62
|
* @param {string} versionInfo - Optional version info to display between banner and navigation
|
|
63
63
|
* @param {Function|null} hintCallback - Optional sync callback(selectedIndex) returning hint string or null
|
|
64
|
+
* @param {string} navigationKey - i18n key for navigation hint (default: 'navigation.use_arrows')
|
|
64
65
|
*/
|
|
65
|
-
displayMenu(versionInfo = null, hintCallback = null) {
|
|
66
|
+
displayMenu(versionInfo = null, hintCallback = null, navigationKey = 'navigation.use_arrows') {
|
|
67
|
+
this._navigationKey = navigationKey;
|
|
68
|
+
const isTTY = process.stdin.isTTY;
|
|
66
69
|
const lines = [];
|
|
67
70
|
lines.push('');
|
|
68
71
|
lines.push(colors.orange + ' ┌──────────────────────────────────────────────────────┐' + colors.reset);
|
|
@@ -75,16 +78,18 @@ class Menu {
|
|
|
75
78
|
lines.push('');
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
lines.push(colors.gray + ' ' + i18n.tSync(
|
|
81
|
+
lines.push(colors.gray + ' ' + i18n.tSync(navigationKey) + colors.reset);
|
|
79
82
|
lines.push('');
|
|
80
83
|
|
|
81
84
|
this.menuOptions.forEach((option, index) => {
|
|
85
|
+
const prefix = isTTY ? '' : (index + 1) + '. ';
|
|
82
86
|
if (index === this.selectedIndex) {
|
|
83
|
-
const
|
|
84
|
-
const
|
|
87
|
+
const prefixedOption = prefix + option;
|
|
88
|
+
const displayWidth = getStringWidth(prefixedOption);
|
|
89
|
+
const paddedOption = padStringToWidth(prefixedOption, Math.max(40, displayWidth + 2));
|
|
85
90
|
lines.push(colors.orange + ' → ' + colors.black + colors.bgAmber + paddedOption + colors.reset);
|
|
86
91
|
} else {
|
|
87
|
-
lines.push(colors.gray + ' ' + option + colors.reset);
|
|
92
|
+
lines.push(colors.gray + ' ' + prefix + option + colors.reset);
|
|
88
93
|
}
|
|
89
94
|
});
|
|
90
95
|
|
|
@@ -127,8 +132,9 @@ class Menu {
|
|
|
127
132
|
* Handle keyboard navigation
|
|
128
133
|
* @param {string} versionInfo - Optional version info to display
|
|
129
134
|
* @param {Function|null} hintCallback - Optional sync callback(selectedIndex) returning hint string or null
|
|
135
|
+
* @param {string} navigationKey - i18n key for navigation hint (default: 'navigation.use_arrows')
|
|
130
136
|
*/
|
|
131
|
-
async navigate(versionInfo = null, hintCallback = null) {
|
|
137
|
+
async navigate(versionInfo = null, hintCallback = null, navigationKey = 'navigation.use_arrows') {
|
|
132
138
|
// Guard against empty menu to prevent NaN from modulo operations
|
|
133
139
|
if (!this.menuOptions || this.menuOptions.length === 0) {
|
|
134
140
|
screen.write(colors.yellow + ' Warning: No menu options available' + colors.reset + '\n');
|
|
@@ -139,7 +145,7 @@ class Menu {
|
|
|
139
145
|
this.hintCallback = hintCallback; // Store for redrawing
|
|
140
146
|
|
|
141
147
|
return new Promise((resolve, reject) => {
|
|
142
|
-
this.displayMenu(versionInfo, hintCallback);
|
|
148
|
+
this.displayMenu(versionInfo, hintCallback, navigationKey);
|
|
143
149
|
|
|
144
150
|
if (process.stdin.isTTY) {
|
|
145
151
|
const scope = stdinManager.acquire('raw', {
|
|
@@ -192,12 +198,12 @@ class Menu {
|
|
|
192
198
|
switch (key) {
|
|
193
199
|
case '\u001b[A': // Up arrow
|
|
194
200
|
this.selectedIndex = (this.selectedIndex - 1 + this.menuOptions.length) % this.menuOptions.length;
|
|
195
|
-
this.displayMenu(this.versionInfo, this.hintCallback);
|
|
201
|
+
this.displayMenu(this.versionInfo, this.hintCallback, this._navigationKey);
|
|
196
202
|
break;
|
|
197
203
|
|
|
198
204
|
case '\u001b[B': // Down arrow
|
|
199
205
|
this.selectedIndex = (this.selectedIndex + 1) % this.menuOptions.length;
|
|
200
|
-
this.displayMenu(this.versionInfo, this.hintCallback);
|
|
206
|
+
this.displayMenu(this.versionInfo, this.hintCallback, this._navigationKey);
|
|
201
207
|
break;
|
|
202
208
|
|
|
203
209
|
case '\r': // Enter
|
|
@@ -242,7 +248,7 @@ class Menu {
|
|
|
242
248
|
lineScope.release();
|
|
243
249
|
resolve(-1);
|
|
244
250
|
} else {
|
|
245
|
-
screen.write(colors.red + '
|
|
251
|
+
screen.write(colors.red + ' ' + i18n.tSync('navigation.invalid_selection', this.menuOptions.length) + colors.reset + '\n');
|
|
246
252
|
}
|
|
247
253
|
});
|
|
248
254
|
}
|
|
@@ -265,7 +271,7 @@ class Menu {
|
|
|
265
271
|
lines.push('');
|
|
266
272
|
lines.push(colors.bright + colors.orange + `[*] ${title}` + colors.reset);
|
|
267
273
|
lines.push('');
|
|
268
|
-
lines.push(colors.gray + '
|
|
274
|
+
lines.push(colors.gray + ' ' + i18n.tSync('navigation.use_arrows_esc', i18n.tSync('navigation.action.select')) + colors.reset);
|
|
269
275
|
lines.push('');
|
|
270
276
|
|
|
271
277
|
items.forEach((item, index) => {
|
|
@@ -381,7 +387,7 @@ class Menu {
|
|
|
381
387
|
|
|
382
388
|
const rl = lineScope.createReadline();
|
|
383
389
|
|
|
384
|
-
screen.write(colors.yellow + '
|
|
390
|
+
screen.write(colors.yellow + ' ' + i18n.tSync('navigation.arrow_keys_not_available', items.length) + colors.reset + '\n');
|
|
385
391
|
|
|
386
392
|
rl.on('line', (input) => {
|
|
387
393
|
const choice = parseInt(input.trim());
|
|
@@ -394,7 +400,7 @@ class Menu {
|
|
|
394
400
|
lineScope.release();
|
|
395
401
|
resolve(null);
|
|
396
402
|
} else {
|
|
397
|
-
screen.write(colors.red + '
|
|
403
|
+
screen.write(colors.red + ' ' + i18n.tSync('navigation.invalid_selection', items.length) + colors.reset + '\n');
|
|
398
404
|
}
|
|
399
405
|
});
|
|
400
406
|
}
|
package/lib/ui/screen.js
CHANGED
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screen Singleton - ANSI terminal rendering layer
|
|
3
|
-
*
|
|
4
|
-
* All UI output goes through this module. Eliminates position drift
|
|
5
|
-
* by using absolute cursor positioning (cursorHome + clearScreen)
|
|
6
|
-
* and alternate screen buffer for isolation.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const ANSI = {
|
|
10
|
-
enterAltScreen: '\x1b[?1049h',
|
|
11
|
-
exitAltScreen: '\x1b[?1049l',
|
|
12
|
-
cursorHome: '\x1b[H',
|
|
13
|
-
clearScreen: '\x1b[2J',
|
|
14
|
-
cursorHide: '\x1b[?25l',
|
|
15
|
-
cursorShow: '\x1b[?25h',
|
|
16
|
-
reset: '\x1b[0m',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
class Screen {
|
|
20
|
-
constructor() {
|
|
21
|
-
this.inAltScreen = false;
|
|
22
|
-
this.isTTY = process.stdout.isTTY || false;
|
|
23
|
-
this.noAlt = process.env.SCREEN_NO_ALT === '1';
|
|
24
|
-
this.testMode = process.env.SCREEN_TEST === '1';
|
|
25
|
-
this.readlineActive = false;
|
|
26
|
-
this.currentTag = null;
|
|
27
|
-
this._log = [];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
enter() {
|
|
31
|
-
if (!this.isTTY) return;
|
|
32
|
-
if (this.inAltScreen) return;
|
|
33
|
-
if (!this.noAlt) {
|
|
34
|
-
this._rawWrite(ANSI.enterAltScreen);
|
|
35
|
-
}
|
|
36
|
-
this._rawWrite(ANSI.cursorHide);
|
|
37
|
-
this.inAltScreen = true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
exit() {
|
|
41
|
-
if (!this.inAltScreen) return;
|
|
42
|
-
this._rawWrite(ANSI.cursorShow);
|
|
43
|
-
if (!this.noAlt) {
|
|
44
|
-
this._rawWrite(ANSI.exitAltScreen);
|
|
45
|
-
}
|
|
46
|
-
this.inAltScreen = false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
exitForHandoff() {
|
|
50
|
-
if (!this.inAltScreen) return;
|
|
51
|
-
this._rawWrite(ANSI.cursorShow);
|
|
52
|
-
this._rawWrite(ANSI.reset);
|
|
53
|
-
if (!this.noAlt) {
|
|
54
|
-
this._rawWrite(ANSI.exitAltScreen);
|
|
55
|
-
}
|
|
56
|
-
if (this.isTTY && process.stdin.isTTY) {
|
|
57
|
-
try { process.stdin.setRawMode(false); } catch (_) {}
|
|
58
|
-
}
|
|
59
|
-
this.inAltScreen = false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
render(lines) {
|
|
63
|
-
this.currentTag = 'render';
|
|
64
|
-
if (this.isTTY) {
|
|
65
|
-
this._rawWrite(ANSI.cursorHome + ANSI.clearScreen);
|
|
66
|
-
}
|
|
67
|
-
for (const line of lines) {
|
|
68
|
-
this._rawWrite(line + '\n');
|
|
69
|
-
}
|
|
70
|
-
this.currentTag = null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
write(text) {
|
|
74
|
-
this.currentTag = 'write';
|
|
75
|
-
this._rawWrite(text);
|
|
76
|
-
this.currentTag = null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
showCursor() {
|
|
80
|
-
if (this.isTTY) {
|
|
81
|
-
this._rawWrite(ANSI.cursorShow);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
hideCursor() {
|
|
86
|
-
if (this.isTTY) {
|
|
87
|
-
this._rawWrite(ANSI.cursorHide);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
isActive() {
|
|
92
|
-
return this.inAltScreen;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
debug(message) {
|
|
96
|
-
if (this.inAltScreen) return; // Suppress during alt-screen
|
|
97
|
-
this._rawStderr(message + '\n');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
setReadlineActive(active) {
|
|
101
|
-
this.readlineActive = active;
|
|
102
|
-
this.currentTag = active ? 'readline' : null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
getLog() {
|
|
106
|
-
return this._log;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
_rawWrite(data) {
|
|
110
|
-
if (this.testMode) {
|
|
111
|
-
const tag = this.readlineActive ? 'readline' : (this.currentTag || 'untagged');
|
|
112
|
-
this._log.push({ channel: 'stdout', tag, data: data.substring(0, 80), time: Date.now() });
|
|
113
|
-
}
|
|
114
|
-
process.stdout.write(data);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
_rawStderr(data) {
|
|
118
|
-
if (this.testMode) {
|
|
119
|
-
this._log.push({ channel: 'stderr', tag: 'debug', data: data.substring(0, 80), time: Date.now() });
|
|
120
|
-
}
|
|
121
|
-
process.stderr.write(data);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
module.exports = new Screen();
|
|
1
|
+
/**
|
|
2
|
+
* Screen Singleton - ANSI terminal rendering layer
|
|
3
|
+
*
|
|
4
|
+
* All UI output goes through this module. Eliminates position drift
|
|
5
|
+
* by using absolute cursor positioning (cursorHome + clearScreen)
|
|
6
|
+
* and alternate screen buffer for isolation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const ANSI = {
|
|
10
|
+
enterAltScreen: '\x1b[?1049h',
|
|
11
|
+
exitAltScreen: '\x1b[?1049l',
|
|
12
|
+
cursorHome: '\x1b[H',
|
|
13
|
+
clearScreen: '\x1b[2J',
|
|
14
|
+
cursorHide: '\x1b[?25l',
|
|
15
|
+
cursorShow: '\x1b[?25h',
|
|
16
|
+
reset: '\x1b[0m',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
class Screen {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.inAltScreen = false;
|
|
22
|
+
this.isTTY = process.stdout.isTTY || false;
|
|
23
|
+
this.noAlt = process.env.SCREEN_NO_ALT === '1';
|
|
24
|
+
this.testMode = process.env.SCREEN_TEST === '1';
|
|
25
|
+
this.readlineActive = false;
|
|
26
|
+
this.currentTag = null;
|
|
27
|
+
this._log = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
enter() {
|
|
31
|
+
if (!this.isTTY) return;
|
|
32
|
+
if (this.inAltScreen) return;
|
|
33
|
+
if (!this.noAlt) {
|
|
34
|
+
this._rawWrite(ANSI.enterAltScreen);
|
|
35
|
+
}
|
|
36
|
+
this._rawWrite(ANSI.cursorHide);
|
|
37
|
+
this.inAltScreen = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exit() {
|
|
41
|
+
if (!this.inAltScreen) return;
|
|
42
|
+
this._rawWrite(ANSI.cursorShow);
|
|
43
|
+
if (!this.noAlt) {
|
|
44
|
+
this._rawWrite(ANSI.exitAltScreen);
|
|
45
|
+
}
|
|
46
|
+
this.inAltScreen = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exitForHandoff() {
|
|
50
|
+
if (!this.inAltScreen) return;
|
|
51
|
+
this._rawWrite(ANSI.cursorShow);
|
|
52
|
+
this._rawWrite(ANSI.reset);
|
|
53
|
+
if (!this.noAlt) {
|
|
54
|
+
this._rawWrite(ANSI.exitAltScreen);
|
|
55
|
+
}
|
|
56
|
+
if (this.isTTY && process.stdin.isTTY) {
|
|
57
|
+
try { process.stdin.setRawMode(false); } catch (_) {}
|
|
58
|
+
}
|
|
59
|
+
this.inAltScreen = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
render(lines) {
|
|
63
|
+
this.currentTag = 'render';
|
|
64
|
+
if (this.isTTY) {
|
|
65
|
+
this._rawWrite(ANSI.cursorHome + ANSI.clearScreen);
|
|
66
|
+
}
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
this._rawWrite(line + '\n');
|
|
69
|
+
}
|
|
70
|
+
this.currentTag = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
write(text) {
|
|
74
|
+
this.currentTag = 'write';
|
|
75
|
+
this._rawWrite(text);
|
|
76
|
+
this.currentTag = null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
showCursor() {
|
|
80
|
+
if (this.isTTY) {
|
|
81
|
+
this._rawWrite(ANSI.cursorShow);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hideCursor() {
|
|
86
|
+
if (this.isTTY) {
|
|
87
|
+
this._rawWrite(ANSI.cursorHide);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isActive() {
|
|
92
|
+
return this.inAltScreen;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
debug(message) {
|
|
96
|
+
if (this.inAltScreen) return; // Suppress during alt-screen
|
|
97
|
+
this._rawStderr(message + '\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setReadlineActive(active) {
|
|
101
|
+
this.readlineActive = active;
|
|
102
|
+
this.currentTag = active ? 'readline' : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getLog() {
|
|
106
|
+
return this._log;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_rawWrite(data) {
|
|
110
|
+
if (this.testMode) {
|
|
111
|
+
const tag = this.readlineActive ? 'readline' : (this.currentTag || 'untagged');
|
|
112
|
+
this._log.push({ channel: 'stdout', tag, data: data.substring(0, 80), time: Date.now() });
|
|
113
|
+
}
|
|
114
|
+
process.stdout.write(data);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_rawStderr(data) {
|
|
118
|
+
if (this.testMode) {
|
|
119
|
+
this._log.push({ channel: 'stderr', tag: 'debug', data: data.substring(0, 80), time: Date.now() });
|
|
120
|
+
}
|
|
121
|
+
process.stderr.write(data);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = new Screen();
|
|
@@ -30,11 +30,10 @@ async function loadConfig() {
|
|
|
30
30
|
if (config.disableTelemetry === undefined) config.disableTelemetry = true;
|
|
31
31
|
if (config.showModelUpgradeNotification === undefined) config.showModelUpgradeNotification = true;
|
|
32
32
|
if (config.apiLaunchMode === undefined) config.apiLaunchMode = 'direct';
|
|
33
|
+
if (config.noFlicker === undefined) config.noFlicker = true;
|
|
33
34
|
|
|
34
35
|
return config;
|
|
35
36
|
} catch (error) {
|
|
36
|
-
// Return default config if file doesn't exist
|
|
37
|
-
// Default language must be 'en' to match LanguageManager default
|
|
38
37
|
const defaultConfig = {
|
|
39
38
|
language: 'en',
|
|
40
39
|
lastVersionCheck: 0,
|
|
@@ -43,7 +42,8 @@ async function loadConfig() {
|
|
|
43
42
|
lastModelUpgradeCheck: 0,
|
|
44
43
|
disableTelemetry: true,
|
|
45
44
|
showModelUpgradeNotification: true,
|
|
46
|
-
apiLaunchMode: 'direct'
|
|
45
|
+
apiLaunchMode: 'direct',
|
|
46
|
+
noFlicker: false,
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
// Write default config on first run so subsequent reads are consistent
|
|
@@ -281,10 +281,10 @@ function loadConfigSync() {
|
|
|
281
281
|
if (config.disableTelemetry === undefined) config.disableTelemetry = true;
|
|
282
282
|
if (config.showModelUpgradeNotification === undefined) config.showModelUpgradeNotification = true;
|
|
283
283
|
if (config.apiLaunchMode === undefined) config.apiLaunchMode = 'direct';
|
|
284
|
+
if (config.noFlicker === undefined) config.noFlicker = true;
|
|
284
285
|
|
|
285
286
|
return config;
|
|
286
287
|
} catch (_) {
|
|
287
|
-
// Default language must be 'en' to match LanguageManager default
|
|
288
288
|
const defaultConfig = {
|
|
289
289
|
language: 'en',
|
|
290
290
|
lastVersionCheck: 0,
|
|
@@ -293,7 +293,8 @@ function loadConfigSync() {
|
|
|
293
293
|
lastModelUpgradeCheck: 0,
|
|
294
294
|
disableTelemetry: true,
|
|
295
295
|
showModelUpgradeNotification: true,
|
|
296
|
-
apiLaunchMode: 'direct'
|
|
296
|
+
apiLaunchMode: 'direct',
|
|
297
|
+
noFlicker: false,
|
|
297
298
|
};
|
|
298
299
|
|
|
299
300
|
// Write default config on first run so subsequent reads are consistent
|
package/lib/validators.js
CHANGED
|
@@ -120,11 +120,112 @@ function maskApiToken(token) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// ============================================================
|
|
124
|
+
// Environment variable constants & validators
|
|
125
|
+
// ============================================================
|
|
126
|
+
|
|
127
|
+
const RESERVED_ENV_KEYS = [
|
|
128
|
+
'ANTHROPIC_BASE_URL',
|
|
129
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
130
|
+
'ANTHROPIC_API_KEY',
|
|
131
|
+
'ANTHROPIC_MODEL',
|
|
132
|
+
'ANTHROPIC_SMALL_FAST_MODEL',
|
|
133
|
+
'CLAUDE_CODE_OAUTH_TOKEN',
|
|
134
|
+
'DISABLE_TELEMETRY',
|
|
135
|
+
'CLAUDE_CODE_NO_FLICKER',
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const PREDEFINED_RUNTIME_KEYS = [
|
|
139
|
+
'API_TIMEOUT_MS',
|
|
140
|
+
'CLAUDE_CODE_ATTRIBUTION_HEADER',
|
|
141
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
|
|
142
|
+
'CLAUDE_CODE_EFFORT_LEVEL',
|
|
143
|
+
'CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS',
|
|
144
|
+
'CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const PREDEFINED_MODEL_ENV_KEYS = [
|
|
148
|
+
'ANTHROPIC_CUSTOM_MODEL_OPTION',
|
|
149
|
+
'ANTHROPIC_CUSTOM_MODEL_OPTION_NAME',
|
|
150
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
151
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
152
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
153
|
+
'CLAUDE_CODE_SUBAGENT_MODEL',
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
const TYPE_A_FIELDS = [
|
|
157
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
|
|
158
|
+
'CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS',
|
|
159
|
+
'CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK',
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const TYPE_B_FIELDS = [
|
|
163
|
+
'CLAUDE_CODE_ATTRIBUTION_HEADER',
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const ALL_PREDEFINED_KEYS = new Set([
|
|
167
|
+
...RESERVED_ENV_KEYS,
|
|
168
|
+
...PREDEFINED_RUNTIME_KEYS,
|
|
169
|
+
...PREDEFINED_MODEL_ENV_KEYS,
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
function validateEnvKey(key) {
|
|
173
|
+
if (typeof key !== 'string' || key.trim() === '') {
|
|
174
|
+
return { valid: false, error: 'custom_env_key_empty' };
|
|
175
|
+
}
|
|
176
|
+
if (ALL_PREDEFINED_KEYS.has(key.trim())) {
|
|
177
|
+
return { valid: false, error: 'custom_env_key_reserved' };
|
|
178
|
+
}
|
|
179
|
+
return { valid: true, value: key.trim() };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function validateTypeATriState(value) {
|
|
183
|
+
if (value === '' || value === '1' || value === 'off') {
|
|
184
|
+
return { valid: true, value };
|
|
185
|
+
}
|
|
186
|
+
return { valid: false, error: 'tri_state_type_a_invalid' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function validateTypeBTriState(value) {
|
|
190
|
+
if (value === '' || value === '1' || value === '0') {
|
|
191
|
+
return { valid: true, value };
|
|
192
|
+
}
|
|
193
|
+
return { valid: false, error: 'tri_state_type_b_invalid' };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function validateRuntimeEnvValue(key, value) {
|
|
197
|
+
if (typeof value !== 'string') {
|
|
198
|
+
return { valid: false, error: 'env_value_not_string' };
|
|
199
|
+
}
|
|
200
|
+
if (TYPE_A_FIELDS.includes(key)) return validateTypeATriState(value);
|
|
201
|
+
if (TYPE_B_FIELDS.includes(key)) return validateTypeBTriState(value);
|
|
202
|
+
if (key === 'API_TIMEOUT_MS') {
|
|
203
|
+
if (value === '') return { valid: true, value };
|
|
204
|
+
if (/^\d+$/.test(value) && parseInt(value, 10) > 0) return { valid: true, value };
|
|
205
|
+
return { valid: false, error: 'env_value_timeout_invalid' };
|
|
206
|
+
}
|
|
207
|
+
if (key === 'CLAUDE_CODE_EFFORT_LEVEL') {
|
|
208
|
+
if (value === '') return { valid: true, value };
|
|
209
|
+
if (['low','medium','high','xhigh','max','auto'].includes(value)) return { valid: true, value };
|
|
210
|
+
return { valid: false, error: 'env_value_effort_invalid' };
|
|
211
|
+
}
|
|
212
|
+
return { valid: true, value };
|
|
213
|
+
}
|
|
214
|
+
|
|
123
215
|
module.exports = {
|
|
124
216
|
validateBaseUrl,
|
|
125
217
|
validateAuthToken,
|
|
126
218
|
validateModel,
|
|
127
219
|
validateApiName,
|
|
128
220
|
maskSensitiveData,
|
|
129
|
-
maskApiToken
|
|
221
|
+
maskApiToken,
|
|
222
|
+
RESERVED_ENV_KEYS,
|
|
223
|
+
PREDEFINED_RUNTIME_KEYS,
|
|
224
|
+
PREDEFINED_MODEL_ENV_KEYS,
|
|
225
|
+
TYPE_A_FIELDS,
|
|
226
|
+
TYPE_B_FIELDS,
|
|
227
|
+
validateEnvKey,
|
|
228
|
+
validateTypeATriState,
|
|
229
|
+
validateTypeBTriState,
|
|
230
|
+
validateRuntimeEnvValue,
|
|
130
231
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kikkimo/claude-launcher",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Interactive launcher for Claude Code with beautiful Claude-style interface",
|
|
5
5
|
"main": "claude-launcher",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node claude-launcher",
|
|
11
|
-
"test": "node test/providers.test.js && node test/menu-hints.test.js && node test/version-checker.test.js && node test/api-manager.test.js && node test/launcher.test.js && node test/config-management.test.js && node test/api-select.test.js",
|
|
11
|
+
"test": "node test/providers.test.js && node test/menu-hints.test.js && node test/version-checker.test.js && node test/api-manager.test.js && node test/launcher.test.js && node test/config-management.test.js && node test/api-select.test.js && node test/env-vars-validators.test.js && node test/env-vars-providers.test.js && node test/env-vars-config.test.js && node test/env-vars-migration.test.js && node test/env-vars-add-api.test.js && node test/env-vars-write-interfaces.test.js && node test/env-vars-import-export.test.js && node test/env-vars-ui.test.js",
|
|
12
12
|
"prepublishOnly": "echo \"Publishing claude-launcher...\"",
|
|
13
13
|
"postinstall": "echo \"Claude Launcher installed successfully! Run 'claude-launcher' to start.\"",
|
|
14
14
|
"publish:public": "npm publish --access public"
|