@kikkimo/claude-launcher 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/README.md +103 -41
- package/claude-launcher +1071 -576
- package/docs/README-zh.md +107 -45
- package/lib/api-manager.js +449 -0
- package/lib/auth/password-input.js +158 -0
- package/lib/auth/password-strength.js +154 -0
- package/lib/auth/password-validator.js +255 -0
- package/lib/crypto.js +85 -0
- package/lib/i18n/formatter.js +62 -0
- package/lib/i18n/index.js +218 -0
- package/lib/i18n/language-manager.js +160 -0
- package/lib/i18n/locales/de.js +538 -0
- package/lib/i18n/locales/en.js +539 -0
- package/lib/i18n/locales/es.js +538 -0
- package/lib/i18n/locales/fr.js +538 -0
- package/lib/i18n/locales/it.js +539 -0
- package/lib/i18n/locales/ja.js +538 -0
- package/lib/i18n/locales/ko.js +538 -0
- package/lib/i18n/locales/pt.js +539 -0
- package/lib/i18n/locales/ru.js +539 -0
- package/lib/i18n/locales/zh-TW.js +538 -0
- package/lib/i18n/locales/zh.js +538 -0
- package/lib/launcher.js +359 -0
- package/lib/presets/providers.js +148 -0
- package/lib/ui/colors.js +32 -0
- package/lib/ui/interactive-table.js +338 -0
- package/lib/ui/menu.js +383 -0
- package/lib/ui/prompts.js +571 -0
- package/lib/utils/stdin-manager.js +715 -0
- package/lib/utils/string-width.js +180 -0
- package/lib/utils/version-checker.js +240 -0
- package/lib/validators.js +130 -0
- package/package.json +2 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Width Utilities - Handle multi-language character width calculations
|
|
3
|
+
* Properly calculates display width for CJK (Chinese, Japanese, Korean) characters
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get the display width of a string in terminal
|
|
8
|
+
* @param {string} str - The string to measure
|
|
9
|
+
* @returns {number} - Display width in terminal columns
|
|
10
|
+
*/
|
|
11
|
+
function getStringWidth(str) {
|
|
12
|
+
if (!str || typeof str !== 'string') {
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let width = 0;
|
|
17
|
+
|
|
18
|
+
// Remove ANSI color codes for accurate width calculation
|
|
19
|
+
const cleanStr = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
20
|
+
|
|
21
|
+
for (const char of cleanStr) {
|
|
22
|
+
const code = char.codePointAt(0);
|
|
23
|
+
|
|
24
|
+
if (code == null) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// East Asian Full Width characters (including Chinese)
|
|
29
|
+
if (isFullWidth(code)) {
|
|
30
|
+
width += 2;
|
|
31
|
+
}
|
|
32
|
+
// Control characters (width 0)
|
|
33
|
+
else if (isControlCharacter(code)) {
|
|
34
|
+
width += 0;
|
|
35
|
+
}
|
|
36
|
+
// Normal characters (width 1)
|
|
37
|
+
else {
|
|
38
|
+
width += 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return width;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Pad string to specified display width (considering CJK characters)
|
|
47
|
+
* @param {string} str - String to pad
|
|
48
|
+
* @param {number} targetWidth - Target display width
|
|
49
|
+
* @param {string} padChar - Character to pad with (default: space)
|
|
50
|
+
* @param {string} padDirection - 'end' or 'start' (default: 'end')
|
|
51
|
+
* @returns {string} - Padded string
|
|
52
|
+
*/
|
|
53
|
+
function padStringToWidth(str, targetWidth, padChar = ' ', padDirection = 'end') {
|
|
54
|
+
const currentWidth = getStringWidth(str);
|
|
55
|
+
const paddingNeeded = Math.max(0, targetWidth - currentWidth);
|
|
56
|
+
const padding = padChar.repeat(paddingNeeded);
|
|
57
|
+
|
|
58
|
+
return padDirection === 'start' ? padding + str : str + padding;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Truncate string to specified display width (considering CJK characters)
|
|
63
|
+
* @param {string} str - String to truncate
|
|
64
|
+
* @param {number} maxWidth - Maximum display width
|
|
65
|
+
* @param {string} ellipsis - Ellipsis string (default: '...')
|
|
66
|
+
* @returns {string} - Truncated string
|
|
67
|
+
*/
|
|
68
|
+
function truncateStringToWidth(str, maxWidth, ellipsis = '...') {
|
|
69
|
+
const ellipsisWidth = getStringWidth(ellipsis);
|
|
70
|
+
|
|
71
|
+
if (getStringWidth(str) <= maxWidth) {
|
|
72
|
+
return str;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (maxWidth <= ellipsisWidth) {
|
|
76
|
+
return ellipsis.substring(0, maxWidth);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let width = 0;
|
|
80
|
+
let result = '';
|
|
81
|
+
|
|
82
|
+
for (const char of str) {
|
|
83
|
+
const charWidth = getStringWidth(char);
|
|
84
|
+
|
|
85
|
+
if (width + charWidth + ellipsisWidth > maxWidth) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
result += char;
|
|
90
|
+
width += charWidth;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result + ellipsis;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if a character code represents a full-width character
|
|
98
|
+
* @param {number} code - Unicode code point
|
|
99
|
+
* @returns {boolean} - True if full-width
|
|
100
|
+
*/
|
|
101
|
+
function isFullWidth(code) {
|
|
102
|
+
// Based on Unicode East Asian Width property
|
|
103
|
+
// Full Width (F) and Wide (W) characters
|
|
104
|
+
return (
|
|
105
|
+
// CJK Unified Ideographs
|
|
106
|
+
(code >= 0x4E00 && code <= 0x9FFF) ||
|
|
107
|
+
// CJK Compatibility Ideographs
|
|
108
|
+
(code >= 0xF900 && code <= 0xFAFF) ||
|
|
109
|
+
// CJK Unified Ideographs Extension A
|
|
110
|
+
(code >= 0x3400 && code <= 0x4DBF) ||
|
|
111
|
+
// CJK Unified Ideographs Extension B
|
|
112
|
+
(code >= 0x20000 && code <= 0x2A6DF) ||
|
|
113
|
+
// CJK Unified Ideographs Extension C
|
|
114
|
+
(code >= 0x2A700 && code <= 0x2B73F) ||
|
|
115
|
+
// CJK Unified Ideographs Extension D
|
|
116
|
+
(code >= 0x2B740 && code <= 0x2B81F) ||
|
|
117
|
+
// CJK Unified Ideographs Extension E
|
|
118
|
+
(code >= 0x2B820 && code <= 0x2CEAF) ||
|
|
119
|
+
// CJK Symbols and Punctuation
|
|
120
|
+
(code >= 0x3000 && code <= 0x303F) ||
|
|
121
|
+
// Hiragana
|
|
122
|
+
(code >= 0x3040 && code <= 0x309F) ||
|
|
123
|
+
// Katakana
|
|
124
|
+
(code >= 0x30A0 && code <= 0x30FF) ||
|
|
125
|
+
// Halfwidth and Fullwidth Forms (Fullwidth part)
|
|
126
|
+
(code >= 0xFF01 && code <= 0xFF60) ||
|
|
127
|
+
// CJK Compatibility Forms
|
|
128
|
+
(code >= 0xFE30 && code <= 0xFE4F) ||
|
|
129
|
+
// Other common full-width characters
|
|
130
|
+
code === 0x3000 || // Ideographic space
|
|
131
|
+
code === 0xFF0C || // Fullwidth comma
|
|
132
|
+
code === 0xFF0E || // Fullwidth full stop
|
|
133
|
+
code === 0xFF1A || // Fullwidth colon
|
|
134
|
+
code === 0xFF1B || // Fullwidth semicolon
|
|
135
|
+
code === 0xFF1F || // Fullwidth question mark
|
|
136
|
+
code === 0xFF01 // Fullwidth exclamation mark
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if a character code represents a control character
|
|
142
|
+
* @param {number} code - Unicode code point
|
|
143
|
+
* @returns {boolean} - True if control character
|
|
144
|
+
*/
|
|
145
|
+
function isControlCharacter(code) {
|
|
146
|
+
return (
|
|
147
|
+
// C0 Controls
|
|
148
|
+
(code >= 0x0000 && code <= 0x001F) ||
|
|
149
|
+
// DEL
|
|
150
|
+
code === 0x007F ||
|
|
151
|
+
// C1 Controls
|
|
152
|
+
(code >= 0x0080 && code <= 0x009F)
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a line with proper alignment for mixed-width content
|
|
158
|
+
* @param {string} left - Left content
|
|
159
|
+
* @param {string} right - Right content
|
|
160
|
+
* @param {number} totalWidth - Total line width
|
|
161
|
+
* @param {string} fillChar - Fill character (default: space)
|
|
162
|
+
* @returns {string} - Aligned line
|
|
163
|
+
*/
|
|
164
|
+
function createAlignedLine(left, right, totalWidth, fillChar = ' ') {
|
|
165
|
+
const leftWidth = getStringWidth(left);
|
|
166
|
+
const rightWidth = getStringWidth(right);
|
|
167
|
+
const fillWidth = Math.max(0, totalWidth - leftWidth - rightWidth);
|
|
168
|
+
const fill = fillChar.repeat(fillWidth);
|
|
169
|
+
|
|
170
|
+
return left + fill + right;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
getStringWidth,
|
|
175
|
+
padStringToWidth,
|
|
176
|
+
truncateStringToWidth,
|
|
177
|
+
createAlignedLine,
|
|
178
|
+
isFullWidth,
|
|
179
|
+
isControlCharacter
|
|
180
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Check Module
|
|
3
|
+
* Checks for updates from npm registry with persistent config file caching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get configuration file path
|
|
13
|
+
*/
|
|
14
|
+
function getConfigPath() {
|
|
15
|
+
return path.join(os.homedir(), '.claude-launcher-config.json');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load configuration from file
|
|
20
|
+
*/
|
|
21
|
+
async function loadConfig() {
|
|
22
|
+
try {
|
|
23
|
+
const configPath = getConfigPath();
|
|
24
|
+
const data = await fs.readFile(configPath, 'utf8');
|
|
25
|
+
return JSON.parse(data);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// Return default config if file doesn't exist
|
|
28
|
+
return {
|
|
29
|
+
language: 'zh',
|
|
30
|
+
lastVersionCheck: 0,
|
|
31
|
+
cachedLatestVersion: null
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Save configuration to file
|
|
38
|
+
*/
|
|
39
|
+
async function saveConfig(config) {
|
|
40
|
+
try {
|
|
41
|
+
const configPath = getConfigPath();
|
|
42
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Silently fail - not critical
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fetch latest version from npm registry with timeout
|
|
50
|
+
* @param {string} packageName - Package name to check
|
|
51
|
+
* @param {number} timeoutMs - Timeout in milliseconds (default: 15000)
|
|
52
|
+
*/
|
|
53
|
+
function fetchLatestVersion(packageName, timeoutMs = 15000) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const url = `https://registry.npmjs.org/${packageName}/latest`;
|
|
56
|
+
|
|
57
|
+
const req = https.get(url, (res) => {
|
|
58
|
+
let data = '';
|
|
59
|
+
|
|
60
|
+
res.on('data', (chunk) => {
|
|
61
|
+
data += chunk;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
res.on('end', () => {
|
|
65
|
+
try {
|
|
66
|
+
const json = JSON.parse(data);
|
|
67
|
+
resolve(json.version);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
reject(new Error(`Failed to parse response: ${error.message}`));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}).on('error', (err) => {
|
|
73
|
+
reject(new Error(`Network error: ${err.message}`));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Set timeout
|
|
77
|
+
req.setTimeout(timeoutMs, () => {
|
|
78
|
+
req.destroy();
|
|
79
|
+
reject(new Error(`Request timeout (${timeoutMs}ms)`));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Compare version strings (semantic versioning)
|
|
86
|
+
* Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
|
87
|
+
*/
|
|
88
|
+
function compareVersions(v1, v2) {
|
|
89
|
+
const parts1 = v1.split('.').map(Number);
|
|
90
|
+
const parts2 = v2.split('.').map(Number);
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
93
|
+
const part1 = parts1[i] || 0;
|
|
94
|
+
const part2 = parts2[i] || 0;
|
|
95
|
+
|
|
96
|
+
if (part1 > part2) return 1;
|
|
97
|
+
if (part1 < part2) return -1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check for updates with persistent config file caching
|
|
105
|
+
* @param {boolean} force - Force check regardless of cache
|
|
106
|
+
* @param {number} intervalHours - Cache duration in hours (default: 12)
|
|
107
|
+
* @returns {Promise<{available: boolean, currentVersion: string, latestVersion: string, packageUrl: string}>}
|
|
108
|
+
*/
|
|
109
|
+
async function checkForUpdates(force = false, intervalHours = 12) {
|
|
110
|
+
const CACHE_DURATION = intervalHours * 60 * 60 * 1000; // Convert hours to milliseconds
|
|
111
|
+
try {
|
|
112
|
+
const packageInfo = require('../../package.json');
|
|
113
|
+
const currentVersion = packageInfo.version;
|
|
114
|
+
const packageName = packageInfo.name;
|
|
115
|
+
const packageUrl = `https://www.npmjs.com/package/${packageName}`;
|
|
116
|
+
|
|
117
|
+
// Load config to check cache
|
|
118
|
+
const config = await loadConfig();
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
|
|
121
|
+
// Check if we need to fetch the online version
|
|
122
|
+
const needsCheck = force ||
|
|
123
|
+
!config.cachedLatestVersion ||
|
|
124
|
+
!config.lastVersionCheck ||
|
|
125
|
+
(now - config.lastVersionCheck > CACHE_DURATION);
|
|
126
|
+
|
|
127
|
+
let latestVersion;
|
|
128
|
+
|
|
129
|
+
if (needsCheck) {
|
|
130
|
+
// Need to check online
|
|
131
|
+
try {
|
|
132
|
+
latestVersion = await fetchLatestVersion(packageName);
|
|
133
|
+
|
|
134
|
+
// Update config file cache
|
|
135
|
+
config.cachedLatestVersion = latestVersion;
|
|
136
|
+
config.lastVersionCheck = now;
|
|
137
|
+
await saveConfig(config);
|
|
138
|
+
|
|
139
|
+
} catch (networkError) {
|
|
140
|
+
// Use cached version on network error, fallback to current version if no cache
|
|
141
|
+
latestVersion = config.cachedLatestVersion || currentVersion;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// Use cached version from config file
|
|
145
|
+
latestVersion = config.cachedLatestVersion;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Compare versions
|
|
149
|
+
const updateAvailable = compareVersions(latestVersion, currentVersion) > 0;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
available: updateAvailable,
|
|
153
|
+
currentVersion,
|
|
154
|
+
latestVersion,
|
|
155
|
+
packageUrl,
|
|
156
|
+
fromCache: !needsCheck
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
} catch (error) {
|
|
160
|
+
// Return current version info on error
|
|
161
|
+
const packageInfo = require('../../package.json');
|
|
162
|
+
return {
|
|
163
|
+
available: false,
|
|
164
|
+
currentVersion: packageInfo.version,
|
|
165
|
+
latestVersion: packageInfo.version,
|
|
166
|
+
packageUrl: `https://www.npmjs.com/package/${packageInfo.name}`,
|
|
167
|
+
error: true
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Clear the config file cache (force next check to be from network)
|
|
174
|
+
*/
|
|
175
|
+
async function clearCache() {
|
|
176
|
+
try {
|
|
177
|
+
const config = await loadConfig();
|
|
178
|
+
config.lastVersionCheck = 0;
|
|
179
|
+
config.cachedLatestVersion = null;
|
|
180
|
+
await saveConfig(config);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
// Silently fail
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Force check for updates (always check online, ignore cache, with custom timeout)
|
|
188
|
+
* @param {number} timeoutMs - Timeout in milliseconds (default: 15000)
|
|
189
|
+
* @returns {Promise<{available: boolean, currentVersion: string, latestVersion: string, packageUrl: string, error?: string}>}
|
|
190
|
+
*/
|
|
191
|
+
async function forceCheckForUpdates(timeoutMs = 15000) {
|
|
192
|
+
try {
|
|
193
|
+
const packageInfo = require('../../package.json');
|
|
194
|
+
const currentVersion = packageInfo.version;
|
|
195
|
+
const packageName = packageInfo.name;
|
|
196
|
+
const packageUrl = `https://www.npmjs.com/package/${packageName}`;
|
|
197
|
+
|
|
198
|
+
// Always fetch from network
|
|
199
|
+
const latestVersion = await fetchLatestVersion(packageName, timeoutMs);
|
|
200
|
+
|
|
201
|
+
// Update config cache with new result
|
|
202
|
+
try {
|
|
203
|
+
const config = await loadConfig();
|
|
204
|
+
config.cachedLatestVersion = latestVersion;
|
|
205
|
+
config.lastVersionCheck = Date.now();
|
|
206
|
+
await saveConfig(config);
|
|
207
|
+
} catch (configError) {
|
|
208
|
+
// Ignore config save errors
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Compare versions
|
|
212
|
+
const updateAvailable = compareVersions(latestVersion, currentVersion) > 0;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
available: updateAvailable,
|
|
216
|
+
currentVersion,
|
|
217
|
+
latestVersion,
|
|
218
|
+
packageUrl
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
} catch (error) {
|
|
222
|
+
// Return error information
|
|
223
|
+
const packageInfo = require('../../package.json');
|
|
224
|
+
return {
|
|
225
|
+
available: false,
|
|
226
|
+
currentVersion: packageInfo.version,
|
|
227
|
+
latestVersion: packageInfo.version,
|
|
228
|
+
packageUrl: `https://www.npmjs.com/package/${packageInfo.name}`,
|
|
229
|
+
error: error.message
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
checkForUpdates,
|
|
236
|
+
forceCheckForUpdates,
|
|
237
|
+
clearCache,
|
|
238
|
+
loadConfig,
|
|
239
|
+
saveConfig
|
|
240
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validators Module - Input validation functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const i18n = require('./i18n');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate base URL format
|
|
9
|
+
*/
|
|
10
|
+
function validateBaseUrl(url) {
|
|
11
|
+
if (!url || url.trim() === '') {
|
|
12
|
+
return { valid: false, error: i18n.tSync('errors.validation.base_url_empty') };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const urlObj = new URL(url);
|
|
17
|
+
|
|
18
|
+
// Ensure it's HTTP or HTTPS
|
|
19
|
+
if (!['http:', 'https:'].includes(urlObj.protocol)) {
|
|
20
|
+
return { valid: false, error: 'URL must use HTTP or HTTPS protocol' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return { valid: true, value: url.trim() };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return { valid: false, error: i18n.tSync('errors.validation.invalid_url_format') };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate authentication token
|
|
31
|
+
*/
|
|
32
|
+
function validateAuthToken(token) {
|
|
33
|
+
if (!token || token.trim() === '') {
|
|
34
|
+
return { valid: false, error: i18n.tSync('errors.validation.auth_token_empty') };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (token.length < 10) {
|
|
38
|
+
return { valid: false, error: i18n.tSync('errors.validation.auth_token_too_short') };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { valid: true, value: token.trim() };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate model name
|
|
46
|
+
*/
|
|
47
|
+
function validateModel(model) {
|
|
48
|
+
if (!model || model.trim() === '') {
|
|
49
|
+
return { valid: false, error: i18n.tSync('errors.validation.model_name_empty') };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check for common model name patterns
|
|
53
|
+
const validPatterns = [
|
|
54
|
+
/^claude-/i, // Claude models
|
|
55
|
+
/^gpt-/i, // OpenAI models
|
|
56
|
+
/^gemini-/i, // Google models
|
|
57
|
+
/^llama-/i, // Meta models
|
|
58
|
+
/^mistral-/i, // Mistral models
|
|
59
|
+
/^deepseek-/i, // DeepSeek models
|
|
60
|
+
/^qwen-/i, // Qwen models
|
|
61
|
+
/^moonshot-/i, // Moonshot models
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const hasValidPattern = validPatterns.some(pattern => pattern.test(model));
|
|
65
|
+
|
|
66
|
+
if (!hasValidPattern && model.length < 3) {
|
|
67
|
+
return { valid: false, error: i18n.tSync('errors.validation.model_name_invalid') };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { valid: true, value: model.trim() };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate API name
|
|
75
|
+
*/
|
|
76
|
+
function validateApiName(name) {
|
|
77
|
+
if (!name || name.trim() === '') {
|
|
78
|
+
return { valid: true, value: '' }; // Name is optional
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (name.length > 20) {
|
|
82
|
+
return { valid: false, error: 'Name is too long (maximum 20 characters)' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for invalid characters
|
|
86
|
+
if (!/^[a-zA-Z0-9\s\-_\.]+$/.test(name)) {
|
|
87
|
+
return { valid: false, error: 'Name contains invalid characters' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { valid: true, value: name.trim() };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Mask sensitive data for display
|
|
95
|
+
*/
|
|
96
|
+
function maskSensitiveData(data, visibleChars = 4) {
|
|
97
|
+
if (!data || data.length <= visibleChars * 2) {
|
|
98
|
+
return '***';
|
|
99
|
+
}
|
|
100
|
+
return data.substring(0, visibleChars) + '***' + data.substring(data.length - visibleChars);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Mask API token for display with optimized formatting
|
|
105
|
+
*/
|
|
106
|
+
function maskApiToken(token) {
|
|
107
|
+
if (!token || typeof token !== 'string') {
|
|
108
|
+
return '***INVALID***';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle different token lengths according to requirements
|
|
112
|
+
if (token.length < 10) {
|
|
113
|
+
return '***INVALID_API***';
|
|
114
|
+
} else if (token.length >= 16) {
|
|
115
|
+
// Show first 10, last 6, middle 16 stars: sk-a53*************e2bc
|
|
116
|
+
return token.substring(0, 10) + '*'.repeat(16) + token.substring(token.length - 6);
|
|
117
|
+
} else {
|
|
118
|
+
// Length 10-15: Show first 5, last 5, middle 16 stars
|
|
119
|
+
return token.substring(0, 5) + '*'.repeat(16) + token.substring(token.length - 5);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
validateBaseUrl,
|
|
125
|
+
validateAuthToken,
|
|
126
|
+
validateModel,
|
|
127
|
+
validateApiName,
|
|
128
|
+
maskSensitiveData,
|
|
129
|
+
maskApiToken
|
|
130
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kikkimo/claude-launcher",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Interactive launcher for Claude Code with beautiful Claude-style interface",
|
|
5
5
|
"main": "claude-launcher",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"claude-launcher",
|
|
21
|
-
"
|
|
21
|
+
"lib/",
|
|
22
22
|
"README.md",
|
|
23
23
|
"LICENSE",
|
|
24
24
|
"CHANGELOG.md",
|