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.
- package/CHANGELOG.md +17 -2
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-D6LzK9hk.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D_hRb55W.js → ConfigTemplates-BUDYuxRi.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-D7KX7iF8.js +1 -0
- package/dist/web/assets/{PluginManager-JXsyym1s.js → PluginManager-DTgQ--vB.js} +1 -1
- package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-DMCiGmCT.js} +1 -1
- package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CRBsdVRe.js} +1 -1
- package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-DMwx2Q4k.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-DapB4ljL.js} +1 -1
- package/dist/web/assets/{icons-Cuc23WS7.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-CL-qpoJ_.js +2 -0
- package/dist/web/assets/index-D_5dRFOL.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/ui-config.js +5 -0
- 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 +41 -87
- package/src/server/services/codex-env-manager.js +423 -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 +5 -5
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +1 -1
- package/src/server/websocket-server.js +5 -4
- 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
|
// 加载总体统计
|
|
@@ -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
|
|
|
@@ -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 =
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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}}
|