coding-tool-x 3.3.8 → 3.3.9

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 (75) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/README.md +253 -326
  3. package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-D6LzK9hk.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-D_hRb55W.js → ConfigTemplates-BUDYuxRi.js} +1 -1
  5. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  6. package/dist/web/assets/Home-D7KX7iF8.js +1 -0
  7. package/dist/web/assets/{PluginManager-JXsyym1s.js → PluginManager-DTgQ--vB.js} +1 -1
  8. package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-DMCiGmCT.js} +1 -1
  9. package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CRBsdVRe.js} +1 -1
  10. package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-DMwx2Q4k.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-DapB4ljL.js} +1 -1
  12. package/dist/web/assets/{icons-Cuc23WS7.js → icons-B5Pl4lrD.js} +1 -1
  13. package/dist/web/assets/index-CL-qpoJ_.js +2 -0
  14. package/dist/web/assets/index-D_5dRFOL.css +1 -0
  15. package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
  16. package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
  17. package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
  18. package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
  19. package/dist/web/index.html +7 -7
  20. package/docs/home.png +0 -0
  21. package/package.json +13 -5
  22. package/src/commands/daemon.js +3 -2
  23. package/src/commands/security.js +1 -2
  24. package/src/config/paths.js +638 -93
  25. package/src/server/api/agents.js +1 -1
  26. package/src/server/api/claude-hooks.js +13 -8
  27. package/src/server/api/codex-proxy.js +5 -4
  28. package/src/server/api/hooks.js +45 -0
  29. package/src/server/api/plugins.js +0 -1
  30. package/src/server/api/ui-config.js +5 -0
  31. package/src/server/codex-proxy-server.js +89 -59
  32. package/src/server/gemini-proxy-server.js +107 -88
  33. package/src/server/index.js +1 -0
  34. package/src/server/opencode-proxy-server.js +381 -225
  35. package/src/server/proxy-server.js +86 -60
  36. package/src/server/services/alias.js +3 -3
  37. package/src/server/services/channels.js +3 -2
  38. package/src/server/services/codex-channels.js +41 -87
  39. package/src/server/services/codex-env-manager.js +423 -0
  40. package/src/server/services/codex-settings-manager.js +15 -15
  41. package/src/server/services/codex-statistics-service.js +3 -27
  42. package/src/server/services/config-export-service.js +20 -7
  43. package/src/server/services/config-registry-service.js +3 -2
  44. package/src/server/services/config-sync-manager.js +1 -1
  45. package/src/server/services/favorites.js +4 -3
  46. package/src/server/services/gemini-channels.js +3 -3
  47. package/src/server/services/gemini-statistics-service.js +3 -25
  48. package/src/server/services/mcp-service.js +2 -3
  49. package/src/server/services/model-detector.js +4 -3
  50. package/src/server/services/native-oauth-adapters.js +2 -1
  51. package/src/server/services/network-access.js +39 -1
  52. package/src/server/services/notification-hooks.js +951 -0
  53. package/src/server/services/opencode-channels.js +6 -6
  54. package/src/server/services/opencode-sessions.js +2 -2
  55. package/src/server/services/opencode-statistics-service.js +3 -27
  56. package/src/server/services/plugins-service.js +110 -31
  57. package/src/server/services/prompts-service.js +2 -3
  58. package/src/server/services/proxy-log-helper.js +242 -0
  59. package/src/server/services/proxy-runtime.js +6 -4
  60. package/src/server/services/repo-scanner-base.js +12 -4
  61. package/src/server/services/request-logger.js +7 -7
  62. package/src/server/services/security-config.js +4 -4
  63. package/src/server/services/session-cache.js +2 -2
  64. package/src/server/services/sessions.js +2 -2
  65. package/src/server/services/skill-service.js +174 -55
  66. package/src/server/services/statistics-service.js +5 -5
  67. package/src/server/services/ui-config.js +4 -3
  68. package/src/server/services/workspace-service.js +1 -1
  69. package/src/server/websocket-server.js +5 -4
  70. package/dist/web/assets/Home-BMoFdAwy.css +0 -1
  71. package/dist/web/assets/Home-DNwp-0J-.js +0 -1
  72. package/dist/web/assets/index-BXeSvAwU.js +0 -2
  73. package/dist/web/assets/index-DWAC3Tdv.css +0 -1
  74. package/docs/bannel.png +0 -0
  75. 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.base;
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 = path.join(getCcToolDir(), 'session-has-cache.json');
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 path.join(PATHS.base, 'fork-relations.json');
32
+ return PATHS.forkRelations;
33
33
  }
34
34
 
35
35
  // Get path for storing session order
36
36
  function getSessionOrderFilePath() {
37
- return path.join(getCcToolDir(), 'session-order.json');
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: 'skills',
163
- reposFile: 'skill-repos.json',
164
- cacheFile: 'skills-cache.json'
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: 'codex-skills',
169
- reposFile: 'codex-skill-repos.json',
170
- cacheFile: 'codex-skills-cache.json'
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: 'gemini-skills',
175
- reposFile: 'gemini-skill-repos.json',
176
- cacheFile: 'gemini-skills-cache.json'
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: 'opencode-skills',
181
- reposFile: 'opencode-skill-repos.json',
182
- cacheFile: 'opencode-skills-cache.json'
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 = path.join(HOME_DIR, '.cc-tool');
203
+ this.configDir = PATHS.config;
193
204
 
194
205
  const platformConfig = PLATFORM_CONFIG[this.platform];
195
206
  this.installDir = platformConfig.installDir;
196
- this.storageDir = path.join(this.configDir, platformConfig.storageDir);
197
- this.reposConfigPath = path.join(this.configDir, platformConfig.reposFile);
198
- this.cachePath = path.join(this.configDir, platformConfig.cacheFile);
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({ removeFile: true });
452
+ this.clearCache();
421
453
  }
422
454
 
455
+ const fileCache = this.loadCacheFromFile();
456
+
423
457
  // 检查内存缓存
424
- if (!forceRefresh && this.skillsCache) {
425
- this.updateInstallStatus(this.skillsCache);
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
- const fileCache = this.loadCacheFromFile();
432
- if (fileCache) {
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 repoInfo = `${enabledRepos[i].owner}/${enabledRepos[i].name}`;
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
- this.mergeLocalSkills(skills);
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
- this.deduplicateSkills(skills);
474
- skills.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
522
+ if (shouldUseStaleFileCache) {
523
+ this.skillsCache = preparedFileCache;
524
+ this.cacheTime = Date.now();
525
+ return this.skillsCache;
526
+ }
475
527
 
476
528
  // 更新缓存
477
- this.skillsCache = skills;
529
+ this.skillsCache = preparedSkills;
478
530
  this.cacheTime = Date.now();
479
- this.saveCacheToFile(skills);
531
+ this.saveCacheToFile(preparedSkills);
480
532
 
481
- return skills;
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 repo.localPath || null;
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
- getGitHubToken() {
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, 'github-token.txt');
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
- getGitLabToken() {
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
- try {
808
- const configPath = path.join(this.configDir, 'gitlab-token.txt');
809
- if (fs.existsSync(configPath)) {
810
- return fs.readFileSync(configPath, 'utf-8').trim();
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
- return null;
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.base;
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 = path.join(getBaseDir(), 'daily-stats');
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(getBaseDir(), 'request-logs', `${year}-${month.toString().padStart(2, '0')}`);
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 path.join(getBaseDir(), 'statistics.json');
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 path.join(getBaseDir(), 'proxy-logs.json');
85
+ return PATHS.statistics.proxyLogs;
86
86
  }
87
87
 
88
88
  // 加载总体统计
@@ -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
- if (!fs.existsSync(UI_CONFIG_DIR)) {
38
- fs.mkdirSync(UI_CONFIG_DIR, { recursive: true });
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
 
@@ -6,7 +6,7 @@ const { PATHS } = require('../../config/paths');
6
6
  const configTemplatesService = require('./config-templates-service');
7
7
 
8
8
  // 工作区配置文件路径
9
- const WORKSPACES_CONFIG = path.join(PATHS.base, 'workspaces.json');
9
+ const WORKSPACES_CONFIG = PATHS.workspaces;
10
10
 
11
11
  function runGitCommand(args, options = {}) {
12
12
  const execOptions = {
@@ -159,11 +159,12 @@ function installOriginGuard(server) {
159
159
 
160
160
  // 日志持久化文件路径
161
161
  function getLogsFilePath() {
162
- const ccToolDir = PATHS.base;
163
- if (!fs.existsSync(ccToolDir)) {
164
- fs.mkdirSync(ccToolDir, { recursive: true });
162
+ const filePath = PATHS.statistics.proxyLogs;
163
+ const dir = path.dirname(filePath);
164
+ if (!fs.existsSync(dir)) {
165
+ fs.mkdirSync(dir, { recursive: true });
165
166
  }
166
- return path.join(ccToolDir, 'proxy-logs.json');
167
+ return filePath;
167
168
  }
168
169
 
169
170
  function getTodayRange() {
@@ -1 +0,0 @@
1
- .channel-column[data-v-fb3a530a]{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--gradient-card);border:1px solid var(--border-primary);border-radius:8px;overflow:hidden;box-shadow:var(--shadow-sm)}.channel-header[data-v-fb3a530a]{display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--bg-primary);position:relative}.drag-handle[data-v-fb3a530a]{cursor:grab;color:var(--text-tertiary);display:flex;align-items:center;padding:4px;margin:-4px 0 -4px -4px;border-radius:4px;transition:all .2s}.drag-handle[data-v-fb3a530a]:hover{color:var(--text-secondary);background:var(--bg-tertiary)}.drag-handle[data-v-fb3a530a]:active{cursor:grabbing}.channel-header[data-v-fb3a530a]:after{content:"";position:absolute;bottom:0;left:14px;right:14px;height:2px;border-radius:1px}.channel-header.claude[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(24,160,88,.08) 0%,transparent 100%)}.channel-header.claude[data-v-fb3a530a]:after{background:linear-gradient(90deg,#18a058,#18a0584d)}.channel-header.codex[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(59,130,246,.08) 0%,transparent 100%)}.channel-header.codex[data-v-fb3a530a]:after{background:linear-gradient(90deg,#3b82f6,#3b82f64d)}.channel-header.gemini[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(168,85,247,.08) 0%,transparent 100%)}.channel-header.gemini[data-v-fb3a530a]:after{background:linear-gradient(90deg,#a855f7,#a855f74d)}.channel-header.opencode[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(234,88,12,.08) 0%,transparent 100%)}.channel-header.opencode[data-v-fb3a530a]:after{background:linear-gradient(90deg,#ea580c,#ea580c4d)}.header-icon[data-v-fb3a530a]{width:32px;height:32px;border-radius:6px;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px #0000001a}.channel-header.claude .header-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#18a058,#15803d);color:#fff}.channel-header.codex .header-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff}.channel-header.gemini .header-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#a855f7,#9333ea);color:#fff}.channel-header.opencode .header-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#ea580c,#c2410c);color:#fff}.channel-title[data-v-fb3a530a]{font-size:15px;font-weight:700;margin:0;color:var(--text-primary);letter-spacing:.3px}.channel-content[data-v-fb3a530a]{flex:1;min-height:0;overflow:hidden;padding:10px;display:flex;flex-direction:column;gap:8px}.card[data-v-fb3a530a]{background:var(--bg-primary);border:1px solid var(--border-primary);border-radius:8px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 12px #0000000d;position:relative}.card[data-v-fb3a530a]:before{content:"";position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.1),transparent);opacity:0;transition:opacity .3s ease}.card[data-v-fb3a530a]:hover{box-shadow:0 6px 20px #0000001a;border-color:var(--border-secondary)}.card[data-v-fb3a530a]:hover:before{opacity:1}.card.clickable[data-v-fb3a530a]{cursor:pointer}.card.clickable[data-v-fb3a530a]:hover{border-color:#18a058;transform:translateY(-2px);box-shadow:0 8px 24px #18a0582e}.card.clickable[data-v-fb3a530a]:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(135deg,rgba(24,160,88,.02) 0%,transparent 100%);opacity:0;transition:opacity .3s ease;pointer-events:none}.card.clickable[data-v-fb3a530a]:hover:after{opacity:1}.card-header[data-v-fb3a530a]{display:flex;align-items:center;gap:8px;padding:10px 12px;background:linear-gradient(180deg,var(--bg-secondary) 0%,var(--bg-primary) 100%);border-bottom:1px solid var(--border-primary);position:relative;min-height:24px}.card-header.compact[data-v-fb3a530a]{padding:8px 12px}.card-header .n-icon[data-v-fb3a530a]{color:var(--text-tertiary);transition:color .2s ease}.card:hover .card-header .n-icon[data-v-fb3a530a]{color:var(--text-secondary)}.card-title[data-v-fb3a530a]{font-size:11px;font-weight:700;margin:0;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.8px}.runtime-badge[data-v-fb3a530a]{display:inline-flex;align-items:center;padding:3px 8px;margin-left:8px;font-size:10px;font-weight:600;color:#10b981;background:linear-gradient(135deg,#10b9811a,#34d3991a);border:1px solid rgba(16,185,129,.2);border-radius:4px;white-space:nowrap;animation:pulse-runtime-fb3a530a 2s ease-in-out infinite}@keyframes pulse-runtime-fb3a530a{0%,to{opacity:1;transform:scale(1)}50%{opacity:.8;transform:scale(.98)}}.card-body[data-v-fb3a530a]{padding:12px;background:var(--bg-primary)}.card-body.compact[data-v-fb3a530a]{padding:10px 12px}.proxy-control[data-v-fb3a530a]{display:flex;justify-content:space-between;align-items:center;gap:16px}.proxy-info[data-v-fb3a530a]{display:flex;flex-direction:column;gap:4px}.proxy-info-row[data-v-fb3a530a]{display:flex;align-items:center;justify-content:space-between}.proxy-status[data-v-fb3a530a]{display:flex;align-items:center;gap:6px}.proxy-port[data-v-fb3a530a]{font-size:11px;color:var(--text-secondary);margin-left:6px}.channel-selector[data-v-fb3a530a]{display:flex;align-items:center;padding:4px 8px;background:linear-gradient(135deg,var(--bg-secondary) 0%,var(--bg-tertiary) 100%);border:1px solid var(--border-primary);border-radius:4px;color:var(--text-secondary);transition:all .2s ease}.channel-selector[data-v-fb3a530a]:hover{background:var(--hover-bg);border-color:var(--border-secondary);color:var(--text-primary)}.channel-selector[data-v-fb3a530a]:disabled{opacity:.5;cursor:not-allowed}.channel-name[data-v-fb3a530a]{font-size:11px;font-weight:500;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-dot[data-v-fb3a530a]{width:8px;height:8px;border-radius:50%;background:var(--border-secondary);transition:all .3s ease}.status-dot.active[data-v-fb3a530a]{background:#18a058;box-shadow:0 0 8px #18a05880}.quick-stats[data-v-fb3a530a]{display:flex;gap:16px;padding:4px 0}.stat-item[data-v-fb3a530a]{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:4px 0}.stat-divider[data-v-fb3a530a]{width:1px;background:linear-gradient(180deg,transparent 0%,var(--border-primary) 50%,transparent 100%)}.quick-access-list[data-v-fb3a530a]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.access-card[data-v-fb3a530a]{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:6px;background:var(--bg-secondary);border:1px solid var(--border-color);transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;min-height:52px;overflow:hidden}.access-card[data-v-fb3a530a]:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s ease;pointer-events:none}.access-icon[data-v-fb3a530a]{width:32px;height:32px;border-radius:5px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .3s ease}.access-content[data-v-fb3a530a]{display:flex;flex-direction:column;gap:4px;flex:1}.access-card-projects .access-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#6366f126,#8b5cf626);color:#6366f1}.access-card-projects[data-v-fb3a530a]:before{background:linear-gradient(135deg,#6366f114,#8b5cf614)}.access-card-projects:hover .access-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#6366f140,#8b5cf640);transform:scale(1.1) rotate(-5deg)}.access-card-sessions .access-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#10b98126,#05966926);color:#10b981}.access-card-sessions[data-v-fb3a530a]:before{background:linear-gradient(135deg,#10b98114,#05966914)}.access-card-sessions:hover .access-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#10b98140,#05966940);transform:scale(1.1) rotate(5deg)}.access-card-goto .access-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#f59e0b26,#fb923c26);color:#f59e0b}.access-card-goto[data-v-fb3a530a]:before{background:linear-gradient(135deg,#f59e0b14,#fb923c14)}.access-card-goto:hover .access-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#f59e0b40,#fb923c40);transform:scale(1.1) translate(3px)}.access-card.clickable[data-v-fb3a530a]:hover{transform:translateY(-2px);box-shadow:0 8px 20px #0000001a;border-color:var(--border-secondary)}.access-card.clickable[data-v-fb3a530a]:hover:before{opacity:1}.access-card.clickable[data-v-fb3a530a]:active{transform:translateY(0)}.access-label[data-v-fb3a530a]{display:block;font-size:10px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.3px}.access-value[data-v-fb3a530a]{display:block;font-size:18px;font-weight:800;color:var(--text-primary);line-height:1}.access-goto[data-v-fb3a530a]{display:block;font-size:12px;font-weight:700;color:var(--text-primary);line-height:1.2;white-space:nowrap}.stats-inline[data-v-fb3a530a]{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.stats-inline.stats-3col[data-v-fb3a530a]{grid-template-columns:repeat(3,1fr)}.stat-inline-item[data-v-fb3a530a]{display:flex;align-items:center;gap:8px;padding:10px;background:var(--bg-secondary);border-radius:6px;border:1px solid var(--border-primary);transition:all .2s ease}.stat-inline-item[data-v-fb3a530a]:hover{border-color:var(--border-secondary);background:var(--hover-bg)}.stat-icon-dot[data-v-fb3a530a]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.stat-icon-dot.requests[data-v-fb3a530a]{background:#3b82f6;box-shadow:0 0 6px #3b82f680}.stat-icon-dot.tokens[data-v-fb3a530a]{background:#18a058;box-shadow:0 0 6px #18a05880}.stat-icon-dot.cost[data-v-fb3a530a]{background:#f59e0b;box-shadow:0 0 6px #f59e0b80}.stat-info[data-v-fb3a530a]{display:flex;flex-direction:column;gap:1px;min-width:0}.stat-label[data-v-fb3a530a]{font-size:10px;color:var(--text-tertiary);font-weight:500;text-transform:uppercase;letter-spacing:.2px}.stat-value[data-v-fb3a530a]{font-size:16px;font-weight:700;color:var(--text-primary);line-height:1.2;transition:all .3s ease}.stat-value.animating[data-v-fb3a530a]{animation:numberChange-fb3a530a .6s ease}@keyframes numberChange-fb3a530a{0%{transform:translateY(-6px);opacity:.5}50%{transform:translateY(0);opacity:1;color:var(--primary-color)}to{transform:translateY(0);opacity:1}}.stats-card[data-v-fb3a530a],.chart-card[data-v-fb3a530a]{border-left:2px solid transparent}.stats-card-claude[data-v-fb3a530a],.chart-card[data-v-fb3a530a]:has(+.stats-card-claude),.card[data-v-fb3a530a]:has(.panel-card):nth-child(4){border-left-color:#18a058}.chart-card.chart-card-claude[data-v-fb3a530a]{border-left-color:#18a058}.stats-card-codex[data-v-fb3a530a],.chart-card[data-v-fb3a530a]:has(+.stats-card-codex),.card[data-v-fb3a530a]:has(.panel-card):nth-child(4){border-left-color:#3b82f6}.chart-card.chart-card-codex[data-v-fb3a530a]{border-left-color:#3b82f6}.stats-card-gemini[data-v-fb3a530a],.chart-card[data-v-fb3a530a]:has(+.stats-card-gemini),.card[data-v-fb3a530a]:has(.panel-card):nth-child(4){border-left-color:#a855f7}.chart-card.chart-card-gemini[data-v-fb3a530a]{border-left-color:#a855f7}.stats-card-opencode[data-v-fb3a530a],.chart-card[data-v-fb3a530a]:has(+.stats-card-opencode),.card[data-v-fb3a530a]:has(.panel-card):nth-child(4){border-left-color:#ea580c}.chart-card.chart-card-opencode[data-v-fb3a530a]{border-left-color:#ea580c}.chart-card[data-v-fb3a530a]{padding:0;overflow:hidden}.chart-card[data-v-fb3a530a] .panel-card{border:none;border-radius:0;box-shadow:none;background:transparent}.stat-requests .stat-value[data-v-fb3a530a]{color:#3b82f6}.stat-input .stat-value[data-v-fb3a530a]{color:#18a058}.stat-output .stat-value[data-v-fb3a530a]{color:#f59e0b}.token-label[data-v-fb3a530a]{display:flex;align-items:center;gap:8px}.token-dot[data-v-fb3a530a]{width:8px;height:8px;border-radius:50%}.logs-card[data-v-fb3a530a]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--border-primary);border-radius:6px}.logs-card-body[data-v-fb3a530a]{flex:1;min-height:0;display:flex;flex-direction:column;padding:0}.logs-card .card-header[data-v-fb3a530a]{background:linear-gradient(135deg,var(--bg-secondary) 0%,var(--bg-tertiary) 100%)}.logs-table-wrapper[data-v-fb3a530a]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-primary)}.logs-table-header[data-v-fb3a530a]{display:flex;padding:10px 12px;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border-bottom:2px solid var(--border-primary);font-size:11px;font-weight:700;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.6px;flex-shrink:0}.logs-header-claude .log-col[data-v-fb3a530a]{color:#18a058b3;font-weight:600}.logs-header-codex .log-col[data-v-fb3a530a]{color:#3b82f6b3;font-weight:600}.logs-header-gemini .log-col[data-v-fb3a530a]{color:#a855f7b3;font-weight:600}.logs-header-opencode .log-col[data-v-fb3a530a]{color:#ea580cb3;font-weight:600}[data-theme=dark] .logs-header-claude .log-col[data-v-fb3a530a]{color:#34d399a6}[data-theme=dark] .logs-header-codex .log-col[data-v-fb3a530a]{color:#60a5faa6}[data-theme=dark] .logs-header-gemini .log-col[data-v-fb3a530a]{color:#c084fca6}[data-theme=dark] .logs-header-opencode .log-col[data-v-fb3a530a]{color:#fb923ca6}.logs-container[data-v-fb3a530a]{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden}.logs-container[data-v-fb3a530a]::-webkit-scrollbar{width:4px}.logs-container[data-v-fb3a530a]::-webkit-scrollbar-thumb{background:#18a0584d;border-radius:2px}.logs-container[data-v-fb3a530a]::-webkit-scrollbar-thumb:hover{background:#18a05880}.empty-logs[data-v-fb3a530a]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--text-tertiary);min-height:200px;text-align:center}.log-row[data-v-fb3a530a]{display:flex;align-items:center;padding:10px 12px;min-height:40px;border-bottom:1px solid var(--border-primary);font-size:12px;transition:all .2s ease;background:var(--bg-primary);position:relative}.log-row[data-v-fb3a530a]:nth-child(2n){background:var(--bg-secondary)}.log-row[data-v-fb3a530a]:hover{background:var(--hover-bg);transform:translate(2px)}.log-row.new-log[data-v-fb3a530a]{animation:newLogPulse-fb3a530a 4.5s cubic-bezier(.25,.46,.45,.94) forwards;border-left:3px solid #18a058}@keyframes newLogPulse-fb3a530a{0%{background:linear-gradient(90deg,#18a05826,#18a0580f);box-shadow:0 0 8px #18a05833}5%{background:linear-gradient(90deg,#18a0582e,#18a05814);box-shadow:0 0 12px 1px #18a05840}15%{background:linear-gradient(90deg,#18a05829,#18a05812);box-shadow:0 0 10px 1px #18a05833}30%{background:linear-gradient(90deg,#18a0581f,#18a0580d);box-shadow:0 0 6px #18a05826}50%{background:linear-gradient(90deg,#18a05814,#18a05808);box-shadow:0 0 4px #18a0581a}70%{background:linear-gradient(90deg,#18a0580a,#18a05804);box-shadow:0 0 2px #18a0580f}85%{background:linear-gradient(90deg,#18a05805,#18a05802);box-shadow:0 0 1px #18a05808}to{background:transparent;box-shadow:0 0 #18a05800;border-left-color:transparent}}[data-theme=dark] .log-row.new-log[data-v-fb3a530a]{animation:newLogPulseDark-fb3a530a 4.5s cubic-bezier(.25,.46,.45,.94) forwards}@keyframes newLogPulseDark-fb3a530a{0%{background:linear-gradient(90deg,#18a05833,#18a05814);box-shadow:0 0 10px #18a05840}5%{background:linear-gradient(90deg,#18a0583d,#18a0581a);box-shadow:0 0 14px 2px #18a0584d}15%{background:linear-gradient(90deg,#18a05836,#18a05817);box-shadow:0 0 12px 1px #18a05840}30%{background:linear-gradient(90deg,#18a05829,#18a05812);box-shadow:0 0 8px 1px #18a0582e}50%{background:linear-gradient(90deg,#18a0581c,#18a0580b);box-shadow:0 0 5px #18a0581f}70%{background:linear-gradient(90deg,#18a0580f,#18a05806);box-shadow:0 0 3px #18a05814}85%{background:linear-gradient(90deg,#18a05808,#18a05803);box-shadow:0 0 1px #18a0580a}to{background:transparent;box-shadow:0 0 #18a05800;border-left-color:transparent}}.log-row.new-log.action-row[data-v-fb3a530a]{border-left:3px solid #18a058}.log-row.action-row[data-v-fb3a530a]{background:linear-gradient(90deg,#18a0581f,#18a0580a);border-left:3px solid #18a058;padding-left:8px}.log-row.action-row[data-v-fb3a530a]:hover{background:linear-gradient(90deg,#18a0582e,#18a05814)}.action-content[data-v-fb3a530a]{display:flex;align-items:center;gap:8px;width:100%}.action-msg[data-v-fb3a530a]{flex:1;font-size:11px;color:#18a058;font-weight:600;letter-spacing:.2px}.action-time[data-v-fb3a530a]{font-size:10px;font-family:SF Mono,Monaco,monospace;color:var(--text-tertiary);background:#18a0581a;padding:2px 6px;border-radius:4px}.log-col[data-v-fb3a530a]{display:flex;align-items:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-channel[data-v-fb3a530a]{min-width:0}.col-channel .n-tag[data-v-fb3a530a]{max-width:100%;font-size:11px;font-weight:600;border-radius:4px;padding:3px 10px}.col-token[data-v-fb3a530a]{justify-content:center;font-family:SF Mono,Monaco,Consolas,monospace;font-size:12px;font-weight:600;color:var(--text-secondary);background:#00000008;padding:4px 8px;border-radius:4px;margin:0 3px}[data-theme=dark] .col-token[data-v-fb3a530a]{background:#ffffff0f}.col-time[data-v-fb3a530a]{justify-content:flex-end;font-family:SF Mono,Monaco,Consolas,monospace;font-size:11px;font-weight:500;color:var(--text-tertiary);padding-right:2px;opacity:.85}.log-row:hover .col-time[data-v-fb3a530a]{opacity:1;color:var(--text-secondary)}.col-channel-claude[data-v-fb3a530a]{flex:2 1 60px;min-width:50px}.col-token-claude[data-v-fb3a530a]{flex:1 1 40px;min-width:35px}.col-time-claude[data-v-fb3a530a]{flex:1.5 1 55px;min-width:50px}.col-channel-codex[data-v-fb3a530a]{flex:2 1 60px;min-width:50px}.col-token-codex[data-v-fb3a530a]{flex:1 1 40px;min-width:35px}.col-time-codex[data-v-fb3a530a]{flex:1.5 1 55px;min-width:50px}.col-channel-gemini[data-v-fb3a530a]{flex:2.5 1 70px;min-width:55px}.col-token-gemini[data-v-fb3a530a]{flex:1.2 1 45px;min-width:40px}.col-time-gemini[data-v-fb3a530a]{flex:1.8 1 60px;min-width:55px}.col-channel-opencode[data-v-fb3a530a]{flex:2.5 1 70px;min-width:55px}.col-token-opencode[data-v-fb3a530a]{flex:1.2 1 45px;min-width:40px}.col-time-opencode[data-v-fb3a530a]{flex:1.8 1 60px;min-width:55px}.claude-extra-area[data-v-fb3a530a]{display:flex;align-items:center;gap:6px;margin-left:auto}.mcp-count-tag[data-v-fb3a530a],.skills-count-tag[data-v-fb3a530a]{font-size:10px;font-weight:500;padding:0 8px;height:22px;line-height:22px;border-radius:4px;cursor:pointer;transition:all .2s ease}.mcp-count-tag.clickable[data-v-fb3a530a]:hover,.skills-count-tag.clickable[data-v-fb3a530a]:hover{transform:scale(1.03);filter:brightness(1.1)}.mcp-quick-panel[data-v-fb3a530a],.skills-quick-panel[data-v-fb3a530a]{padding:2px 0}.mcp-quick-panel .panel-title[data-v-fb3a530a],.skills-quick-panel .panel-title[data-v-fb3a530a]{display:flex;justify-content:space-between;align-items:center;padding:6px 10px 8px;border-bottom:1px solid var(--border-primary);margin-bottom:4px}.mcp-quick-panel .panel-title span[data-v-fb3a530a]:first-child,.skills-quick-panel .panel-title span[data-v-fb3a530a]:first-child{font-size:13px;font-weight:600;color:var(--text-primary)}.no-items[data-v-fb3a530a]{padding:16px 10px;text-align:center}.mcp-quick-list[data-v-fb3a530a],.skills-quick-list[data-v-fb3a530a]{max-height:300px;overflow-y:auto;padding:0 4px 4px}.mcp-quick-item[data-v-fb3a530a],.skill-quick-item[data-v-fb3a530a]{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:5px;margin-bottom:4px;background:var(--bg-secondary);transition:all .2s ease}.mcp-quick-item[data-v-fb3a530a]:hover,.skill-quick-item[data-v-fb3a530a]:hover{background:var(--hover-bg)}.mcp-item-icon[data-v-fb3a530a],.skill-item-icon[data-v-fb3a530a]{width:24px;height:24px;border-radius:5px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.mcp-item-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#3b82f626,#3b82f60d);color:#3b82f6}.skill-item-icon[data-v-fb3a530a]{background:linear-gradient(135deg,#18a05826,#18a0580d);color:#18a058}.mcp-item-info[data-v-fb3a530a],.skill-item-info[data-v-fb3a530a]{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.mcp-item-name[data-v-fb3a530a],.skill-item-name[data-v-fb3a530a]{font-size:12px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mcp-item-type[data-v-fb3a530a]{font-size:10px;color:var(--text-tertiary);text-transform:uppercase}.skill-item-desc[data-v-fb3a530a]{font-size:10px;color:var(--text-tertiary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mcp-item-status[data-v-fb3a530a]{flex-shrink:0}.mcp-item-status .status-dot[data-v-fb3a530a]{width:8px;height:8px;border-radius:50%;background:var(--border-secondary)}.mcp-item-status.online .status-dot[data-v-fb3a530a]{background:#18a058;box-shadow:0 0 6px #18a05880}.mcp-item-status.error .status-dot[data-v-fb3a530a]{background:#d03050;box-shadow:0 0 6px #d0305080}.mcp-quick-list[data-v-fb3a530a]::-webkit-scrollbar,.skills-quick-list[data-v-fb3a530a]::-webkit-scrollbar{width:4px}.mcp-quick-list[data-v-fb3a530a]::-webkit-scrollbar-thumb,.skills-quick-list[data-v-fb3a530a]::-webkit-scrollbar-thumb{background:#00000026;border-radius:2px}[data-theme=dark] .mcp-quick-list[data-v-fb3a530a]::-webkit-scrollbar-thumb,[data-theme=dark] .skills-quick-list[data-v-fb3a530a]::-webkit-scrollbar-thumb{background:#ffffff26}.skills-button[data-v-fb3a530a]{padding:6px!important;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:5px;background:var(--bg-secondary);border:1px solid var(--border-primary);transition:all .25s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 4px #0000000d}.skills-button .n-icon[data-v-fb3a530a]{color:var(--text-color-3);transition:all .25s ease}.skills-button[data-v-fb3a530a]:hover{background:linear-gradient(135deg,#18a0581a,#18a0580d);border-color:#18a0584d;box-shadow:0 4px 12px #18a05826;transform:translateY(-1px)}.skills-button:hover .n-icon[data-v-fb3a530a]{color:#18a058;transform:scale(1.1)}.skills-button[data-v-fb3a530a]:active{transform:translateY(0);box-shadow:0 2px 4px #0000000d}.lock-button[data-v-fb3a530a]{margin-left:6px;padding:6px!important;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:5px;background:var(--bg-secondary);border:1px solid var(--border-primary);transition:all .25s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 4px #0000000d}.lock-button .n-icon[data-v-fb3a530a]{color:var(--text-color-3);transition:all .25s ease}.lock-button[data-v-fb3a530a]:hover{background:linear-gradient(135deg,#6b72801a,#6b72800d);border-color:#6b72804d;box-shadow:0 4px 12px #0000001a;transform:translateY(-1px)}.lock-button:hover .n-icon[data-v-fb3a530a]{color:var(--text-color-1);transform:scale(1.1)}.lock-button[data-v-fb3a530a]:active{transform:translateY(0);box-shadow:0 2px 4px #0000000d}.locked-overlay[data-v-fb3a530a]{flex:1;display:flex;align-items:center;justify-content:center;border-radius:6px;margin:10px;position:relative;overflow:hidden;background:var(--bg-secondary);border:1px solid var(--border-primary);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.locked-overlay[data-v-fb3a530a]:before{content:"";position:absolute;width:400px;height:400px;border-radius:50%;top:50%;left:50%;transform:translate(-50%,-50%);opacity:.4;filter:blur(60px);pointer-events:none}.locked-claude[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(24,160,88,.03) 0%,var(--bg-secondary) 50%,rgba(24,160,88,.02) 100%)}.locked-claude[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(24,160,88,.15) 0%,transparent 70%)}.locked-codex[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(59,130,246,.03) 0%,var(--bg-secondary) 50%,rgba(59,130,246,.02) 100%)}.locked-codex[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(59,130,246,.15) 0%,transparent 70%)}.locked-gemini[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(168,85,247,.03) 0%,var(--bg-secondary) 50%,rgba(168,85,247,.02) 100%)}.locked-gemini[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(168,85,247,.15) 0%,transparent 70%)}.locked-opencode[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(234,88,12,.03) 0%,var(--bg-secondary) 50%,rgba(234,88,12,.02) 100%)}.locked-opencode[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(234,88,12,.15) 0%,transparent 70%)}.locked-content[data-v-fb3a530a]{display:flex;flex-direction:column;align-items:center;gap:20px;padding:60px 40px;text-align:center;position:relative;z-index:1}.lock-icon[data-v-fb3a530a]{width:96px;height:96px;display:flex;align-items:center;justify-content:center;border-radius:10px;background:var(--bg-primary);border:2px solid var(--border-primary);margin-bottom:8px;position:relative;box-shadow:0 8px 24px #0000001f;animation:lock-float-fb3a530a 3s ease-in-out infinite}@keyframes lock-float-fb3a530a{0%,to{transform:translateY(0)}50%{transform:translateY(-6px)}}.lock-icon[data-v-fb3a530a]:before{content:"";position:absolute;top:-8px;right:-8px;bottom:-8px;left:-8px;border-radius:20px;opacity:.6;filter:blur(12px)}.locked-claude .lock-icon[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(24,160,88,.12) 0%,var(--bg-primary) 100%);border-color:#18a05840;box-shadow:0 8px 24px #18a05826,0 0 0 1px #18a0581a inset}.locked-claude .lock-icon[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(24,160,88,.3) 0%,transparent 60%)}.locked-claude .lock-icon .n-icon[data-v-fb3a530a]{color:#18a058;filter:drop-shadow(0 2px 4px rgba(24,160,88,.3))}.locked-codex .lock-icon[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(59,130,246,.12) 0%,var(--bg-primary) 100%);border-color:#3b82f640;box-shadow:0 8px 24px #3b82f626,0 0 0 1px #3b82f61a inset}.locked-codex .lock-icon[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(59,130,246,.3) 0%,transparent 60%)}.locked-codex .lock-icon .n-icon[data-v-fb3a530a]{color:#3b82f6;filter:drop-shadow(0 2px 4px rgba(59,130,246,.3))}.locked-gemini .lock-icon[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(168,85,247,.12) 0%,var(--bg-primary) 100%);border-color:#a855f740;box-shadow:0 8px 24px #a855f726,0 0 0 1px #a855f71a inset}.locked-gemini .lock-icon[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(168,85,247,.3) 0%,transparent 60%)}.locked-gemini .lock-icon .n-icon[data-v-fb3a530a]{color:#a855f7;filter:drop-shadow(0 2px 4px rgba(168,85,247,.3))}.locked-opencode .lock-icon[data-v-fb3a530a]{background:linear-gradient(135deg,rgba(234,88,12,.12) 0%,var(--bg-primary) 100%);border-color:#ea580c40;box-shadow:0 8px 24px #ea580c26,0 0 0 1px #ea580c1a inset}.locked-opencode .lock-icon[data-v-fb3a530a]:before{background:radial-gradient(circle,rgba(234,88,12,.3) 0%,transparent 60%)}.locked-opencode .lock-icon .n-icon[data-v-fb3a530a]{color:#ea580c;filter:drop-shadow(0 2px 4px rgba(234,88,12,.3))}.lock-icon .n-icon[data-v-fb3a530a]{opacity:.85;position:relative;z-index:1}.locked-title[data-v-fb3a530a]{font-size:17px;font-weight:600;color:var(--text-color-1);margin:0;letter-spacing:.3px}.locked-hint[data-v-fb3a530a]{font-size:13px;color:var(--text-color-3);max-width:220px;line-height:1.7;opacity:.9}.channel-status[data-v-fb3a530a]{cursor:pointer;padding:4px 8px!important;border-radius:4px;transition:all .2s ease}.channel-status[data-v-fb3a530a]:hover{background:var(--hover-bg)}.channel-quick-panel[data-v-fb3a530a]{padding:4px 0}.panel-title[data-v-fb3a530a]{display:flex;justify-content:space-between;align-items:center;padding:8px 12px 12px;border-bottom:1px solid var(--border-primary);margin-bottom:8px}.panel-title span[data-v-fb3a530a]:first-child{font-size:13px;font-weight:600;color:var(--text-primary)}.no-channels[data-v-fb3a530a]{padding:24px 12px;text-align:center}.channel-quick-list[data-v-fb3a530a]{max-height:360px;overflow-y:auto}.channel-quick-item[data-v-fb3a530a]{display:flex;flex-direction:column;gap:8px;padding:10px 12px;border-radius:6px;margin:0 4px 6px;background:var(--bg-secondary);transition:all .2s ease}.channel-quick-item[data-v-fb3a530a]:hover{background:var(--hover-bg)}.channel-quick-item.disabled[data-v-fb3a530a]{opacity:.6}.channel-quick-info[data-v-fb3a530a]{display:flex;align-items:center;gap:8px;width:100%}.channel-quick-name[data-v-fb3a530a]{font-size:13px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.channel-metrics[data-v-fb3a530a]{display:flex;gap:6px;width:100%}.channel-metrics .metric-item[data-v-fb3a530a]{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 4px;background:var(--bg-primary);border-radius:4px;border:1px solid var(--border-primary)}.channel-metrics .metric-label[data-v-fb3a530a]{font-size:10px;color:var(--text-tertiary);font-weight:500}.channel-metrics .metric-value[data-v-fb3a530a]{font-size:12px;font-weight:700;color:var(--text-primary);font-family:SF Mono,Monaco,monospace}.channel-metrics .metric-value.active[data-v-fb3a530a]{color:#f59e0b}.channel-switching-tip[data-v-fb3a530a]{font-size:11px;color:var(--text-tertiary);padding:0 2px}.channel-quick-item.disabled .channel-metrics .metric-value[data-v-fb3a530a]{color:var(--text-tertiary)}.channel-quick-list[data-v-fb3a530a]::-webkit-scrollbar{width:4px}.channel-quick-list[data-v-fb3a530a]::-webkit-scrollbar-thumb{background:#00000026;border-radius:2px}[data-theme=dark] .channel-quick-list[data-v-fb3a530a]::-webkit-scrollbar-thumb{background:#ffffff26}@media (max-width: 1024px){.channel-column[data-v-fb3a530a]{border-radius:6px}.card-header[data-v-fb3a530a]{padding:8px 10px}.card-title[data-v-fb3a530a]{font-size:13px}.stats-inline[data-v-fb3a530a]{gap:6px}.stat-inline-item[data-v-fb3a530a]{padding:8px}.stat-value[data-v-fb3a530a]{font-size:14px}.stat-label[data-v-fb3a530a]{font-size:9px}.logs-table-header[data-v-fb3a530a]{padding:8px 10px;font-size:10px}.log-row[data-v-fb3a530a]{padding:8px 10px;font-size:11px}}@media (max-width: 768px){.channel-column[data-v-fb3a530a]{border-radius:6px;min-height:300px;height:auto;max-height:400px}.card-header[data-v-fb3a530a]{padding:8px;gap:6px}.card-title[data-v-fb3a530a]{font-size:12px}.stats-inline[data-v-fb3a530a]{gap:4px}.stat-inline-item[data-v-fb3a530a]{padding:6px;gap:6px}.stat-icon-dot[data-v-fb3a530a]{width:6px;height:6px}.stat-value[data-v-fb3a530a]{font-size:13px}.stat-label[data-v-fb3a530a]{font-size:8px}.logs-card[data-v-fb3a530a]{min-height:150px}.logs-table-header[data-v-fb3a530a]{padding:6px 8px;font-size:9px}.log-row[data-v-fb3a530a]{padding:6px 8px;font-size:10px;min-height:32px}.col-token[data-v-fb3a530a]{font-size:10px;padding:2px 4px}.col-time[data-v-fb3a530a]{font-size:9px}.mcp-count-tag[data-v-fb3a530a],.skills-count-tag[data-v-fb3a530a]{font-size:9px;padding:0 6px;height:18px;line-height:18px}.skills-button[data-v-fb3a530a]{padding:4px 8px;font-size:10px;gap:4px}.lock-button[data-v-fb3a530a]{width:26px;height:26px}}@media (max-width: 640px){.channel-column[data-v-fb3a530a]{min-height:250px;max-height:350px}.card-header[data-v-fb3a530a]{padding:6px;gap:4px;flex-wrap:wrap}.card-title[data-v-fb3a530a]{font-size:11px}.stats-card .card-content[data-v-fb3a530a]{padding:6px}.stats-inline[data-v-fb3a530a]{gap:3px;flex-wrap:wrap}.stat-inline-item[data-v-fb3a530a]{padding:5px;gap:4px;min-width:calc(33% - 3px);flex:1 1 auto}.stat-icon-dot[data-v-fb3a530a]{width:5px;height:5px}.stat-value[data-v-fb3a530a]{font-size:12px}.stat-label[data-v-fb3a530a]{font-size:7px}.logs-card[data-v-fb3a530a]{min-height:120px}.logs-table-header[data-v-fb3a530a]{padding:5px 6px;font-size:8px;letter-spacing:.3px}.log-row[data-v-fb3a530a]{padding:5px 6px;font-size:9px;min-height:28px}.col-channel .n-tag[data-v-fb3a530a]{font-size:9px;padding:2px 6px}.col-token[data-v-fb3a530a]{font-size:9px;padding:2px 3px;margin:0 1px}.col-time[data-v-fb3a530a]{font-size:8px}.claude-extra-area[data-v-fb3a530a]{gap:4px}.mcp-count-tag[data-v-fb3a530a],.skills-count-tag[data-v-fb3a530a]{font-size:8px;padding:0 4px;height:16px;line-height:16px}.skills-button[data-v-fb3a530a]{padding:3px 6px;font-size:9px;gap:3px}.skills-button .n-icon[data-v-fb3a530a]{font-size:12px!important}.lock-button[data-v-fb3a530a]{width:24px;height:24px;margin-left:4px}.lock-button .n-icon[data-v-fb3a530a]{font-size:12px!important}.locked-content[data-v-fb3a530a]{padding:30px 20px;gap:12px}.lock-icon[data-v-fb3a530a]{width:64px;height:64px}.lock-icon .n-icon[data-v-fb3a530a]{font-size:28px!important}.lock-title[data-v-fb3a530a]{font-size:14px}.lock-subtitle[data-v-fb3a530a]{font-size:10px}.unlock-btn[data-v-fb3a530a]{padding:8px 16px;font-size:11px}}@media (max-width: 480px){.channel-column[data-v-fb3a530a]{min-height:220px;max-height:300px}.card-header[data-v-fb3a530a]{padding:5px}.card-title[data-v-fb3a530a]{font-size:10px}.stats-inline[data-v-fb3a530a]{gap:2px}.stat-inline-item[data-v-fb3a530a]{padding:4px;gap:3px}.stat-value[data-v-fb3a530a]{font-size:11px}.stat-label[data-v-fb3a530a]{font-size:6px}.logs-table-header[data-v-fb3a530a]{padding:4px 5px;font-size:7px}.log-row[data-v-fb3a530a]{padding:4px 5px;font-size:8px;min-height:24px}.col-token[data-v-fb3a530a]{font-size:8px}.col-time[data-v-fb3a530a]{font-size:7px}}.card[data-v-fb3a530a]:not(.logs-card) .n-collapse{background:transparent}.card[data-v-fb3a530a]:not(.logs-card) .n-collapse-item{background:transparent}.card[data-v-fb3a530a] .n-collapse-item__header{padding:12px 16px;background:transparent;transition:all .3s ease}.card[data-v-fb3a530a] .n-collapse-item__header:hover{background:#00000005}.card[data-v-fb3a530a] .n-collapse-item__header-main{width:100%}.card[data-v-fb3a530a]:not(.logs-card) .n-collapse-item__content-wrapper{padding:0 16px 12px}.card[data-v-fb3a530a]:not(.logs-card) .n-collapse-item__content-inner{padding-top:0}.chart-card[data-v-fb3a530a]:has(.n-collapse){padding:0}.dashboard-container[data-v-06100f0f]{height:100%;background:var(--bg-primary);overflow:hidden;padding:12px;box-sizing:border-box;display:flex;flex-direction:column}.dashboard-toolbar[data-v-06100f0f]{display:flex;justify-content:flex-end;margin-bottom:8px;flex-shrink:0}.analytics-link[data-v-06100f0f]{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--primary-color, #18a058);text-decoration:none;padding:4px 10px;border-radius:4px;background:#18a05814;transition:background .2s}.analytics-link[data-v-06100f0f]:hover{background:#18a05826}.analytics-link-icon[data-v-06100f0f]{font-size:14px}.dashboard-grid[data-v-06100f0f]{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;flex:1;min-height:0;overflow:hidden;box-sizing:border-box}.dashboard-grid[data-v-06100f0f] .sortable-ghost{opacity:.4}.dashboard-grid[data-v-06100f0f] .sortable-chosen{box-shadow:0 8px 24px #00000026}@media (max-width: 1600px){.dashboard-grid[data-v-06100f0f]{grid-template-columns:repeat(3,1fr)}}@media (max-width: 1200px){.dashboard-grid[data-v-06100f0f]{grid-template-columns:repeat(2,1fr)}}@media (max-width: 1024px){.dashboard-container[data-v-06100f0f]{padding:10px}.dashboard-grid[data-v-06100f0f]{gap:10px}}@media (max-width: 900px){.dashboard-grid[data-v-06100f0f]{grid-template-columns:1fr}}@media (max-width: 768px){.dashboard-container[data-v-06100f0f]{padding:8px;overflow-y:auto}.dashboard-grid[data-v-06100f0f]{gap:8px;overflow:visible;min-height:auto}}@media (max-width: 640px){.dashboard-container[data-v-06100f0f]{padding:6px}.dashboard-grid[data-v-06100f0f]{gap:6px}}@media (max-width: 480px){.dashboard-container[data-v-06100f0f]{padding:4px}.dashboard-grid[data-v-06100f0f]{gap:4px}}