coding-tool-x 3.3.8 → 3.4.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 +17 -2
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-DEjfL5Jx.js} +4 -4
- package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
- package/dist/web/assets/{ConfigTemplates-D_hRb55W.js → ConfigTemplates-DkRL_-tf.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-CF-L640I.js +1 -0
- package/dist/web/assets/{PluginManager-JXsyym1s.js → PluginManager-BzNYTdNB.js} +1 -1
- package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-C0-JgHMM.js} +1 -1
- package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CkZUdX5N.js} +1 -1
- package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-Cak0-4d4.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-CGDJzwEr.js} +1 -1
- package/dist/web/assets/{icons-Cuc23WS7.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-D_WItvHE.js +2 -0
- package/dist/web/assets/index-Dz7v9OM0.css +1 -0
- package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
- package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
- package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
- package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
- package/dist/web/index.html +7 -7
- package/docs/home.png +0 -0
- package/package.json +13 -5
- package/src/commands/daemon.js +3 -2
- package/src/commands/security.js +1 -2
- package/src/config/paths.js +638 -93
- package/src/server/api/agents.js +1 -1
- package/src/server/api/claude-hooks.js +13 -8
- package/src/server/api/codex-proxy.js +5 -4
- package/src/server/api/hooks.js +45 -0
- package/src/server/api/plugins.js +0 -1
- package/src/server/api/statistics.js +4 -4
- package/src/server/api/ui-config.js +5 -0
- package/src/server/api/workspaces.js +1 -3
- package/src/server/codex-proxy-server.js +89 -59
- package/src/server/gemini-proxy-server.js +107 -88
- package/src/server/index.js +1 -0
- package/src/server/opencode-proxy-server.js +381 -225
- package/src/server/proxy-server.js +86 -60
- package/src/server/services/alias.js +3 -3
- package/src/server/services/channels.js +3 -2
- package/src/server/services/codex-channels.js +38 -87
- package/src/server/services/codex-env-manager.js +426 -0
- package/src/server/services/codex-settings-manager.js +15 -15
- package/src/server/services/codex-statistics-service.js +3 -27
- package/src/server/services/config-export-service.js +20 -7
- package/src/server/services/config-registry-service.js +3 -2
- package/src/server/services/config-sync-manager.js +1 -1
- package/src/server/services/favorites.js +4 -3
- package/src/server/services/gemini-channels.js +3 -3
- package/src/server/services/gemini-statistics-service.js +3 -25
- package/src/server/services/mcp-service.js +2 -3
- package/src/server/services/model-detector.js +4 -3
- package/src/server/services/native-oauth-adapters.js +2 -1
- package/src/server/services/network-access.js +39 -1
- package/src/server/services/notification-hooks.js +951 -0
- package/src/server/services/opencode-channels.js +6 -6
- package/src/server/services/opencode-sessions.js +2 -2
- package/src/server/services/opencode-statistics-service.js +3 -27
- package/src/server/services/plugins-service.js +110 -31
- package/src/server/services/prompts-service.js +2 -3
- package/src/server/services/proxy-log-helper.js +242 -0
- package/src/server/services/proxy-runtime.js +6 -4
- package/src/server/services/repo-scanner-base.js +12 -4
- package/src/server/services/request-logger.js +7 -7
- package/src/server/services/security-config.js +4 -4
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/sessions.js +2 -2
- package/src/server/services/skill-service.js +174 -55
- package/src/server/services/statistics-service.js +10 -6
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +101 -156
- package/src/server/websocket-server.js +5 -4
- package/dist/web/assets/Analytics-DuYvId7u.css +0 -1
- package/dist/web/assets/Home-BMoFdAwy.css +0 -1
- package/dist/web/assets/Home-DNwp-0J-.js +0 -1
- package/dist/web/assets/index-BXeSvAwU.js +0 -2
- package/dist/web/assets/index-DWAC3Tdv.css +0 -1
- package/docs/bannel.png +0 -0
- package/docs/model-redirection.md +0 -251
|
@@ -11,7 +11,7 @@ let hasMessagesPersisted = {};
|
|
|
11
11
|
let hasMessagesPersistTimer = null;
|
|
12
12
|
|
|
13
13
|
function getCcToolDir() {
|
|
14
|
-
return PATHS.
|
|
14
|
+
return path.dirname(PATHS.sessionHasCache);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function ensureDirExists(dir) {
|
|
@@ -52,7 +52,7 @@ function invalidateProjectsCache(configOrPath) {
|
|
|
52
52
|
projectsCache.delete(key);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const hasMessagesCacheFile =
|
|
55
|
+
const hasMessagesCacheFile = PATHS.sessionHasCache;
|
|
56
56
|
loadHasMessagesCacheFromDisk();
|
|
57
57
|
|
|
58
58
|
function loadHasMessagesCacheFromDisk() {
|
|
@@ -29,12 +29,12 @@ function getOrderFilePath() {
|
|
|
29
29
|
|
|
30
30
|
// Get path for storing fork relations
|
|
31
31
|
function getForkRelationsFilePath() {
|
|
32
|
-
return
|
|
32
|
+
return PATHS.forkRelations;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Get path for storing session order
|
|
36
36
|
function getSessionOrderFilePath() {
|
|
37
|
-
return
|
|
37
|
+
return PATHS.sessionOrder;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Get saved project order
|
|
@@ -10,13 +10,14 @@ const path = require('path');
|
|
|
10
10
|
const os = require('os');
|
|
11
11
|
const https = require('https');
|
|
12
12
|
const http = require('http');
|
|
13
|
+
const { execFileSync } = require('child_process');
|
|
13
14
|
const { createWriteStream } = require('fs');
|
|
14
15
|
const { pipeline } = require('stream/promises');
|
|
15
16
|
const AdmZip = require('adm-zip');
|
|
16
17
|
const {
|
|
17
18
|
parseSkillContent,
|
|
18
19
|
} = require('./format-converter');
|
|
19
|
-
const { NATIVE_PATHS, HOME_DIR } = require('../../config/paths');
|
|
20
|
+
const { NATIVE_PATHS, HOME_DIR, PATHS } = require('../../config/paths');
|
|
20
21
|
|
|
21
22
|
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
22
23
|
const SUPPORTED_REPO_PROVIDERS = ['github', 'gitlab', 'local'];
|
|
@@ -105,6 +106,16 @@ function normalizeRepoHost(host, provider = 'github') {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
function extractHostname(host = '') {
|
|
110
|
+
const normalized = String(host || '').trim();
|
|
111
|
+
if (!normalized) return '';
|
|
112
|
+
try {
|
|
113
|
+
return new URL(normalized).hostname || '';
|
|
114
|
+
} catch {
|
|
115
|
+
return normalized.replace(/^https?:\/\//i, '').replace(/\/.*$/, '');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
function buildRepoUrl(repo) {
|
|
109
120
|
if (repo.provider === 'local') {
|
|
110
121
|
return repo.localPath || '';
|
|
@@ -159,27 +170,27 @@ const DEFAULT_REPOS_BY_PLATFORM = {
|
|
|
159
170
|
const PLATFORM_CONFIG = {
|
|
160
171
|
claude: {
|
|
161
172
|
installDir: path.join(HOME_DIR, '.claude', 'skills'),
|
|
162
|
-
storageDir:
|
|
163
|
-
reposFile:
|
|
164
|
-
cacheFile:
|
|
173
|
+
storageDir: PATHS.localSkills.claude,
|
|
174
|
+
reposFile: PATHS.skillRepos.claude,
|
|
175
|
+
cacheFile: PATHS.skillCaches.claude
|
|
165
176
|
},
|
|
166
177
|
codex: {
|
|
167
178
|
installDir: path.join(HOME_DIR, '.codex', 'skills'),
|
|
168
|
-
storageDir:
|
|
169
|
-
reposFile:
|
|
170
|
-
cacheFile:
|
|
179
|
+
storageDir: PATHS.localSkills.codex,
|
|
180
|
+
reposFile: PATHS.skillRepos.codex,
|
|
181
|
+
cacheFile: PATHS.skillCaches.codex
|
|
171
182
|
},
|
|
172
183
|
gemini: {
|
|
173
184
|
installDir: path.join(HOME_DIR, '.gemini', 'skills'),
|
|
174
|
-
storageDir:
|
|
175
|
-
reposFile:
|
|
176
|
-
cacheFile:
|
|
185
|
+
storageDir: PATHS.localSkills.gemini,
|
|
186
|
+
reposFile: PATHS.skillRepos.gemini,
|
|
187
|
+
cacheFile: PATHS.skillCaches.gemini
|
|
177
188
|
},
|
|
178
189
|
opencode: {
|
|
179
190
|
installDir: path.join(NATIVE_PATHS.opencode.config, 'skills'),
|
|
180
|
-
storageDir:
|
|
181
|
-
reposFile:
|
|
182
|
-
cacheFile:
|
|
191
|
+
storageDir: PATHS.localSkills.opencode,
|
|
192
|
+
reposFile: PATHS.skillRepos.opencode,
|
|
193
|
+
cacheFile: PATHS.skillCaches.opencode
|
|
183
194
|
}
|
|
184
195
|
};
|
|
185
196
|
|
|
@@ -189,13 +200,13 @@ const CACHE_TTL = 5 * 60 * 1000;
|
|
|
189
200
|
class SkillService {
|
|
190
201
|
constructor(platform = 'claude') {
|
|
191
202
|
this.platform = normalizePlatform(platform);
|
|
192
|
-
this.configDir =
|
|
203
|
+
this.configDir = PATHS.config;
|
|
193
204
|
|
|
194
205
|
const platformConfig = PLATFORM_CONFIG[this.platform];
|
|
195
206
|
this.installDir = platformConfig.installDir;
|
|
196
|
-
this.storageDir =
|
|
197
|
-
this.reposConfigPath =
|
|
198
|
-
this.cachePath =
|
|
207
|
+
this.storageDir = platformConfig.storageDir;
|
|
208
|
+
this.reposConfigPath = platformConfig.reposFile;
|
|
209
|
+
this.cachePath = platformConfig.cacheFile;
|
|
199
210
|
|
|
200
211
|
// 内存缓存
|
|
201
212
|
this.skillsCache = null;
|
|
@@ -215,6 +226,14 @@ class SkillService {
|
|
|
215
226
|
if (!fs.existsSync(this.storageDir)) {
|
|
216
227
|
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
217
228
|
}
|
|
229
|
+
const reposDir = path.dirname(this.reposConfigPath);
|
|
230
|
+
if (!fs.existsSync(reposDir)) {
|
|
231
|
+
fs.mkdirSync(reposDir, { recursive: true });
|
|
232
|
+
}
|
|
233
|
+
const cacheDir = path.dirname(this.cachePath);
|
|
234
|
+
if (!fs.existsSync(cacheDir)) {
|
|
235
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
236
|
+
}
|
|
218
237
|
}
|
|
219
238
|
|
|
220
239
|
clearCache({ removeFile = false } = {}) {
|
|
@@ -232,6 +251,19 @@ class SkillService {
|
|
|
232
251
|
}
|
|
233
252
|
}
|
|
234
253
|
|
|
254
|
+
prepareSkills(skills = []) {
|
|
255
|
+
const preparedSkills = Array.isArray(skills)
|
|
256
|
+
? skills.map(skill => ({ ...skill }))
|
|
257
|
+
: [];
|
|
258
|
+
|
|
259
|
+
this.mergeLocalSkills(preparedSkills);
|
|
260
|
+
this.deduplicateSkills(preparedSkills);
|
|
261
|
+
preparedSkills.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
|
262
|
+
this.updateInstallStatus(preparedSkills);
|
|
263
|
+
|
|
264
|
+
return preparedSkills;
|
|
265
|
+
}
|
|
266
|
+
|
|
235
267
|
getDefaultSkillDirectory(repo) {
|
|
236
268
|
if (repo.provider === 'local') {
|
|
237
269
|
return path.basename(repo.localPath || '') || 'skill';
|
|
@@ -415,24 +447,30 @@ class SkillService {
|
|
|
415
447
|
* 获取所有技能列表(带缓存)
|
|
416
448
|
*/
|
|
417
449
|
async listSkills(forceRefresh = false) {
|
|
418
|
-
//
|
|
450
|
+
// 强制刷新时仅清空内存缓存,保留磁盘缓存作为回退来源
|
|
419
451
|
if (forceRefresh) {
|
|
420
|
-
this.clearCache(
|
|
452
|
+
this.clearCache();
|
|
421
453
|
}
|
|
422
454
|
|
|
455
|
+
const fileCache = this.loadCacheFromFile();
|
|
456
|
+
|
|
423
457
|
// 检查内存缓存
|
|
424
|
-
if (!forceRefresh && this.skillsCache) {
|
|
425
|
-
|
|
458
|
+
if (!forceRefresh && Array.isArray(this.skillsCache) && this.skillsCache.length > 0) {
|
|
459
|
+
if (Array.isArray(fileCache) && fileCache.length > this.skillsCache.length) {
|
|
460
|
+
this.skillsCache = this.prepareSkills(fileCache);
|
|
461
|
+
this.cacheTime = Date.now();
|
|
462
|
+
return this.skillsCache;
|
|
463
|
+
}
|
|
464
|
+
this.skillsCache = this.prepareSkills(this.skillsCache);
|
|
465
|
+
this.cacheTime = Date.now();
|
|
426
466
|
return this.skillsCache;
|
|
427
467
|
}
|
|
428
468
|
|
|
429
469
|
// 检查文件缓存
|
|
430
470
|
if (!forceRefresh) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.skillsCache = fileCache;
|
|
471
|
+
if (fileCache && fileCache.length > 0) {
|
|
472
|
+
this.skillsCache = this.prepareSkills(fileCache);
|
|
434
473
|
this.cacheTime = Date.now();
|
|
435
|
-
this.updateInstallStatus(this.skillsCache);
|
|
436
474
|
return this.skillsCache;
|
|
437
475
|
}
|
|
438
476
|
}
|
|
@@ -442,6 +480,8 @@ class SkillService {
|
|
|
442
480
|
|
|
443
481
|
// 并行获取所有启用仓库的技能(带超时保护)
|
|
444
482
|
const enabledRepos = repos.filter(r => r.enabled);
|
|
483
|
+
const enabledRemoteRepos = enabledRepos.filter(repo => repo.provider !== 'local');
|
|
484
|
+
let remoteFailureCount = 0;
|
|
445
485
|
|
|
446
486
|
if (enabledRepos.length > 0) {
|
|
447
487
|
const results = await Promise.allSettled(
|
|
@@ -457,28 +497,40 @@ class SkillService {
|
|
|
457
497
|
|
|
458
498
|
for (let i = 0; i < results.length; i++) {
|
|
459
499
|
const result = results[i];
|
|
460
|
-
const
|
|
500
|
+
const repo = enabledRepos[i];
|
|
501
|
+
const repoInfo = `${repo.owner}/${repo.name}`;
|
|
461
502
|
if (result.status === 'fulfilled') {
|
|
462
503
|
skills.push(...result.value);
|
|
463
504
|
} else {
|
|
464
505
|
console.warn(`[SkillService] Fetch repo ${repoInfo} failed:`, result.reason?.message);
|
|
506
|
+
if (repo.provider !== 'local') {
|
|
507
|
+
remoteFailureCount++;
|
|
508
|
+
}
|
|
465
509
|
}
|
|
466
510
|
}
|
|
467
511
|
}
|
|
468
512
|
|
|
469
|
-
|
|
470
|
-
|
|
513
|
+
const preparedSkills = this.prepareSkills(skills);
|
|
514
|
+
|
|
515
|
+
const hasUsableFileCache = Array.isArray(fileCache) && fileCache.length > 0;
|
|
516
|
+
const preparedFileCache = hasUsableFileCache ? this.prepareSkills(fileCache) : null;
|
|
517
|
+
const shouldUseStaleFileCache = hasUsableFileCache && (
|
|
518
|
+
(enabledRemoteRepos.length > 0 && remoteFailureCount === enabledRemoteRepos.length) ||
|
|
519
|
+
(remoteFailureCount > 0 && preparedFileCache.length > preparedSkills.length)
|
|
520
|
+
);
|
|
471
521
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
522
|
+
if (shouldUseStaleFileCache) {
|
|
523
|
+
this.skillsCache = preparedFileCache;
|
|
524
|
+
this.cacheTime = Date.now();
|
|
525
|
+
return this.skillsCache;
|
|
526
|
+
}
|
|
475
527
|
|
|
476
528
|
// 更新缓存
|
|
477
|
-
this.skillsCache =
|
|
529
|
+
this.skillsCache = preparedSkills;
|
|
478
530
|
this.cacheTime = Date.now();
|
|
479
|
-
this.saveCacheToFile(
|
|
531
|
+
this.saveCacheToFile(preparedSkills);
|
|
480
532
|
|
|
481
|
-
return
|
|
533
|
+
return preparedSkills;
|
|
482
534
|
}
|
|
483
535
|
|
|
484
536
|
/**
|
|
@@ -744,7 +796,7 @@ class SkillService {
|
|
|
744
796
|
buildSkillReadmeUrl(repo, fullDirectory = '') {
|
|
745
797
|
const normalizedDirectory = normalizeRepoPath(fullDirectory);
|
|
746
798
|
if (repo.provider === 'local') {
|
|
747
|
-
return
|
|
799
|
+
return null;
|
|
748
800
|
}
|
|
749
801
|
if (repo.provider === 'gitlab') {
|
|
750
802
|
const suffix = normalizedDirectory ? `/-/tree/${repo.branch}/${normalizedDirectory}` : `/-/tree/${repo.branch}`;
|
|
@@ -780,16 +832,11 @@ class SkillService {
|
|
|
780
832
|
/**
|
|
781
833
|
* 获取 GitHub Token(从环境变量或配置文件)
|
|
782
834
|
*/
|
|
783
|
-
|
|
784
|
-
// 优先从环境变量获取
|
|
785
|
-
if (process.env.GITHUB_TOKEN) {
|
|
786
|
-
return process.env.GITHUB_TOKEN;
|
|
787
|
-
}
|
|
788
|
-
// 从配置文件获取
|
|
835
|
+
getTokenFromConfigFile(fileName) {
|
|
789
836
|
try {
|
|
790
|
-
const configPath = path.join(this.configDir,
|
|
837
|
+
const configPath = path.join(this.configDir, fileName);
|
|
791
838
|
if (fs.existsSync(configPath)) {
|
|
792
|
-
return fs.readFileSync(configPath, 'utf-8').trim();
|
|
839
|
+
return fs.readFileSync(configPath, 'utf-8').trim() || null;
|
|
793
840
|
}
|
|
794
841
|
} catch (err) {
|
|
795
842
|
// ignore
|
|
@@ -797,29 +844,101 @@ class SkillService {
|
|
|
797
844
|
return null;
|
|
798
845
|
}
|
|
799
846
|
|
|
800
|
-
|
|
847
|
+
getTokenFromCommand(command, args = []) {
|
|
848
|
+
try {
|
|
849
|
+
const output = execFileSync(command, args, {
|
|
850
|
+
encoding: 'utf-8',
|
|
851
|
+
timeout: 3000,
|
|
852
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
853
|
+
}).trim();
|
|
854
|
+
return output || null;
|
|
855
|
+
} catch {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
getTokenFromGitCredential(host) {
|
|
861
|
+
const hostname = extractHostname(host);
|
|
862
|
+
if (!hostname) return null;
|
|
863
|
+
|
|
864
|
+
try {
|
|
865
|
+
const output = execFileSync('git', ['credential', 'fill'], {
|
|
866
|
+
input: `protocol=https\nhost=${hostname}\n\n`,
|
|
867
|
+
encoding: 'utf-8',
|
|
868
|
+
timeout: 3000,
|
|
869
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
870
|
+
});
|
|
871
|
+
const passwordLine = output
|
|
872
|
+
.split(/\r?\n/)
|
|
873
|
+
.find(line => line.startsWith('password='));
|
|
874
|
+
if (!passwordLine) return null;
|
|
875
|
+
return passwordLine.slice('password='.length).trim() || null;
|
|
876
|
+
} catch {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
getGitHubToken(host = DEFAULT_GITHUB_HOST) {
|
|
882
|
+
// 优先从环境变量获取
|
|
883
|
+
if (process.env.GITHUB_TOKEN) {
|
|
884
|
+
return process.env.GITHUB_TOKEN;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const configToken = this.getTokenFromConfigFile('github-token.txt');
|
|
888
|
+
if (configToken) {
|
|
889
|
+
return configToken;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const hostname = extractHostname(host);
|
|
893
|
+
if (hostname) {
|
|
894
|
+
const ghHostToken = this.getTokenFromCommand('gh', ['auth', 'token', '--hostname', hostname]);
|
|
895
|
+
if (ghHostToken) {
|
|
896
|
+
return ghHostToken;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const ghToken = this.getTokenFromCommand('gh', ['auth', 'token']);
|
|
901
|
+
if (ghToken) {
|
|
902
|
+
return ghToken;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return this.getTokenFromGitCredential(host);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
getGitLabToken(host = DEFAULT_GITLAB_HOST) {
|
|
801
909
|
if (process.env.GITLAB_TOKEN) {
|
|
802
910
|
return process.env.GITLAB_TOKEN;
|
|
803
911
|
}
|
|
804
912
|
if (process.env.GITLAB_PRIVATE_TOKEN) {
|
|
805
913
|
return process.env.GITLAB_PRIVATE_TOKEN;
|
|
806
914
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
915
|
+
|
|
916
|
+
const configToken = this.getTokenFromConfigFile('gitlab-token.txt');
|
|
917
|
+
if (configToken) {
|
|
918
|
+
return configToken;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const hostname = extractHostname(host);
|
|
922
|
+
if (hostname) {
|
|
923
|
+
const glabHostToken = this.getTokenFromCommand('glab', ['auth', 'token', '--hostname', hostname]);
|
|
924
|
+
if (glabHostToken) {
|
|
925
|
+
return glabHostToken;
|
|
811
926
|
}
|
|
812
|
-
} catch (err) {
|
|
813
|
-
// ignore
|
|
814
927
|
}
|
|
815
|
-
|
|
928
|
+
|
|
929
|
+
const glabToken = this.getTokenFromCommand('glab', ['auth', 'token']);
|
|
930
|
+
if (glabToken) {
|
|
931
|
+
return glabToken;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
return this.getTokenFromGitCredential(host);
|
|
816
935
|
}
|
|
817
936
|
|
|
818
937
|
/**
|
|
819
938
|
* 通用 GitHub API 请求
|
|
820
939
|
*/
|
|
821
940
|
async fetchGitHubApi(url) {
|
|
822
|
-
const token = this.getGitHubToken();
|
|
941
|
+
const token = this.getGitHubToken(url);
|
|
823
942
|
const headers = {
|
|
824
943
|
'User-Agent': 'cc-cli-skill-service',
|
|
825
944
|
'Accept': 'application/vnd.github.v3+json'
|
|
@@ -866,7 +985,7 @@ class SkillService {
|
|
|
866
985
|
}
|
|
867
986
|
|
|
868
987
|
async fetchGitLabApi(url, { raw = false } = {}) {
|
|
869
|
-
const token = this.getGitLabToken();
|
|
988
|
+
const token = this.getGitLabToken(url);
|
|
870
989
|
const headers = {
|
|
871
990
|
'User-Agent': 'cc-cli-skill-service'
|
|
872
991
|
};
|
|
@@ -1246,13 +1365,13 @@ class SkillService {
|
|
|
1246
1365
|
if (normalizedRepo.provider === 'gitlab') {
|
|
1247
1366
|
const projectId = encodeURIComponent(normalizedRepo.projectPath);
|
|
1248
1367
|
zipUrl = `${normalizedRepo.host}/api/v4/projects/${projectId}/repository/archive.zip?sha=${encodeURIComponent(normalizedRepo.branch)}`;
|
|
1249
|
-
const token = this.getGitLabToken();
|
|
1368
|
+
const token = this.getGitLabToken(normalizedRepo.host);
|
|
1250
1369
|
if (token) {
|
|
1251
1370
|
zipHeaders['PRIVATE-TOKEN'] = token;
|
|
1252
1371
|
}
|
|
1253
1372
|
} else {
|
|
1254
1373
|
zipUrl = `https://api.github.com/repos/${normalizedRepo.owner}/${normalizedRepo.name}/zipball/${encodeURIComponent(normalizedRepo.branch)}`;
|
|
1255
|
-
const token = this.getGitHubToken();
|
|
1374
|
+
const token = this.getGitHubToken(normalizedRepo.host);
|
|
1256
1375
|
zipHeaders.Accept = 'application/vnd.github+json';
|
|
1257
1376
|
if (token) {
|
|
1258
1377
|
zipHeaders.Authorization = `token ${token}`;
|
|
@@ -39,7 +39,7 @@ function getCSTHour(ts) {
|
|
|
39
39
|
|
|
40
40
|
// 获取基础目录
|
|
41
41
|
function getBaseDir() {
|
|
42
|
-
const dir = PATHS.
|
|
42
|
+
const dir = path.dirname(PATHS.statistics.summary);
|
|
43
43
|
if (!fs.existsSync(dir)) {
|
|
44
44
|
fs.mkdirSync(dir, { recursive: true });
|
|
45
45
|
}
|
|
@@ -48,7 +48,7 @@ function getBaseDir() {
|
|
|
48
48
|
|
|
49
49
|
// 获取每日统计目录
|
|
50
50
|
function getDailyStatsDir() {
|
|
51
|
-
const dir =
|
|
51
|
+
const dir = PATHS.statistics.dailyStats;
|
|
52
52
|
if (!fs.existsSync(dir)) {
|
|
53
53
|
fs.mkdirSync(dir, { recursive: true });
|
|
54
54
|
}
|
|
@@ -57,7 +57,7 @@ function getDailyStatsDir() {
|
|
|
57
57
|
|
|
58
58
|
// 获取请求日志目录
|
|
59
59
|
function getRequestLogsDir(year, month) {
|
|
60
|
-
const baseDir = path.join(
|
|
60
|
+
const baseDir = path.join(PATHS.statistics.requestLogs, `${year}-${month.toString().padStart(2, '0')}`);
|
|
61
61
|
if (!fs.existsSync(baseDir)) {
|
|
62
62
|
fs.mkdirSync(baseDir, { recursive: true });
|
|
63
63
|
}
|
|
@@ -66,7 +66,7 @@ function getRequestLogsDir(year, month) {
|
|
|
66
66
|
|
|
67
67
|
// 获取统计文件路径
|
|
68
68
|
function getStatisticsFilePath() {
|
|
69
|
-
return
|
|
69
|
+
return PATHS.statistics.summary;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// 获取每日统计文件路径
|
|
@@ -82,7 +82,7 @@ function getRequestLogFilePath(year, month, day) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function getProxyLogsFilePath() {
|
|
85
|
-
return
|
|
85
|
+
return PATHS.statistics.proxyLogs;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// 加载总体统计
|
|
@@ -920,9 +920,13 @@ async function getTrendStatistics({ startDate, endDate, granularity = 'day', ste
|
|
|
920
920
|
|
|
921
921
|
if (granularity === 'day') {
|
|
922
922
|
labels.push(dateStr);
|
|
923
|
-
|
|
923
|
+
let byDimension = activeFilters
|
|
924
924
|
? readJsonlForDay(year, month, day, groupBy, activeFilters)
|
|
925
925
|
: mergeAllToolsDailyStats(dateStr, groupBy);
|
|
926
|
+
if (!activeFilters && Object.keys(byDimension).length === 0) {
|
|
927
|
+
// Fallback: if daily stats are missing, derive from JSONL logs
|
|
928
|
+
byDimension = readJsonlForDay(year, month, day, groupBy);
|
|
929
|
+
}
|
|
926
930
|
|
|
927
931
|
// Accumulate dimensions seen so far with 0 for this label position
|
|
928
932
|
const labelIdx = labels.length - 1;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const { PATHS } = require('../../config/paths');
|
|
3
4
|
|
|
4
|
-
const UI_CONFIG_DIR = PATHS.base;
|
|
5
5
|
const UI_CONFIG_FILE = PATHS.uiConfig;
|
|
6
6
|
|
|
7
7
|
// Default UI config
|
|
@@ -34,8 +34,9 @@ let cacheInitialized = false;
|
|
|
34
34
|
|
|
35
35
|
// Ensure UI config directory exists
|
|
36
36
|
function ensureConfigDir() {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const dir = path.dirname(UI_CONFIG_FILE);
|
|
38
|
+
if (!fs.existsSync(dir)) {
|
|
39
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
|