coding-tool-x 3.4.3 → 3.4.5

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.
Files changed (63) hide show
  1. package/dist/web/assets/{Analytics-CbGxotgz.js → Analytics-DFWyPf5C.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-oP6nrFEb.js → ConfigTemplates-BFE7hmKd.js} +1 -1
  3. package/dist/web/assets/{Home-DMntmEvh.js → Home-DZUuCrxk.js} +1 -1
  4. package/dist/web/assets/{PluginManager-BUC_c7nH.js → PluginManager-WyGY2BQN.js} +1 -1
  5. package/dist/web/assets/{ProjectList-CW8J49n7.js → ProjectList-CBc0QawN.js} +1 -1
  6. package/dist/web/assets/{ProjectList-oJIyIRkP.css → ProjectList-DL4JK6ci.css} +1 -1
  7. package/dist/web/assets/{SessionList-7lYnF92v.js → SessionList-CdPR7QLq.js} +1 -1
  8. package/dist/web/assets/{SkillManager-Cs08216i.js → SkillManager-B5-DxQOS.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-CY-oGtyB.js → WorkspaceManager-C7yqFjpi.js} +1 -1
  10. package/dist/web/assets/index-BDsmoSfO.js +2 -0
  11. package/dist/web/assets/{index-5qy5NMIP.css → index-C1pzEgmj.css} +1 -1
  12. package/dist/web/index.html +2 -2
  13. package/package.json +2 -2
  14. package/src/commands/channels.js +13 -13
  15. package/src/commands/cli-type.js +5 -5
  16. package/src/commands/daemon.js +31 -31
  17. package/src/commands/doctor.js +14 -14
  18. package/src/commands/export-config.js +23 -23
  19. package/src/commands/list.js +4 -4
  20. package/src/commands/logs.js +19 -19
  21. package/src/commands/plugin.js +62 -62
  22. package/src/commands/port-config.js +4 -4
  23. package/src/commands/proxy-control.js +35 -35
  24. package/src/commands/proxy.js +28 -28
  25. package/src/commands/resume.js +4 -4
  26. package/src/commands/search.js +9 -9
  27. package/src/commands/security.js +5 -5
  28. package/src/commands/stats.js +18 -18
  29. package/src/commands/switch.js +1 -1
  30. package/src/commands/toggle-proxy.js +18 -18
  31. package/src/commands/ui.js +11 -11
  32. package/src/commands/update.js +9 -9
  33. package/src/commands/workspace.js +11 -11
  34. package/src/index.js +24 -24
  35. package/src/plugins/plugin-installer.js +1 -1
  36. package/src/reset-config.js +9 -9
  37. package/src/server/api/channels.js +1 -1
  38. package/src/server/api/claude-hooks.js +3 -2
  39. package/src/server/api/plugins.js +165 -14
  40. package/src/server/api/pm2-autostart.js +2 -2
  41. package/src/server/api/proxy.js +6 -6
  42. package/src/server/api/skills.js +66 -7
  43. package/src/server/codex-proxy-server.js +10 -2
  44. package/src/server/dev-server.js +2 -2
  45. package/src/server/gemini-proxy-server.js +10 -2
  46. package/src/server/index.js +37 -37
  47. package/src/server/opencode-proxy-server.js +10 -2
  48. package/src/server/proxy-server.js +14 -6
  49. package/src/server/services/codex-channels.js +64 -21
  50. package/src/server/services/codex-env-manager.js +44 -28
  51. package/src/server/services/config-export-service.js +1 -1
  52. package/src/server/services/mcp-service.js +2 -1
  53. package/src/server/services/model-detector.js +2 -2
  54. package/src/server/services/native-keychain.js +1 -0
  55. package/src/server/services/plugins-service.js +1066 -261
  56. package/src/server/services/proxy-runtime.js +129 -5
  57. package/src/server/services/server-shutdown.js +79 -0
  58. package/src/server/services/settings-manager.js +3 -3
  59. package/src/server/services/skill-service.js +146 -29
  60. package/src/server/websocket-server.js +8 -8
  61. package/src/ui/menu.js +2 -2
  62. package/src/ui/prompts.js +5 -5
  63. package/dist/web/assets/index-ClCqKpvX.js +0 -2
@@ -1,6 +1,18 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { PATHS } = require('../../config/paths');
4
+
5
+ const LOG_RECOVERY_BYTES = 1024 * 1024;
6
+ const LOG_FILE_PATH = path.join(PATHS.logs, 'cc-tool-out.log');
7
+ const PROXY_START_LOG_PATTERNS = {
8
+ claude: [
9
+ /Proxy server started on http:\/\/127\.0\.0\.1:\d+/,
10
+ /Claude 代理已自动启动,端口: \d+/
11
+ ],
12
+ codex: [/Codex proxy server started on http:\/\/127\.0\.0\.1:\d+/],
13
+ gemini: [/Gemini proxy server started on http:\/\/127\.0\.0\.1:\d+/],
14
+ opencode: [/OpenCode proxy server started on http:\/\/127\.0\.0\.1:\d+/]
15
+ };
4
16
 
5
17
  function getRuntimeFilePath(proxyType) {
6
18
  const filePath = PATHS.proxyRuntime?.[proxyType]
@@ -27,17 +39,129 @@ function saveProxyStartTime(proxyType, preserveExisting = false) {
27
39
  }
28
40
  }
29
41
 
30
- function getProxyStartTime(proxyType) {
42
+ function toValidStartTime(value) {
43
+ const parsed = Number(value);
44
+ if (!Number.isFinite(parsed) || parsed <= 0) {
45
+ return null;
46
+ }
47
+ return Math.floor(parsed);
48
+ }
49
+
50
+ function persistRecoveredStartTime(proxyType, startTime, recoveredFrom) {
51
+ const validStartTime = toValidStartTime(startTime);
52
+ if (!validStartTime) {
53
+ return null;
54
+ }
55
+
56
+ const runtimeFilePath = getRuntimeFilePath(proxyType);
57
+ const data = {
58
+ startTime: validStartTime,
59
+ type: proxyType,
60
+ recoveredFrom
61
+ };
62
+ fs.writeFileSync(runtimeFilePath, JSON.stringify(data, null, 2), 'utf8');
63
+ return validStartTime;
64
+ }
65
+
66
+ function readStoredProxyStartTime(proxyType) {
31
67
  try {
32
68
  const filePath = getRuntimeFilePath(proxyType);
33
69
  if (!fs.existsSync(filePath)) return null;
34
70
  const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
35
- return data.startTime || null;
71
+ return toValidStartTime(data.startTime);
36
72
  } catch (err) {
37
73
  return null;
38
74
  }
39
75
  }
40
76
 
77
+ function parseTimestampPrefix(line) {
78
+ const match = String(line || '').match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}):/);
79
+ if (!match) {
80
+ return null;
81
+ }
82
+
83
+ const parsed = Date.parse(match[1].replace(' ', 'T'));
84
+ return toValidStartTime(parsed);
85
+ }
86
+
87
+ function recoverProxyStartTimeFromLogs(proxyType) {
88
+ try {
89
+ if (!fs.existsSync(LOG_FILE_PATH)) {
90
+ return null;
91
+ }
92
+
93
+ const patterns = PROXY_START_LOG_PATTERNS[proxyType];
94
+ if (!Array.isArray(patterns) || patterns.length === 0) {
95
+ return null;
96
+ }
97
+
98
+ const stat = fs.statSync(LOG_FILE_PATH);
99
+ const start = Math.max(0, stat.size - LOG_RECOVERY_BYTES);
100
+ const fd = fs.openSync(LOG_FILE_PATH, 'r');
101
+
102
+ try {
103
+ const length = stat.size - start;
104
+ const buffer = Buffer.alloc(length);
105
+ fs.readSync(fd, buffer, 0, length, start);
106
+
107
+ const lines = buffer.toString('utf8').split(/\r?\n/).reverse();
108
+ for (const line of lines) {
109
+ if (!patterns.some(pattern => pattern.test(line))) {
110
+ continue;
111
+ }
112
+
113
+ const timestamp = parseTimestampPrefix(line);
114
+ if (timestamp) {
115
+ return persistRecoveredStartTime(proxyType, timestamp, 'log');
116
+ }
117
+ }
118
+ } finally {
119
+ fs.closeSync(fd);
120
+ }
121
+ } catch (err) {
122
+ console.warn(`Failed to recover ${proxyType} proxy start time from logs:`, err.message);
123
+ }
124
+
125
+ return null;
126
+ }
127
+
128
+ function recoverProxyStartTime(proxyType) {
129
+ try {
130
+ const logRecoveredStartTime = recoverProxyStartTimeFromLogs(proxyType);
131
+ if (logRecoveredStartTime) {
132
+ return logRecoveredStartTime;
133
+ }
134
+
135
+ const activeChannelPath = PATHS.activeChannel?.[proxyType];
136
+ if (!activeChannelPath || !fs.existsSync(activeChannelPath)) {
137
+ return null;
138
+ }
139
+
140
+ const recoveredStartTime = toValidStartTime(fs.statSync(activeChannelPath).mtimeMs);
141
+ if (!recoveredStartTime) {
142
+ return null;
143
+ }
144
+
145
+ return persistRecoveredStartTime(proxyType, recoveredStartTime, 'active-channel');
146
+ } catch (err) {
147
+ console.warn(`Failed to recover ${proxyType} proxy start time from active channel:`, err.message);
148
+ return null;
149
+ }
150
+ }
151
+
152
+ function getProxyStartTime(proxyType, options = {}) {
153
+ const storedStartTime = readStoredProxyStartTime(proxyType);
154
+ if (storedStartTime) {
155
+ return storedStartTime;
156
+ }
157
+
158
+ if (options.allowRecovery) {
159
+ return recoverProxyStartTime(proxyType);
160
+ }
161
+
162
+ return null;
163
+ }
164
+
41
165
  function clearProxyStartTime(proxyType) {
42
166
  try {
43
167
  const filePath = getRuntimeFilePath(proxyType);
@@ -49,9 +173,9 @@ function clearProxyStartTime(proxyType) {
49
173
  }
50
174
  }
51
175
 
52
- function getProxyRuntime(proxyType) {
53
- const startTime = getProxyStartTime(proxyType);
54
- return startTime ? Date.now() - startTime : null;
176
+ function getProxyRuntime(proxyType, options = {}) {
177
+ const startTime = getProxyStartTime(proxyType, options);
178
+ return startTime ? Math.max(0, Date.now() - startTime) : null;
55
179
  }
56
180
 
57
181
  function formatRuntime(ms) {
@@ -0,0 +1,79 @@
1
+ const SOCKET_TRACKER = Symbol('ccTool.socketTracker');
2
+
3
+ function attachServerShutdownHandling(server, options = {}) {
4
+ if (!server || server[SOCKET_TRACKER]) {
5
+ return server;
6
+ }
7
+
8
+ const sockets = new Set();
9
+ server.on('connection', (socket) => {
10
+ sockets.add(socket);
11
+ socket.on('close', () => {
12
+ sockets.delete(socket);
13
+ });
14
+ });
15
+
16
+ const keepAliveTimeout = Number.isFinite(options.keepAliveTimeout)
17
+ ? Math.max(0, options.keepAliveTimeout)
18
+ : 1000;
19
+ const headersTimeout = Number.isFinite(options.headersTimeout)
20
+ ? Math.max(keepAliveTimeout + 1000, options.headersTimeout)
21
+ : keepAliveTimeout + 1000;
22
+
23
+ server.keepAliveTimeout = keepAliveTimeout;
24
+ server.headersTimeout = headersTimeout;
25
+ server[SOCKET_TRACKER] = { sockets };
26
+ return server;
27
+ }
28
+
29
+ function expediteServerShutdown(server, options = {}) {
30
+ if (!server) {
31
+ return null;
32
+ }
33
+
34
+ try {
35
+ if (typeof server.closeIdleConnections === 'function') {
36
+ server.closeIdleConnections();
37
+ }
38
+ } catch {
39
+ // ignore idle-connection close failures
40
+ }
41
+
42
+ const delay = Number.isFinite(options.forceAfterMs)
43
+ ? Math.max(0, options.forceAfterMs)
44
+ : 300;
45
+
46
+ const timer = setTimeout(() => {
47
+ try {
48
+ if (typeof server.closeAllConnections === 'function') {
49
+ server.closeAllConnections();
50
+ }
51
+ } catch {
52
+ // ignore force-close failures and fallback to socket destroy
53
+ }
54
+
55
+ const tracker = server[SOCKET_TRACKER];
56
+ if (!tracker?.sockets) {
57
+ return;
58
+ }
59
+
60
+ for (const socket of tracker.sockets) {
61
+ try {
62
+ socket.destroy();
63
+ } catch {
64
+ // ignore socket destroy failures
65
+ }
66
+ }
67
+ }, delay);
68
+
69
+ if (typeof timer.unref === 'function') {
70
+ timer.unref();
71
+ }
72
+
73
+ return timer;
74
+ }
75
+
76
+ module.exports = {
77
+ attachServerShutdownHandling,
78
+ expediteServerShutdown
79
+ };
@@ -57,7 +57,7 @@ function backupSettings() {
57
57
  const content = fs.readFileSync(getSettingsPath(), 'utf8');
58
58
  fs.writeFileSync(getBackupPath(), content, 'utf8');
59
59
 
60
- console.log(' Settings backed up to:', getBackupPath());
60
+ console.log('[OK] Settings backed up to:', getBackupPath());
61
61
  return { success: true, alreadyExists: false };
62
62
  } catch (err) {
63
63
  throw new Error('Failed to backup settings: ' + err.message);
@@ -77,7 +77,7 @@ function restoreSettings() {
77
77
  // 删除备份文件
78
78
  fs.unlinkSync(getBackupPath());
79
79
 
80
- console.log(' Settings restored from backup');
80
+ console.log('[OK] Settings restored from backup');
81
81
  return { success: true };
82
82
  } catch (err) {
83
83
  throw new Error('Failed to restore settings: ' + err.message);
@@ -118,7 +118,7 @@ function setProxyConfig(proxyPort) {
118
118
  // 写入
119
119
  writeSettings(settings);
120
120
 
121
- console.log(`✅ Settings updated to use proxy on port ${proxyPort}`);
121
+ console.log(`[OK] Settings updated to use proxy on port ${proxyPort}`);
122
122
  return { success: true, port: proxyPort };
123
123
  } catch (err) {
124
124
  throw new Error('Failed to set proxy config: ' + err.message);
@@ -17,6 +17,7 @@ const AdmZip = require('adm-zip');
17
17
  const {
18
18
  parseSkillContent,
19
19
  } = require('./format-converter');
20
+ const { maskToken } = require('./oauth-utils');
20
21
  const { NATIVE_PATHS, HOME_DIR, PATHS } = require('../../config/paths');
21
22
 
22
23
  const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
@@ -47,6 +48,10 @@ function stripGitSuffix(value = '') {
47
48
  return String(value || '').replace(/\.git$/i, '');
48
49
  }
49
50
 
51
+ function normalizeRepoToken(token = '') {
52
+ return String(token || '').trim();
53
+ }
54
+
50
55
  function isWindowsAbsolutePath(input = '') {
51
56
  return /^[a-zA-Z]:[\\/]/.test(String(input || ''));
52
57
  }
@@ -153,18 +158,10 @@ function isRootSkillFile(filePath = '') {
153
158
  }
154
159
 
155
160
  const DEFAULT_REPOS_BY_PLATFORM = {
156
- claude: [
157
- { owner: 'anthropics', name: 'skills', branch: 'main', directory: '', enabled: true }
158
- ],
159
- codex: [
160
- { owner: 'openai', name: 'skills', branch: 'main', directory: 'skills/.curated', enabled: true }
161
- ],
162
- gemini: [
163
- { owner: 'google-gemini', name: 'gemini-cli', branch: 'main', directory: '.gemini/skills', enabled: true }
164
- ],
165
- opencode: [
166
- { owner: 'Shakudo-io', name: 'opencode-skills', branch: 'main', directory: '', enabled: true }
167
- ]
161
+ claude: [],
162
+ codex: [],
163
+ gemini: [],
164
+ opencode: []
168
165
  };
169
166
 
170
167
  const PLATFORM_CONFIG = {
@@ -334,6 +331,13 @@ class SkillService {
334
331
  normalized.label = buildRepoLabel(normalized);
335
332
  normalized.id = buildRepoId(normalized);
336
333
 
334
+ if (provider !== 'local') {
335
+ const token = normalizeRepoToken(repo.token);
336
+ if (token) {
337
+ normalized.token = token;
338
+ }
339
+ }
340
+
337
341
  return normalized;
338
342
  }
339
343
 
@@ -366,6 +370,55 @@ class SkillService {
366
370
  fs.writeFileSync(this.reposConfigPath, JSON.stringify({ repos: normalizedRepos }, null, 2));
367
371
  }
368
372
 
373
+ toClientRepo(repo = {}) {
374
+ const normalizedRepo = this.normalizeRepoConfig(repo);
375
+ const token = normalizeRepoToken(normalizedRepo.token);
376
+ const clientRepo = {
377
+ ...normalizedRepo,
378
+ hasToken: Boolean(token),
379
+ tokenPreview: token ? maskToken(token) : ''
380
+ };
381
+ delete clientRepo.token;
382
+ return clientRepo;
383
+ }
384
+
385
+ getReposForClient(repos = null) {
386
+ const sourceRepos = Array.isArray(repos) ? repos : this.loadRepos();
387
+ return sourceRepos.map(repo => this.toClientRepo(repo));
388
+ }
389
+
390
+ findStoredRepo(repo = {}) {
391
+ const repoId = String(repo.id || repo.repoId || '').trim();
392
+ const repos = this.loadRepos();
393
+
394
+ if (repoId) {
395
+ return repos.find(candidate => candidate.id === repoId) || null;
396
+ }
397
+
398
+ try {
399
+ const normalizedRepo = this.normalizeRepoConfig(repo);
400
+ return repos.find(candidate => candidate.id === normalizedRepo.id) || null;
401
+ } catch {
402
+ return null;
403
+ }
404
+ }
405
+
406
+ resolveRepoToken(repo = null) {
407
+ if (!repo || typeof repo !== 'object') return null;
408
+
409
+ const directToken = normalizeRepoToken(repo.token);
410
+ if (directToken) {
411
+ return directToken;
412
+ }
413
+
414
+ const storedRepo = this.findStoredRepo(repo);
415
+ if (!storedRepo) {
416
+ return null;
417
+ }
418
+
419
+ return normalizeRepoToken(storedRepo.token) || null;
420
+ }
421
+
369
422
  /**
370
423
  * 添加仓库
371
424
  * @param {Object} repo - 仓库配置
@@ -443,6 +496,43 @@ class SkillService {
443
496
  return this.loadRepos();
444
497
  }
445
498
 
499
+ updateRepoAuth(owner, name, directory = '', token = '', clearToken = false, repoId = '') {
500
+ const repos = this.loadRepos();
501
+ const normalizedDirectory = normalizeRepoDirectory(directory);
502
+ const repo = repos.find(r => {
503
+ if (repoId) {
504
+ return r.id === repoId;
505
+ }
506
+ return (
507
+ (r.owner || '') === owner &&
508
+ (r.name || '') === name &&
509
+ normalizeRepoDirectory(r.directory) === normalizedDirectory
510
+ );
511
+ });
512
+
513
+ if (!repo) {
514
+ throw new Error('Repository not found');
515
+ }
516
+
517
+ if (repo.provider === 'local') {
518
+ throw new Error('Local repository does not support token auth');
519
+ }
520
+
521
+ if (clearToken) {
522
+ delete repo.token;
523
+ } else {
524
+ const normalizedToken = normalizeRepoToken(token);
525
+ if (!normalizedToken) {
526
+ throw new Error('Missing token');
527
+ }
528
+ repo.token = normalizedToken;
529
+ }
530
+
531
+ this.saveRepos(repos);
532
+ this.clearCache({ removeFile: true });
533
+ return this.loadRepos();
534
+ }
535
+
446
536
  /**
447
537
  * 获取所有技能列表(带缓存)
448
538
  */
@@ -880,7 +970,18 @@ class SkillService {
880
970
  }
881
971
  }
882
972
 
883
- getGitHubToken(host = DEFAULT_GITHUB_HOST) {
973
+ getGitHubToken(repoOrHost = DEFAULT_GITHUB_HOST) {
974
+ if (repoOrHost && typeof repoOrHost === 'object') {
975
+ const repoToken = this.resolveRepoToken(repoOrHost);
976
+ if (repoToken) {
977
+ return repoToken;
978
+ }
979
+ }
980
+
981
+ const host = typeof repoOrHost === 'string'
982
+ ? repoOrHost
983
+ : (repoOrHost?.host || DEFAULT_GITHUB_HOST);
984
+
884
985
  // 优先从环境变量获取
885
986
  if (process.env.GITHUB_TOKEN) {
886
987
  return process.env.GITHUB_TOKEN;
@@ -907,7 +1008,18 @@ class SkillService {
907
1008
  return this.getTokenFromGitCredential(host);
908
1009
  }
909
1010
 
910
- getGitLabToken(host = DEFAULT_GITLAB_HOST) {
1011
+ getGitLabToken(repoOrHost = DEFAULT_GITLAB_HOST) {
1012
+ if (repoOrHost && typeof repoOrHost === 'object') {
1013
+ const repoToken = this.resolveRepoToken(repoOrHost);
1014
+ if (repoToken) {
1015
+ return repoToken;
1016
+ }
1017
+ }
1018
+
1019
+ const host = typeof repoOrHost === 'string'
1020
+ ? repoOrHost
1021
+ : (repoOrHost?.host || DEFAULT_GITLAB_HOST);
1022
+
911
1023
  if (process.env.GITLAB_TOKEN) {
912
1024
  return process.env.GITLAB_TOKEN;
913
1025
  }
@@ -939,8 +1051,8 @@ class SkillService {
939
1051
  /**
940
1052
  * 通用 GitHub API 请求
941
1053
  */
942
- async fetchGitHubApi(url) {
943
- const token = this.getGitHubToken(url);
1054
+ async fetchGitHubApi(url, repo = null) {
1055
+ const token = this.getGitHubToken(repo || url);
944
1056
  const headers = {
945
1057
  'User-Agent': 'cc-cli-skill-service',
946
1058
  'Accept': 'application/vnd.github.v3+json'
@@ -979,15 +1091,15 @@ class SkillService {
979
1091
 
980
1092
  async fetchGitHubRepoTree(repo) {
981
1093
  const treeUrl = `https://api.github.com/repos/${repo.owner}/${repo.name}/git/trees/${repo.branch}?recursive=1`;
982
- const tree = await this.fetchGitHubApi(treeUrl);
1094
+ const tree = await this.fetchGitHubApi(treeUrl, repo);
983
1095
  if (tree?.truncated) {
984
1096
  console.warn(`[SkillService] GitHub tree truncated for ${repo.owner}/${repo.name}`);
985
1097
  }
986
1098
  return tree?.tree || [];
987
1099
  }
988
1100
 
989
- async fetchGitLabApi(url, { raw = false } = {}) {
990
- const token = this.getGitLabToken(url);
1101
+ async fetchGitLabApi(url, { raw = false, repo = null } = {}) {
1102
+ const token = this.getGitLabToken(repo || url);
991
1103
  const headers = {
992
1104
  'User-Agent': 'cc-cli-skill-service'
993
1105
  };
@@ -1041,7 +1153,7 @@ class SkillService {
1041
1153
 
1042
1154
  while (page) {
1043
1155
  const url = `${repo.host}/api/v4/projects/${projectId}/repository/tree?ref=${encodeURIComponent(repo.branch)}&recursive=true&per_page=100&page=${page}`;
1044
- const response = await this.fetchGitLabApi(url);
1156
+ const response = await this.fetchGitLabApi(url, { repo });
1045
1157
  tree.push(...(response.data || []).map(item => ({
1046
1158
  ...item,
1047
1159
  type: item.type === 'tree' ? 'tree' : 'blob'
@@ -1058,21 +1170,26 @@ class SkillService {
1058
1170
  const projectId = encodeURIComponent(repo.projectPath);
1059
1171
  const normalizedFilePath = encodeURIComponent(normalizeRepoPath(filePath));
1060
1172
  const url = `${repo.host}/api/v4/projects/${projectId}/repository/files/${normalizedFilePath}/raw?ref=${encodeURIComponent(repo.branch)}`;
1061
- return this.fetchGitLabApi(url, { raw: true });
1173
+ return this.fetchGitLabApi(url, { raw: true, repo });
1062
1174
  }
1063
1175
 
1064
1176
  /**
1065
1177
  * 使用 GitHub API 获取目录内容
1066
1178
  */
1067
- async fetchGitHubContents(owner, name, path, branch) {
1179
+ async fetchGitHubContents(owner, name, path, branch, repo = null) {
1068
1180
  const url = `https://api.github.com/repos/${owner}/${name}/contents/${path}?ref=${branch}`;
1181
+ const token = this.getGitHubToken(repo || url);
1182
+ const headers = {
1183
+ 'User-Agent': 'cc-cli-skill-service',
1184
+ 'Accept': 'application/vnd.github.v3+json'
1185
+ };
1186
+ if (token) {
1187
+ headers.Authorization = `token ${token}`;
1188
+ }
1069
1189
 
1070
1190
  return new Promise((resolve, reject) => {
1071
1191
  const req = https.get(url, {
1072
- headers: {
1073
- 'User-Agent': 'cc-cli-skill-service',
1074
- 'Accept': 'application/vnd.github.v3+json'
1075
- },
1192
+ headers,
1076
1193
  timeout: 15000
1077
1194
  }, (res) => {
1078
1195
  let data = '';
@@ -1144,7 +1261,7 @@ class SkillService {
1144
1261
  if (dir.name.startsWith('.') || dir.name === 'node_modules') continue;
1145
1262
 
1146
1263
  try {
1147
- const subContents = await this.fetchGitHubContents(repo.owner, repo.name, dir.path, repo.branch);
1264
+ const subContents = await this.fetchGitHubContents(repo.owner, repo.name, dir.path, repo.branch, repo);
1148
1265
  await this.scanRepoContents(subContents, repo, dir.path, skills);
1149
1266
  } catch (err) {
1150
1267
  // 忽略子目录错误,继续扫描
@@ -1367,13 +1484,13 @@ class SkillService {
1367
1484
  if (normalizedRepo.provider === 'gitlab') {
1368
1485
  const projectId = encodeURIComponent(normalizedRepo.projectPath);
1369
1486
  zipUrl = `${normalizedRepo.host}/api/v4/projects/${projectId}/repository/archive.zip?sha=${encodeURIComponent(normalizedRepo.branch)}`;
1370
- const token = this.getGitLabToken(normalizedRepo.host);
1487
+ const token = this.getGitLabToken(normalizedRepo);
1371
1488
  if (token) {
1372
1489
  zipHeaders['PRIVATE-TOKEN'] = token;
1373
1490
  }
1374
1491
  } else {
1375
1492
  zipUrl = `https://api.github.com/repos/${normalizedRepo.owner}/${normalizedRepo.name}/zipball/${encodeURIComponent(normalizedRepo.branch)}`;
1376
- const token = this.getGitHubToken(normalizedRepo.host);
1493
+ const token = this.getGitHubToken(normalizedRepo);
1377
1494
  zipHeaders.Accept = 'application/vnd.github+json';
1378
1495
  if (token) {
1379
1496
  zipHeaders.Authorization = `token ${token}`;
@@ -291,7 +291,7 @@ function startWebSocketServer(httpServer, options = {}) {
291
291
  acc[source] = (acc[source] || 0) + 1;
292
292
  return acc;
293
293
  }, {});
294
- console.log(`📝 Loaded ${logsCache.length} persisted logs today ->`, counts);
294
+ console.log(`[NOTE] Loaded ${logsCache.length} persisted logs today ->`, counts);
295
295
 
296
296
  try {
297
297
  // 如果传入的是 HTTP server,则附加到该服务器;否则创建独立的 WebSocket 服务器
@@ -301,7 +301,7 @@ function startWebSocketServer(httpServer, options = {}) {
301
301
  path: '/ws' // 指定 WebSocket 路径
302
302
  });
303
303
  installOriginGuard(wss);
304
- console.log(`✅ WebSocket server attached to HTTP server at /ws`);
304
+ console.log(`[OK] WebSocket server attached to HTTP server at /ws`);
305
305
  } else {
306
306
  // 创建独立的 WebSocket 服务器,使用配置的 webUI 端口
307
307
  const config = loadConfig();
@@ -311,7 +311,7 @@ function startWebSocketServer(httpServer, options = {}) {
311
311
  path: '/ws'
312
312
  });
313
313
  installOriginGuard(wss);
314
- console.log(`✅ WebSocket server started on ws://127.0.0.1:${port}/ws`);
314
+ console.log(`[OK] WebSocket server started on ws://127.0.0.1:${port}/ws`);
315
315
  }
316
316
 
317
317
  wss.on('connection', (ws, req) => {
@@ -352,7 +352,7 @@ function startWebSocketServer(httpServer, options = {}) {
352
352
  wsClients.forEach(ws => {
353
353
  if (ws.isAlive === false) {
354
354
  // 客户端没有响应 pong,断开连接
355
- console.log(' WebSocket client timeout, terminating');
355
+ console.log('[ERROR] WebSocket client timeout, terminating');
356
356
  wsClients.delete(ws);
357
357
  return ws.terminate();
358
358
  }
@@ -369,8 +369,8 @@ function startWebSocketServer(httpServer, options = {}) {
369
369
  wss.on('error', (error) => {
370
370
  console.error('WebSocket server error:', error);
371
371
  if (error.code === 'EADDRINUSE') {
372
- console.error(chalk.red('\n WebSocket 端口已被占用'));
373
- console.error(chalk.yellow('\n💡 请检查端口配置\n'));
372
+ console.error(chalk.red('\n[ERROR] WebSocket 端口已被占用'));
373
+ console.error(chalk.yellow('\n[TIP] 请检查端口配置\n'));
374
374
  wss = null;
375
375
  }
376
376
  });
@@ -400,7 +400,7 @@ function stopWebSocketServer() {
400
400
 
401
401
  // 关闭服务器
402
402
  wss.close(() => {
403
- console.log(' WebSocket server stopped');
403
+ console.log('[OK] WebSocket server stopped');
404
404
  });
405
405
 
406
406
  wss = null;
@@ -443,7 +443,7 @@ function broadcastLog(logData) {
443
443
  function clearAllLogs() {
444
444
  logsCache = [];
445
445
  saveLogsToFile([]);
446
- console.log(' All logs cleared');
446
+ console.log('[OK] All logs cleared');
447
447
  }
448
448
 
449
449
  // 去掉敏感字段
package/src/ui/menu.js CHANGED
@@ -50,9 +50,9 @@ function getChannelAndProxyStatus(cliType) {
50
50
  * 显示主菜单
51
51
  */
52
52
  async function showMainMenu(config) {
53
- console.log(chalk.bold.cyan('\n╔═══════════════════════════════════════════════╗'));
53
+ console.log(chalk.bold.cyan('\n╔===============================================╗'));
54
54
  console.log(chalk.bold.cyan(`║ Claude Code 会话管理工具 v${packageInfo.version} ║`));
55
- console.log(chalk.bold.cyan('╚═══════════════════════════════════════════════╝\n'));
55
+ console.log(chalk.bold.cyan('╚===============================================╝\n'));
56
56
 
57
57
  // 显示当前CLI类型
58
58
  const cliTypes = {
package/src/ui/prompts.js CHANGED
@@ -31,15 +31,15 @@ async function promptForkConfirm() {
31
31
  default: 'continue',
32
32
  choices: [
33
33
  {
34
- name: chalk.green('📝 继续原会话 (推荐) - 在原会话上继续对话,所有内容会追加到原文件'),
34
+ name: chalk.green('[NOTE] 继续原会话 (推荐) - 在原会话上继续对话,所有内容会追加到原文件'),
35
35
  value: 'continue',
36
36
  },
37
37
  {
38
- name: chalk.yellow('🌿 创建新分支 (Fork) - 基于原会话创建新会话,保留原会话不变'),
38
+ name: chalk.yellow('[FORK] 创建新分支 (Fork) - 基于原会话创建新会话,保留原会话不变'),
39
39
  value: 'fork',
40
40
  },
41
41
  new inquirer.Separator(chalk.gray('─'.repeat(14))),
42
- { name: chalk.blue('↩️ 返回重新选择'), value: 'back' },
42
+ { name: chalk.blue('[<-] 返回重新选择'), value: 'back' },
43
43
  ],
44
44
  },
45
45
  ]);
@@ -55,7 +55,7 @@ async function promptSearchKeyword() {
55
55
  {
56
56
  type: 'input',
57
57
  name: 'keyword',
58
- message: chalk.cyan('🔎 输入搜索关键词:'),
58
+ message: chalk.cyan('[SEARCH] 输入搜索关键词:'),
59
59
  validate: (input) => {
60
60
  if (!input.trim()) {
61
61
  return '请输入搜索关键词';
@@ -76,7 +76,7 @@ async function promptSelectProject(projects) {
76
76
  const choices = [
77
77
  ...projects,
78
78
  new inquirer.Separator(chalk.gray('─'.repeat(14))),
79
- { name: chalk.gray('↩️ 取消切换'), value: null }
79
+ { name: chalk.gray('[<-] 取消切换'), value: null }
80
80
  ];
81
81
 
82
82
  const { project } = await inquirer.prompt([