coding-tool-x 3.6.13 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +46 -33
  2. package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dma_j2Rq.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-C49Y1PLH.js} +1 -1
  4. package/dist/web/assets/Home-DGGC8H4g.js +1 -0
  5. package/dist/web/assets/Home-ewgeIhF7.css +1 -0
  6. package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-B1ECTHyY.js} +1 -1
  7. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
  8. package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
  9. package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
  10. package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-CRyVhjYQ.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-CO9tclVt.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-CM26UrnJ.css +1 -0
  14. package/dist/web/assets/index-EQq56pRa.js +2 -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 +2 -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 +48 -6
  22. package/src/commands/port-config.js +83 -75
  23. package/src/commands/proxy-control.js +52 -19
  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 +99 -11
  32. package/src/index.js +11 -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/pi-channels.js +349 -0
  40. package/src/server/api/pi-config.js +164 -0
  41. package/src/server/api/pi-projects.js +76 -0
  42. package/src/server/api/pi-proxy.js +154 -0
  43. package/src/server/api/pi-sessions.js +334 -0
  44. package/src/server/api/pi-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/pi-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 +275 -18
  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/pi-auth-providers.js +294 -0
  64. package/src/server/services/pi-channels.js +94 -0
  65. package/src/server/services/pi-config.js +287 -0
  66. package/src/server/services/pi-sessions.js +490 -0
  67. package/src/server/services/pi-settings-manager.js +571 -0
  68. package/src/server/services/pi-statistics-service.js +317 -0
  69. package/src/server/services/plugins-service.js +673 -11
  70. package/src/server/services/prompts-service.js +112 -7
  71. package/src/server/services/proxy-log-helper.js +1 -0
  72. package/src/server/services/proxy-runtime.js +5 -1
  73. package/src/server/services/sessions.js +19 -35
  74. package/src/server/services/skill-service.js +13 -3
  75. package/src/server/services/speed-test.js +9 -3
  76. package/src/server/services/ui-config.js +50 -12
  77. package/src/server/services/workspace-service.js +117 -225
  78. package/src/server/websocket-server.js +7 -1
  79. package/src/utils/session.js +4 -10
  80. package/dist/web/assets/Home-BiaKZMah.js +0 -1
  81. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  82. package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
  83. package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
  84. package/dist/web/assets/index-BpBFF3b2.js +0 -2
  85. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  86. package/src/utils/cli-flags.js +0 -7
@@ -392,6 +392,8 @@ const PATHS = {
392
392
  forkRelations: path.join(CONFIG_DIR, 'fork-relations.json'),
393
393
  opencodeProjectOrder: path.join(CONFIG_DIR, 'opencode-project-order.json'),
394
394
  opencodeSessionOrder: path.join(CONFIG_DIR, 'opencode-session-order.json'),
395
+ piProjectOrder: path.join(CONFIG_DIR, 'pi-project-order.json'),
396
+ piSessionOrder: path.join(CONFIG_DIR, 'pi-session-order.json'),
395
397
 
396
398
  // 渠道配置
397
399
  channelsDir: CHANNELS_DIR,
@@ -399,14 +401,16 @@ const PATHS = {
399
401
  claude: path.join(CHANNELS_DIR, 'claude.json'),
400
402
  codex: path.join(CHANNELS_DIR, 'codex.json'),
401
403
  gemini: path.join(CHANNELS_DIR, 'gemini.json'),
402
- opencode: path.join(CHANNELS_DIR, 'opencode.json')
404
+ opencode: path.join(CHANNELS_DIR, 'opencode.json'),
405
+ pi: path.join(CHANNELS_DIR, 'pi.json')
403
406
  },
404
407
  activeChannelDir: ACTIVE_CHANNELS_DIR,
405
408
  activeChannel: {
406
409
  claude: path.join(ACTIVE_CHANNELS_DIR, 'claude.json'),
407
410
  codex: path.join(ACTIVE_CHANNELS_DIR, 'codex.json'),
408
411
  gemini: path.join(ACTIVE_CHANNELS_DIR, 'gemini.json'),
409
- opencode: path.join(ACTIVE_CHANNELS_DIR, 'opencode.json')
412
+ opencode: path.join(ACTIVE_CHANNELS_DIR, 'opencode.json'),
413
+ pi: path.join(ACTIVE_CHANNELS_DIR, 'pi.json')
410
414
  },
411
415
 
412
416
  // 统计与日志快照
@@ -437,7 +441,8 @@ const PATHS = {
437
441
  claude: path.join(RUNTIME_DIR, 'claude-proxy.json'),
438
442
  codex: path.join(RUNTIME_DIR, 'codex-proxy.json'),
439
443
  gemini: path.join(RUNTIME_DIR, 'gemini-proxy.json'),
440
- opencode: path.join(RUNTIME_DIR, 'opencode-proxy.json')
444
+ opencode: path.join(RUNTIME_DIR, 'opencode-proxy.json'),
445
+ pi: path.join(RUNTIME_DIR, 'pi-proxy.json')
441
446
  },
442
447
 
443
448
  // 请求记录
@@ -445,7 +450,8 @@ const PATHS = {
445
450
  claude: path.join(REQUESTS_DIR, 'claude.jsonl'),
446
451
  codex: path.join(REQUESTS_DIR, 'codex.jsonl'),
447
452
  gemini: path.join(REQUESTS_DIR, 'gemini.jsonl'),
448
- opencode: path.join(REQUESTS_DIR, 'opencode.jsonl')
453
+ opencode: path.join(REQUESTS_DIR, 'opencode.jsonl'),
454
+ pi: path.join(REQUESTS_DIR, 'pi.jsonl')
449
455
  },
450
456
  claudeRequestTemplate: path.join(REQUESTS_DIR, 'claude-request-template.json'),
451
457
 
@@ -457,35 +463,40 @@ const PATHS = {
457
463
  claude: path.join(LOCAL_SKILLS_DIR, 'claude'),
458
464
  codex: path.join(LOCAL_SKILLS_DIR, 'codex'),
459
465
  gemini: path.join(LOCAL_SKILLS_DIR, 'gemini'),
460
- opencode: path.join(LOCAL_SKILLS_DIR, 'opencode')
466
+ opencode: path.join(LOCAL_SKILLS_DIR, 'opencode'),
467
+ pi: path.join(LOCAL_SKILLS_DIR, 'pi')
461
468
  },
462
469
  skillRepos: {
463
470
  claude: path.join(REPOS_SKILLS_DIR, 'claude.json'),
464
471
  codex: path.join(REPOS_SKILLS_DIR, 'codex.json'),
465
472
  gemini: path.join(REPOS_SKILLS_DIR, 'gemini.json'),
466
- opencode: path.join(REPOS_SKILLS_DIR, 'opencode.json')
473
+ opencode: path.join(REPOS_SKILLS_DIR, 'opencode.json'),
474
+ pi: path.join(REPOS_SKILLS_DIR, 'pi.json')
467
475
  },
468
476
  skillCaches: {
469
477
  claude: path.join(CACHE_SKILLS_DIR, 'claude.json'),
470
478
  codex: path.join(CACHE_SKILLS_DIR, 'codex.json'),
471
479
  gemini: path.join(CACHE_SKILLS_DIR, 'gemini.json'),
472
- opencode: path.join(CACHE_SKILLS_DIR, 'opencode.json')
480
+ opencode: path.join(CACHE_SKILLS_DIR, 'opencode.json'),
481
+ pi: path.join(CACHE_SKILLS_DIR, 'pi.json')
473
482
  },
474
483
  pluginRepos: {
475
484
  claude: path.join(REPOS_PLUGINS_DIR, 'claude.json'),
476
485
  codex: path.join(REPOS_PLUGINS_DIR, 'codex.json'),
477
486
  gemini: path.join(REPOS_PLUGINS_DIR, 'gemini.json'),
478
- opencode: path.join(REPOS_PLUGINS_DIR, 'opencode.json')
487
+ opencode: path.join(REPOS_PLUGINS_DIR, 'opencode.json'),
488
+ pi: path.join(REPOS_PLUGINS_DIR, 'pi.json')
479
489
  },
480
490
  pluginMarketCache: {
481
491
  claude: path.join(CACHE_PLUGINS_DIR, 'claude-market.json'),
482
492
  codex: path.join(CACHE_PLUGINS_DIR, 'codex-market.json'),
483
493
  gemini: path.join(CACHE_PLUGINS_DIR, 'gemini-market.json'),
484
- opencode: path.join(CACHE_PLUGINS_DIR, 'opencode-market.json')
494
+ opencode: path.join(CACHE_PLUGINS_DIR, 'opencode-market.json'),
495
+ pi: path.join(CACHE_PLUGINS_DIR, 'pi-market.json')
485
496
  },
486
497
 
487
498
  // 原生路径兼容
488
- skills: path.join(HOME_DIR, '.claude', 'skills'),
499
+ skills: path.join(getClaudeConfigDir(), 'skills'),
489
500
 
490
501
  // 旧版本遗留文件搬迁目标
491
502
  legacy: {
@@ -521,16 +532,20 @@ const LEGACY_STORAGE_RELOCATIONS = [
521
532
  { source: rootEntry('fork-relations.json'), target: PATHS.forkRelations },
522
533
  { source: rootEntry('opencode-project-order.json'), target: PATHS.opencodeProjectOrder },
523
534
  { source: rootEntry('opencode-session-order.json'), target: PATHS.opencodeSessionOrder },
535
+ { source: rootEntry('pi-project-order.json'), target: PATHS.piProjectOrder },
536
+ { source: rootEntry('pi-session-order.json'), target: PATHS.piSessionOrder },
524
537
 
525
538
  // 渠道相关
526
539
  { source: rootEntry('channels.json'), target: PATHS.channels.claude },
527
540
  { source: rootEntry('codex-channels.json'), target: PATHS.channels.codex },
528
541
  { source: rootEntry('gemini-channels.json'), target: PATHS.channels.gemini },
529
542
  { source: rootEntry('opencode-channels.json'), target: PATHS.channels.opencode },
543
+ { source: rootEntry('pi-channels.json'), target: PATHS.channels.pi },
530
544
  { source: rootEntry('active-channel.json'), target: PATHS.activeChannel.claude },
531
545
  { source: rootEntry('codex-active-channel.json'), target: PATHS.activeChannel.codex },
532
546
  { source: rootEntry('gemini-active-channel.json'), target: PATHS.activeChannel.gemini },
533
547
  { source: rootEntry('opencode-active-channel.json'), target: PATHS.activeChannel.opencode },
548
+ { source: rootEntry('pi-active-channel.json'), target: PATHS.activeChannel.pi },
534
549
 
535
550
  // 统计与日志
536
551
  { source: rootEntry('statistics.json'), target: PATHS.statistics.summary },
@@ -553,12 +568,14 @@ const LEGACY_STORAGE_RELOCATIONS = [
553
568
  { source: rootEntry('codex-proxy-runtime.json'), target: PATHS.proxyRuntime.codex },
554
569
  { source: rootEntry('gemini-proxy-runtime.json'), target: PATHS.proxyRuntime.gemini },
555
570
  { source: rootEntry('opencode-proxy-runtime.json'), target: PATHS.proxyRuntime.opencode },
571
+ { source: rootEntry('pi-proxy-runtime.json'), target: PATHS.proxyRuntime.pi },
556
572
  { source: rootEntry('notify-hook.js'), target: PATHS.notifyHook },
557
573
  { source: rootEntry('claude-request-template.json'), target: PATHS.claudeRequestTemplate },
558
574
  { source: rootEntry('claude-requests.jsonl'), target: PATHS.requestSnapshots.claude },
559
575
  { source: rootEntry('codex-requests.jsonl'), target: PATHS.requestSnapshots.codex },
560
576
  { source: rootEntry('gemini-requests.jsonl'), target: PATHS.requestSnapshots.gemini },
561
577
  { source: rootEntry('opencode-requests.jsonl'), target: PATHS.requestSnapshots.opencode },
578
+ { source: rootEntry('pi-requests.jsonl'), target: PATHS.requestSnapshots.pi },
562
579
 
563
580
  // 备份
564
581
  { source: rootEntry('env-backups'), target: PATHS.envBackups },
@@ -568,24 +585,29 @@ const LEGACY_STORAGE_RELOCATIONS = [
568
585
  { source: rootEntry('codex-skills'), target: PATHS.localSkills.codex },
569
586
  { source: rootEntry('gemini-skills'), target: PATHS.localSkills.gemini },
570
587
  { source: rootEntry('opencode-skills'), target: PATHS.localSkills.opencode },
588
+ { source: rootEntry('pi-skills'), target: PATHS.localSkills.pi },
571
589
  { source: rootEntry('skill-repos.json'), target: PATHS.skillRepos.claude },
572
590
  { source: rootEntry('codex-skill-repos.json'), target: PATHS.skillRepos.codex },
573
591
  { source: rootEntry('gemini-skill-repos.json'), target: PATHS.skillRepos.gemini },
574
592
  { source: rootEntry('opencode-skill-repos.json'), target: PATHS.skillRepos.opencode },
593
+ { source: rootEntry('pi-skill-repos.json'), target: PATHS.skillRepos.pi },
575
594
  { source: rootEntry('skills-cache.json'), target: PATHS.skillCaches.claude },
576
595
  { source: rootEntry('codex-skills-cache.json'), target: PATHS.skillCaches.codex },
577
596
  { source: rootEntry('gemini-skills-cache.json'), target: PATHS.skillCaches.gemini },
578
597
  { source: rootEntry('opencode-skills-cache.json'), target: PATHS.skillCaches.opencode },
598
+ { source: rootEntry('pi-skills-cache.json'), target: PATHS.skillCaches.pi },
579
599
 
580
600
  // 插件仓库缓存
581
601
  { source: rootEntry('plugin-repos.json'), target: PATHS.pluginRepos.claude },
582
602
  { source: rootEntry('codex-plugin-repos.json'), target: PATHS.pluginRepos.codex },
583
603
  { source: rootEntry('gemini-plugin-repos.json'), target: PATHS.pluginRepos.gemini },
584
604
  { source: rootEntry('opencode-plugin-repos.json'), target: PATHS.pluginRepos.opencode },
605
+ { source: rootEntry('pi-plugin-repos.json'), target: PATHS.pluginRepos.pi },
585
606
  { source: rootEntry('plugins-market-cache.json'), target: PATHS.pluginMarketCache.claude },
586
607
  { source: rootEntry('codex-plugins-market-cache.json'), target: PATHS.pluginMarketCache.codex },
587
608
  { source: rootEntry('gemini-plugins-market-cache.json'), target: PATHS.pluginMarketCache.gemini },
588
609
  { source: rootEntry('opencode-plugins-market-cache.json'), target: PATHS.pluginMarketCache.opencode },
610
+ { source: rootEntry('pi-plugins-market-cache.json'), target: PATHS.pluginMarketCache.pi },
589
611
 
590
612
  // 已废弃但需要保留的数据
591
613
  { source: rootEntry('oauth-tokens.json'), target: PATHS.legacy.oauthTokens }
@@ -708,6 +730,16 @@ function resolveExistingEnvPath(envValue) {
708
730
  return trimmed || '';
709
731
  }
710
732
 
733
+ function expandHomePath(input = '') {
734
+ if (input === '~') {
735
+ return HOME_DIR;
736
+ }
737
+ if (input.startsWith('~/') || input.startsWith('~\\')) {
738
+ return path.join(HOME_DIR, input.slice(2));
739
+ }
740
+ return input;
741
+ }
742
+
711
743
  function pickExistingDir(candidates, fallback) {
712
744
  for (const candidate of candidates) {
713
745
  if (candidate && fs.existsSync(candidate)) {
@@ -765,6 +797,34 @@ function getOpenCodeConfigDir() {
765
797
  return preferredDir;
766
798
  }
767
799
 
800
+ function normalizeOmpProfileName(profile = '') {
801
+ const value = String(profile || '').trim();
802
+ return value && value !== 'default' ? value : '';
803
+ }
804
+
805
+ function getOmpProfileName() {
806
+ if (process.env.OMP_PROFILE !== undefined) {
807
+ return normalizeOmpProfileName(process.env.OMP_PROFILE);
808
+ }
809
+ return normalizeOmpProfileName(process.env.PI_PROFILE);
810
+ }
811
+
812
+ function getOmpConfigRoot() {
813
+ return expandHomePath(resolveExistingEnvPath(process.env.PI_CONFIG_DIR) || path.join(HOME_DIR, '.omp'));
814
+ }
815
+
816
+ function getPiAgentDir() {
817
+ const configuredDir = resolveExistingEnvPath(process.env.PI_CODING_AGENT_DIR);
818
+ if (configuredDir) {
819
+ return path.resolve(expandHomePath(configuredDir));
820
+ }
821
+ const profile = getOmpProfileName();
822
+ const agentDir = profile
823
+ ? path.join(getOmpConfigRoot(), 'profiles', profile, 'agent')
824
+ : path.join(getOmpConfigRoot(), 'agent');
825
+ return path.resolve(agentDir);
826
+ }
827
+
768
828
  // 工具特定的原生配置路径(不改变)
769
829
  const NATIVE_PATHS = {
770
830
  // Claude Code 原生配置
@@ -772,7 +832,13 @@ const NATIVE_PATHS = {
772
832
  dir: getClaudeConfigDir(),
773
833
  settings: path.join(getClaudeConfigDir(), 'settings.json'),
774
834
  settingsBackup: path.join(getClaudeConfigDir(), 'settings.json.cc-tool-backup'),
835
+ prompt: path.join(getClaudeConfigDir(), 'CLAUDE.md'),
775
836
  projects: path.join(getClaudeConfigDir(), 'projects'),
837
+ skills: path.join(getClaudeConfigDir(), 'skills'),
838
+ commands: path.join(getClaudeConfigDir(), 'commands'),
839
+ agents: path.join(getClaudeConfigDir(), 'agents'),
840
+ plugins: path.join(getClaudeConfigDir(), 'plugins'),
841
+ mcp: path.join(HOME_DIR, '.claude.json'),
776
842
  credentials: path.join(getClaudeConfigDir(), '.credentials.json')
777
843
  },
778
844
 
@@ -808,6 +874,27 @@ const NATIVE_PATHS = {
808
874
  messages: path.join(getOpenCodeDataDir(), 'storage', 'message'),
809
875
  log: path.join(getOpenCodeDataDir(), 'log'),
810
876
  auth: path.join(getOpenCodeDataDir(), 'auth.json')
877
+ },
878
+
879
+ // OMP 原生配置。外部仍使用 pi 渠道键,内部默认落到 OMP 配置目录。
880
+ pi: {
881
+ dir: getPiAgentDir(),
882
+ config: path.join(getPiAgentDir(), 'config.yml'),
883
+ settings: path.join(getPiAgentDir(), 'config.yml'),
884
+ settingsJsonLegacy: path.join(getPiAgentDir(), 'settings.json'),
885
+ auth: path.join(getPiAgentDir(), 'auth.json'),
886
+ models: path.join(getPiAgentDir(), 'models.yml'),
887
+ modelsYml: path.join(getPiAgentDir(), 'models.yml'),
888
+ modelsJsonLegacy: path.join(getPiAgentDir(), 'models.json'),
889
+ mcp: path.join(getPiAgentDir(), 'mcp.json'),
890
+ sessions: path.join(getPiAgentDir(), 'sessions'),
891
+ skills: path.join(getPiAgentDir(), 'skills'),
892
+ prompts: path.join(getPiAgentDir(), 'prompts'),
893
+ commands: path.join(getPiAgentDir(), 'commands'),
894
+ notes: path.join(getPiAgentDir(), 'notes'),
895
+ extensions: path.join(getPiAgentDir(), 'extensions'),
896
+ themes: path.join(getPiAgentDir(), 'themes'),
897
+ packages: path.join(getPiAgentDir(), 'packages')
811
898
  }
812
899
  };
813
900
 
@@ -827,5 +914,6 @@ module.exports = {
827
914
  getCodexDir,
828
915
  getGeminiDir,
829
916
  getOpenCodeDataDir,
830
- getOpenCodeConfigDir
917
+ getOpenCodeConfigDir,
918
+ getPiAgentDir
831
919
  };
package/src/index.js CHANGED
@@ -14,9 +14,9 @@ const { handleStats, handleStatsExport } = require('./commands/stats');
14
14
  const { handleDoctor } = require('./commands/doctor');
15
15
  const { handleUpdate } = require('./commands/update');
16
16
  const { ensureStorageDirMigrated } = require('./config/paths');
17
+ const { normalizePlatformKey } = require('./shared/platforms');
17
18
  const PluginManager = require('./plugins/plugin-manager');
18
19
  const eventBus = require('./plugins/event-bus');
19
- const { hasHostFlag } = require('./utils/cli-flags');
20
20
  const chalk = require('chalk');
21
21
  const path = require('path');
22
22
  const fs = require('fs');
@@ -54,12 +54,9 @@ function showHelp() {
54
54
  console.log(' ctx ui 前台启动 Web UI(仅本地访问)');
55
55
  console.log(' ctx ui --https 前台启动 Web UI(本地 HTTPS)');
56
56
  console.log(' ctx ui --host 前台启动 Web UI(允许 LAN 访问)');
57
- console.log(' ctx ui --hosts 前台启动 Web UI(同 --host)');
58
- console.log(' ctx --hosts 前台启动 Web UI(同 ctx ui --host)');
59
57
  console.log(' ctx ui start 后台启动 Web UI');
60
58
  console.log(' ctx ui start --https 后台启动 Web UI(本地 HTTPS)');
61
59
  console.log(' ctx ui start --host 后台启动 Web UI(允许 LAN 访问)');
62
- console.log(' ctx ui start --hosts 后台启动 Web UI(同 --host)');
63
60
  console.log(' ctx ui stop 停止 Web UI');
64
61
  console.log(' ctx ui restart 重启 Web UI\n');
65
62
 
@@ -70,12 +67,15 @@ function showHelp() {
70
67
  console.log(' ctx codex start 启动 Codex 代理');
71
68
  console.log(' ctx gemini start 启动 Gemini 代理');
72
69
  console.log(' ctx opencode start 启动 OpenCode 代理');
73
- console.log(chalk.gray(' (codex/gemini/opencode 命令与 claude 类似)\n'));
70
+ console.log(' ctx pi start 启用 OMP 受管模型配置');
71
+ console.log(' ctx omp start ctx pi 的别名,启用 OMP 受管模型配置');
72
+ console.log(chalk.gray(' (codex/gemini/opencode/pi/omp 命令与 claude 类似)\n'));
74
73
 
75
74
  console.log(chalk.yellow('[LOG] 日志管理:'));
76
75
  console.log(' ctx logs 查看所有日志');
77
76
  console.log(' ctx logs ui 查看 UI 日志');
78
77
  console.log(' ctx logs claude 查看 Claude 日志');
78
+ console.log(' ctx logs omp 查看 OMP 相关 UI/server 日志');
79
79
  console.log(' ctx logs --lines 100 查看最近 100 行');
80
80
  console.log(' ctx logs --follow 实时跟踪日志');
81
81
  console.log(' ctx logs --clear 清空日志\n');
@@ -83,6 +83,7 @@ function showHelp() {
83
83
  console.log(chalk.yellow('[STATS] 统计信息:'));
84
84
  console.log(' ctx stats 查看总体统计');
85
85
  console.log(' ctx stats claude 查看 Claude 统计');
86
+ console.log(' ctx stats omp 查看 OMP 统计');
86
87
  console.log(' ctx stats --today 查看今日统计');
87
88
  console.log(' ctx stats export 导出统计数据\n');
88
89
 
@@ -160,13 +161,6 @@ async function main() {
160
161
  return;
161
162
  }
162
163
 
163
- // LAN 快捷入口,等同于 ctx ui --host/--hosts
164
- if (hasHostFlag(process.argv) && !args.some((arg) => arg && !arg.startsWith('-'))) {
165
- const { handleUI } = require('./commands/ui');
166
- await handleUI();
167
- return;
168
- }
169
-
170
164
  // daemon 命令兼容(保持与 README 中旧命令一致)
171
165
  if (args[0] === 'daemon') {
172
166
  const subCommand = args[1] || 'status';
@@ -271,10 +265,11 @@ async function main() {
271
265
  return;
272
266
  }
273
267
 
274
- // claude/codex/gemini/opencode 代理管理命令
275
- const channels = ['claude', 'codex', 'gemini', 'opencode'];
276
- if (channels.includes(args[0])) {
277
- const channel = args[0];
268
+ // claude/codex/gemini/opencode/pi 代理管理命令;omp 是 pi 的输入别名
269
+ const channels = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
270
+ const normalizedChannel = normalizePlatformKey(args[0]);
271
+ if (channels.includes(normalizedChannel)) {
272
+ const channel = String(args[0] || '').trim().toLowerCase();
278
273
  const action = args[1] || 'status';
279
274
 
280
275
  switch (action) {
@@ -9,7 +9,7 @@ const { CommandsService } = require('../services/commands-service');
9
9
  const { sendApiError } = require('./validation-errors');
10
10
 
11
11
  const router = express.Router();
12
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
12
+ const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
13
13
  const commandServices = new Map();
14
14
 
15
15
  function resolvePlatform(rawPlatform) {
@@ -25,7 +25,8 @@ function resolveChannelsByType(exportData) {
25
25
  claude: Array.isArray(typed.claude) ? typed.claude : (Array.isArray(raw.channels) ? raw.channels : []),
26
26
  codex: Array.isArray(typed.codex) ? typed.codex : [],
27
27
  gemini: Array.isArray(typed.gemini) ? typed.gemini : [],
28
- opencode: Array.isArray(typed.opencode) ? typed.opencode : []
28
+ opencode: Array.isArray(typed.opencode) ? typed.opencode : [],
29
+ pi: Array.isArray(typed.pi) ? typed.pi : []
29
30
  };
30
31
  }
31
32
 
@@ -44,7 +45,8 @@ function buildPreviewSummary(data) {
44
45
  ...channelsByType.claude.map(c => ({ ...c, type: c.type || 'claude' })),
45
46
  ...channelsByType.codex.map(c => ({ ...c, type: c.type || 'codex' })),
46
47
  ...channelsByType.gemini.map(c => ({ ...c, type: c.type || 'gemini' })),
47
- ...channelsByType.opencode.map(c => ({ ...c, type: c.type || 'opencode' }))
48
+ ...channelsByType.opencode.map(c => ({ ...c, type: c.type || 'opencode' })),
49
+ ...channelsByType.pi.map(c => ({ ...c, type: c.type || 'pi' }))
48
50
  ];
49
51
 
50
52
  return {
@@ -43,6 +43,42 @@ function validatePlatform(platform) {
43
43
  return null;
44
44
  }
45
45
 
46
+ const PLATFORM_SYNC_METHODS = {
47
+ claude: { sync: 'syncToClaude', remove: 'removeFromClaude' },
48
+ codex: { sync: 'syncToCodex', remove: 'removeFromCodex' },
49
+ gemini: { sync: 'syncToGemini', remove: 'removeFromGemini' },
50
+ opencode: { sync: 'syncToOpenCode', remove: 'removeFromOpenCode' },
51
+ pi: { sync: 'syncToPi', remove: 'removeFromPi' }
52
+ };
53
+
54
+ function syncPlatform(type, name, platform) {
55
+ const method = PLATFORM_SYNC_METHODS[platform]?.sync;
56
+ if (method && typeof syncManager[method] === 'function') {
57
+ syncManager[method](type, name);
58
+ }
59
+ }
60
+
61
+ function removePlatform(type, name, platform) {
62
+ const method = PLATFORM_SYNC_METHODS[platform]?.remove;
63
+ if (method && typeof syncManager[method] === 'function') {
64
+ syncManager[method](type, name);
65
+ }
66
+ }
67
+
68
+ function syncEnabledPlatforms(type, name, platforms = {}) {
69
+ for (const platform of VALID_PLATFORMS) {
70
+ if (platforms?.[platform]) {
71
+ syncPlatform(type, name, platform);
72
+ }
73
+ }
74
+ }
75
+
76
+ function removeAllPlatforms(type, name) {
77
+ for (const platform of VALID_PLATFORMS) {
78
+ removePlatform(type, name, platform);
79
+ }
80
+ }
81
+
46
82
  /**
47
83
  * GET /api/config-registry/stats
48
84
  * Get statistics for all config types
@@ -193,24 +229,10 @@ router.put('/:type/:name/toggle', async (req, res) => {
193
229
  // Apply sync based on new state
194
230
  if (enabled) {
195
231
  // Sync to platforms where platform=true
196
- if (item.platforms?.claude) {
197
- syncManager.syncToClaude(type, name);
198
- }
199
- if (item.platforms?.codex) {
200
- syncManager.syncToCodex(type, name);
201
- }
202
- if (item.platforms?.gemini) {
203
- syncManager.syncToGemini(type, name);
204
- }
205
- if (item.platforms?.opencode) {
206
- syncManager.syncToOpenCode(type, name);
207
- }
232
+ syncEnabledPlatforms(type, name, item.platforms);
208
233
  } else {
209
234
  // Remove from all platforms
210
- syncManager.removeFromClaude(type, name);
211
- syncManager.removeFromCodex(type, name);
212
- syncManager.removeFromGemini(type, name);
213
- syncManager.removeFromOpenCode(type, name);
235
+ removeAllPlatforms(type, name);
214
236
  }
215
237
 
216
238
  res.json({
@@ -279,26 +301,10 @@ router.put('/:type/:name/platform/:platform', async (req, res) => {
279
301
  // Apply sync based on new state
280
302
  if (enabled && item.enabled) {
281
303
  // Sync to this platform (only if item is enabled)
282
- if (platform === 'claude') {
283
- syncManager.syncToClaude(type, name);
284
- } else if (platform === 'codex') {
285
- syncManager.syncToCodex(type, name);
286
- } else if (platform === 'gemini') {
287
- syncManager.syncToGemini(type, name);
288
- } else if (platform === 'opencode') {
289
- syncManager.syncToOpenCode(type, name);
290
- }
304
+ syncPlatform(type, name, platform);
291
305
  } else {
292
306
  // Remove from this platform
293
- if (platform === 'claude') {
294
- syncManager.removeFromClaude(type, name);
295
- } else if (platform === 'codex') {
296
- syncManager.removeFromCodex(type, name);
297
- } else if (platform === 'gemini') {
298
- syncManager.removeFromGemini(type, name);
299
- } else if (platform === 'opencode') {
300
- syncManager.removeFromOpenCode(type, name);
301
- }
307
+ removePlatform(type, name, platform);
302
308
  }
303
309
 
304
310
  res.json({
@@ -330,6 +330,7 @@ router.post('/default-models', (req, res) => {
330
330
  // Save config
331
331
  const newConfig = {
332
332
  ...config,
333
+ projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
333
334
  defaultModels: newDefaultModels
334
335
  };
335
336
 
@@ -377,6 +378,7 @@ router.post('/default-models/reset', (req, res) => {
377
378
  // Save config
378
379
  const newConfig = {
379
380
  ...config,
381
+ projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
380
382
  defaultModels: newDefaultModels
381
383
  };
382
384
 
@@ -406,7 +408,8 @@ router.get('/advanced', (req, res) => {
406
408
  proxy: config.ports?.proxy || 20088,
407
409
  codexProxy: config.ports?.codexProxy || 20089,
408
410
  geminiProxy: config.ports?.geminiProxy || 20090,
409
- opencodeProxy: config.ports?.opencodeProxy || 20091
411
+ opencodeProxy: config.ports?.opencodeProxy || 20091,
412
+ piProxy: config.ports?.piProxy || 20092
410
413
  },
411
414
  maxLogs: config.maxLogs || 100,
412
415
  statsInterval: config.statsInterval || 30,
@@ -487,6 +490,7 @@ router.post('/advanced', (req, res) => {
487
490
  // 更新配置
488
491
  const newConfig = {
489
492
  ...config,
493
+ projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
490
494
  ports: normalizedPorts,
491
495
  maxLogs: maxLogs !== undefined ? parseInt(maxLogs) : config.maxLogs,
492
496
  statsInterval: statsInterval !== undefined ? parseInt(statsInterval) : config.statsInterval,
@@ -10,21 +10,25 @@ const { getProxyStatus } = require('../proxy-server');
10
10
  const { getCodexProxyStatus } = require('../codex-proxy-server');
11
11
  const { getGeminiProxyStatus } = require('../gemini-proxy-server');
12
12
  const { getOpenCodeProxyStatus } = require('../opencode-proxy-server');
13
+ const { getPiProxyStatus } = require('../pi-proxy-server');
13
14
  const { getProjectAndSessionCounts: getClaudeCounts } = require('../services/sessions');
14
15
  const { getProjectAndSessionCounts: getCodexCounts } = require('../services/codex-sessions');
15
16
  const { getProjectAndSessionCounts: getGeminiCounts } = require('../services/gemini-sessions');
16
17
  const { getProjectAndSessionCounts: getOpenCodeCounts } = require('../services/opencode-sessions');
18
+ const { getProjectAndSessionCounts: getPiCounts } = require('../services/pi-sessions');
17
19
 
18
20
  // Channel-specific services
19
21
  const { getChannels: getCodexChannels } = require('../services/codex-channels');
20
22
  const { getChannels: getGeminiChannels } = require('../services/gemini-channels');
21
23
  const { getChannels: getOpenCodeChannels } = require('../services/opencode-channels');
24
+ const { getChannels: getPiChannels } = require('../services/pi-channels');
22
25
 
23
26
  // Statistics
24
27
  const { getTodayStatistics: getClaudeTodayStatistics } = require('../services/claude-statistics-service');
25
28
  const { getTodayStatistics: getCodexTodayStatistics } = require('../services/codex-statistics-service');
26
29
  const { getTodayStatistics: getGeminiTodayStatistics } = require('../services/gemini-statistics-service');
27
30
  const { getTodayStatistics: getOpenCodeTodayStatistics } = require('../services/opencode-statistics-service');
31
+ const { getTodayStatistics: getPiTodayStatistics } = require('../services/pi-statistics-service');
28
32
 
29
33
  /**
30
34
  * GET /api/dashboard/init
@@ -42,18 +46,22 @@ router.get('/init', async (req, res) => {
42
46
  codexChannels,
43
47
  geminiChannels,
44
48
  opencodeChannels,
49
+ piChannels,
45
50
  claudeProxyStatus,
46
51
  codexProxyStatus,
47
52
  geminiProxyStatus,
48
53
  opencodeProxyStatus,
54
+ piProxyStatus,
49
55
  claudeTodayStats,
50
56
  codexTodayStats,
51
57
  geminiTodayStats,
52
58
  opencodeTodayStats,
59
+ piTodayStats,
53
60
  claudeCounts,
54
61
  codexCounts,
55
62
  geminiCounts,
56
- opencodeCounts
63
+ opencodeCounts,
64
+ piCounts
57
65
  ] = await Promise.all([
58
66
  // UI Config
59
67
  Promise.resolve(loadUIConfig()),
@@ -66,24 +74,28 @@ router.get('/init', async (req, res) => {
66
74
  Promise.resolve(getCodexChannels()),
67
75
  Promise.resolve(getGeminiChannels()),
68
76
  Promise.resolve(getOpenCodeChannels()),
77
+ Promise.resolve(getPiChannels()),
69
78
 
70
79
  // Proxy Status
71
80
  Promise.resolve(getProxyStatus()),
72
81
  Promise.resolve(getCodexProxyStatus()),
73
82
  Promise.resolve(getGeminiProxyStatus()),
74
83
  Promise.resolve(getOpenCodeProxyStatus()),
84
+ Promise.resolve(getPiProxyStatus()),
75
85
 
76
86
  // Today Stats (所有平台)
77
87
  Promise.resolve(getClaudeTodayStatistics()),
78
88
  Promise.resolve(getCodexTodayStatistics()),
79
89
  Promise.resolve(getGeminiTodayStatistics()),
80
90
  Promise.resolve(getOpenCodeTodayStatistics()),
91
+ Promise.resolve(getPiTodayStatistics()),
81
92
 
82
93
  // 轻量级统计
83
94
  Promise.resolve(getClaudeCounts(config)),
84
95
  Promise.resolve(getCodexCounts()),
85
96
  Promise.resolve(getGeminiCounts()),
86
- Promise.resolve(getOpenCodeCounts())
97
+ Promise.resolve(getOpenCodeCounts()),
98
+ Promise.resolve(getPiCounts())
87
99
  ]);
88
100
 
89
101
  // 格式化统计数据:取 summary 和 byModel 中的数据
@@ -108,25 +120,29 @@ router.get('/init', async (req, res) => {
108
120
  claude: claudeChannels,
109
121
  codex: codexChannels,
110
122
  gemini: geminiChannels,
111
- opencode: opencodeChannels.channels || []
123
+ opencode: opencodeChannels.channels || [],
124
+ pi: piChannels.channels || []
112
125
  },
113
126
  proxyStatus: {
114
127
  claude: claudeProxyStatus,
115
128
  codex: codexProxyStatus,
116
129
  gemini: geminiProxyStatus,
117
- opencode: opencodeProxyStatus
130
+ opencode: opencodeProxyStatus,
131
+ pi: piProxyStatus
118
132
  },
119
133
  counts: {
120
134
  claude: claudeCounts || { projectCount: 0, sessionCount: 0 },
121
135
  codex: codexCounts || { projectCount: 0, sessionCount: 0 },
122
136
  gemini: geminiCounts || { projectCount: 0, sessionCount: 0 },
123
- opencode: opencodeCounts || { projectCount: 0, sessionCount: 0 }
137
+ opencode: opencodeCounts || { projectCount: 0, sessionCount: 0 },
138
+ pi: piCounts || { projectCount: 0, sessionCount: 0 }
124
139
  },
125
140
  todayStats: {
126
141
  claude: formatStats(claudeTodayStats),
127
142
  codex: formatStats(codexTodayStats),
128
143
  gemini: formatStats(geminiTodayStats),
129
- opencode: formatStats(opencodeTodayStats)
144
+ opencode: formatStats(opencodeTodayStats),
145
+ pi: formatStats(piTodayStats)
130
146
  }
131
147
  }
132
148
  });
@@ -5,6 +5,8 @@
5
5
  const express = require('express');
6
6
  const router = express.Router();
7
7
  const mcpService = require('../services/mcp-service');
8
+ const MCP_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
9
+ const MCP_EXPORT_FORMATS = ['json', 'claude', 'codex', 'gemini', 'opencode', 'pi'];
8
10
 
9
11
  /**
10
12
  * GET /api/mcp/servers
@@ -177,7 +179,7 @@ router.post('/import/:platform', async (req, res) => {
177
179
  try {
178
180
  const { platform } = req.params;
179
181
 
180
- if (!['claude', 'codex', 'gemini', 'opencode'].includes(platform)) {
182
+ if (!MCP_PLATFORMS.includes(platform)) {
181
183
  return res.status(400).json({
182
184
  success: false,
183
185
  error: `无效的平台: ${platform}`
@@ -285,7 +287,7 @@ router.get('/export', (req, res) => {
285
287
  try {
286
288
  const format = req.query.format || 'json';
287
289
 
288
- if (!['json', 'claude', 'codex', 'opencode'].includes(format)) {
290
+ if (!MCP_EXPORT_FORMATS.includes(format)) {
289
291
  return res.status(400).json({
290
292
  success: false,
291
293
  error: `无效的导出格式: ${format}`
@@ -314,7 +316,7 @@ router.get('/export/download', (req, res) => {
314
316
  try {
315
317
  const format = req.query.format || 'json';
316
318
 
317
- if (!['json', 'claude', 'codex', 'opencode'].includes(format)) {
319
+ if (!MCP_EXPORT_FORMATS.includes(format)) {
318
320
  return res.status(400).json({
319
321
  success: false,
320
322
  error: `无效的导出格式: ${format}`