coding-tool-x 3.4.4 → 3.4.6
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/dist/web/assets/{Analytics-_Byi9M6y.js → Analytics-0PgPv5qO.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DIwosdtG.js → ConfigTemplates-pBGoYbCP.js} +1 -1
- package/dist/web/assets/{Home-DdNMuQ9c.js → Home-BRN882om.js} +1 -1
- package/dist/web/assets/{PluginManager-iuY24cnW.js → PluginManager-am97Huts.js} +1 -1
- package/dist/web/assets/{ProjectList-DSkMulzL.js → ProjectList-CXS9KJN1.js} +1 -1
- package/dist/web/assets/{SessionList-B6pGquIr.js → SessionList-BZyrzH7J.js} +1 -1
- package/dist/web/assets/{SkillManager-CHtQX5r8.js → SkillManager-p1CI0tYa.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-gNPs-VaI.js → WorkspaceManager-CUPvLoba.js} +1 -1
- package/dist/web/assets/index-B4Wl3JfR.js +2 -0
- package/dist/web/assets/{index-pMqqe9ei.css → index-Bgt_oqoE.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/src/server/api/claude-hooks.js +1 -0
- package/src/server/api/codex-channels.js +26 -0
- package/src/server/api/oauth-credentials.js +23 -1
- package/src/server/api/opencode-proxy.js +0 -2
- package/src/server/api/plugins.js +161 -14
- package/src/server/api/skills.js +62 -7
- package/src/server/codex-proxy-server.js +10 -2
- package/src/server/gemini-proxy-server.js +10 -2
- package/src/server/opencode-proxy-server.js +10 -2
- package/src/server/proxy-server.js +10 -2
- package/src/server/services/codex-channels.js +64 -21
- package/src/server/services/codex-env-manager.js +44 -28
- package/src/server/services/native-oauth-adapters.js +94 -10
- package/src/server/services/oauth-credentials-service.js +44 -2
- package/src/server/services/opencode-channels.js +0 -2
- package/src/server/services/plugins-service.js +1060 -235
- package/src/server/services/proxy-runtime.js +129 -5
- package/src/server/services/server-shutdown.js +79 -0
- package/src/server/services/skill-service.js +142 -17
- package/dist/web/assets/index-DGjGCo37.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
|
|
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
|
|
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
|
+
};
|
|
@@ -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
|
}
|
|
@@ -326,6 +331,13 @@ class SkillService {
|
|
|
326
331
|
normalized.label = buildRepoLabel(normalized);
|
|
327
332
|
normalized.id = buildRepoId(normalized);
|
|
328
333
|
|
|
334
|
+
if (provider !== 'local') {
|
|
335
|
+
const token = normalizeRepoToken(repo.token);
|
|
336
|
+
if (token) {
|
|
337
|
+
normalized.token = token;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
329
341
|
return normalized;
|
|
330
342
|
}
|
|
331
343
|
|
|
@@ -358,6 +370,55 @@ class SkillService {
|
|
|
358
370
|
fs.writeFileSync(this.reposConfigPath, JSON.stringify({ repos: normalizedRepos }, null, 2));
|
|
359
371
|
}
|
|
360
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
|
+
|
|
361
422
|
/**
|
|
362
423
|
* 添加仓库
|
|
363
424
|
* @param {Object} repo - 仓库配置
|
|
@@ -435,6 +496,43 @@ class SkillService {
|
|
|
435
496
|
return this.loadRepos();
|
|
436
497
|
}
|
|
437
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
|
+
|
|
438
536
|
/**
|
|
439
537
|
* 获取所有技能列表(带缓存)
|
|
440
538
|
*/
|
|
@@ -872,7 +970,18 @@ class SkillService {
|
|
|
872
970
|
}
|
|
873
971
|
}
|
|
874
972
|
|
|
875
|
-
getGitHubToken(
|
|
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
|
+
|
|
876
985
|
// 优先从环境变量获取
|
|
877
986
|
if (process.env.GITHUB_TOKEN) {
|
|
878
987
|
return process.env.GITHUB_TOKEN;
|
|
@@ -899,7 +1008,18 @@ class SkillService {
|
|
|
899
1008
|
return this.getTokenFromGitCredential(host);
|
|
900
1009
|
}
|
|
901
1010
|
|
|
902
|
-
getGitLabToken(
|
|
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
|
+
|
|
903
1023
|
if (process.env.GITLAB_TOKEN) {
|
|
904
1024
|
return process.env.GITLAB_TOKEN;
|
|
905
1025
|
}
|
|
@@ -931,8 +1051,8 @@ class SkillService {
|
|
|
931
1051
|
/**
|
|
932
1052
|
* 通用 GitHub API 请求
|
|
933
1053
|
*/
|
|
934
|
-
async fetchGitHubApi(url) {
|
|
935
|
-
const token = this.getGitHubToken(url);
|
|
1054
|
+
async fetchGitHubApi(url, repo = null) {
|
|
1055
|
+
const token = this.getGitHubToken(repo || url);
|
|
936
1056
|
const headers = {
|
|
937
1057
|
'User-Agent': 'cc-cli-skill-service',
|
|
938
1058
|
'Accept': 'application/vnd.github.v3+json'
|
|
@@ -971,15 +1091,15 @@ class SkillService {
|
|
|
971
1091
|
|
|
972
1092
|
async fetchGitHubRepoTree(repo) {
|
|
973
1093
|
const treeUrl = `https://api.github.com/repos/${repo.owner}/${repo.name}/git/trees/${repo.branch}?recursive=1`;
|
|
974
|
-
const tree = await this.fetchGitHubApi(treeUrl);
|
|
1094
|
+
const tree = await this.fetchGitHubApi(treeUrl, repo);
|
|
975
1095
|
if (tree?.truncated) {
|
|
976
1096
|
console.warn(`[SkillService] GitHub tree truncated for ${repo.owner}/${repo.name}`);
|
|
977
1097
|
}
|
|
978
1098
|
return tree?.tree || [];
|
|
979
1099
|
}
|
|
980
1100
|
|
|
981
|
-
async fetchGitLabApi(url, { raw = false } = {}) {
|
|
982
|
-
const token = this.getGitLabToken(url);
|
|
1101
|
+
async fetchGitLabApi(url, { raw = false, repo = null } = {}) {
|
|
1102
|
+
const token = this.getGitLabToken(repo || url);
|
|
983
1103
|
const headers = {
|
|
984
1104
|
'User-Agent': 'cc-cli-skill-service'
|
|
985
1105
|
};
|
|
@@ -1033,7 +1153,7 @@ class SkillService {
|
|
|
1033
1153
|
|
|
1034
1154
|
while (page) {
|
|
1035
1155
|
const url = `${repo.host}/api/v4/projects/${projectId}/repository/tree?ref=${encodeURIComponent(repo.branch)}&recursive=true&per_page=100&page=${page}`;
|
|
1036
|
-
const response = await this.fetchGitLabApi(url);
|
|
1156
|
+
const response = await this.fetchGitLabApi(url, { repo });
|
|
1037
1157
|
tree.push(...(response.data || []).map(item => ({
|
|
1038
1158
|
...item,
|
|
1039
1159
|
type: item.type === 'tree' ? 'tree' : 'blob'
|
|
@@ -1050,21 +1170,26 @@ class SkillService {
|
|
|
1050
1170
|
const projectId = encodeURIComponent(repo.projectPath);
|
|
1051
1171
|
const normalizedFilePath = encodeURIComponent(normalizeRepoPath(filePath));
|
|
1052
1172
|
const url = `${repo.host}/api/v4/projects/${projectId}/repository/files/${normalizedFilePath}/raw?ref=${encodeURIComponent(repo.branch)}`;
|
|
1053
|
-
return this.fetchGitLabApi(url, { raw: true });
|
|
1173
|
+
return this.fetchGitLabApi(url, { raw: true, repo });
|
|
1054
1174
|
}
|
|
1055
1175
|
|
|
1056
1176
|
/**
|
|
1057
1177
|
* 使用 GitHub API 获取目录内容
|
|
1058
1178
|
*/
|
|
1059
|
-
async fetchGitHubContents(owner, name, path, branch) {
|
|
1179
|
+
async fetchGitHubContents(owner, name, path, branch, repo = null) {
|
|
1060
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
|
+
}
|
|
1061
1189
|
|
|
1062
1190
|
return new Promise((resolve, reject) => {
|
|
1063
1191
|
const req = https.get(url, {
|
|
1064
|
-
headers
|
|
1065
|
-
'User-Agent': 'cc-cli-skill-service',
|
|
1066
|
-
'Accept': 'application/vnd.github.v3+json'
|
|
1067
|
-
},
|
|
1192
|
+
headers,
|
|
1068
1193
|
timeout: 15000
|
|
1069
1194
|
}, (res) => {
|
|
1070
1195
|
let data = '';
|
|
@@ -1136,7 +1261,7 @@ class SkillService {
|
|
|
1136
1261
|
if (dir.name.startsWith('.') || dir.name === 'node_modules') continue;
|
|
1137
1262
|
|
|
1138
1263
|
try {
|
|
1139
|
-
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);
|
|
1140
1265
|
await this.scanRepoContents(subContents, repo, dir.path, skills);
|
|
1141
1266
|
} catch (err) {
|
|
1142
1267
|
// 忽略子目录错误,继续扫描
|
|
@@ -1359,13 +1484,13 @@ class SkillService {
|
|
|
1359
1484
|
if (normalizedRepo.provider === 'gitlab') {
|
|
1360
1485
|
const projectId = encodeURIComponent(normalizedRepo.projectPath);
|
|
1361
1486
|
zipUrl = `${normalizedRepo.host}/api/v4/projects/${projectId}/repository/archive.zip?sha=${encodeURIComponent(normalizedRepo.branch)}`;
|
|
1362
|
-
const token = this.getGitLabToken(normalizedRepo
|
|
1487
|
+
const token = this.getGitLabToken(normalizedRepo);
|
|
1363
1488
|
if (token) {
|
|
1364
1489
|
zipHeaders['PRIVATE-TOKEN'] = token;
|
|
1365
1490
|
}
|
|
1366
1491
|
} else {
|
|
1367
1492
|
zipUrl = `https://api.github.com/repos/${normalizedRepo.owner}/${normalizedRepo.name}/zipball/${encodeURIComponent(normalizedRepo.branch)}`;
|
|
1368
|
-
const token = this.getGitHubToken(normalizedRepo
|
|
1493
|
+
const token = this.getGitHubToken(normalizedRepo);
|
|
1369
1494
|
zipHeaders.Accept = 'application/vnd.github+json';
|
|
1370
1495
|
if (token) {
|
|
1371
1496
|
zipHeaders.Authorization = `token ${token}`;
|