coding-tool-x 3.8.4 → 3.8.5

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 (434) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +86 -15
  3. package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
  4. package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
  5. package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
  6. package/dist/web/assets/Analytics-CyHhR126.css +1 -0
  7. package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
  8. package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
  9. package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
  10. package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
  11. package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
  12. package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
  13. package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
  14. package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
  15. package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
  16. package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
  17. package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
  18. package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
  19. package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
  20. package/dist/web/assets/Divider-D28g1gwj.js +1 -0
  21. package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
  22. package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
  23. package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
  24. package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
  25. package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
  26. package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
  27. package/dist/web/assets/Home-D7CCOFQC.js +1 -0
  28. package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
  29. package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
  30. package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
  31. package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
  32. package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
  33. package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
  34. package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
  35. package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
  36. package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
  37. package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
  38. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
  39. package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
  40. package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
  41. package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
  42. package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
  43. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
  44. package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
  45. package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
  46. package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
  47. package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
  48. package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
  49. package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
  50. package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
  51. package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
  52. package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
  53. package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
  54. package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
  55. package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
  56. package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
  57. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
  58. package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
  59. package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
  60. package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
  61. package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
  62. package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
  63. package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
  64. package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
  65. package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
  66. package/dist/web/assets/client-UDwXBP9i.js +1 -0
  67. package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
  68. package/dist/web/assets/headers-cf-QapUq.js +1 -0
  69. package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
  70. package/dist/web/assets/index-C0EHRym0.css +1 -0
  71. package/dist/web/assets/index-DnEocV0M.js +2 -0
  72. package/dist/web/assets/light-Cp0t7v32.js +1 -0
  73. package/dist/web/assets/markdown-u082bbtj.js +1 -0
  74. package/dist/web/assets/platforms-CBkB45JP.js +1 -0
  75. package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
  76. package/dist/web/assets/project-config-B2baPzYL.js +1 -0
  77. package/dist/web/assets/sessions-Cmy31slD.js +1 -0
  78. package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
  79. package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
  80. package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
  81. package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
  82. package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
  83. package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
  84. package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
  85. package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
  86. package/dist/web/assets/xml-CgY3RMlX.js +1 -0
  87. package/dist/web/index.html +12 -8
  88. package/docs/native-log-memory-2026-09-16.md +43 -0
  89. package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
  90. package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
  91. package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
  92. package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
  93. package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
  94. package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
  95. package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
  96. package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
  97. package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
  98. package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
  99. package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
  100. package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
  101. package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
  102. package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
  103. package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
  104. package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
  105. package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
  106. package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
  107. package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
  108. package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
  109. package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
  110. package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
  111. package/package.json +8 -4
  112. package/src/commands/channels.js +88 -191
  113. package/src/commands/cli-type.js +19 -11
  114. package/src/commands/daemon.js +94 -32
  115. package/src/commands/doctor.js +16 -9
  116. package/src/commands/export-config.js +23 -5
  117. package/src/commands/list.js +130 -52
  118. package/src/commands/logs.js +44 -27
  119. package/src/commands/platform-command-registry.js +78 -0
  120. package/src/commands/plugin.js +11 -5
  121. package/src/commands/port-config.js +34 -52
  122. package/src/commands/proxy-control.js +207 -145
  123. package/src/commands/proxy.js +2 -2
  124. package/src/commands/resume.js +87 -91
  125. package/src/commands/search.js +137 -67
  126. package/src/commands/stats.js +30 -22
  127. package/src/commands/switch.js +1 -1
  128. package/src/commands/toggle-proxy.js +94 -111
  129. package/src/commands/ui.js +67 -6
  130. package/src/commands/workspace.js +90 -8
  131. package/src/config/default.js +6 -53
  132. package/src/config/loader.js +61 -24
  133. package/src/config/model-metadata.js +49 -59
  134. package/src/config/model-metadata.json +4095 -99
  135. package/src/config/paths.js +181 -8
  136. package/src/index.js +134 -71
  137. package/src/platforms/access.js +196 -0
  138. package/src/{server/services → platforms}/agents-service.js +354 -197
  139. package/src/platforms/channel-auth-registry.js +162 -0
  140. package/src/platforms/channel-auth-service.js +82 -0
  141. package/src/{server/services → platforms}/commands-service.js +195 -115
  142. package/src/platforms/driver-registry.js +59 -0
  143. package/src/platforms/drivers/claude/api-operations.js +62 -0
  144. package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
  145. package/src/platforms/drivers/claude/channels.js +39 -0
  146. package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
  147. package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
  148. package/src/platforms/drivers/claude/health.js +22 -0
  149. package/src/platforms/drivers/claude/mcp.js +9 -0
  150. package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
  151. package/src/platforms/drivers/claude/native-config.js +46 -0
  152. package/src/platforms/drivers/claude/native-logs.js +72 -0
  153. package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
  154. package/src/platforms/drivers/claude/project-config.js +21 -0
  155. package/src/platforms/drivers/claude/projects.js +62 -0
  156. package/src/platforms/drivers/claude/prompts.js +9 -0
  157. package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
  158. package/src/platforms/drivers/claude/proxy.js +16 -0
  159. package/src/platforms/drivers/claude/resource-sync.js +9 -0
  160. package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
  161. package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
  162. package/src/platforms/drivers/claude/sessions.js +76 -0
  163. package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
  164. package/src/platforms/drivers/claude/statistics.js +16 -0
  165. package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
  166. package/src/platforms/drivers/codex/api-operations.js +9 -0
  167. package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
  168. package/src/platforms/drivers/codex/channels.js +45 -0
  169. package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
  170. package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
  171. package/src/platforms/drivers/codex/mcp.js +9 -0
  172. package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
  173. package/src/platforms/drivers/codex/native-config.js +47 -0
  174. package/src/platforms/drivers/codex/native-logs.js +166 -0
  175. package/src/platforms/drivers/codex/project-config.js +21 -0
  176. package/src/platforms/drivers/codex/projects.js +26 -0
  177. package/src/platforms/drivers/codex/prompts.js +9 -0
  178. package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
  179. package/src/platforms/drivers/codex/proxy.js +16 -0
  180. package/src/platforms/drivers/codex/resource-sync.js +9 -0
  181. package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
  182. package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
  183. package/src/platforms/drivers/codex/sessions.js +36 -0
  184. package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
  185. package/src/platforms/drivers/codex/statistics.js +16 -0
  186. package/src/platforms/drivers/gemini/api-operations.js +9 -0
  187. package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
  188. package/src/platforms/drivers/gemini/channels.js +39 -0
  189. package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
  190. package/src/platforms/drivers/gemini/mcp.js +9 -0
  191. package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
  192. package/src/platforms/drivers/gemini/native-config.js +39 -0
  193. package/src/platforms/drivers/gemini/native-logs.js +180 -0
  194. package/src/platforms/drivers/gemini/project-config.js +21 -0
  195. package/src/platforms/drivers/gemini/projects.js +26 -0
  196. package/src/platforms/drivers/gemini/prompts.js +9 -0
  197. package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
  198. package/src/platforms/drivers/gemini/proxy.js +16 -0
  199. package/src/platforms/drivers/gemini/resource-sync.js +9 -0
  200. package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
  201. package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
  202. package/src/platforms/drivers/gemini/sessions.js +47 -0
  203. package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
  204. package/src/platforms/drivers/gemini/statistics.js +16 -0
  205. package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
  206. package/src/platforms/drivers/generic-filesystem.js +166 -0
  207. package/src/platforms/drivers/generic-jsonl.js +141 -0
  208. package/src/platforms/drivers/generic-mcp.js +44 -0
  209. package/src/platforms/drivers/generic-openai-compatible.js +45 -0
  210. package/src/platforms/drivers/generic-prompt.js +41 -0
  211. package/src/platforms/drivers/legacy.js +677 -0
  212. package/src/platforms/drivers/native-config-snapshot.js +125 -0
  213. package/src/platforms/drivers/native-log-utils.js +1292 -0
  214. package/src/platforms/drivers/notification-hooks.js +578 -0
  215. package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
  216. package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
  217. package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
  218. package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
  219. package/src/platforms/drivers/omp/channels.js +58 -0
  220. package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
  221. package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
  222. package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
  223. package/src/platforms/drivers/omp/mcp.js +9 -0
  224. package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
  225. package/src/platforms/drivers/omp/native-config.js +46 -0
  226. package/src/platforms/drivers/omp/native-logs.js +52 -0
  227. package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
  228. package/src/platforms/drivers/omp/project-config.js +29 -0
  229. package/src/platforms/drivers/omp/projects.js +20 -0
  230. package/src/platforms/drivers/omp/prompts.js +9 -0
  231. package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
  232. package/src/platforms/drivers/omp/proxy.js +16 -0
  233. package/src/platforms/drivers/omp/resource-sync.js +9 -0
  234. package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
  235. package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
  236. package/src/platforms/drivers/omp/sessions.js +36 -0
  237. package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
  238. package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
  239. package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
  240. package/src/platforms/drivers/omp/statistics.js +16 -0
  241. package/src/platforms/drivers/opencode/api-operations.js +9 -0
  242. package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
  243. package/src/platforms/drivers/opencode/channels.js +40 -0
  244. package/src/platforms/drivers/opencode/conversion.js +41 -0
  245. package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
  246. package/src/platforms/drivers/opencode/mcp.js +9 -0
  247. package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
  248. package/src/platforms/drivers/opencode/native-config.js +26 -0
  249. package/src/platforms/drivers/opencode/native-logs.js +133 -0
  250. package/src/platforms/drivers/opencode/project-config.js +98 -0
  251. package/src/platforms/drivers/opencode/projects.js +20 -0
  252. package/src/platforms/drivers/opencode/prompts.js +9 -0
  253. package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
  254. package/src/platforms/drivers/opencode/proxy.js +16 -0
  255. package/src/platforms/drivers/opencode/resource-sync.js +9 -0
  256. package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
  257. package/src/platforms/drivers/opencode/sessions.js +48 -0
  258. package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
  259. package/src/platforms/drivers/opencode/statistics.js +16 -0
  260. package/src/platforms/drivers/secure-file-driver.js +272 -0
  261. package/src/platforms/drivers/unsupported.js +7 -0
  262. package/src/platforms/manifest-schema.js +398 -0
  263. package/src/platforms/manifests/claude.json +489 -0
  264. package/src/platforms/manifests/codex.json +448 -0
  265. package/src/platforms/manifests/gemini.json +447 -0
  266. package/src/platforms/manifests/omp.json +496 -0
  267. package/src/platforms/manifests/opencode.json +422 -0
  268. package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
  269. package/src/{server/services → platforms}/notification-hooks.js +140 -1023
  270. package/src/platforms/oauth-credentials-service.js +506 -0
  271. package/src/platforms/path-resolver.js +179 -0
  272. package/src/platforms/platform-path-context.js +305 -0
  273. package/src/platforms/registry.js +248 -0
  274. package/src/platforms/remote-notification-providers.js +678 -0
  275. package/src/platforms/runtime.js +259 -0
  276. package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
  277. package/src/plugins/plugin-loader.js +2 -2
  278. package/src/plugins/plugin-manager.js +2 -2
  279. package/src/plugins/registry.js +27 -10
  280. package/src/reset-config.js +21 -5
  281. package/src/server/api/agents.js +46 -72
  282. package/src/server/api/commands.js +16 -6
  283. package/src/server/api/config-registry.js +206 -59
  284. package/src/server/api/config-sync.js +3 -2
  285. package/src/server/api/config-templates.js +20 -4
  286. package/src/server/api/config.js +111 -102
  287. package/src/server/api/convert.js +54 -178
  288. package/src/server/api/dashboard.js +168 -102
  289. package/src/server/api/hooks.js +1 -1
  290. package/src/server/api/mcp.js +224 -84
  291. package/src/server/api/platform-api-config.js +102 -0
  292. package/src/server/api/platform-route-factory.js +215 -0
  293. package/src/server/api/platform-routes.js +26 -0
  294. package/src/server/api/platforms.js +110 -0
  295. package/src/server/api/plugins.js +57 -57
  296. package/src/server/api/project-config.js +168 -0
  297. package/src/server/api/prompts.js +58 -45
  298. package/src/server/api/settings.js +55 -8
  299. package/src/server/api/skills.js +262 -133
  300. package/src/server/api/ui-config.js +2 -2
  301. package/src/server/api/validation-errors.js +18 -4
  302. package/src/server/api/workspaces.js +47 -12
  303. package/src/server/index.js +164 -213
  304. package/src/server/platform-context.js +81 -0
  305. package/src/server/services/channel-balance.js +11 -20
  306. package/src/server/services/channel-health.js +21 -6
  307. package/src/server/services/channel-scheduler.js +29 -39
  308. package/src/server/services/channel-sync-utils.js +23 -13
  309. package/src/server/services/config-export-service.js +350 -319
  310. package/src/server/services/config-registry-service.js +280 -54
  311. package/src/server/services/config-sync-manager.js +172 -944
  312. package/src/server/services/config-templates-service.js +419 -353
  313. package/src/server/services/control-manifest-store.js +206 -0
  314. package/src/server/services/dashboard-snapshot-worker.js +656 -100
  315. package/src/server/services/effective-control-service.js +557 -0
  316. package/src/server/services/local-resource-index.js +314 -0
  317. package/src/server/services/mcp-service.js +607 -977
  318. package/src/server/services/model-detector.js +15 -10
  319. package/src/server/services/native-log-channel-resolver.js +214 -0
  320. package/src/server/services/native-log-observer.js +291 -0
  321. package/src/server/services/network-access.js +1 -1
  322. package/src/server/services/platform-catalog.js +82 -0
  323. package/src/server/services/platform-resolution.js +32 -29
  324. package/src/server/services/plugins-service.js +376 -122
  325. package/src/server/services/project-config-service.js +550 -0
  326. package/src/server/services/project-path-validation.js +31 -0
  327. package/src/server/services/prompts-service.js +267 -233
  328. package/src/server/services/proxy-runtime.js +7 -4
  329. package/src/server/services/remote-credential-cache.js +43 -0
  330. package/src/server/services/repo-scanner-base.js +182 -75
  331. package/src/server/services/request-logger.js +7 -4
  332. package/src/server/services/session-history-index.js +780 -213
  333. package/src/server/services/session-history-worker.js +228 -33
  334. package/src/server/services/session-snapshot-worker.js +124 -34
  335. package/src/server/services/session-snapshots.js +4 -2
  336. package/src/server/services/skill-artifact-store.js +355 -0
  337. package/src/server/services/skill-format-adapters.js +65 -0
  338. package/src/server/services/skill-projection-service.js +323 -0
  339. package/src/server/services/skill-refresh-task-service.js +275 -0
  340. package/src/server/services/skill-service.js +2020 -677
  341. package/src/server/services/snapshot-cache.js +30 -8
  342. package/src/server/services/speed-test.js +8 -8
  343. package/src/server/services/sqlite-connection.js +19 -3
  344. package/src/server/services/statistics-service.js +240 -57
  345. package/src/server/services/ui-config.js +107 -76
  346. package/src/server/services/usage-log-utils.js +98 -0
  347. package/src/server/services/web-build.js +1 -1
  348. package/src/server/services/workspace-service.js +59 -29
  349. package/src/server/utils/pricing.js +10 -19
  350. package/src/server/websocket-server.js +153 -28
  351. package/src/{server/services/base → shared}/base-channel-service.js +156 -28
  352. package/src/shared/capability-driver.js +104 -0
  353. package/src/shared/channel-auth-policy.js +27 -0
  354. package/src/shared/channel-driver.js +161 -0
  355. package/src/shared/driver-factories/api.js +180 -0
  356. package/src/shared/driver-factories/mcp.js +244 -0
  357. package/src/shared/driver-factories/projects.js +121 -0
  358. package/src/shared/driver-factories/prompts.js +58 -0
  359. package/src/shared/driver-factories/resource-sync.js +375 -0
  360. package/src/shared/driver-result.js +78 -0
  361. package/src/shared/mcp-format.js +189 -0
  362. package/src/shared/platforms.js +104 -167
  363. package/src/shared/project-config.js +512 -0
  364. package/src/shared/proxy-driver.js +57 -0
  365. package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
  366. package/src/ui/menu.js +91 -67
  367. package/src/utils/port-helper.js +35 -19
  368. package/src/utils/session.js +1 -1
  369. package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
  370. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  371. package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
  372. package/dist/web/assets/Home-DCEyLzwX.js +0 -1
  373. package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
  374. package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
  375. package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
  376. package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
  377. package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
  378. package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
  379. package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
  380. package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
  381. package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
  382. package/dist/web/assets/icons-nGdSKQn2.js +0 -1
  383. package/dist/web/assets/index-D5hyHPj7.js +0 -2
  384. package/dist/web/assets/index-bJRG3RXH.css +0 -1
  385. package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
  386. package/dist/web/assets/markdown-BfC0goYb.css +0 -10
  387. package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
  388. package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
  389. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
  390. package/src/config/model-pricing.js +0 -35
  391. package/src/server/api/channels.js +0 -439
  392. package/src/server/api/claude-hooks.js +0 -56
  393. package/src/server/api/claude-statistics.js +0 -57
  394. package/src/server/api/codex-channels.js +0 -491
  395. package/src/server/api/codex-projects.js +0 -117
  396. package/src/server/api/codex-proxy.js +0 -293
  397. package/src/server/api/codex-sessions.js +0 -652
  398. package/src/server/api/codex-statistics.js +0 -57
  399. package/src/server/api/gemini-channels.js +0 -392
  400. package/src/server/api/gemini-projects.js +0 -103
  401. package/src/server/api/gemini-proxy.js +0 -231
  402. package/src/server/api/gemini-sessions.js +0 -550
  403. package/src/server/api/gemini-statistics.js +0 -57
  404. package/src/server/api/health-check.js +0 -31
  405. package/src/server/api/oauth-credentials.js +0 -198
  406. package/src/server/api/omp-channels.js +0 -575
  407. package/src/server/api/omp-projects.js +0 -88
  408. package/src/server/api/omp-proxy.js +0 -125
  409. package/src/server/api/omp-sessions.js +0 -386
  410. package/src/server/api/omp-statistics.js +0 -40
  411. package/src/server/api/opencode-channels.js +0 -471
  412. package/src/server/api/opencode-projects.js +0 -110
  413. package/src/server/api/opencode-proxy.js +0 -354
  414. package/src/server/api/opencode-sessions.js +0 -466
  415. package/src/server/api/opencode-statistics.js +0 -57
  416. package/src/server/api/projects.js +0 -120
  417. package/src/server/api/proxy.js +0 -330
  418. package/src/server/api/sessions.js +0 -810
  419. package/src/server/services/oauth-credentials-service.js +0 -945
  420. package/src/server/services/omp-session-log-observer.js +0 -138
  421. package/src/server/services/opencode-gateway-adapters.js +0 -43
  422. package/src/server/services/project-snapshots.js +0 -59
  423. package/src/server/services/proxy-log-helper.js +0 -298
  424. package/src/server/services/session-cache.js +0 -127
  425. package/src/server/services/session-converter.js +0 -582
  426. package/src/server/services/session-history-adapters/claude.js +0 -235
  427. /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
  428. /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
  429. /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
  430. /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
  431. /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
  432. /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
  433. /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
  434. /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ const { readAllNativeOAuth } = require('./native-oauth-adapters');
4
+ const oauthStore = require('./oauth-credentials-service');
5
+ const { getOmpAuthProviderSnapshot } = require('./drivers/omp/auth-providers');
6
+
7
+ function getRegistry() {
8
+ return require('./registry').createPlatformRegistry();
9
+ }
10
+
11
+ function safeRef(value = {}) {
12
+ return {
13
+ credentialId: String(value.credentialId || '').trim(),
14
+ providerId: String(value.providerId || '').trim(),
15
+ accountId: String(value.accountId || '').trim(),
16
+ identityKey: String(value.identityKey || '').trim(),
17
+ accountEmail: String(value.accountEmail || '').trim()
18
+ };
19
+ }
20
+
21
+ function nativeCandidate(adapter, metadata, credential) {
22
+ const ref = safeRef({
23
+ credentialId: credential?.id,
24
+ providerId: metadata.providerId || credential?.providerId,
25
+ accountId: metadata.accountId || credential?.accountId,
26
+ identityKey: metadata.identityKey,
27
+ accountEmail: metadata.accountEmail || credential?.accountEmail
28
+ });
29
+ return {
30
+ id: ref.credentialId || `${adapter}:${ref.accountId || ref.identityKey || ref.accountEmail || ref.providerId}`,
31
+ tool: adapter,
32
+ authMode: 'oauth',
33
+ authRef: ref,
34
+ authSource: 'synced-local',
35
+ authStatus: 'available',
36
+ providerId: ref.providerId,
37
+ accountId: ref.accountId,
38
+ accountEmail: ref.accountEmail,
39
+ expiresAt: credential?.expiresAt || metadata.expiresAt || null,
40
+ lastSyncAt: credential?.updatedAt || new Date().toISOString()
41
+ };
42
+ }
43
+
44
+ function scanNative(adapter) {
45
+ const native = readAllNativeOAuth(adapter) || [];
46
+ const synced = native.length ? oauthStore.syncLocalCredential(adapter) : null;
47
+ const credentials = synced?.credentials || [];
48
+ const candidates = native.map((metadata, index) => nativeCandidate(adapter, metadata, credentials[index]));
49
+ return {
50
+ candidates,
51
+ nativeState: {
52
+ available: candidates.length > 0,
53
+ candidateCount: candidates.length,
54
+ checkedAt: new Date().toISOString()
55
+ },
56
+ warnings: candidates.length ? [] : [`${adapter}: unavailable: no local OAuth credential`]
57
+ };
58
+ }
59
+
60
+ function scanOmp() {
61
+ const snapshot = getOmpAuthProviderSnapshot({ forceRefresh: true, accountCheck: true });
62
+ const hasAccounts = (snapshot.providers || []).some(provider => (
63
+ provider.loggedIn === true && Array.isArray(provider.accounts) && provider.accounts.length > 0
64
+ ));
65
+ let synced = null;
66
+ if (hasAccounts) {
67
+ try {
68
+ synced = oauthStore.syncLocalCredential('omp');
69
+ } catch {
70
+ synced = null;
71
+ }
72
+ }
73
+ const credentials = synced?.credentials || [];
74
+ const candidates = [];
75
+ for (const provider of snapshot.providers || []) {
76
+ if (provider.loggedIn !== true || !Array.isArray(provider.accounts) || provider.accounts.length === 0) continue;
77
+ for (const account of provider.accounts) {
78
+ const accountId = account.id || account.accountId || account.index;
79
+ const accountEmail = account.email || account.accountEmail || account.identity;
80
+ const identityKey = account.identityKey || accountEmail || accountId;
81
+ const credential = credentials.find(item => (
82
+ item.providerId === provider.id
83
+ && (item.accountId === String(accountId || '') || item.accountEmail === String(accountEmail || ''))
84
+ ));
85
+ const ref = safeRef({
86
+ credentialId: credential?.id,
87
+ providerId: provider.id,
88
+ accountId,
89
+ identityKey,
90
+ accountEmail
91
+ });
92
+ if (!ref.accountId && !ref.identityKey && (provider.accounts || []).length > 1) continue;
93
+ candidates.push({
94
+ id: credential?.id || `omp:${provider.id}:${ref.accountId || ref.identityKey || 'account'}`,
95
+ tool: 'omp',
96
+ authMode: 'oauth',
97
+ authRef: ref,
98
+ authSource: 'synced-local',
99
+ authStatus: ref.accountId || ref.identityKey ? 'available' : 'ambiguous',
100
+ oauthProviderId: provider.id,
101
+ providerId: provider.id,
102
+ accountId: ref.accountId,
103
+ accountEmail: ref.accountEmail
104
+ });
105
+ }
106
+ }
107
+ return {
108
+ candidates,
109
+ nativeState: { available: snapshot.available === true, providers: snapshot.providers || [], checkedAt: snapshot.checkedAt },
110
+ warnings: candidates.length ? [] : [snapshot.reason || 'omp: unavailable: no logged-in local provider']
111
+ };
112
+ }
113
+
114
+ const adapters = Object.freeze({
115
+ claude: {
116
+ scan: () => scanNative('claude'),
117
+ quota: (ref) => oauthStore.fetchCredentialUsage('claude', ref.credentialId),
118
+ channelServicePath: './drivers/claude/channels-implementation'
119
+ },
120
+ codex: {
121
+ scan: () => scanNative('codex'),
122
+ quota: (ref) => oauthStore.fetchCredentialUsage('codex', ref.credentialId),
123
+ channelServicePath: './drivers/codex/channels-implementation'
124
+ },
125
+ gemini: {
126
+ scan: () => scanNative('gemini'),
127
+ quota: (ref) => oauthStore.fetchCredentialUsage('gemini', ref.credentialId),
128
+ channelServicePath: './drivers/gemini/channels-implementation'
129
+ },
130
+ omp: {
131
+ scan: scanOmp,
132
+ quota: async () => ({ status: 'unavailable', error: 'OMP native OAuth quota is unavailable' }),
133
+ channelServicePath: './drivers/omp/channels-implementation'
134
+ }
135
+ });
136
+
137
+ function getOAuthManifests() {
138
+ return getRegistry().list({ enabledOnly: false }).filter(manifest => (
139
+ manifest.auth?.oauth?.adapter && adapters[manifest.auth.oauth.adapter]
140
+ ));
141
+ }
142
+ function getChannelAuthAdapter(platform) {
143
+ const key = String(platform || '').trim().toLowerCase();
144
+ const manifest = getOAuthManifests().find(item => item.key === key);
145
+ const adapterId = manifest?.auth?.oauth?.adapter;
146
+ const adapter = adapterId ? adapters[adapterId] : null;
147
+ if (!adapter) return null;
148
+ return {
149
+ ...adapter,
150
+ platform: manifest.key,
151
+ adapterId,
152
+ policy: manifest.auth.oauth.policy,
153
+ quotaId: manifest.auth.oauth.quota || null
154
+ };
155
+ }
156
+
157
+ function listChannelAuthPlatforms() {
158
+ return getOAuthManifests().map(manifest => manifest.key);
159
+ }
160
+
161
+
162
+ module.exports = { getChannelAuthAdapter, listChannelAuthPlatforms, safeRef };
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const { getChannelAuthAdapter, listChannelAuthPlatforms, safeRef } = require('./channel-auth-registry');
4
+
5
+ const quotaCache = new Map();
6
+ const QUOTA_TTL_MS = 5 * 60 * 1000;
7
+
8
+ function getAdapter(platform) {
9
+ const adapter = getChannelAuthAdapter(platform);
10
+ if (!adapter) throw new Error(`Unsupported channel auth platform: ${platform}`);
11
+ return adapter;
12
+ }
13
+
14
+ function matchChannel(channel, candidate) {
15
+ if (!channel || channel.authMode !== 'oauth') return false;
16
+ const ref = safeRef(channel.authRef);
17
+ const next = safeRef(candidate.authRef);
18
+ if (ref.credentialId && next.credentialId) return ref.credentialId === next.credentialId;
19
+ if (ref.providerId && next.providerId && (ref.accountId || ref.identityKey) && (next.accountId || next.identityKey)) {
20
+ return ref.providerId === next.providerId
21
+ && (ref.accountId || ref.identityKey) === (next.accountId || next.identityKey);
22
+ }
23
+ return false;
24
+ }
25
+
26
+ function getChannel(platform, channelId) {
27
+ if (!channelId) return null;
28
+ const adapter = getAdapter(platform);
29
+ const service = require(adapter.channelServicePath);
30
+ return (service.getChannels?.().channels || []).find(channel => channel.id === channelId) || null;
31
+ }
32
+
33
+ function getChannelAuth(platform, { channelId = '' } = {}) {
34
+ const adapter = getAdapter(platform);
35
+ const result = adapter.scan();
36
+ return {
37
+ channel: getChannel(platform, channelId),
38
+ candidates: result.candidates || [],
39
+ nativeState: result.nativeState || { available: false },
40
+ warnings: result.warnings || []
41
+ };
42
+ }
43
+
44
+ function syncLocalChannelAuth(platform, options = {}) {
45
+ return getChannelAuth(platform, options);
46
+ }
47
+
48
+ async function fetchChannelAuthQuota(platform, channelId, { refresh = false } = {}) {
49
+ const adapter = getAdapter(platform);
50
+ const checkedAt = () => new Date().toISOString();
51
+ const channel = getChannel(platform, channelId);
52
+ if (!channel) return { quota: null, status: 'unavailable', checkedAt: checkedAt(), warning: 'Channel not found' };
53
+ if (channel.authMode !== 'oauth') return { quota: null, status: 'unsupported', checkedAt: checkedAt(), warning: 'Channel is not OAuth' };
54
+ const ref = safeRef(channel.authRef);
55
+ const cacheKey = `${adapter.adapterId}:${ref.credentialId || ref.providerId}:${ref.accountId || ref.identityKey}`;
56
+ const cached = quotaCache.get(cacheKey);
57
+ if (!refresh && cached && Date.now() - cached.checkedAtMs < QUOTA_TTL_MS) return cached.value;
58
+ try {
59
+ const usage = await adapter.quota(ref);
60
+ const result = {
61
+ ...(usage?.quota ? usage : {
62
+ quota: null,
63
+ status: usage?.status || 'unavailable',
64
+ warning: usage?.error || 'Quota unavailable'
65
+ }),
66
+ checkedAt: checkedAt()
67
+ };
68
+ quotaCache.set(cacheKey, { checkedAtMs: Date.now(), value: result });
69
+ return result;
70
+ } catch (error) {
71
+ return { quota: null, status: 'unavailable', checkedAt: checkedAt(), warning: error.message };
72
+ }
73
+ }
74
+
75
+ module.exports = {
76
+ getChannelAuth,
77
+ syncLocalChannelAuth,
78
+ fetchChannelAuthQuota,
79
+ listChannelAuthPlatforms,
80
+ safeRef,
81
+ matchChannel
82
+ };
@@ -9,20 +9,73 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const toml = require('toml');
11
11
  const tomlStringify = require('@iarna/toml').stringify;
12
- const { RepoScannerBase } = require('./repo-scanner-base');
13
- const { NATIVE_PATHS } = require('../../config/paths');
12
+ const { NATIVE_PATHS } = require('../config/paths');
13
+ const { RepoScannerBase } = require('../server/services/repo-scanner-base');
14
+ const { LocalResourceIndex } = require('../server/services/local-resource-index');
15
+ const { createPlatformAccessError } = require('./access');
16
+ const { getPlatformContext } = require('../server/platform-context');
14
17
  const {
15
18
  parseFrontmatter
16
- } = require('./format-converter');
19
+ } = require('../server/services/format-converter');
17
20
  const {
18
21
  normalizeSafeFileStem,
19
22
  normalizeSafeRelativePath,
20
23
  resolveInsideRoot
21
- } = require('./config-artifact-paths');
24
+ } = require('../shared/config-artifact-paths');
25
+
26
+ function readMetadataPrefix(filePath, platform) {
27
+ const fd = fs.openSync(filePath, 'r');
28
+ const buffer = Buffer.alloc(8192);
29
+ const chunks = [];
30
+ let total = 0;
31
+ const isGemini = platform === 'gemini';
32
+ try {
33
+ while (total < (isGemini ? 64 * 1024 : 1024 * 1024)) {
34
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
35
+ if (!bytesRead) break;
36
+ chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
37
+ total += bytesRead;
38
+ let text = Buffer.concat(chunks).toString('utf8');
39
+ if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
40
+ if (!isGemini && total === bytesRead && !/^\s*---\s*(?:\n|$)/.test(text)) break;
41
+ if (isGemini && /^\s*description\s*=.*(?:\n|$)/m.test(text)) break;
42
+ if (!isGemini && /^\s*---\s*\n[\s\S]*?\n\s*---\s*(?:\n|$)/.test(text)) break;
43
+ }
44
+ let result = Buffer.concat(chunks).toString('utf8');
45
+ if (result.charCodeAt(0) === 0xFEFF) result = result.slice(1);
46
+ if (isGemini && !/^\s*description\s*=/m.test(result)) {
47
+ const stat = fs.fstatSync(fd);
48
+ if (stat.size > total) {
49
+ const remaining = stat.size - total;
50
+ const tailSize = Math.min(1024 * 1024, remaining);
51
+ const offset = remaining <= 1024 * 1024 ? total : stat.size - tailSize;
52
+ const tail = Buffer.alloc(tailSize);
53
+ const bytesRead = fs.readSync(fd, tail, 0, tailSize, offset);
54
+ result += `\n${tail.toString('utf8', 0, bytesRead)}`;
55
+ }
56
+ }
57
+ return result;
58
+ } finally {
59
+ fs.closeSync(fd);
60
+ }
61
+ }
62
+
63
+ function parseCommandMetadata(filePath, platform) {
64
+ const content = readMetadataPrefix(filePath, platform);
65
+ if (platform === 'gemini') {
66
+ try {
67
+ const parsed = toml.parse(content);
68
+ return { frontmatter: {}, gemini: { description: parsed.description || '' } };
69
+ } catch (error) {
70
+ const match = content.match(/^\s*description\s*=\s*(.+)$/m);
71
+ return { frontmatter: {}, gemini: { description: match ? match[1].trim().replace(/^['"]|['"]$/g, '') : '', parseError: error.message } };
72
+ }
73
+ }
74
+ return { ...parseFrontmatter(content), gemini: null };
75
+ }
22
76
 
23
77
  // 默认仓库源
24
78
  const DEFAULT_REPOS = [];
25
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
26
79
  const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
27
80
  const CLAUDE_COMMANDS_DIR = NATIVE_PATHS.claude.commands
28
81
  || path.join(NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings), 'commands');
@@ -67,7 +120,17 @@ const PLATFORM_CONFIG = {
67
120
  };
68
121
 
69
122
  function normalizePlatform(platform) {
70
- return SUPPORTED_PLATFORMS.includes(platform) ? platform : 'claude';
123
+ const normalized = typeof platform === 'string' && platform.trim()
124
+ ? platform.trim().toLowerCase()
125
+ : 'claude';
126
+ if (!Object.prototype.hasOwnProperty.call(PLATFORM_CONFIG, normalized)) {
127
+ throw createPlatformAccessError('unsupported', {
128
+ platform: normalized,
129
+ capability: 'commands',
130
+ message: `Commands are not supported for ${normalized}`
131
+ });
132
+ }
133
+ return normalized;
71
134
  }
72
135
 
73
136
  /**
@@ -165,12 +228,9 @@ function parseGeminiCommandToml(content) {
165
228
  }
166
229
 
167
230
  function generateGeminiCommandToml({ description, body }) {
168
- const data = {
169
- prompt: body || ''
170
- };
171
- if (description) {
172
- data.description = description;
173
- }
231
+ const data = {};
232
+ if (description) data.description = description;
233
+ data.prompt = body || '';
174
234
  return tomlStringify(data);
175
235
  }
176
236
 
@@ -338,23 +398,114 @@ class CommandsRepoScanner extends RepoScannerBase {
338
398
  * Commands 服务类
339
399
  */
340
400
  class CommandsService {
341
- constructor(platform = 'claude') {
401
+ constructor(platform = 'claude', { registry } = {}) {
342
402
  this.platform = normalizePlatform(platform);
343
403
  const config = PLATFORM_CONFIG[this.platform];
344
-
345
- this.userCommandsDir = config.userCommandsDir;
404
+ this.registry = registry || (() => {
405
+ try {
406
+ return getPlatformContext().registry;
407
+ } catch {
408
+ return null;
409
+ }
410
+ })();
411
+ this.pathContext = null;
412
+ try {
413
+ if (typeof this.registry?.resolvePathContext === 'function') {
414
+ this.pathContext = this.registry.resolvePathContext(this.platform);
415
+ }
416
+ } catch {
417
+ this.pathContext = null;
418
+ }
419
+ const native = this.pathContext?.customized
420
+ ? (this.pathContext.native || {})
421
+ : (NATIVE_PATHS[this.platform] || {});
422
+ const configuredUserCommandsDir = this.pathContext?.customized
423
+ ? (native.commands
424
+ || (this.platform === 'opencode' ? path.join(native.config || OPENCODE_CONFIG_DIR, 'commands') : null)
425
+ || (native.dir ? path.join(native.dir, 'commands') : null))
426
+ : null;
427
+
428
+ this.userCommandsDir = configuredUserCommandsDir || config.userCommandsDir;
346
429
  if (this.platform === 'opencode') {
347
430
  const legacyUserDir = config.legacyUserCommandsDir;
348
- if (legacyUserDir && fs.existsSync(legacyUserDir) && !fs.existsSync(this.userCommandsDir)) {
349
- this.userCommandsDir = legacyUserDir;
431
+ const configuredLegacyUserDir = this.pathContext?.customized && native.config
432
+ ? path.join(native.config, 'command')
433
+ : legacyUserDir;
434
+ if (configuredLegacyUserDir && fs.existsSync(configuredLegacyUserDir) && !fs.existsSync(this.userCommandsDir)) {
435
+ this.userCommandsDir = configuredLegacyUserDir;
350
436
  }
351
437
  }
352
438
 
353
439
  this.projectCommandsDir = config.projectCommandsDir;
354
440
  this.repoScanner = new CommandsRepoScanner(this.platform, this.userCommandsDir);
355
441
  ensureDir(this.userCommandsDir);
442
+ this._localIndexes = new Map();
443
+ this._localIndexLimit = 32;
444
+ }
445
+
446
+ _getLocalIndex(scope, projectPath = null) {
447
+ const root = scope === 'user' ? this.userCommandsDir : this.getProjectCommandsDir(projectPath);
448
+ if (!root) return null;
449
+ const key = `${this.platform}:${scope}:${path.resolve(root)}`;
450
+ if (!this._localIndexes.has(key)) {
451
+ if (this._localIndexes.size >= this._localIndexLimit) {
452
+ const [oldKey, oldIndex] = this._localIndexes.entries().next().value;
453
+ oldIndex.dispose();
454
+ this._localIndexes.delete(oldKey);
455
+ }
456
+ const extension = getCommandFileExtension(this.platform);
457
+ this._localIndexes.set(key, new LocalResourceIndex({ key, roots: [root],
458
+ scanFile: (descriptor) => {
459
+ if (!descriptor.fullPath.endsWith(extension)) return null;
460
+ const parsed = parseCommandMetadata(descriptor.fullPath, this.platform);
461
+ const relativePath = descriptor.relativePath;
462
+ const commandName = path.basename(relativePath, extension);
463
+ const namespace = path.dirname(relativePath);
464
+ return {
465
+ name: commandName,
466
+ namespace: namespace === '.' ? null : namespace,
467
+ scope,
468
+ path: relativePath,
469
+ fullPath: descriptor.fullPath,
470
+ description: parsed.gemini?.description || parsed.frontmatter.description || '',
471
+ allowedTools: parsed.frontmatter['allowed-tools'] || '',
472
+ argumentHint: parsed.frontmatter['argument-hint'] || '',
473
+ agent: parsed.frontmatter.agent || '',
474
+ model: parsed.frontmatter.model || '',
475
+ subtask: parsed.frontmatter.subtask || '',
476
+ parseError: parsed.gemini?.parseError || '',
477
+ updatedAt: descriptor.stat.mtime.getTime()
478
+ };
479
+ },
480
+ detailFile: (summary) => {
481
+ const content = fs.readFileSync(summary.fullPath, 'utf-8');
482
+ const parsed = this.platform === 'gemini'
483
+ ? { frontmatter: {}, body: '', gemini: parseGeminiCommandToml(content) }
484
+ : { ...parseFrontmatter(content), gemini: null };
485
+ return {
486
+ body: parsed.gemini?.body ?? parsed.body,
487
+ fullContent: content,
488
+ parseError: parsed.gemini?.parseError || '',
489
+ updatedAt: fs.statSync(summary.fullPath).mtime.getTime()
490
+ };
491
+ }
492
+ }));
493
+ }
494
+ const index = this._localIndexes.get(key);
495
+ this._localIndexes.delete(key);
496
+ this._localIndexes.set(key, index);
497
+ return index;
498
+ }
499
+
500
+ _invalidateLocal(scope, projectPath = null) {
501
+ this._getLocalIndex(scope, projectPath)?.invalidate();
356
502
  }
357
503
 
504
+
505
+ dispose() {
506
+ for (const index of this._localIndexes.values()) index.dispose();
507
+ this._localIndexes.clear();
508
+ }
358
509
  getProjectCommandsDir(projectPath) {
359
510
  if (!projectPath) return null;
360
511
  return this.projectCommandsDir(projectPath);
@@ -406,133 +557,60 @@ class CommandsService {
406
557
  * @param {string} projectPath - 项目路径(可选,用于获取项目级命令)
407
558
  */
408
559
  listCommands(projectPath = null) {
409
- const commands = [];
410
-
411
- // 获取用户级命令
412
- const userCommands = scanCommandsDir(this.userCommandsDir, this.userCommandsDir, 'user', this.platform);
413
- commands.push(...userCommands);
414
-
415
- // 获取项目级命令(如果提供了项目路径)
416
- if (projectPath) {
417
- const projectCommandsDir = this.getProjectCommandsDir(projectPath);
418
- const projectCommands = scanCommandsDir(projectCommandsDir, projectCommandsDir, 'project', this.platform);
419
- commands.push(...projectCommands);
420
- }
421
-
422
- // 按名称排序
560
+ const userCommands = this._getLocalIndex('user').listSync();
561
+ const projectCommands = projectPath ? this._getLocalIndex('project', projectPath).listSync() : [];
562
+ const commands = [...userCommands, ...projectCommands];
423
563
  commands.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
424
-
425
564
  return {
426
565
  commands,
427
566
  total: commands.length,
428
567
  userCount: userCommands.length,
429
- projectCount: commands.length - userCommands.length
568
+ projectCount: projectCommands.length
430
569
  };
431
570
  }
432
571
 
433
- /**
434
- * 获取所有命令(包括远程仓库)
435
- * @param {boolean} forceRefresh - 强制刷新远程缓存
436
- */
572
+ /** 获取所有命令(包括远程仓库) */
437
573
  async listAllCommands(projectPath = null, forceRefresh = false) {
438
- // 获取本地命令
439
574
  const { commands: localCommands, userCount, projectCount } = this.listCommands(projectPath);
440
-
441
- // 获取远程命令
442
575
  let remoteCommands = [];
443
576
  try {
444
577
  remoteCommands = await this.repoScanner.listRemoteItems(forceRefresh);
445
-
446
- // 更新安装状态
447
- for (const cmd of remoteCommands) {
448
- cmd.installed = this.repoScanner.isInstalled(cmd.path);
449
- }
578
+ for (const cmd of remoteCommands) cmd.installed = this.repoScanner.isInstalled(cmd.path);
450
579
  } catch (err) {
451
580
  console.warn('[CommandsService] Failed to fetch remote commands:', err.message);
452
581
  }
453
-
454
- // 合并列表(本地优先)
455
582
  const allCommands = [...localCommands];
456
- const localKeys = new Set(localCommands.map(c =>
457
- c.namespace ? `${c.namespace}/${c.name}`.toLowerCase() : c.name.toLowerCase()
458
- ));
459
-
583
+ const localKeys = new Set(localCommands.map(c => c.namespace ? `${c.namespace}/${c.name}`.toLowerCase() : c.name.toLowerCase()));
460
584
  for (const remote of remoteCommands) {
461
585
  const key = remote.namespace ? `${remote.namespace}/${remote.name}`.toLowerCase() : remote.name.toLowerCase();
462
- if (!localKeys.has(key)) {
463
- allCommands.push(remote);
464
- }
586
+ if (!localKeys.has(key)) allCommands.push(remote);
465
587
  }
466
-
467
- // 排序
468
588
  allCommands.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
469
-
470
- return {
471
- commands: allCommands,
472
- total: allCommands.length,
473
- userCount,
474
- projectCount,
475
- remoteCount: remoteCommands.length
476
- };
589
+ return { commands: allCommands, total: allCommands.length, userCount, projectCount, remoteCount: remoteCommands.length };
477
590
  }
478
591
 
479
- /**
480
- * 获取单个命令详情
481
- */
592
+ /** 获取单个命令详情 */
482
593
  getCommand(name, scope, projectPath = null, namespace = null) {
483
594
  const safeName = normalizeCommandName(name);
484
595
  const safeNamespace = normalizeCommandNamespace(namespace);
485
596
  const baseDir = this._getBaseDir(scope, projectPath);
486
- const { relativePath, fullPath } = this._resolveCommandPath(baseDir, safeName, safeNamespace);
487
-
488
- if (!fs.existsSync(fullPath)) {
489
- return null;
490
- }
491
-
492
- const content = fs.readFileSync(fullPath, 'utf-8');
493
- const parsed = this.platform === 'gemini'
494
- ? { frontmatter: {}, body: '', gemini: parseGeminiCommandToml(content) }
495
- : { ...parseFrontmatter(content), gemini: null };
496
-
497
- return {
498
- name: safeName,
499
- namespace: safeNamespace || null,
500
- scope,
501
- path: relativePath,
502
- fullPath,
503
- description: parsed.gemini?.description || parsed.frontmatter.description || '',
504
- allowedTools: parsed.frontmatter['allowed-tools'] || '',
505
- argumentHint: parsed.frontmatter['argument-hint'] || '',
506
- agent: parsed.frontmatter.agent || '',
507
- model: parsed.frontmatter.model || '',
508
- subtask: parsed.frontmatter.subtask || '',
509
- body: parsed.gemini?.body ?? parsed.body,
510
- parseError: parsed.gemini?.parseError || '',
511
- fullContent: content,
512
- updatedAt: fs.statSync(fullPath).mtime.getTime()
513
- };
597
+ const { relativePath } = this._resolveCommandPath(baseDir, safeName, safeNamespace);
598
+ const detail = this._getLocalIndex(scope, projectPath)?.getSync(relativePath);
599
+ if (!detail) return null;
600
+ return { ...detail, name: safeName, namespace: safeNamespace || null, scope, path: relativePath };
514
601
  }
515
602
 
516
- /**
517
- * 创建命令
518
- */
603
+ /** 创建命令 */
519
604
  createCommand({ name, scope, projectPath, namespace, description, allowedTools, argumentHint, agent, model, subtask, body }) {
520
605
  const safeName = normalizeCommandName(name);
521
606
  const safeNamespace = normalizeCommandNamespace(namespace);
522
607
  const baseDir = this._getBaseDir(scope, projectPath);
523
608
  const { fullPath: filePath } = this._resolveCommandPath(baseDir, safeName, safeNamespace);
524
- const targetDir = path.dirname(filePath);
525
- ensureDir(targetDir);
526
-
527
- // 检查是否已存在
528
- if (fs.existsSync(filePath)) {
529
- throw new Error(`命令 "${safeName}" 已存在`);
530
- }
531
-
609
+ ensureDir(path.dirname(filePath));
610
+ if (fs.existsSync(filePath)) throw new Error(`命令 "${safeName}" 已存在`);
532
611
  const content = this._generateCommandContent({ description, allowedTools, argumentHint, agent, model, subtask, body });
533
-
534
612
  fs.writeFileSync(filePath, content, 'utf-8');
535
-
613
+ this._invalidateLocal(scope, projectPath);
536
614
  return this.getCommand(safeName, scope, projectPath, safeNamespace);
537
615
  }
538
616
 
@@ -550,9 +628,8 @@ class CommandsService {
550
628
  }
551
629
 
552
630
  const content = this._generateCommandContent({ description, allowedTools, argumentHint, agent, model, subtask, body });
553
-
554
631
  fs.writeFileSync(filePath, content, 'utf-8');
555
-
632
+ this._invalidateLocal(scope, projectPath);
556
633
  return this.getCommand(safeName, scope, projectPath, safeNamespace);
557
634
  }
558
635
 
@@ -584,6 +661,7 @@ class CommandsService {
584
661
  }
585
662
  }
586
663
 
664
+ this._invalidateLocal(scope, projectPath);
587
665
  return { success: true, message: '命令已删除' };
588
666
  }
589
667
 
@@ -645,14 +723,16 @@ class CommandsService {
645
723
  * 从远程仓库安装命令
646
724
  */
647
725
  async installFromRemote(command) {
648
- return this.repoScanner.installCommand(command);
726
+ const result = await this.repoScanner.installCommand(command);
727
+ if (result && result.success !== false) this._invalidateLocal('user');
728
+ return result;
649
729
  }
650
730
 
651
- /**
652
- * 卸载命令
653
- */
731
+ /** 卸载命令 */
654
732
  uninstallCommand(relativePath) {
655
- return this.repoScanner.uninstall(relativePath);
733
+ const result = this.repoScanner.uninstall(relativePath);
734
+ if (result && result.success !== false) this._invalidateLocal('user');
735
+ return result;
656
736
  }
657
737
 
658
738
  // ==================== 格式转换 ====================