coding-tool-x 3.6.6 → 3.6.8
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 +7 -0
- package/dist/web/assets/{Analytics-CcGKjDkP.js → Analytics-C57ZRDq5.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DPOa1w5D.js → ConfigTemplates-B3qqqL05.js} +1 -1
- package/dist/web/assets/Home-CG5XrTNc.js +1 -0
- package/dist/web/assets/Home-CIua-b-o.css +1 -0
- package/dist/web/assets/{PluginManager-BVbHUdey.js → PluginManager-BhX0LUzr.js} +1 -1
- package/dist/web/assets/{ProjectList-BQrz2FKo.js → ProjectList-BQ01OZn9.js} +1 -1
- package/dist/web/assets/{SessionList-CRGer64T.js → SessionList-DPv2IOXG.js} +1 -1
- package/dist/web/assets/{SkillManager-BhJKtWYM.js → SkillManager-D5CfQm2O.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-pc6tpF3m.js → WorkspaceManager-Ce8ggteK.js} +1 -1
- package/dist/web/assets/index-CpnFNWqC.js +2 -0
- package/dist/web/assets/index-DJ9VFidd.css +1 -0
- package/dist/web/assets/{naive-ui-DM897mIP.js → naive-ui-kcWDeJwl.js} +1 -1
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/src/commands/stats.js +1 -1
- package/src/config/channel-preset-websites.js +2 -1
- package/src/config/paths.js +8 -0
- package/src/server/api/agents.js +16 -54
- package/src/server/api/claude-hooks.js +1 -1
- package/src/server/api/claude-statistics.js +57 -0
- package/src/server/api/commands.js +16 -54
- package/src/server/api/dashboard.js +2 -2
- package/src/server/api/gemini-channels.js +2 -0
- package/src/server/api/hooks.js +4 -1
- package/src/server/api/plugins.js +40 -65
- package/src/server/api/skills.js +21 -77
- package/src/server/api/validation-errors.js +34 -0
- package/src/server/gemini-proxy-server.js +205 -21
- package/src/server/index.js +1 -0
- package/src/server/services/agents-service.js +166 -75
- package/src/server/services/base/base-channel-service.js +15 -0
- package/src/server/services/base/proxy-utils.js +6 -3
- package/src/server/services/base/response-usage-parser.js +44 -0
- package/src/server/services/channel-balance.js +86 -1
- package/src/server/services/claude-statistics-service.js +139 -0
- package/src/server/services/commands-service.js +203 -121
- package/src/server/services/config-artifact-paths.js +116 -0
- package/src/server/services/config-export-service.js +7 -6
- package/src/server/services/config-registry-service.js +2 -2
- package/src/server/services/config-sync-manager.js +45 -11
- package/src/server/services/config-templates-service.js +21 -19
- package/src/server/services/format-converter.js +42 -4
- package/src/server/services/gemini-channels.js +30 -1
- package/src/server/services/gemini-sessions.js +347 -61
- package/src/server/services/mcp-client.js +236 -49
- package/src/server/services/mcp-service.js +51 -64
- package/src/server/services/notification-hooks.js +659 -88
- package/src/server/services/opencode-channels.js +15 -0
- package/src/server/services/plugins-service.js +771 -63
- package/src/server/services/repo-scanner-base.js +6 -29
- package/src/server/services/skill-service.js +187 -63
- package/dist/web/assets/Home-BP1LxGtp.js +0 -1
- package/dist/web/assets/Home-BeGkC4bb.css +0 -1
- package/dist/web/assets/index-0isNzXNY.css +0 -1
- package/dist/web/assets/index-BsdIJwLf.js +0 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Manages file synchronization between cc-tool central storage and CLI directories:
|
|
5
5
|
* - Claude Code: ~/.claude/{skills,commands,agents,plugins}/
|
|
6
6
|
* - Codex CLI: ~/.codex/skills/, ~/.codex/prompts/
|
|
7
|
-
* - Gemini CLI: ~/.gemini/skills/
|
|
7
|
+
* - Gemini CLI: ~/.gemini/{skills,commands,agents}/
|
|
8
8
|
* - OpenCode CLI: ~/.config/opencode/{skills,commands,agents,plugins}/
|
|
9
9
|
*
|
|
10
10
|
* Config types:
|
|
@@ -19,7 +19,7 @@ const path = require('path');
|
|
|
19
19
|
const os = require('os');
|
|
20
20
|
const toml = require('toml');
|
|
21
21
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
22
|
-
const { convertSkillToCodex, convertCommandToCodex } = require('./format-converter');
|
|
22
|
+
const { convertSkillToCodex, convertCommandToCodex, convertCommandToGemini } = require('./format-converter');
|
|
23
23
|
const { PATHS, NATIVE_PATHS, HOME_DIR, ensureStorageDirMigrated } = require('../../config/paths');
|
|
24
24
|
|
|
25
25
|
// Paths
|
|
@@ -53,7 +53,9 @@ const CONFIG_TYPES = {
|
|
|
53
53
|
codexTarget: 'prompts',
|
|
54
54
|
codexSupported: true,
|
|
55
55
|
convertForCodex: true,
|
|
56
|
-
|
|
56
|
+
geminiTarget: 'commands',
|
|
57
|
+
geminiExtension: '.toml',
|
|
58
|
+
geminiSupported: true,
|
|
57
59
|
opencodeTarget: 'commands',
|
|
58
60
|
opencodeLegacyTarget: 'command',
|
|
59
61
|
opencodeSupported: true
|
|
@@ -63,7 +65,8 @@ const CONFIG_TYPES = {
|
|
|
63
65
|
extension: '.md',
|
|
64
66
|
claudeTarget: 'agents',
|
|
65
67
|
codexSupported: true,
|
|
66
|
-
|
|
68
|
+
geminiTarget: 'agents',
|
|
69
|
+
geminiSupported: true,
|
|
67
70
|
opencodeTarget: 'agents',
|
|
68
71
|
opencodeLegacyTarget: 'agent',
|
|
69
72
|
opencodeSupported: true
|
|
@@ -384,7 +387,7 @@ class ConfigSyncManager {
|
|
|
384
387
|
|
|
385
388
|
/**
|
|
386
389
|
* Sync a config item to Gemini CLI
|
|
387
|
-
*
|
|
390
|
+
* Supports skills, commands, and agents.
|
|
388
391
|
* @param {string} type - Config type
|
|
389
392
|
* @param {string} name - Item name
|
|
390
393
|
* @returns {Object} Result with success status
|
|
@@ -411,10 +414,21 @@ class ConfigSyncManager {
|
|
|
411
414
|
}
|
|
412
415
|
|
|
413
416
|
try {
|
|
414
|
-
const targetPath =
|
|
417
|
+
const targetPath = this._getGeminiTargetPath(config, safeName);
|
|
415
418
|
this._ensureDir(path.dirname(targetPath));
|
|
416
|
-
|
|
417
|
-
|
|
419
|
+
if (config.isDirectory) {
|
|
420
|
+
this._copyDirRecursive(sourcePath, targetPath);
|
|
421
|
+
console.log(`[ConfigSyncManager] Synced ${type}/${name} to Gemini (directory)`);
|
|
422
|
+
} else if (type === 'commands') {
|
|
423
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
424
|
+
const result = convertCommandToGemini(content);
|
|
425
|
+
fs.writeFileSync(targetPath, result.content, 'utf-8');
|
|
426
|
+
console.log(`[ConfigSyncManager] Synced ${type}/${name} to Gemini (command TOML)`);
|
|
427
|
+
return { success: true, target: targetPath, warnings: result.warnings || [] };
|
|
428
|
+
} else {
|
|
429
|
+
this._copyFile(sourcePath, targetPath);
|
|
430
|
+
console.log(`[ConfigSyncManager] Synced ${type}/${name} to Gemini (file)`);
|
|
431
|
+
}
|
|
418
432
|
return { success: true, target: targetPath };
|
|
419
433
|
} catch (err) {
|
|
420
434
|
console.error(`[ConfigSyncManager] Sync to Gemini failed:`, err.message);
|
|
@@ -443,15 +457,21 @@ class ConfigSyncManager {
|
|
|
443
457
|
return { success: true, skipped: true, reason: 'Not supported by Gemini' };
|
|
444
458
|
}
|
|
445
459
|
|
|
446
|
-
const targetPath =
|
|
460
|
+
const targetPath = this._getGeminiTargetPath(config, safeName);
|
|
447
461
|
if (!fs.existsSync(targetPath)) {
|
|
448
462
|
console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
|
|
449
463
|
return { success: true, message: 'Already removed' };
|
|
450
464
|
}
|
|
451
465
|
|
|
452
466
|
try {
|
|
453
|
-
|
|
454
|
-
|
|
467
|
+
if (config.isDirectory) {
|
|
468
|
+
this._removeRecursive(targetPath);
|
|
469
|
+
console.log(`[ConfigSyncManager] Removed ${type}/${name} from Gemini (directory)`);
|
|
470
|
+
} else {
|
|
471
|
+
fs.unlinkSync(targetPath);
|
|
472
|
+
console.log(`[ConfigSyncManager] Removed ${type}/${name} from Gemini (file)`);
|
|
473
|
+
this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.geminiDir, config.geminiTarget));
|
|
474
|
+
}
|
|
455
475
|
return { success: true };
|
|
456
476
|
} catch (err) {
|
|
457
477
|
console.error(`[ConfigSyncManager] Remove from Gemini failed:`, err.message);
|
|
@@ -616,6 +636,9 @@ class ConfigSyncManager {
|
|
|
616
636
|
const result = this.syncToGemini(type, name);
|
|
617
637
|
if (result.success && !result.skipped) {
|
|
618
638
|
results.synced.push({ type, name, platform: 'gemini' });
|
|
639
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
640
|
+
results.warnings.push({ type, name, platform: 'gemini', warnings: result.warnings });
|
|
641
|
+
}
|
|
619
642
|
} else if (!result.success) {
|
|
620
643
|
results.errors.push({ type, name, platform: 'gemini', error: result.error });
|
|
621
644
|
}
|
|
@@ -762,6 +785,17 @@ class ConfigSyncManager {
|
|
|
762
785
|
return modernDir;
|
|
763
786
|
}
|
|
764
787
|
|
|
788
|
+
_getGeminiTargetPath(config, safeName) {
|
|
789
|
+
const targetName = config.geminiExtension
|
|
790
|
+
? safeName.replace(new RegExp(`${this._escapeRegExp(path.extname(safeName))}$`, 'i'), config.geminiExtension)
|
|
791
|
+
: safeName;
|
|
792
|
+
return path.join(this.geminiDir, config.geminiTarget, targetName);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
_escapeRegExp(value) {
|
|
796
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
797
|
+
}
|
|
798
|
+
|
|
765
799
|
/**
|
|
766
800
|
* Recursively remove a file or directory
|
|
767
801
|
*/
|
|
@@ -12,7 +12,7 @@ const { AgentsService } = require('./agents-service');
|
|
|
12
12
|
const { CommandsService } = require('./commands-service');
|
|
13
13
|
const { SkillService } = require('./skill-service');
|
|
14
14
|
const { PluginsService } = require('./plugins-service');
|
|
15
|
-
const { convertCommandToCodex } = require('./format-converter');
|
|
15
|
+
const { convertCommandToCodex, convertCommandToGemini } = require('./format-converter');
|
|
16
16
|
const mcpService = require('./mcp-service');
|
|
17
17
|
const promptsService = require('./prompts-service');
|
|
18
18
|
const pluginsService = new PluginsService();
|
|
@@ -499,6 +499,10 @@ function generateCommandContent(command) {
|
|
|
499
499
|
return lines.join('\n') + '\n\n' + (command.body || '');
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
+
function buildTemplateCommandFileName(commandName, format) {
|
|
503
|
+
return `${commandName}.${format === 'gemini' ? 'toml' : 'md'}`;
|
|
504
|
+
}
|
|
505
|
+
|
|
502
506
|
/**
|
|
503
507
|
* 转换为 OpenCode MCP 结构(local/remote)
|
|
504
508
|
*/
|
|
@@ -588,6 +592,9 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
588
592
|
if (aiConfigTypes.includes('opencode')) {
|
|
589
593
|
agentTargets.push({ baseDir: path.join(targetDir, '.opencode', 'agents'), prefix: '.opencode/agents' });
|
|
590
594
|
}
|
|
595
|
+
if (aiConfigTypes.includes('gemini')) {
|
|
596
|
+
agentTargets.push({ baseDir: path.join(targetDir, '.gemini', 'agents'), prefix: '.gemini/agents' });
|
|
597
|
+
}
|
|
591
598
|
|
|
592
599
|
for (const target of agentTargets) {
|
|
593
600
|
ensureDir(target.baseDir);
|
|
@@ -606,9 +613,6 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
606
613
|
if (aiConfigTypes.includes('codex')) {
|
|
607
614
|
pushSkipped(results.skipped, 'agent', fileName, 'Codex agents 仅支持用户级配置,项目目录应用时已跳过');
|
|
608
615
|
}
|
|
609
|
-
if (aiConfigTypes.includes('gemini')) {
|
|
610
|
-
pushSkipped(results.skipped, 'agent', fileName, 'Gemini 不支持 agents,已跳过');
|
|
611
|
-
}
|
|
612
616
|
if (written) {
|
|
613
617
|
results.agents.applied++;
|
|
614
618
|
}
|
|
@@ -626,6 +630,9 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
626
630
|
if (aiConfigTypes.includes('opencode')) {
|
|
627
631
|
commandTargets.push({ baseDir: path.join(targetDir, '.opencode', 'commands'), prefix: '.opencode/commands', format: 'claude' });
|
|
628
632
|
}
|
|
633
|
+
if (aiConfigTypes.includes('gemini')) {
|
|
634
|
+
commandTargets.push({ baseDir: path.join(targetDir, '.gemini', 'commands'), prefix: '.gemini/commands', format: 'gemini' });
|
|
635
|
+
}
|
|
629
636
|
|
|
630
637
|
for (const target of commandTargets) {
|
|
631
638
|
ensureDir(target.baseDir);
|
|
@@ -638,23 +645,21 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
638
645
|
let content = generateCommandContent(command);
|
|
639
646
|
if (target.format === 'codex') {
|
|
640
647
|
content = convertCommandToCodex(content).content;
|
|
648
|
+
} else if (target.format === 'gemini') {
|
|
649
|
+
content = convertCommandToGemini(content).content;
|
|
641
650
|
}
|
|
642
651
|
const targetCmdDir = command.namespace
|
|
643
652
|
? path.join(target.baseDir, command.namespace)
|
|
644
653
|
: target.baseDir;
|
|
645
654
|
ensureDir(targetCmdDir);
|
|
646
|
-
const filePath = path.join(targetCmdDir,
|
|
655
|
+
const filePath = path.join(targetCmdDir, buildTemplateCommandFileName(commandName, target.format));
|
|
647
656
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
648
657
|
const relativePath = command.namespace
|
|
649
|
-
? `${target.prefix}/${command.namespace}/${commandName}
|
|
650
|
-
: `${target.prefix}/${commandName}
|
|
658
|
+
? `${target.prefix}/${command.namespace}/${buildTemplateCommandFileName(commandName, target.format)}`
|
|
659
|
+
: `${target.prefix}/${buildTemplateCommandFileName(commandName, target.format)}`;
|
|
651
660
|
results.commands.files.push(relativePath);
|
|
652
661
|
written = true;
|
|
653
662
|
}
|
|
654
|
-
|
|
655
|
-
if (aiConfigTypes.includes('gemini')) {
|
|
656
|
-
pushSkipped(results.skipped, 'command', commandName, 'Gemini 不支持 commands,已跳过');
|
|
657
|
-
}
|
|
658
663
|
if (written) {
|
|
659
664
|
results.commands.applied++;
|
|
660
665
|
}
|
|
@@ -795,6 +800,7 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
795
800
|
const agentPrefixes = [];
|
|
796
801
|
if (aiConfigTypes.includes('claude')) agentPrefixes.push('.claude/agents');
|
|
797
802
|
if (aiConfigTypes.includes('opencode')) agentPrefixes.push('.opencode/agents');
|
|
803
|
+
if (aiConfigTypes.includes('gemini')) agentPrefixes.push('.gemini/agents');
|
|
798
804
|
|
|
799
805
|
for (const agent of template.agents) {
|
|
800
806
|
const fileName = resolveItemName(agent.fileName, agent.name, 'agent').toLowerCase().replace(/\s+/g, '-');
|
|
@@ -812,9 +818,6 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
812
818
|
if (aiConfigTypes.includes('codex')) {
|
|
813
819
|
pushSkipped(preview.skipped, 'agent', fileName, 'Codex agents 仅支持用户级配置,项目目录预览时已跳过');
|
|
814
820
|
}
|
|
815
|
-
if (aiConfigTypes.includes('gemini')) {
|
|
816
|
-
pushSkipped(preview.skipped, 'agent', fileName, 'Gemini 不支持 agents,已跳过');
|
|
817
|
-
}
|
|
818
821
|
if (applicable) {
|
|
819
822
|
preview.summary.agents++;
|
|
820
823
|
}
|
|
@@ -826,14 +829,16 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
826
829
|
if (aiConfigTypes.includes('claude')) commandPrefixes.push('.claude/commands');
|
|
827
830
|
if (aiConfigTypes.includes('codex')) commandPrefixes.push('.codex/prompts');
|
|
828
831
|
if (aiConfigTypes.includes('opencode')) commandPrefixes.push('.opencode/commands');
|
|
832
|
+
if (aiConfigTypes.includes('gemini')) commandPrefixes.push('.gemini/commands');
|
|
829
833
|
|
|
830
834
|
for (const command of template.commands) {
|
|
831
835
|
const commandName = resolveItemName(command.name, null, 'command');
|
|
832
836
|
let applicable = false;
|
|
833
837
|
for (const prefix of commandPrefixes) {
|
|
838
|
+
const extension = prefix === '.gemini/commands' ? 'toml' : 'md';
|
|
834
839
|
const relativePath = command.namespace
|
|
835
|
-
? `${prefix}/${command.namespace}/${commandName}
|
|
836
|
-
: `${prefix}/${commandName}
|
|
840
|
+
? `${prefix}/${command.namespace}/${commandName}.${extension}`
|
|
841
|
+
: `${prefix}/${commandName}.${extension}`;
|
|
837
842
|
const fullPath = path.join(targetDir, relativePath);
|
|
838
843
|
if (fs.existsSync(fullPath)) {
|
|
839
844
|
preview.willOverwrite.push(relativePath);
|
|
@@ -842,9 +847,6 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
842
847
|
}
|
|
843
848
|
applicable = true;
|
|
844
849
|
}
|
|
845
|
-
if (aiConfigTypes.includes('gemini')) {
|
|
846
|
-
pushSkipped(preview.skipped, 'command', commandName, 'Gemini 不支持 commands,已跳过');
|
|
847
|
-
}
|
|
848
850
|
if (applicable) {
|
|
849
851
|
preview.summary.commands++;
|
|
850
852
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 格式转换服务
|
|
3
3
|
*
|
|
4
|
-
* 支持 Claude Code ↔ Codex CLI 格式互转
|
|
4
|
+
* 支持 Claude Code ↔ Codex CLI/Gemini CLI 格式互转
|
|
5
5
|
* - Skills: SKILL.md 格式转换
|
|
6
6
|
* - Commands/Prompts: 命令/提示格式转换
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
const tomlStringify = require('@iarna/toml').stringify;
|
|
10
|
+
|
|
9
11
|
// Codex CLI 限制
|
|
10
12
|
const CODEX_LIMITS = {
|
|
11
13
|
skillName: 100,
|
|
@@ -268,8 +270,9 @@ function escapeYamlString(str) {
|
|
|
268
270
|
* 检测 Skill 格式
|
|
269
271
|
* @returns {'claude' | 'codex' | 'unknown'}
|
|
270
272
|
*/
|
|
271
|
-
function detectSkillFormat(content) {
|
|
273
|
+
function detectSkillFormat(content, options = {}) {
|
|
272
274
|
const { frontmatter } = parseFrontmatter(content);
|
|
275
|
+
const platform = options.platform || '';
|
|
273
276
|
|
|
274
277
|
// Codex 特征: 有 metadata 对象
|
|
275
278
|
if (frontmatter.metadata && typeof frontmatter.metadata === 'object') {
|
|
@@ -281,6 +284,11 @@ function detectSkillFormat(content) {
|
|
|
281
284
|
return 'claude';
|
|
282
285
|
}
|
|
283
286
|
|
|
287
|
+
// 在 Codex 平台上下文中,metadata 是可选的;name + description 已足够作为 Codex skill。
|
|
288
|
+
if (platform === 'codex' && frontmatter.name && frontmatter.description) {
|
|
289
|
+
return 'codex';
|
|
290
|
+
}
|
|
291
|
+
|
|
284
292
|
// 两者都有 name 和 description,无法区分时默认 claude
|
|
285
293
|
if (frontmatter.name && frontmatter.description) {
|
|
286
294
|
return 'claude';
|
|
@@ -434,6 +442,35 @@ function convertCommandToCodex(claudeContent, options = {}) {
|
|
|
434
442
|
};
|
|
435
443
|
}
|
|
436
444
|
|
|
445
|
+
/**
|
|
446
|
+
* 转换 Command: Claude Code → Gemini CLI (Custom Command TOML)
|
|
447
|
+
*/
|
|
448
|
+
function convertCommandToGemini(claudeContent, options = {}) {
|
|
449
|
+
const { frontmatter, body } = parseFrontmatter(claudeContent);
|
|
450
|
+
const warnings = [];
|
|
451
|
+
|
|
452
|
+
const commandConfig = {
|
|
453
|
+
prompt: body || ''
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
if (frontmatter.description) {
|
|
457
|
+
commandConfig.description = frontmatter.description;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const unsupportedFields = ['allowed-tools', 'model', 'context', 'agent', 'hooks'];
|
|
461
|
+
for (const field of unsupportedFields) {
|
|
462
|
+
if (frontmatter[field]) {
|
|
463
|
+
warnings.push(`${field} 字段在 Gemini commands 中不支持,已忽略`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
content: tomlStringify(commandConfig),
|
|
469
|
+
warnings,
|
|
470
|
+
format: 'gemini'
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
437
474
|
/**
|
|
438
475
|
* 转换 Command: Codex CLI (Custom Prompt) → Claude Code
|
|
439
476
|
*/
|
|
@@ -528,9 +565,9 @@ function convertCommandsBatch(commands, targetFormat) {
|
|
|
528
565
|
/**
|
|
529
566
|
* 解析 Skill 内容(支持双格式)
|
|
530
567
|
*/
|
|
531
|
-
function parseSkillContent(content) {
|
|
568
|
+
function parseSkillContent(content, options = {}) {
|
|
532
569
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
533
|
-
const format = detectSkillFormat(content);
|
|
570
|
+
const format = detectSkillFormat(content, options);
|
|
534
571
|
|
|
535
572
|
const result = {
|
|
536
573
|
name: frontmatter.name || '',
|
|
@@ -606,6 +643,7 @@ module.exports = {
|
|
|
606
643
|
|
|
607
644
|
// Commands 转换
|
|
608
645
|
convertCommandToCodex,
|
|
646
|
+
convertCommandToGemini,
|
|
609
647
|
convertCommandToClaude,
|
|
610
648
|
convertCommandsBatch,
|
|
611
649
|
|
|
@@ -6,6 +6,21 @@ const { resolveChannelWebsiteUrl } = require('../../config/channel-preset-websit
|
|
|
6
6
|
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
7
7
|
const { normalizeGatewaySourceType } = require('./base/proxy-utils');
|
|
8
8
|
|
|
9
|
+
const GEMINI_API_FORMATS = new Set(['gemini_api', 'vertex_ai_v1']);
|
|
10
|
+
|
|
11
|
+
function clearChannelBalanceCache(channel) {
|
|
12
|
+
try {
|
|
13
|
+
require('./channel-balance').clearChannelBalanceCache('gemini', channel);
|
|
14
|
+
} catch (_) {
|
|
15
|
+
// Balance cache invalidation is best-effort and should not block channel updates.
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeGeminiApiFormat(value) {
|
|
20
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
21
|
+
return GEMINI_API_FORMATS.has(normalized) ? normalized : 'gemini_api';
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
/**
|
|
10
25
|
* Gemini 渠道管理服务(多渠道架构)
|
|
11
26
|
*
|
|
@@ -118,6 +133,7 @@ function loadChannels() {
|
|
|
118
133
|
balanceUserId: ch.balanceUserId || null,
|
|
119
134
|
modelRedirects: ch.modelRedirects || [],
|
|
120
135
|
speedTestModel: ch.speedTestModel || null,
|
|
136
|
+
apiFormat: normalizeGeminiApiFormat(ch.apiFormat),
|
|
121
137
|
gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'gemini')
|
|
122
138
|
};
|
|
123
139
|
normalized.websiteUrl = resolveChannelWebsiteUrl('gemini', normalized);
|
|
@@ -169,6 +185,7 @@ function initializeFromEnv() {
|
|
|
169
185
|
maxConcurrency: null,
|
|
170
186
|
balanceToken: '',
|
|
171
187
|
balanceUserId: null,
|
|
188
|
+
apiFormat: 'gemini_api',
|
|
172
189
|
gatewaySourceType: 'gemini',
|
|
173
190
|
createdAt: Date.now(),
|
|
174
191
|
updatedAt: Date.now()
|
|
@@ -230,6 +247,7 @@ function createChannel(name, baseUrl, apiKey, model = 'gemini-2.5-pro', extraCon
|
|
|
230
247
|
balanceUserId: extraConfig.balanceUserId || null,
|
|
231
248
|
modelRedirects: extraConfig.modelRedirects || [],
|
|
232
249
|
speedTestModel: extraConfig.speedTestModel || null,
|
|
250
|
+
apiFormat: normalizeGeminiApiFormat(extraConfig.apiFormat),
|
|
233
251
|
gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'gemini'),
|
|
234
252
|
createdAt: Date.now(),
|
|
235
253
|
updatedAt: Date.now()
|
|
@@ -275,6 +293,7 @@ function updateChannel(channelId, updates) {
|
|
|
275
293
|
createdAt: oldChannel.createdAt, // 保持创建时间
|
|
276
294
|
modelRedirects: updates.modelRedirects !== undefined ? updates.modelRedirects : (oldChannel.modelRedirects || []),
|
|
277
295
|
speedTestModel: updates.speedTestModel !== undefined ? updates.speedTestModel : (oldChannel.speedTestModel || null),
|
|
296
|
+
apiFormat: updates.apiFormat !== undefined ? normalizeGeminiApiFormat(updates.apiFormat) : normalizeGeminiApiFormat(oldChannel.apiFormat),
|
|
278
297
|
balanceToken: updates.balanceToken !== undefined ? updates.balanceToken : (oldChannel.balanceToken || ''),
|
|
279
298
|
balanceUserId: updates.balanceUserId !== undefined ? updates.balanceUserId : (oldChannel.balanceUserId || null),
|
|
280
299
|
gatewaySourceType: normalizeGatewaySourceType(merged.gatewaySourceType, 'gemini'),
|
|
@@ -300,6 +319,9 @@ function updateChannel(channelId, updates) {
|
|
|
300
319
|
}
|
|
301
320
|
|
|
302
321
|
saveChannels(data);
|
|
322
|
+
if (oldChannel.enabled === false && nextChannel.enabled !== false) {
|
|
323
|
+
clearChannelBalanceCache(nextChannel);
|
|
324
|
+
}
|
|
303
325
|
|
|
304
326
|
// Only sync .env when proxy is OFF.
|
|
305
327
|
// In dynamic switching mode, defer local config writes until proxy stop.
|
|
@@ -329,6 +351,7 @@ function applyChannelToSettings(channelId, channels = null) {
|
|
|
329
351
|
throw new Error('Channel not found');
|
|
330
352
|
}
|
|
331
353
|
|
|
354
|
+
const wasEnabled = channel.enabled !== false;
|
|
332
355
|
// In single-channel mode, only this channel should be enabled
|
|
333
356
|
data.channels.forEach(ch => {
|
|
334
357
|
ch.enabled = ch.id === channelId;
|
|
@@ -337,6 +360,9 @@ function applyChannelToSettings(channelId, channels = null) {
|
|
|
337
360
|
if (!channels) {
|
|
338
361
|
saveChannels(data);
|
|
339
362
|
}
|
|
363
|
+
if (!wasEnabled) {
|
|
364
|
+
clearChannelBalanceCache(channel);
|
|
365
|
+
}
|
|
340
366
|
|
|
341
367
|
clearNativeOAuth('gemini');
|
|
342
368
|
|
|
@@ -502,5 +528,8 @@ module.exports = {
|
|
|
502
528
|
isProxyConfig,
|
|
503
529
|
getGeminiDir,
|
|
504
530
|
applyChannelToSettings,
|
|
505
|
-
disableAllChannels
|
|
531
|
+
disableAllChannels,
|
|
532
|
+
_test: {
|
|
533
|
+
normalizeGeminiApiFormat
|
|
534
|
+
}
|
|
506
535
|
};
|