coding-tool-x 3.6.13 → 3.7.1

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 (88) hide show
  1. package/README.md +45 -33
  2. package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
  4. package/dist/web/assets/Home-BwTaYu3q.js +1 -0
  5. package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
  6. package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
  7. package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
  8. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
  9. package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
  10. package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-Btfvh0GG.js +2 -0
  14. package/dist/web/assets/index-CLNa6DhH.css +1 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +3 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +46 -6
  22. package/src/commands/port-config.js +83 -75
  23. package/src/commands/proxy-control.js +59 -26
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/switch.js +5 -1
  26. package/src/commands/toggle-proxy.js +131 -53
  27. package/src/commands/ui.js +3 -4
  28. package/src/commands/workspace.js +3 -35
  29. package/src/config/default.js +19 -2
  30. package/src/config/loader.js +5 -76
  31. package/src/config/paths.js +96 -11
  32. package/src/index.js +10 -16
  33. package/src/server/api/commands.js +1 -1
  34. package/src/server/api/config-export.js +4 -2
  35. package/src/server/api/config-registry.js +40 -34
  36. package/src/server/api/config.js +5 -1
  37. package/src/server/api/dashboard.js +22 -6
  38. package/src/server/api/mcp.js +5 -3
  39. package/src/server/api/omp-channels.js +349 -0
  40. package/src/server/api/omp-config.js +164 -0
  41. package/src/server/api/omp-projects.js +76 -0
  42. package/src/server/api/omp-proxy.js +154 -0
  43. package/src/server/api/omp-sessions.js +334 -0
  44. package/src/server/api/omp-statistics.js +40 -0
  45. package/src/server/api/plugins.js +1 -1
  46. package/src/server/api/prompts.js +3 -2
  47. package/src/server/api/settings.js +2 -0
  48. package/src/server/api/skills.js +1 -1
  49. package/src/server/api/workspaces.js +3 -41
  50. package/src/server/index.js +41 -0
  51. package/src/server/omp-proxy-server.js +66 -0
  52. package/src/server/services/agents-service.js +2 -1
  53. package/src/server/services/base/proxy-utils.js +1 -0
  54. package/src/server/services/channel-balance.js +151 -1
  55. package/src/server/services/channel-scheduler.js +7 -1
  56. package/src/server/services/commands-service.js +11 -2
  57. package/src/server/services/config-export-service.js +296 -39
  58. package/src/server/services/config-registry-service.js +20 -18
  59. package/src/server/services/config-sync-manager.js +115 -6
  60. package/src/server/services/config-sync-service.js +4 -2
  61. package/src/server/services/config-templates-service.js +183 -45
  62. package/src/server/services/mcp-service.js +217 -33
  63. package/src/server/services/notification-hooks.js +138 -6
  64. package/src/server/services/omp-auth-providers.js +294 -0
  65. package/src/server/services/omp-channels.js +94 -0
  66. package/src/server/services/omp-config.js +269 -0
  67. package/src/server/services/omp-sessions.js +490 -0
  68. package/src/server/services/omp-settings-manager.js +975 -0
  69. package/src/server/services/omp-statistics-service.js +317 -0
  70. package/src/server/services/plugins-service.js +673 -11
  71. package/src/server/services/prompts-service.js +112 -7
  72. package/src/server/services/proxy-log-helper.js +1 -0
  73. package/src/server/services/proxy-runtime.js +5 -1
  74. package/src/server/services/sessions.js +19 -35
  75. package/src/server/services/skill-service.js +13 -3
  76. package/src/server/services/speed-test.js +34 -28
  77. package/src/server/services/ui-config.js +50 -12
  78. package/src/server/services/workspace-service.js +117 -225
  79. package/src/server/websocket-server.js +7 -1
  80. package/src/shared/platforms.js +201 -0
  81. package/src/utils/session.js +4 -10
  82. package/dist/web/assets/Home-BiaKZMah.js +0 -1
  83. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  84. package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
  85. package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
  86. package/dist/web/assets/index-BpBFF3b2.js +0 -2
  87. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  88. package/src/utils/cli-flags.js +0 -7
@@ -6,6 +6,7 @@
6
6
  * - Codex CLI: ~/.codex/skills/, ~/.codex/prompts/
7
7
  * - Gemini CLI: ~/.gemini/{skills,commands,agents}/
8
8
  * - OpenCode CLI: ~/.config/opencode/{skills,commands,agents,plugins}/
9
+ * - OMP: ~/.omp/agent/{skills,commands,prompts,extensions}/
9
10
  *
10
11
  * Config types:
11
12
  * - skills: directory-based (each skill is a dir with SKILL.md)
@@ -25,10 +26,11 @@ const { PATHS, NATIVE_PATHS, HOME_DIR, ensureStorageDirMigrated } = require('../
25
26
  // Paths
26
27
  const HOME = HOME_DIR || os.homedir();
27
28
  const CC_TOOL_CONFIGS = PATHS.configs;
28
- const CLAUDE_CODE_DIR = path.join(HOME, '.claude');
29
+ const CLAUDE_CODE_DIR = NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings) || path.join(HOME, '.claude');
29
30
  const CODEX_DIR = path.join(HOME, '.codex');
30
31
  const GEMINI_DIR = path.join(HOME, '.gemini');
31
32
  const OPENCODE_DIR = NATIVE_PATHS.opencode.config;
33
+ const OMP_AGENT_DIR = NATIVE_PATHS.omp.dir;
32
34
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
33
35
 
34
36
  // Config type definitions
@@ -44,7 +46,9 @@ const CONFIG_TYPES = {
44
46
  geminiSupported: true,
45
47
  opencodeTarget: 'skills',
46
48
  opencodeLegacyTarget: 'skill',
47
- opencodeSupported: true
49
+ opencodeSupported: true,
50
+ ompTarget: 'skills',
51
+ ompSupported: true
48
52
  },
49
53
  commands: {
50
54
  isDirectory: false,
@@ -58,7 +62,9 @@ const CONFIG_TYPES = {
58
62
  geminiSupported: true,
59
63
  opencodeTarget: 'commands',
60
64
  opencodeLegacyTarget: 'command',
61
- opencodeSupported: true
65
+ opencodeSupported: true,
66
+ ompTarget: 'commands',
67
+ ompSupported: true
62
68
  },
63
69
  agents: {
64
70
  isDirectory: false,
@@ -69,7 +75,8 @@ const CONFIG_TYPES = {
69
75
  geminiSupported: true,
70
76
  opencodeTarget: 'agents',
71
77
  opencodeLegacyTarget: 'agent',
72
- opencodeSupported: true
78
+ opencodeSupported: true,
79
+ ompSupported: false
73
80
  },
74
81
  plugins: {
75
82
  isDirectory: true,
@@ -78,7 +85,9 @@ const CONFIG_TYPES = {
78
85
  geminiSupported: false,
79
86
  opencodeTarget: 'plugins',
80
87
  opencodeLegacyTarget: 'plugin',
81
- opencodeSupported: true
88
+ opencodeSupported: true,
89
+ ompTarget: 'extensions',
90
+ ompSupported: true
82
91
  }
83
92
  };
84
93
 
@@ -90,6 +99,7 @@ class ConfigSyncManager {
90
99
  this.codexDir = CODEX_DIR;
91
100
  this.geminiDir = GEMINI_DIR;
92
101
  this.opencodeDir = OPENCODE_DIR;
102
+ this.ompDir = OMP_AGENT_DIR;
93
103
  this.configTypes = CONFIG_TYPES;
94
104
  }
95
105
 
@@ -574,10 +584,90 @@ class ConfigSyncManager {
574
584
  }
575
585
  }
576
586
 
587
+ /**
588
+ * Sync a config item to OMP.
589
+ * OMP treats commands as slash-command files and plugins as extensions/packages.
590
+ */
591
+ syncToOmp(type, name) {
592
+ const config = this.configTypes[type];
593
+ if (!config) {
594
+ return { success: false, error: `Unknown config type: ${type}` };
595
+ }
596
+
597
+ if (!config.ompSupported) {
598
+ console.log(`[ConfigSyncManager] ${type} not supported natively by OMP, skipping`);
599
+ return { success: true, skipped: true, reason: 'Not supported natively by OMP' };
600
+ }
601
+
602
+ const safeName = this._normalizeSafeRelativeName(name);
603
+ if (!safeName) {
604
+ return { success: false, error: 'Invalid config item name' };
605
+ }
606
+
607
+ const sourcePath = path.join(this.ccToolConfigs, type, safeName);
608
+ if (!fs.existsSync(sourcePath)) {
609
+ console.log(`[ConfigSyncManager] Source not found: ${sourcePath}`);
610
+ return { success: false, error: 'Source not found' };
611
+ }
612
+
613
+ const targetPath = path.join(this.ompDir, config.ompTarget, safeName);
614
+ try {
615
+ if (config.isDirectory) {
616
+ this._ensureDir(path.dirname(targetPath));
617
+ this._copyDirRecursive(sourcePath, targetPath);
618
+ console.log(`[ConfigSyncManager] Synced ${type}/${name} to OMP (directory)`);
619
+ } else {
620
+ this._ensureDir(path.dirname(targetPath));
621
+ this._copyFile(sourcePath, targetPath);
622
+ console.log(`[ConfigSyncManager] Synced ${type}/${name} to OMP (file)`);
623
+ }
624
+
625
+ return { success: true, target: targetPath };
626
+ } catch (err) {
627
+ console.error('[ConfigSyncManager] Sync to OMP failed:', err.message);
628
+ return { success: false, error: err.message };
629
+ }
630
+ }
631
+
632
+ removeFromOmp(type, name) {
633
+ const config = this.configTypes[type];
634
+ if (!config) {
635
+ return { success: false, error: `Unknown config type: ${type}` };
636
+ }
637
+
638
+ const safeName = this._normalizeSafeRelativeName(name);
639
+ if (!safeName) {
640
+ return { success: false, error: 'Invalid config item name' };
641
+ }
642
+
643
+ if (!config.ompSupported) {
644
+ return { success: true, skipped: true, reason: 'Not supported natively by OMP' };
645
+ }
646
+
647
+ const targetPath = path.join(this.ompDir, config.ompTarget, safeName);
648
+ if (!fs.existsSync(targetPath)) {
649
+ return { success: true, message: 'Already removed' };
650
+ }
651
+
652
+ try {
653
+ if (config.isDirectory) {
654
+ this._removeRecursive(targetPath);
655
+ } else {
656
+ fs.unlinkSync(targetPath);
657
+ this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.ompDir, config.ompTarget));
658
+ }
659
+ console.log(`[ConfigSyncManager] Removed ${type}/${name} from OMP`);
660
+ return { success: true };
661
+ } catch (err) {
662
+ console.error('[ConfigSyncManager] Remove from OMP failed:', err.message);
663
+ return { success: false, error: err.message };
664
+ }
665
+ }
666
+
577
667
  /**
578
668
  * Batch sync based on registry data
579
669
  * @param {string} type - Config type
580
- * @param {Object} registryItems - Registry items { name: { enabled, platforms: { claude, codex, gemini, opencode } } }
670
+ * @param {Object} registryItems - Registry items { name: { enabled, platforms: { claude, codex, gemini, opencode, omp } } }
581
671
  * @returns {Object} Results summary
582
672
  */
583
673
  syncAll(type, registryItems) {
@@ -662,6 +752,20 @@ class ConfigSyncManager {
662
752
  results.removed.push({ type, name, platform: 'opencode' });
663
753
  }
664
754
  }
755
+
756
+ if (platforms.omp) {
757
+ const result = this.syncToOmp(type, name);
758
+ if (result.success && !result.skipped) {
759
+ results.synced.push({ type, name, platform: 'omp' });
760
+ } else if (!result.success) {
761
+ results.errors.push({ type, name, platform: 'omp', error: result.error });
762
+ }
763
+ } else {
764
+ const result = this.removeFromOmp(type, name);
765
+ if (result.success && !result.message && !result.skipped) {
766
+ results.removed.push({ type, name, platform: 'omp' });
767
+ }
768
+ }
665
769
  } else {
666
770
  // Item disabled, remove from all platforms
667
771
  const claudeResult = this.removeFromClaude(type, name);
@@ -683,6 +787,11 @@ class ConfigSyncManager {
683
787
  if (opencodeResult.success && !opencodeResult.message && !opencodeResult.skipped) {
684
788
  results.removed.push({ type, name, platform: 'opencode' });
685
789
  }
790
+
791
+ const ompResult = this.removeFromOmp(type, name);
792
+ if (ompResult.success && !ompResult.message && !ompResult.skipped) {
793
+ results.removed.push({ type, name, platform: 'omp' });
794
+ }
686
795
  }
687
796
  }
688
797
 
@@ -15,10 +15,12 @@
15
15
 
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
- const { HOME_DIR } = require('../../config/paths');
18
+ const { HOME_DIR, NATIVE_PATHS } = require('../../config/paths');
19
19
 
20
20
  // 全局配置目录
21
- const GLOBAL_CONFIG_DIR = path.join(HOME_DIR, '.claude');
21
+ const GLOBAL_CONFIG_DIR = NATIVE_PATHS.claude.dir
22
+ || path.dirname(NATIVE_PATHS.claude.settings)
23
+ || path.join(HOME_DIR, '.claude');
22
24
 
23
25
  // 配置类型定义
24
26
  const CONFIG_TYPES = {
@@ -7,6 +7,7 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
+ const yaml = require('js-yaml');
10
11
  const { PATHS } = require('../../config/paths');
11
12
  const { AgentsService } = require('./agents-service');
12
13
  const { CommandsService } = require('./commands-service');
@@ -23,14 +24,16 @@ const AI_CONFIG_MAP = {
23
24
  claude: { fileName: 'CLAUDE.md', name: 'Claude' },
24
25
  codex: { fileName: 'AGENTS.md', name: 'Codex' },
25
26
  gemini: { fileName: 'GEMINI.md', name: 'Gemini' },
26
- opencode: { fileName: '.opencode/AGENTS.md', name: 'OpenCode' }
27
+ opencode: { fileName: '.opencode/AGENTS.md', name: 'OpenCode' },
28
+ omp: { fileName: null, name: 'OMP command templates' }
27
29
  };
28
30
 
29
31
  const CLI_DEFAULT_AI_TYPE = {
30
32
  claude: 'claude',
31
33
  codex: 'codex',
32
34
  gemini: 'gemini',
33
- opencode: 'opencode'
35
+ opencode: 'opencode',
36
+ omp: 'omp'
34
37
  };
35
38
 
36
39
  /**
@@ -111,7 +114,8 @@ function normalizeAiConfigs(aiConfigs = {}, claudeMd = null) {
111
114
  claude: { enabled: false, content: '' },
112
115
  codex: { enabled: false, content: '' },
113
116
  gemini: { enabled: false, content: '' },
114
- opencode: { enabled: false, content: '' }
117
+ opencode: { enabled: false, content: '' },
118
+ omp: { enabled: false, content: '' }
115
119
  };
116
120
 
117
121
  for (const key of Object.keys(normalized)) {
@@ -369,36 +373,62 @@ function readCurrentConfig(targetDir) {
369
373
  * 返回用户级的 agents, commands, plugins + MCP 服务器列表
370
374
  */
371
375
  function getAvailableConfigs() {
372
- const agentServices = [new AgentsService('claude')];
373
- const commandServices = [new CommandsService('claude')];
376
+ const agentPlatforms = ['claude', 'codex', 'gemini', 'opencode'];
377
+ const commandPlatforms = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
374
378
  const agentMap = new Map();
375
379
  const commandMap = new Map();
380
+ const agentsByPlatform = {};
381
+ const commandsByPlatform = {};
376
382
 
377
- for (const service of agentServices) {
383
+ for (const platform of agentPlatforms) {
384
+ const service = new AgentsService(platform);
378
385
  const { agents } = service.listAgents();
379
386
  for (const agent of agents || []) {
380
387
  if (agent.scope !== 'user') continue;
388
+ const normalizedAgent = {
389
+ fileName: agent.fileName,
390
+ name: agent.name,
391
+ description: agent.description,
392
+ tools: agent.tools,
393
+ model: agent.model,
394
+ permissionMode: agent.permissionMode,
395
+ skills: agent.skills,
396
+ systemPrompt: agent.systemPrompt
397
+ };
398
+ agentsByPlatform[platform] = agentsByPlatform[platform] || [];
399
+ agentsByPlatform[platform].push(normalizedAgent);
381
400
  const key = `${agent.fileName || agent.name}|${agent.model || ''}|${agent.description || ''}`;
382
401
  if (!agentMap.has(key)) {
383
- agentMap.set(key, agent);
402
+ agentMap.set(key, normalizedAgent);
384
403
  }
385
404
  }
386
405
  }
387
406
 
388
- for (const service of commandServices) {
407
+ for (const platform of commandPlatforms) {
408
+ const service = new CommandsService(platform);
389
409
  const { commands } = service.listCommands();
390
410
  for (const command of commands || []) {
391
411
  if (command.scope !== 'user') continue;
412
+ const normalizedCommand = {
413
+ name: command.name,
414
+ namespace: command.namespace,
415
+ description: command.description,
416
+ allowedTools: command.allowedTools,
417
+ argumentHint: command.argumentHint,
418
+ body: command.body
419
+ };
420
+ commandsByPlatform[platform] = commandsByPlatform[platform] || [];
421
+ commandsByPlatform[platform].push(normalizedCommand);
392
422
  const key = command.namespace ? `${command.namespace}/${command.name}` : command.name;
393
423
  if (!commandMap.has(key)) {
394
- commandMap.set(key, command);
424
+ commandMap.set(key, normalizedCommand);
395
425
  }
396
426
  }
397
427
  }
398
428
 
399
429
  // 按平台分别获取 skills(每个平台有独立的安装目录)
400
430
  const skillsByPlatform = {};
401
- for (const platform of ['claude', 'codex', 'gemini', 'opencode']) {
431
+ for (const platform of ['claude', 'codex', 'gemini', 'opencode', 'omp']) {
402
432
  const service = new SkillService(platform);
403
433
  skillsByPlatform[platform] = service.getInstalledSkills().map(skill => ({
404
434
  directory: skill.directory,
@@ -440,24 +470,10 @@ function getAvailableConfigs() {
440
470
 
441
471
  return {
442
472
  skillsByPlatform,
443
- agents: Array.from(agentMap.values()).map(a => ({
444
- fileName: a.fileName,
445
- name: a.name,
446
- description: a.description,
447
- tools: a.tools,
448
- model: a.model,
449
- permissionMode: a.permissionMode,
450
- skills: a.skills,
451
- systemPrompt: a.systemPrompt
452
- })),
453
- commands: Array.from(commandMap.values()).map(c => ({
454
- name: c.name,
455
- namespace: c.namespace,
456
- description: c.description,
457
- allowedTools: c.allowedTools,
458
- argumentHint: c.argumentHint,
459
- body: c.body
460
- })),
473
+ agents: Array.from(agentMap.values()),
474
+ agentsByPlatform,
475
+ commands: Array.from(commandMap.values()),
476
+ commandsByPlatform,
461
477
  plugins: installedPlugins.map(p => ({
462
478
  name: p.name,
463
479
  description: p.description || '',
@@ -503,6 +519,48 @@ function buildTemplateCommandFileName(commandName, format) {
503
519
  return `${commandName}.${format === 'gemini' ? 'toml' : 'md'}`;
504
520
  }
505
521
 
522
+ function getCommandPreviewExtension(prefix) {
523
+ return prefix === '.gemini/commands' ? 'toml' : 'md';
524
+ }
525
+
526
+ function writeJsonFile(filePath, data) {
527
+ ensureDir(path.dirname(filePath));
528
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
529
+ }
530
+
531
+ function writeYamlFile(filePath, data) {
532
+ ensureDir(path.dirname(filePath));
533
+ fs.writeFileSync(filePath, yaml.dump(data || {}, {
534
+ lineWidth: 120,
535
+ noRefs: true,
536
+ sortKeys: false
537
+ }), 'utf-8');
538
+ }
539
+
540
+ function mergeOmpProjectPackages(targetDir, plugins = []) {
541
+ const packages = plugins.map(plugin => plugin.name).filter(Boolean);
542
+ if (packages.length === 0) {
543
+ return false;
544
+ }
545
+
546
+ const ompDir = path.join(targetDir, '.omp');
547
+ const settingsPath = path.join(ompDir, 'config.yml');
548
+ let settings = {};
549
+ if (fs.existsSync(settingsPath)) {
550
+ try {
551
+ const parsed = yaml.load(fs.readFileSync(settingsPath, 'utf-8'));
552
+ settings = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
553
+ } catch (err) {
554
+ settings = {};
555
+ }
556
+ }
557
+
558
+ const existingPackages = Array.isArray(settings.packages) ? settings.packages : [];
559
+ settings.packages = Array.from(new Set([...existingPackages, ...packages]));
560
+ writeYamlFile(settingsPath, settings);
561
+ return true;
562
+ }
563
+
506
564
  /**
507
565
  * 转换为 OpenCode MCP 结构(local/remote)
508
566
  */
@@ -542,12 +600,36 @@ function convertToOpenCodeMcpSpec(spec = {}) {
542
600
  return result;
543
601
  }
544
602
 
603
+ function convertToOmpMcpSpec(spec = {}) {
604
+ const type = spec.type || 'stdio';
605
+ const copyCommonFields = (target) => {
606
+ for (const key of ['timeout', 'auth', 'oauth']) {
607
+ if (spec[key] !== undefined) {
608
+ target[key] = spec[key];
609
+ }
610
+ }
611
+ return target;
612
+ };
613
+
614
+ if (type === 'streamable_http') {
615
+ const result = {
616
+ type: 'http',
617
+ url: spec.url || ''
618
+ };
619
+ if (spec.headers && typeof spec.headers === 'object') {
620
+ result.headers = spec.headers;
621
+ }
622
+ return copyCommonFields(result);
623
+ }
624
+ return copyCommonFields({ ...spec });
625
+ }
626
+
545
627
  /**
546
628
  * 应用模板到项目目录(完整应用,写入实际文件)
547
629
  * @param {string} targetDir - 目标项目目录
548
630
  * @param {string} templateId - 模板 ID
549
631
  * @param {object} options - 可选配置
550
- * @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini', 'opencode']
632
+ * @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini', 'opencode', 'omp']
551
633
  * @param {string} options.aiConfigType - (兼容旧版) 单个 AI 配置类型
552
634
  */
553
635
  function applyTemplateToProject(targetDir, templateId, options = {}) {
@@ -572,8 +654,10 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
572
654
 
573
655
  for (const aiConfigType of aiConfigTypes) {
574
656
  const aiConfig = resolveAiConfig(template, aiConfigType);
575
- if (aiConfig?.enabled && aiConfig?.content) {
576
- const configInfo = AI_CONFIG_MAP[aiConfigType];
657
+ const configInfo = AI_CONFIG_MAP[aiConfigType];
658
+ if (!configInfo?.fileName) {
659
+ pushSkipped(results.skipped, 'aiConfig', configInfo?.name || aiConfigType, 'OMP 项目级命令模板通过 .omp/commands 写入,未生成单独 AI 配置文件');
660
+ } else if (aiConfig?.enabled && aiConfig?.content) {
577
661
  const configPath = path.join(targetDir, configInfo.fileName);
578
662
  ensureDir(path.dirname(configPath));
579
663
  fs.writeFileSync(configPath, aiConfig.content, 'utf-8');
@@ -613,6 +697,9 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
613
697
  if (aiConfigTypes.includes('codex')) {
614
698
  pushSkipped(results.skipped, 'agent', fileName, 'Codex agents 仅支持用户级配置,项目目录应用时已跳过');
615
699
  }
700
+ if (aiConfigTypes.includes('omp')) {
701
+ pushSkipped(results.skipped, 'agent', fileName, 'OMP agents 需通过扩展或包提供,项目目录应用时已跳过');
702
+ }
616
703
  if (written) {
617
704
  results.agents.applied++;
618
705
  }
@@ -633,6 +720,9 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
633
720
  if (aiConfigTypes.includes('gemini')) {
634
721
  commandTargets.push({ baseDir: path.join(targetDir, '.gemini', 'commands'), prefix: '.gemini/commands', format: 'gemini' });
635
722
  }
723
+ if (aiConfigTypes.includes('omp')) {
724
+ commandTargets.push({ baseDir: path.join(targetDir, '.omp', 'commands'), prefix: '.omp/commands', format: 'omp' });
725
+ }
636
726
 
637
727
  for (const target of commandTargets) {
638
728
  ensureDir(target.baseDir);
@@ -668,7 +758,7 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
668
758
 
669
759
  if (template.plugins?.length > 0) {
670
760
  results.plugins.items = template.plugins.map(p => p.name);
671
- if (aiConfigTypes.includes('opencode')) {
761
+ if (aiConfigTypes.includes('opencode') || aiConfigTypes.includes('omp')) {
672
762
  results.plugins.applied = template.plugins.length;
673
763
  } else {
674
764
  for (const plugin of template.plugins) {
@@ -678,9 +768,13 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
678
768
  }
679
769
 
680
770
  const hasMcp = template.mcpServers?.length > 0;
771
+ const writesGenericMcpConfig = hasMcp && aiConfigTypes.some(type => type !== 'omp');
772
+ const writesOmpMcpConfig = hasMcp && aiConfigTypes.includes('omp');
681
773
  const hasPluginsForOpenCode = aiConfigTypes.includes('opencode') && template.plugins?.length > 0;
682
- if (hasMcp || hasPluginsForOpenCode) {
774
+ const hasPluginsForOmp = aiConfigTypes.includes('omp') && template.plugins?.length > 0;
775
+ if (hasMcp || hasPluginsForOpenCode || hasPluginsForOmp) {
683
776
  const mcpConfig = { mcpServers: {} };
777
+ const ompMcpConfig = { mcpServers: {} };
684
778
  const opencodeConfig = { mcp: {}, plugin: [] };
685
779
  const allServers = mcpService.getAllServers();
686
780
  const presets = mcpService.getPresets();
@@ -696,17 +790,29 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
696
790
  }
697
791
  }
698
792
  if (serverSpec) {
699
- mcpConfig.mcpServers[serverId] = serverSpec;
793
+ if (writesGenericMcpConfig) {
794
+ mcpConfig.mcpServers[serverId] = serverSpec;
795
+ }
796
+ if (writesOmpMcpConfig) {
797
+ ompMcpConfig.mcpServers[serverId] = convertToOmpMcpSpec(serverSpec);
798
+ }
700
799
  opencodeConfig.mcp[serverId] = convertToOpenCodeMcpSpec(serverSpec);
701
- results.mcpServers.applied++;
800
+ if (writesGenericMcpConfig || writesOmpMcpConfig) {
801
+ results.mcpServers.applied++;
802
+ }
702
803
  } else {
703
804
  pushSkipped(results.skipped, 'mcpServer', serverId, '未找到对应 MCP 服务配置,已跳过');
704
805
  }
705
806
  }
706
807
 
707
- if (Object.keys(mcpConfig.mcpServers).length > 0) {
808
+ if (writesGenericMcpConfig && Object.keys(mcpConfig.mcpServers).length > 0) {
708
809
  const mcpPath = path.join(targetDir, '.mcp.json');
709
- fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');
810
+ writeJsonFile(mcpPath, mcpConfig);
811
+ }
812
+
813
+ if (writesOmpMcpConfig && Object.keys(ompMcpConfig.mcpServers).length > 0) {
814
+ const ompMcpPath = path.join(targetDir, '.omp', 'mcp.json');
815
+ writeJsonFile(ompMcpPath, ompMcpConfig);
710
816
  }
711
817
 
712
818
  if (hasPluginsForOpenCode) {
@@ -716,7 +822,11 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
716
822
  const opencodeDir = path.join(targetDir, '.opencode');
717
823
  ensureDir(opencodeDir);
718
824
  const opencodePath = path.join(opencodeDir, 'opencode.json');
719
- fs.writeFileSync(opencodePath, JSON.stringify(opencodeConfig, null, 2), 'utf-8');
825
+ writeJsonFile(opencodePath, opencodeConfig);
826
+ }
827
+
828
+ if (hasPluginsForOmp) {
829
+ mergeOmpProjectPackages(targetDir, template.plugins || []);
720
830
  }
721
831
  }
722
832
 
@@ -748,7 +858,7 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
748
858
  * @param {string} targetDir - 目标项目目录
749
859
  * @param {string} templateId - 模板 ID
750
860
  * @param {object} options - 可选配置
751
- * @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini', 'opencode']
861
+ * @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini', 'opencode', 'omp']
752
862
  * @param {string} options.aiConfigType - (兼容旧版) 单个 AI 配置类型
753
863
  */
754
864
  function previewTemplateApplication(targetDir, templateId, options = {}) {
@@ -776,8 +886,10 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
776
886
 
777
887
  for (const aiConfigType of aiConfigTypes) {
778
888
  const aiConfig = resolveAiConfig(template, aiConfigType);
779
- if (aiConfig?.enabled && aiConfig?.content) {
780
- const configInfo = AI_CONFIG_MAP[aiConfigType];
889
+ const configInfo = AI_CONFIG_MAP[aiConfigType];
890
+ if (!configInfo?.fileName) {
891
+ pushSkipped(preview.skipped, 'aiConfig', configInfo?.name || aiConfigType, 'OMP 项目级命令模板通过 .omp/commands 写入,预览不生成单独 AI 配置文件');
892
+ } else if (aiConfig?.enabled && aiConfig?.content) {
781
893
  const configPath = path.join(targetDir, configInfo.fileName);
782
894
  if (fs.existsSync(configPath)) {
783
895
  preview.willOverwrite.push(configInfo.fileName);
@@ -818,6 +930,9 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
818
930
  if (aiConfigTypes.includes('codex')) {
819
931
  pushSkipped(preview.skipped, 'agent', fileName, 'Codex agents 仅支持用户级配置,项目目录预览时已跳过');
820
932
  }
933
+ if (aiConfigTypes.includes('omp')) {
934
+ pushSkipped(preview.skipped, 'agent', fileName, 'OMP agents 需通过扩展或包提供,项目目录预览时已跳过');
935
+ }
821
936
  if (applicable) {
822
937
  preview.summary.agents++;
823
938
  }
@@ -830,12 +945,13 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
830
945
  if (aiConfigTypes.includes('codex')) commandPrefixes.push('.codex/prompts');
831
946
  if (aiConfigTypes.includes('opencode')) commandPrefixes.push('.opencode/commands');
832
947
  if (aiConfigTypes.includes('gemini')) commandPrefixes.push('.gemini/commands');
948
+ if (aiConfigTypes.includes('omp')) commandPrefixes.push('.omp/commands');
833
949
 
834
950
  for (const command of template.commands) {
835
951
  const commandName = resolveItemName(command.name, null, 'command');
836
952
  let applicable = false;
837
953
  for (const prefix of commandPrefixes) {
838
- const extension = prefix === '.gemini/commands' ? 'toml' : 'md';
954
+ const extension = getCommandPreviewExtension(prefix);
839
955
  const relativePath = command.namespace
840
956
  ? `${prefix}/${command.namespace}/${commandName}.${extension}`
841
957
  : `${prefix}/${commandName}.${extension}`;
@@ -853,6 +969,8 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
853
969
  }
854
970
  }
855
971
 
972
+ const writesGenericMcpConfig = template.mcpServers?.length > 0 && aiConfigTypes.some(type => type !== 'omp');
973
+ const writesOmpMcpConfig = template.mcpServers?.length > 0 && aiConfigTypes.includes('omp');
856
974
  const allServers = mcpService.getAllServers();
857
975
  const presets = mcpService.getPresets();
858
976
  let resolvableMcpCount = 0;
@@ -865,13 +983,15 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
865
983
  }
866
984
  }
867
985
  if (serverSpec) {
868
- resolvableMcpCount++;
986
+ if (writesGenericMcpConfig || writesOmpMcpConfig) {
987
+ resolvableMcpCount++;
988
+ }
869
989
  } else {
870
990
  pushSkipped(preview.skipped, 'mcpServer', serverId, '未找到对应 MCP 服务配置,预览已跳过');
871
991
  }
872
992
  }
873
993
 
874
- if (resolvableMcpCount > 0) {
994
+ if (writesGenericMcpConfig && resolvableMcpCount > 0) {
875
995
  const mcpPath = path.join(targetDir, '.mcp.json');
876
996
  if (fs.existsSync(mcpPath)) {
877
997
  preview.willOverwrite.push('.mcp.json');
@@ -881,6 +1001,16 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
881
1001
  preview.summary.mcpServers = resolvableMcpCount;
882
1002
  }
883
1003
 
1004
+ if (writesOmpMcpConfig && resolvableMcpCount > 0) {
1005
+ const ompMcpPath = path.join(targetDir, '.omp/mcp.json');
1006
+ if (fs.existsSync(ompMcpPath)) {
1007
+ preview.willOverwrite.push('.omp/mcp.json');
1008
+ } else {
1009
+ preview.willCreate.push('.omp/mcp.json');
1010
+ }
1011
+ preview.summary.mcpServers = resolvableMcpCount;
1012
+ }
1013
+
884
1014
  if (aiConfigTypes.includes('opencode') && (resolvableMcpCount > 0 || template.plugins?.length > 0)) {
885
1015
  const opencodeConfigPath = path.join(targetDir, '.opencode/opencode.json');
886
1016
  if (fs.existsSync(opencodeConfigPath)) {
@@ -891,8 +1021,16 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
891
1021
  }
892
1022
 
893
1023
  if (template.plugins?.length > 0) {
894
- if (aiConfigTypes.includes('opencode')) {
1024
+ if (aiConfigTypes.includes('opencode') || aiConfigTypes.includes('omp')) {
895
1025
  preview.summary.plugins = template.plugins.length;
1026
+ if (aiConfigTypes.includes('omp')) {
1027
+ const ompSettingsPath = path.join(targetDir, '.omp/config.yml');
1028
+ if (fs.existsSync(ompSettingsPath)) {
1029
+ preview.willOverwrite.push('.omp/config.yml');
1030
+ } else {
1031
+ preview.willCreate.push('.omp/config.yml');
1032
+ }
1033
+ }
896
1034
  } else {
897
1035
  for (const plugin of template.plugins) {
898
1036
  pushSkipped(preview.skipped, 'plugin', plugin.name, '当前未选择 OpenCode,已跳过插件写入预览');