@pikecode/api-key-manager 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.
@@ -0,0 +1,227 @@
1
+ const chalk = require('chalk');
2
+
3
+ class UIHelper {
4
+ // 颜色主题
5
+ static colors = {
6
+ primary: chalk.cyan,
7
+ secondary: chalk.blue,
8
+ success: chalk.green,
9
+ warning: chalk.yellow,
10
+ error: chalk.red,
11
+ info: chalk.white,
12
+ muted: chalk.gray,
13
+ accent: chalk.magenta
14
+ };
15
+
16
+ // 图标
17
+ static icons = {
18
+ success: '✅',
19
+ error: '❌',
20
+ warning: '⚠️',
21
+ info: 'ℹ️',
22
+ loading: '⏳',
23
+ arrow: '→',
24
+ back: '🔙',
25
+ home: '🏠',
26
+ settings: '⚙️',
27
+ add: '➕',
28
+ edit: '✏️',
29
+ delete: '🗑️',
30
+ launch: '🚀',
31
+ list: '📋',
32
+ config: '🛠️',
33
+ current: '🎯',
34
+ search: '🔍'
35
+ };
36
+
37
+ // 创建标题
38
+ static createTitle(text, icon = '') {
39
+ const fullIcon = icon ? `${icon} ` : '';
40
+ return chalk.bold.cyan(`\n╭─────────────────────────────────────╮\n│ ${fullIcon}${chalk.white(text)}\n╰─────────────────────────────────────╯`);
41
+ }
42
+
43
+ // 创建分隔线
44
+ static createSeparator() {
45
+ return chalk.gray('─'.repeat(45));
46
+ }
47
+
48
+ // 创建项目列表
49
+ static createItem(label, value, isSelected = false) {
50
+ const icon = isSelected ? this.icons.current : '•';
51
+ const color = isSelected ? this.colors.primary : this.colors.info;
52
+ return `${color(icon)} ${label}`;
53
+ }
54
+
55
+ // 创建操作按钮
56
+ static createButton(label, action, icon = '') {
57
+ const fullIcon = icon ? `${icon} ` : '';
58
+ return `${this.colors.accent(fullIcon)}${this.colors.info(label)}`;
59
+ }
60
+
61
+ // 创建状态指示器
62
+ static createStatus(status, label) {
63
+ const statusConfig = {
64
+ current: { icon: this.icons.current, color: this.colors.success },
65
+ active: { icon: '🟢', color: this.colors.success },
66
+ inactive: { icon: '⚫', color: this.colors.muted },
67
+ loading: { icon: this.icons.loading, color: this.colors.warning },
68
+ error: { icon: this.icons.error, color: this.colors.error }
69
+ };
70
+
71
+ const config = statusConfig[status] || statusConfig.inactive;
72
+ return `${config.color(config.icon)} ${this.colors.info(label)}`;
73
+ }
74
+
75
+ // 格式化供应商信息
76
+ static formatProvider(provider) {
77
+ const status = provider.current ? 'current' : 'inactive';
78
+ const statusText = this.createStatus(status, provider.name);
79
+ const displayName = this.colors.secondary(`(${provider.displayName})`);
80
+ return `${statusText} ${displayName}`;
81
+ }
82
+
83
+ // 创建进度条
84
+ static createProgressBar(current, total, width = 30) {
85
+ const progress = Math.floor((current / total) * width);
86
+ const empty = width - progress;
87
+ const filled = '█'.repeat(progress);
88
+ const emptySpace = '░'.repeat(empty);
89
+ const percentage = Math.floor((current / total) * 100);
90
+
91
+ return `${this.colors.primary(filled)}${this.colors.muted(emptySpace)} ${this.colors.info(percentage + '%')}`;
92
+ }
93
+
94
+ // 创建表格
95
+ static createTable(headers, rows) {
96
+ const columnWidths = headers.map(header => Math.max(header.length, ...rows.map(row => String(row[headers.indexOf(header)]).length)));
97
+
98
+ let result = '';
99
+
100
+ // 表头
101
+ const headerRow = headers.map((header, i) => header.padEnd(columnWidths[i])).join(' │ ');
102
+ result += `${this.colors.primary(headerRow)}\n`;
103
+
104
+ // 分隔线
105
+ const separator = columnWidths.map(width => '─'.repeat(width)).join('─┼─');
106
+ result += `${this.colors.muted(separator)}\n`;
107
+
108
+ // 数据行
109
+ rows.forEach(row => {
110
+ const dataRow = row.map((cell, i) => String(cell).padEnd(columnWidths[i])).join(' │ ');
111
+ result += `${this.colors.info(dataRow)}\n`;
112
+ });
113
+
114
+ return result;
115
+ }
116
+
117
+ // 创建卡片式布局
118
+ static createCard(title, content, icon = '') {
119
+ const lines = content.split('\n');
120
+ const maxLineLength = Math.max(...lines.map(line => line.length));
121
+ const horizontalBorder = '─'.repeat(maxLineLength + 4);
122
+
123
+ let result = `${this.colors.primary(`┌─${horizontalBorder}─┐`)}\n`;
124
+ result += `${this.colors.primary('│')} ${chalk.bold.white(icon ? `${icon} ` : '')}${chalk.bold.white(title)}${' '.repeat(maxLineLength - title.length - (icon ? 2 : 0))} ${this.colors.primary('│')}\n`;
125
+ result += `${this.colors.primary('├─')}${this.colors.muted(horizontalBorder)}${this.colors.primary('─┤')}\n`;
126
+
127
+ lines.forEach(line => {
128
+ result += `${this.colors.primary('│')} ${this.colors.info(line)}${' '.repeat(maxLineLength - line.length)} ${this.colors.primary('│')}\n`;
129
+ });
130
+
131
+ result += `${this.colors.primary('└─')}${this.colors.muted(horizontalBorder)}${this.colors.primary('─┘')}`;
132
+ return result;
133
+ }
134
+
135
+ // 创建操作菜单
136
+ static createMenu(title, options) {
137
+ let result = `${this.createTitle(title, this.icons.list)}\n\n`;
138
+
139
+ options.forEach((option, index) => {
140
+ const number = this.colors.muted(`[${index + 1}]`);
141
+ const icon = option.icon || '•';
142
+ const description = option.description ? this.colors.muted(` - ${option.description}`) : '';
143
+ result += `${number} ${this.colors.accent(icon)} ${this.colors.info(option.label)}${description}\n`;
144
+ });
145
+
146
+ result += `\n${this.colors.muted(this.createSeparator())}\n`;
147
+ result += `${this.colors.warning('请选择操作 (输入数字): ')}`;
148
+
149
+ return result;
150
+ }
151
+
152
+ // 创建确认对话框
153
+ static createConfirmDialog(message, options = ['确认', '取消']) {
154
+ return `${this.colors.warning(message)}\n\n` +
155
+ `${this.colors.success('[Y]')} ${this.colors.info(options[0])} ` +
156
+ `${this.colors.error('[N]')} ${this.colors.info(options[1])}`;
157
+ }
158
+
159
+ // 格式化时间
160
+ static formatTime(dateString) {
161
+ const date = new Date(dateString);
162
+ const now = new Date();
163
+ const diff = now - date;
164
+
165
+ if (diff < 60000) return '刚刚';
166
+ if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`;
167
+ if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`;
168
+ if (diff < 2592000000) return `${Math.floor(diff / 86400000)} 天前`;
169
+
170
+ return date.toLocaleDateString('zh-CN');
171
+ }
172
+
173
+ // 创建搜索框
174
+ static createSearchBox(placeholder = '搜索...') {
175
+ return `${this.colors.info(this.icons.search)} ${this.colors.muted(placeholder)}`;
176
+ }
177
+
178
+ // 创建提示框
179
+ static createTooltip(text) {
180
+ return `${this.colors.muted('💡 ' + text)}`;
181
+ }
182
+
183
+ // 创建加载动画
184
+ static createLoadingAnimation(text = '加载中...') {
185
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
186
+ let frameIndex = 0;
187
+
188
+ return setInterval(() => {
189
+ process.stdout.write(`\r${this.colors.warning(frames[frameIndex])} ${this.colors.info(text)}`);
190
+ frameIndex = (frameIndex + 1) % frames.length;
191
+ }, 100);
192
+ }
193
+
194
+ // 清除加载动画
195
+ static clearLoadingAnimation(interval) {
196
+ clearInterval(interval);
197
+ process.stdout.write('\r');
198
+ }
199
+
200
+ // 创建快捷键提示
201
+ static createShortcutHint(key, action) {
202
+ return `${this.colors.muted('[')}${this.colors.primary(key)}${this.colors.muted(']')} ${this.colors.info(action)}`;
203
+ }
204
+
205
+ // 创建 ESC 键提示
206
+ static createESCHint(action = '返回') {
207
+ return `${this.colors.muted('[')}${this.colors.primary('ESC')}${this.colors.muted(']')} ${this.colors.info(action)}`;
208
+ }
209
+
210
+ // 创建提示行
211
+ static createHintLine(pairs = []) {
212
+ if (!pairs.length) {
213
+ return '';
214
+ }
215
+ const hints = pairs.map(([key, action]) => this.createShortcutHint(key, action));
216
+ return `${this.colors.muted('提示: ')}${hints.join(this.colors.muted(' · '))}`;
217
+ }
218
+
219
+ // 创建步骤指示
220
+ static createStepIndicator(current, total, label) {
221
+ const prefix = this.colors.muted(`步骤 ${current}/${total}`);
222
+ const title = label ? ` ${this.colors.info(label)}` : '';
223
+ return `${prefix}${title}`;
224
+ }
225
+ }
226
+
227
+ module.exports = { UIHelper };
@@ -0,0 +1,121 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require('inquirer');
3
+ const spawn = require('cross-spawn');
4
+
5
+ function compareVersions(a, b) {
6
+ const pa = String(a).split('.').map(Number);
7
+ const pb = String(b).split('.').map(Number);
8
+ const len = Math.max(pa.length, pb.length);
9
+ for (let i = 0; i < len; i++) {
10
+ const na = pa[i] || 0;
11
+ const nb = pb[i] || 0;
12
+ if (na > nb) return 1;
13
+ if (na < nb) return -1;
14
+ }
15
+ return 0;
16
+ }
17
+
18
+ function getLatestVersion(pkgName, timeoutMs = 4000) {
19
+ return new Promise((resolve, reject) => {
20
+ let done = false;
21
+ const child = spawn('npm', ['view', pkgName, 'version', '--json'], {
22
+ shell: process.platform === 'win32',
23
+ stdio: ['ignore', 'pipe', 'ignore'],
24
+ });
25
+
26
+ let out = '';
27
+ child.stdout.on('data', (d) => (out += d.toString()))
28
+
29
+ const t = setTimeout(() => {
30
+ if (done) return;
31
+ done = true;
32
+ try { child.kill(); } catch {}
33
+ resolve(null);
34
+ }, timeoutMs);
35
+
36
+ child.on('error', () => {
37
+ if (done) return;
38
+ done = true;
39
+ clearTimeout(t);
40
+ resolve(null);
41
+ });
42
+
43
+ child.on('close', () => {
44
+ if (done) return;
45
+ done = true;
46
+ clearTimeout(t);
47
+ const text = (out || '').trim();
48
+ if (!text) return resolve(null);
49
+ try {
50
+ const parsed = JSON.parse(text);
51
+ if (typeof parsed === 'string') return resolve(parsed);
52
+ if (Array.isArray(parsed)) return resolve(parsed[parsed.length - 1] || null);
53
+ if (parsed && parsed.version) return resolve(parsed.version);
54
+ return resolve(String(text));
55
+ } catch {
56
+ return resolve(text);
57
+ }
58
+ });
59
+ });
60
+ }
61
+
62
+ async function checkForUpdates({ packageName, currentVersion }) {
63
+ try {
64
+ if (
65
+ process.env.NODE_ENV === 'test' ||
66
+ process.env.CC_NO_UPDATE_CHECK === '1' ||
67
+ process.env.CI === 'true'
68
+ ) {
69
+ return;
70
+ }
71
+
72
+ const latest = await getLatestVersion(packageName);
73
+ if (!latest) return;
74
+
75
+ if (compareVersions(latest, currentVersion) > 0) {
76
+ const installCmd = `npm i -g ${packageName}@latest`;
77
+ console.log('\n' + chalk.yellow(`🔔 检测到新版本 ${latest},当前版本 ${currentVersion}`));
78
+ console.log(chalk.gray('更新命令: ') + chalk.cyan(installCmd) + '\n');
79
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
80
+ if (!interactive) {
81
+ return;
82
+ }
83
+
84
+ const { doUpdate } = await inquirer.prompt([
85
+ {
86
+ type: 'confirm',
87
+ name: 'doUpdate',
88
+ message: '是否立即更新并重启?',
89
+ default: false,
90
+ },
91
+ ]);
92
+
93
+ if (!doUpdate) return;
94
+
95
+ console.log(chalk.yellow('开始更新,请稍候...'));
96
+ await new Promise((resolve) => {
97
+ const child = spawn('npm', ['i', '-g', `${packageName}@latest`], {
98
+ shell: process.platform === 'win32',
99
+ stdio: 'inherit',
100
+ });
101
+ child.on('close', (code) => resolve(code === 0));
102
+ }).then((ok) => {
103
+ if (!ok) {
104
+ console.log(chalk.red('更新失败,请稍后重试或手动执行: ') + chalk.cyan(installCmd));
105
+ return;
106
+ }
107
+ console.log(chalk.green('更新成功,正在重启...'));
108
+ const child = spawn('cc', process.argv.slice(2), {
109
+ shell: true,
110
+ stdio: 'inherit',
111
+ });
112
+ child.on('close', (code) => {
113
+ process.exit(code || 0);
114
+ });
115
+ });
116
+ }
117
+ } catch {
118
+ }
119
+ }
120
+
121
+ module.exports = { checkForUpdates };
@@ -0,0 +1,157 @@
1
+ const validator = {
2
+ validateName(name) {
3
+ if (!name || typeof name !== 'string') {
4
+ return '供应商名称不能为空';
5
+ }
6
+
7
+ if (name.trim().length === 0) {
8
+ return '供应商名称不能为空或只包含空格';
9
+ }
10
+
11
+ if (name.length > 100) {
12
+ return '供应商名称不能超过100个字符';
13
+ }
14
+
15
+ return null;
16
+ },
17
+
18
+ validateDisplayName(displayName) {
19
+ // 允许空值,表示可选
20
+ if (displayName === null || displayName === undefined || displayName === '') {
21
+ return null;
22
+ }
23
+
24
+ if (typeof displayName !== 'string') {
25
+ return '显示名称必须是字符串';
26
+ }
27
+
28
+ if (displayName.length > 100) {
29
+ return '显示名称不能超过100个字符';
30
+ }
31
+
32
+ return null;
33
+ },
34
+
35
+ validateUrl(url, required = true) {
36
+ if (!url || typeof url !== 'string') {
37
+ return required ? 'URL不能为空' : null;
38
+ }
39
+
40
+ try {
41
+ new URL(url);
42
+ } catch (error) {
43
+ return '请输入有效的URL';
44
+ }
45
+
46
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
47
+ return 'URL必须以http://或https://开头';
48
+ }
49
+
50
+ return null;
51
+ },
52
+
53
+ validateToken(token) {
54
+ if (!token || typeof token !== 'string') {
55
+ return 'Token不能为空';
56
+ }
57
+
58
+ if (token.length < 10) {
59
+ return 'Token长度不能少于10个字符';
60
+ }
61
+
62
+ return null;
63
+ },
64
+
65
+ validateModel(model) {
66
+ // 允许空值,表示可选
67
+ if (!model) {
68
+ return null;
69
+ }
70
+
71
+ if (typeof model !== 'string') {
72
+ return '模型名称必须是字符串';
73
+ }
74
+
75
+ if (model.trim().length === 0) {
76
+ return '模型名称不能为空字符串';
77
+ }
78
+
79
+ if (model.length > 100) {
80
+ return '模型名称不能超过100个字符';
81
+ }
82
+
83
+ return null;
84
+ },
85
+
86
+ validateLaunchArgs(args) {
87
+ if (!Array.isArray(args)) {
88
+ return '启动参数必须是数组';
89
+ }
90
+
91
+ const validArgs = [
92
+ '--dangerously-skip-permissions',
93
+ '--no-confirm',
94
+ '--allow-all',
95
+ '--auto-approve',
96
+ '--yes',
97
+ '--force'
98
+ ];
99
+
100
+ for (const arg of args) {
101
+ if (!validArgs.includes(arg)) {
102
+ return `无效的启动参数: ${arg}`;
103
+ }
104
+ }
105
+
106
+ return null;
107
+ },
108
+
109
+ getAvailableLaunchArgs() {
110
+ return [
111
+ {
112
+ name: '--continue',
113
+ label: '继续上次对话',
114
+ description: '恢复上次的对话记录',
115
+ checked: false
116
+ },
117
+ {
118
+ name: '--dangerously-skip-permissions',
119
+ label: '最高权限',
120
+ description: '仅限沙盒环境使用',
121
+ checked: false
122
+ },
123
+ {
124
+ name: '--no-confirm',
125
+ label: '直接执行操作',
126
+ description: '跳过确认提示',
127
+ checked: false
128
+ },
129
+ {
130
+ name: '--allow-all',
131
+ label: '允许全部操作',
132
+ description: '移除安全限制',
133
+ checked: false
134
+ },
135
+ {
136
+ name: '--auto-approve',
137
+ label: '自动批准请求',
138
+ description: '无需人工同意',
139
+ checked: false
140
+ },
141
+ {
142
+ name: '--yes',
143
+ label: '默认回答 yes',
144
+ description: '自动同意所有询问',
145
+ checked: false
146
+ },
147
+ {
148
+ name: '--force',
149
+ label: '强制执行',
150
+ description: '忽略可能的警告',
151
+ checked: false
152
+ }
153
+ ];
154
+ }
155
+ };
156
+
157
+ module.exports = { validator };