coding-tool-x 3.5.7 → 3.5.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.
Files changed (57) hide show
  1. package/dist/web/assets/{Analytics-C6DEmD3D.js → Analytics-BzoNzfbi.js} +2 -2
  2. package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
  3. package/dist/web/assets/{ConfigTemplates-Cf_iTpC4.js → ConfigTemplates-O4ikBt1o.js} +1 -1
  4. package/dist/web/assets/{Home-BtBmYLJ1.js → Home-BQjsnblU.js} +1 -1
  5. package/dist/web/assets/Home-qzk118Of.css +1 -0
  6. package/dist/web/assets/{PluginManager-DEk8vSw5.js → PluginManager-DS_DJnVc.js} +1 -1
  7. package/dist/web/assets/ProjectList-CqYDtsHx.js +1 -0
  8. package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
  9. package/dist/web/assets/SessionList-CfPtcq6Y.css +1 -0
  10. package/dist/web/assets/SessionList-DMlLtMCz.js +1 -0
  11. package/dist/web/assets/{SkillManager-DcZOiiSf.js → SkillManager-DpNE02r0.js} +1 -1
  12. package/dist/web/assets/{WorkspaceManager-BHqI8aGV.js → WorkspaceManager-DMY7_SHh.js} +1 -1
  13. package/dist/web/assets/icons-CEq2hYB-.js +1 -0
  14. package/dist/web/assets/index-Clf0l3wc.js +2 -0
  15. package/dist/web/assets/index-Dih_bOsv.css +1 -0
  16. package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
  17. package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
  18. package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
  19. package/dist/web/index.html +7 -7
  20. package/package.json +1 -1
  21. package/src/commands/export-config.js +6 -6
  22. package/src/config/default.js +2 -6
  23. package/src/config/loader.js +2 -2
  24. package/src/config/paths.js +160 -33
  25. package/src/server/api/agents.js +52 -2
  26. package/src/server/api/commands.js +38 -2
  27. package/src/server/api/plugins.js +104 -1
  28. package/src/server/api/sessions.js +5 -5
  29. package/src/server/services/agents-service.js +269 -62
  30. package/src/server/services/commands-service.js +281 -81
  31. package/src/server/services/config-export-service.js +7 -7
  32. package/src/server/services/config-registry-service.js +4 -5
  33. package/src/server/services/config-sync-manager.js +61 -41
  34. package/src/server/services/config-sync-service.js +3 -3
  35. package/src/server/services/gemini-channels.js +5 -5
  36. package/src/server/services/gemini-config.js +3 -4
  37. package/src/server/services/gemini-sessions.js +23 -20
  38. package/src/server/services/gemini-settings-manager.js +2 -3
  39. package/src/server/services/mcp-service.js +9 -14
  40. package/src/server/services/native-oauth-adapters.js +3 -3
  41. package/src/server/services/notification-hooks.js +3 -3
  42. package/src/server/services/opencode-sessions.js +4 -4
  43. package/src/server/services/opencode-settings-manager.js +3 -3
  44. package/src/server/services/plugins-service.js +499 -23
  45. package/src/server/services/prompts-service.js +5 -9
  46. package/src/server/services/sessions.js +2 -2
  47. package/src/server/services/skill-service.js +155 -18
  48. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  49. package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
  50. package/dist/web/assets/ProjectList-BMVhA_Kh.js +0 -1
  51. package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
  52. package/dist/web/assets/SessionList-B5ioAXxg.js +0 -1
  53. package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
  54. package/dist/web/assets/icons-CQuif85v.js +0 -1
  55. package/dist/web/assets/index-CtByKdkA.js +0 -2
  56. package/dist/web/assets/index-VGAxnLqi.css +0 -1
  57. package/dist/web/assets/vue-vendor-aWwwFAao.js +0 -45
@@ -28,6 +28,9 @@ const REPOS_SKILLS_DIR = path.join(REPOS_DIR, 'skills');
28
28
  const REPOS_PLUGINS_DIR = path.join(REPOS_DIR, 'plugins');
29
29
  const LOCAL_DIR = path.join(STORAGE_DIR, 'local');
30
30
  const LOCAL_SKILLS_DIR = path.join(LOCAL_DIR, 'skills');
31
+ const LOCAL_COMMANDS_DIR = path.join(LOCAL_DIR, 'commands');
32
+ const LOCAL_AGENTS_DIR = path.join(LOCAL_DIR, 'agents');
33
+ const LOCAL_PLUGINS_DIR = path.join(LOCAL_DIR, 'plugins');
31
34
  const REQUESTS_DIR = path.join(STORAGE_DIR, 'requests');
32
35
  const BACKUPS_DIR = path.join(STORAGE_DIR, 'backups');
33
36
  const SCRIPTS_DIR = path.join(STORAGE_DIR, 'scripts');
@@ -39,8 +42,8 @@ const LEGACY_STATS_DIR = path.join(LEGACY_DIR, 'stats');
39
42
 
40
43
  // 旧目录(升级时自动合并到 ~/.cc-tool)
41
44
  const LEGACY_BASE_DIRS = [
42
- path.join(HOME_DIR, '.claude', 'ctx'),
43
- path.join(HOME_DIR, '.claude', 'cc-tool')
45
+ joinNativeBasePath(HOME_DIR, '.claude', 'ctx'),
46
+ joinNativeBasePath(HOME_DIR, '.claude', 'cc-tool')
44
47
  ];
45
48
 
46
49
  let migrationChecked = false;
@@ -464,6 +467,19 @@ const PATHS = {
464
467
  gemini: path.join(LOCAL_SKILLS_DIR, 'gemini'),
465
468
  opencode: path.join(LOCAL_SKILLS_DIR, 'opencode')
466
469
  },
470
+ localCommands: {
471
+ claude: path.join(LOCAL_COMMANDS_DIR, 'claude'),
472
+ opencode: path.join(LOCAL_COMMANDS_DIR, 'opencode')
473
+ },
474
+ localAgents: {
475
+ claude: path.join(LOCAL_AGENTS_DIR, 'claude'),
476
+ codex: path.join(LOCAL_AGENTS_DIR, 'codex'),
477
+ opencode: path.join(LOCAL_AGENTS_DIR, 'opencode')
478
+ },
479
+ localPlugins: {
480
+ claude: path.join(LOCAL_PLUGINS_DIR, 'claude'),
481
+ opencode: path.join(LOCAL_PLUGINS_DIR, 'opencode')
482
+ },
467
483
  skillRepos: {
468
484
  claude: path.join(REPOS_SKILLS_DIR, 'claude.json'),
469
485
  codex: path.join(REPOS_SKILLS_DIR, 'codex.json'),
@@ -486,7 +502,7 @@ const PATHS = {
486
502
  },
487
503
 
488
504
  // 原生路径兼容
489
- skills: path.join(HOME_DIR, '.claude', 'skills'),
505
+ skills: getNativePlatformSkillsDir('claude'),
490
506
 
491
507
  // 旧版本遗留文件搬迁目标
492
508
  legacy: {
@@ -715,15 +731,15 @@ function pickExistingDir(candidates, fallback) {
715
731
  }
716
732
 
717
733
  function getClaudeConfigDir() {
718
- return resolveExistingEnvPath(process.env.CLAUDE_CONFIG_DIR) || path.join(HOME_DIR, '.claude');
734
+ return resolveExistingEnvPath(process.env.CLAUDE_CONFIG_DIR) || joinNativeBasePath(HOME_DIR, '.claude');
719
735
  }
720
736
 
721
737
  function getCodexDir() {
722
- return resolveExistingEnvPath(process.env.CODEX_HOME) || path.join(HOME_DIR, '.codex');
738
+ return resolveExistingEnvPath(process.env.CODEX_HOME) || joinNativeBasePath(HOME_DIR, '.codex');
723
739
  }
724
740
 
725
741
  function getGeminiDir() {
726
- return path.join(HOME_DIR, '.gemini');
742
+ return joinNativeBasePath(HOME_DIR, '.gemini');
727
743
  }
728
744
 
729
745
  function getOpenCodeDataDir() {
@@ -762,49 +778,151 @@ function getOpenCodeConfigDir() {
762
778
  return preferredDir;
763
779
  }
764
780
 
781
+ function isWindowsAbsolutePath(value = '') {
782
+ return /^[a-zA-Z]:[\\/]/.test(String(value || ''));
783
+ }
784
+
785
+ function isNativeAbsolutePath(value = '') {
786
+ return path.isAbsolute(String(value || '')) || isWindowsAbsolutePath(value);
787
+ }
788
+
789
+ function joinNativeBasePath(baseDir, ...segments) {
790
+ const base = String(baseDir || '');
791
+ const pathModule = isWindowsAbsolutePath(base) ? path.win32 : path;
792
+ return pathModule.join(base, ...segments);
793
+ }
794
+
795
+ function resolveNativeBasePath(baseDir, ...segments) {
796
+ const base = String(baseDir || '');
797
+ const pathModule = isWindowsAbsolutePath(base) ? path.win32 : path;
798
+ return pathModule.resolve(base, ...segments);
799
+ }
800
+
801
+ function getNativePathDir(filePath = '') {
802
+ const target = String(filePath || '');
803
+ const pathModule = isWindowsAbsolutePath(target) ? path.win32 : path;
804
+ return pathModule.dirname(target);
805
+ }
806
+
807
+ function getNativePlatformDir(platform) {
808
+ switch (platform) {
809
+ case 'claude':
810
+ return getClaudeConfigDir();
811
+ case 'codex':
812
+ return getCodexDir();
813
+ case 'gemini':
814
+ return getGeminiDir();
815
+ case 'opencode':
816
+ return getOpenCodeConfigDir();
817
+ default:
818
+ return '';
819
+ }
820
+ }
821
+
822
+ function getNativePlatformSubPath(platform, ...segments) {
823
+ const baseDir = getNativePlatformDir(platform);
824
+ return baseDir ? joinNativeBasePath(baseDir, ...segments) : '';
825
+ }
826
+
827
+ function getNativePlatformSkillsDir(platform) {
828
+ return getNativePlatformSubPath(platform, 'skills');
829
+ }
830
+
831
+ function getNativePlatformPromptPath(platform) {
832
+ if (!getNativePlatformDir(platform)) {
833
+ return '';
834
+ }
835
+
836
+ switch (platform) {
837
+ case 'claude':
838
+ return getNativePlatformSubPath(platform, 'CLAUDE.md');
839
+ case 'codex':
840
+ return getNativePlatformSubPath(platform, 'AGENTS.md');
841
+ case 'gemini':
842
+ return getNativePlatformSubPath(platform, 'GEMINI.md');
843
+ case 'opencode':
844
+ return getNativePlatformSubPath(platform, 'AGENTS.md');
845
+ default:
846
+ return '';
847
+ }
848
+ }
849
+
850
+ const CLAUDE_NATIVE_DIR = getNativePlatformDir('claude');
851
+ const CODEX_NATIVE_DIR = getNativePlatformDir('codex');
852
+ const GEMINI_NATIVE_DIR = getNativePlatformDir('gemini');
853
+ const OPENCODE_CONFIG_DIR = getNativePlatformDir('opencode');
854
+ const OPENCODE_DATA_DIR = getOpenCodeDataDir();
855
+
765
856
  // 工具特定的原生配置路径(不改变)
766
857
  const NATIVE_PATHS = {
767
858
  // Claude Code 原生配置
768
859
  claude: {
769
- dir: getClaudeConfigDir(),
770
- settings: path.join(getClaudeConfigDir(), 'settings.json'),
771
- settingsBackup: path.join(getClaudeConfigDir(), 'settings.json.cc-tool-backup'),
772
- projects: path.join(getClaudeConfigDir(), 'projects'),
773
- credentials: path.join(getClaudeConfigDir(), '.credentials.json')
860
+ dir: CLAUDE_NATIVE_DIR,
861
+ settings: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'settings.json'),
862
+ settingsBackup: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'settings.json.cc-tool-backup'),
863
+ projects: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'projects'),
864
+ commands: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'commands'),
865
+ agents: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'agents'),
866
+ plugins: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'plugins'),
867
+ installedPlugins: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'plugins', 'installed_plugins.json'),
868
+ pluginMarketplaces: joinNativeBasePath(CLAUDE_NATIVE_DIR, 'plugins', 'known_marketplaces.json'),
869
+ credentials: joinNativeBasePath(CLAUDE_NATIVE_DIR, '.credentials.json'),
870
+ skills: getNativePlatformSkillsDir('claude'),
871
+ prompt: getNativePlatformPromptPath('claude')
774
872
  },
775
873
 
776
874
  // Codex 原生配置
777
875
  codex: {
778
- dir: getCodexDir(),
779
- config: path.join(getCodexDir(), 'config.toml'),
780
- configBackup: path.join(getCodexDir(), 'config.toml.cc-tool-backup'),
781
- auth: path.join(getCodexDir(), 'auth.json'),
782
- authBackup: path.join(getCodexDir(), 'auth.json.cc-tool-backup'),
783
- sessions: path.join(getCodexDir(), 'sessions')
876
+ dir: CODEX_NATIVE_DIR,
877
+ config: joinNativeBasePath(CODEX_NATIVE_DIR, 'config.toml'),
878
+ configBackup: joinNativeBasePath(CODEX_NATIVE_DIR, 'config.toml.cc-tool-backup'),
879
+ auth: joinNativeBasePath(CODEX_NATIVE_DIR, 'auth.json'),
880
+ authBackup: joinNativeBasePath(CODEX_NATIVE_DIR, 'auth.json.cc-tool-backup'),
881
+ sessions: joinNativeBasePath(CODEX_NATIVE_DIR, 'sessions'),
882
+ projects: joinNativeBasePath(CODEX_NATIVE_DIR, 'projects'),
883
+ agents: joinNativeBasePath(CODEX_NATIVE_DIR, 'agents'),
884
+ prompts: joinNativeBasePath(CODEX_NATIVE_DIR, 'prompts'),
885
+ skills: getNativePlatformSkillsDir('codex'),
886
+ prompt: getNativePlatformPromptPath('codex')
784
887
  },
785
888
 
786
889
  // Gemini 原生配置
787
890
  gemini: {
788
- dir: getGeminiDir(),
789
- env: path.join(getGeminiDir(), '.env'),
790
- envBackup: path.join(getGeminiDir(), '.env.cc-tool-backup'),
791
- tmp: path.join(getGeminiDir(), 'tmp'),
792
- settings: path.join(getGeminiDir(), 'settings.json'),
793
- settingsBackup: path.join(getGeminiDir(), 'settings.json.cc-tool-backup'),
794
- googleAccounts: path.join(getGeminiDir(), 'google_accounts.json'),
795
- oauthCredentialsLegacy: path.join(getGeminiDir(), 'oauth_creds.json'),
796
- oauthCredentialsEncrypted: path.join(getGeminiDir(), 'mcp-oauth-tokens-v2.json')
891
+ dir: GEMINI_NATIVE_DIR,
892
+ env: joinNativeBasePath(GEMINI_NATIVE_DIR, '.env'),
893
+ envBackup: joinNativeBasePath(GEMINI_NATIVE_DIR, '.env.cc-tool-backup'),
894
+ tmp: joinNativeBasePath(GEMINI_NATIVE_DIR, 'tmp'),
895
+ settings: joinNativeBasePath(GEMINI_NATIVE_DIR, 'settings.json'),
896
+ settingsBackup: joinNativeBasePath(GEMINI_NATIVE_DIR, 'settings.json.cc-tool-backup'),
897
+ projects: joinNativeBasePath(GEMINI_NATIVE_DIR, 'projects'),
898
+ googleAccounts: joinNativeBasePath(GEMINI_NATIVE_DIR, 'google_accounts.json'),
899
+ oauthCredentialsLegacy: joinNativeBasePath(GEMINI_NATIVE_DIR, 'oauth_creds.json'),
900
+ oauthCredentialsEncrypted: joinNativeBasePath(GEMINI_NATIVE_DIR, 'mcp-oauth-tokens-v2.json'),
901
+ skills: getNativePlatformSkillsDir('gemini'),
902
+ prompt: getNativePlatformPromptPath('gemini')
797
903
  },
798
904
 
799
905
  // OpenCode 原生配置
800
906
  opencode: {
801
- data: getOpenCodeDataDir(),
802
- config: getOpenCodeConfigDir(),
803
- sessions: path.join(getOpenCodeDataDir(), 'storage', 'session'),
804
- projects: path.join(getOpenCodeDataDir(), 'storage', 'project'),
805
- messages: path.join(getOpenCodeDataDir(), 'storage', 'message'),
806
- log: path.join(getOpenCodeDataDir(), 'log'),
807
- auth: path.join(getOpenCodeDataDir(), 'auth.json')
907
+ data: OPENCODE_DATA_DIR,
908
+ config: OPENCODE_CONFIG_DIR,
909
+ sessions: joinNativeBasePath(OPENCODE_DATA_DIR, 'storage', 'session'),
910
+ projects: joinNativeBasePath(OPENCODE_DATA_DIR, 'storage', 'project'),
911
+ messages: joinNativeBasePath(OPENCODE_DATA_DIR, 'storage', 'message'),
912
+ log: joinNativeBasePath(OPENCODE_DATA_DIR, 'log'),
913
+ auth: joinNativeBasePath(OPENCODE_DATA_DIR, 'auth.json'),
914
+ commands: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'commands'),
915
+ commandsLegacy: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'command'),
916
+ agents: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'agents'),
917
+ agentsLegacy: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'agent'),
918
+ plugins: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'plugins'),
919
+ pluginsLegacy: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'plugin'),
920
+ pluginsConfig: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'plugins-config'),
921
+ configJsonc: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
922
+ configJson: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'opencode.json'),
923
+ configLegacy: joinNativeBasePath(OPENCODE_CONFIG_DIR, 'config.json'),
924
+ skills: getNativePlatformSkillsDir('opencode'),
925
+ prompt: getNativePlatformPromptPath('opencode')
808
926
  }
809
927
  };
810
928
 
@@ -823,6 +941,15 @@ module.exports = {
823
941
  getClaudeConfigDir,
824
942
  getCodexDir,
825
943
  getGeminiDir,
944
+ isWindowsAbsolutePath,
945
+ isNativeAbsolutePath,
946
+ joinNativeBasePath,
947
+ resolveNativeBasePath,
948
+ getNativePathDir,
949
+ getNativePlatformDir,
950
+ getNativePlatformSubPath,
951
+ getNativePlatformSkillsDir,
952
+ getNativePlatformPromptPath,
826
953
  getOpenCodeDataDir,
827
954
  getOpenCodeConfigDir
828
955
  };
@@ -228,7 +228,7 @@ router.use((req, res, next) => {
228
228
  router.get('/', (req, res) => {
229
229
  try {
230
230
  const { platform, service } = getAgentsService(req);
231
- const { projectPath } = req.query;
231
+ const { projectPath, refresh } = req.query;
232
232
  const normalizedProjectPath = normalizeOptionalProjectPath(projectPath);
233
233
  if (normalizedProjectPath.error) {
234
234
  return res.status(400).json({
@@ -236,7 +236,10 @@ router.get('/', (req, res) => {
236
236
  message: normalizedProjectPath.error
237
237
  });
238
238
  }
239
- const result = service.listAgents(normalizedProjectPath.projectPath);
239
+ const forceRefresh = refresh === '1';
240
+ const result = service.listAgents(normalizedProjectPath.projectPath, {
241
+ syncManagedLocalAgents: forceRefresh
242
+ });
240
243
 
241
244
  res.json({
242
245
  success: true,
@@ -775,6 +778,53 @@ router.post('/install', async (req, res) => {
775
778
  }
776
779
  });
777
780
 
781
+ /**
782
+ * 安装本地托管代理
783
+ * POST /api/agents/install-local
784
+ * Body: { fileName }
785
+ */
786
+ router.post('/install-local', (req, res) => {
787
+ try {
788
+ const { platform, service } = getAgentsService(req);
789
+ if (isCodexRepoOperationUnsupported(platform)) {
790
+ return res.status(400).json({
791
+ success: false,
792
+ message: 'Codex 平台暂不支持本地托管代理安装'
793
+ });
794
+ }
795
+
796
+ const { fileName } = req.body;
797
+ if (!fileName) {
798
+ return res.status(400).json({
799
+ success: false,
800
+ message: 'Missing fileName'
801
+ });
802
+ }
803
+
804
+ const fileNameError = validateAgentFileName(fileName);
805
+ if (fileNameError) {
806
+ return res.status(400).json({
807
+ success: false,
808
+ message: fileNameError
809
+ });
810
+ }
811
+
812
+ const result = service.installLocalAgent(fileName);
813
+
814
+ res.json({
815
+ success: true,
816
+ platform,
817
+ ...result
818
+ });
819
+ } catch (err) {
820
+ console.error('[Agents API] Install local agent error:', err);
821
+ res.status(500).json({
822
+ success: false,
823
+ message: err.message
824
+ });
825
+ }
826
+ });
827
+
778
828
  /**
779
829
  * 卸载代理
780
830
  * POST /api/agents/uninstall
@@ -35,8 +35,11 @@ function getCommandsService(req) {
35
35
  router.get('/', (req, res) => {
36
36
  try {
37
37
  const { platform, service } = getCommandsService(req);
38
- const { projectPath } = req.query;
39
- const result = service.listCommands(projectPath || null);
38
+ const { projectPath, refresh } = req.query;
39
+ const forceRefresh = refresh === '1';
40
+ const result = service.listCommands(projectPath || null, {
41
+ syncManagedLocalCommands: forceRefresh
42
+ });
40
43
 
41
44
  res.json({
42
45
  success: true,
@@ -446,6 +449,39 @@ router.post('/install', async (req, res) => {
446
449
  }
447
450
  });
448
451
 
452
+ /**
453
+ * 安装本地托管命令
454
+ * POST /api/commands/install-local
455
+ * Body: { path }
456
+ */
457
+ router.post('/install-local', (req, res) => {
458
+ try {
459
+ const { platform, service } = getCommandsService(req);
460
+ const { path } = req.body;
461
+
462
+ if (!path) {
463
+ return res.status(400).json({
464
+ success: false,
465
+ message: 'Missing path'
466
+ });
467
+ }
468
+
469
+ const result = service.installLocalCommand(path);
470
+
471
+ res.json({
472
+ success: true,
473
+ platform,
474
+ ...result
475
+ });
476
+ } catch (err) {
477
+ console.error('[Commands API] Install local command error:', err);
478
+ res.status(500).json({
479
+ success: false,
480
+ message: err.message
481
+ });
482
+ }
483
+ });
484
+
449
485
  /**
450
486
  * 卸载命令
451
487
  * POST /api/commands/uninstall
@@ -88,7 +88,10 @@ function sanitizeRepos(service, repos = []) {
88
88
  router.get('/', (req, res) => {
89
89
  try {
90
90
  const { platform, service } = getPluginsService(req);
91
- const result = service.listPlugins();
91
+ const forceRefresh = req.query.refresh === '1';
92
+ const result = service.listPlugins({
93
+ syncManagedLocalPlugins: forceRefresh
94
+ });
92
95
 
93
96
  res.json({
94
97
  success: true,
@@ -189,6 +192,46 @@ router.post('/install', async (req, res) => {
189
192
  }
190
193
  });
191
194
 
195
+ /**
196
+ * 安装本地托管插件
197
+ * POST /api/plugins/install-local
198
+ * Body: { name }
199
+ */
200
+ router.post('/install-local', (req, res) => {
201
+ try {
202
+ const { platform, service } = getPluginsService(req);
203
+ const { name } = req.body;
204
+
205
+ if (!name) {
206
+ return res.status(400).json({
207
+ success: false,
208
+ message: 'name is required'
209
+ });
210
+ }
211
+
212
+ const result = service.installLocalPlugin(name);
213
+ if (!result.success) {
214
+ return res.status(400).json({
215
+ success: false,
216
+ message: result.error || result.message || 'Install failed'
217
+ });
218
+ }
219
+
220
+ res.json({
221
+ success: true,
222
+ platform,
223
+ plugin: result.plugin,
224
+ message: result.message || `Plugin "${result.plugin?.name || name}" installed successfully`
225
+ });
226
+ } catch (err) {
227
+ console.error('[Plugins API] Install local plugin error:', err);
228
+ res.status(500).json({
229
+ success: false,
230
+ message: err.message
231
+ });
232
+ }
233
+ });
234
+
192
235
  // ==================== 仓库管理 API ====================
193
236
 
194
237
  /**
@@ -440,6 +483,66 @@ router.post('/sync', async (req, res) => {
440
483
  }
441
484
  });
442
485
 
486
+ /**
487
+ * 获取插件文件列表
488
+ * GET /api/plugins/:name/files
489
+ */
490
+ router.get('/:name/files', async (req, res) => {
491
+ try {
492
+ const { platform, service } = getPluginsService(req);
493
+ const { name } = req.params;
494
+ const pluginInfo = extractPluginQueryInfo(name, req.query);
495
+ const result = await service.getPluginFiles(pluginInfo);
496
+
497
+ res.json({
498
+ success: true,
499
+ platform,
500
+ ...result
501
+ });
502
+ } catch (err) {
503
+ console.error('[Plugins API] Get plugin files error:', err);
504
+ res.status(500).json({
505
+ success: false,
506
+ message: err.message,
507
+ files: []
508
+ });
509
+ }
510
+ });
511
+
512
+ /**
513
+ * 获取插件单个文件内容
514
+ * GET /api/plugins/:name/file/*
515
+ */
516
+ router.get('/:name/file/*', async (req, res) => {
517
+ try {
518
+ const { platform, service } = getPluginsService(req);
519
+ const { name } = req.params;
520
+ const filePath = req.params[0];
521
+
522
+ if (!filePath) {
523
+ return res.status(400).json({
524
+ success: false,
525
+ message: '请指定文件路径'
526
+ });
527
+ }
528
+
529
+ const pluginInfo = extractPluginQueryInfo(name, req.query);
530
+ const result = await service.getPluginFileContent(pluginInfo, filePath);
531
+
532
+ res.json({
533
+ success: true,
534
+ platform,
535
+ ...result
536
+ });
537
+ } catch (err) {
538
+ console.error('[Plugins API] Get plugin file content error:', err);
539
+ res.status(500).json({
540
+ success: false,
541
+ message: err.message
542
+ });
543
+ }
544
+ });
545
+
443
546
  /**
444
547
  * 获取插件 README
445
548
  * GET /api/plugins/:name/readme
@@ -7,7 +7,7 @@ const { getSessionsForProject, deleteSession, forkSession, saveSessionOrder, par
7
7
  const { loadAliases } = require('../services/alias');
8
8
  const { broadcastLog } = require('../websocket-server');
9
9
  const { buildLaunchCommand } = require('../services/session-launch-command');
10
- const { NATIVE_PATHS } = require('../../config/paths');
10
+ const { NATIVE_PATHS, joinNativeBasePath } = require('../../config/paths');
11
11
  const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
12
12
 
13
13
  module.exports = (config) => {
@@ -171,13 +171,13 @@ module.exports = (config) => {
171
171
  const year = now.getFullYear();
172
172
  const month = String(now.getMonth() + 1).padStart(2, '0');
173
173
  const day = String(now.getDate()).padStart(2, '0');
174
- sessionDir = path.join(NATIVE_PATHS.codex.sessions, String(year), month, day);
175
- sessionFile = path.join(sessionDir, `${newSessionId}.jsonl`);
174
+ sessionDir = joinNativeBasePath(NATIVE_PATHS.codex.sessions, String(year), month, day);
175
+ sessionFile = joinNativeBasePath(sessionDir, `${newSessionId}.jsonl`);
176
176
  } else if (toolType === 'gemini') {
177
177
  // Gemini: ~/.gemini/tmp/{hash}/chats/{sessionId}.json
178
178
  const pathHash = crypto.createHash('sha256').update(fullPath).digest('hex');
179
- sessionDir = path.join(NATIVE_PATHS.gemini.tmp, pathHash, 'chats');
180
- sessionFile = path.join(sessionDir, `${newSessionId}.json`);
179
+ sessionDir = joinNativeBasePath(NATIVE_PATHS.gemini.tmp, pathHash, 'chats');
180
+ sessionFile = joinNativeBasePath(sessionDir, `${newSessionId}.json`);
181
181
  } else {
182
182
  return res.status(400).json({ error: 'Invalid toolType. Must be claude, codex, or gemini' });
183
183
  }