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
@@ -7,48 +7,55 @@
7
7
  const express = require('express');
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
- const { AgentsService } = require('../services/agents-service');
10
+ const { AgentsService } = require('../../platforms/agents-service');
11
11
  const { PATHS, HOME_DIR } = require('../../config/paths');
12
+ const { resolvePlatform, createPlatformAccessError } = require('../../platforms/access');
13
+ const { getPlatformCatalog } = require('../services/platform-catalog');
12
14
  const { sendApiError } = require('./validation-errors');
13
15
 
14
16
  const router = express.Router();
15
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
16
17
  const agentServices = new Map();
17
18
  const DEFAULT_PROJECT_ALLOWED_ROOTS = [HOME_DIR, process.cwd()];
18
19
 
19
- function isSupportedPlatform(platform) {
20
- return SUPPORTED_PLATFORMS.includes(platform);
21
- }
22
-
23
20
  function getRawPlatform(req) {
24
21
  const queryPlatform = typeof req.query?.platform === 'string' ? req.query.platform.trim() : '';
25
22
  const bodyPlatform = typeof req.body?.platform === 'string' ? req.body.platform.trim() : '';
26
23
  return queryPlatform || bodyPlatform || '';
27
24
  }
28
25
 
29
- function resolvePlatform(rawPlatform) {
30
- return rawPlatform || 'claude';
31
- }
32
-
33
26
  function getPlatform(req) {
34
- return resolvePlatform(getRawPlatform(req));
27
+ return resolvePlatform(getRawPlatform(req), { fallback: 'claude' }).key;
35
28
  }
36
29
 
37
30
  function getAgentsService(req) {
38
31
  const platform = getPlatform(req);
39
32
  if (!agentServices.has(platform)) {
40
- agentServices.set(platform, new AgentsService(platform));
33
+ const service = new AgentsService(platform);
34
+ if (!service || typeof service.listAgents !== 'function') {
35
+ throw createPlatformAccessError('unsupported', {
36
+ platform,
37
+ capability: 'agents',
38
+ message: `Agents are not supported for ${platform}`
39
+ });
40
+ }
41
+ agentServices.set(platform, service);
41
42
  }
42
43
  return { platform, service: agentServices.get(platform) };
43
44
  }
44
45
 
45
- function validateScopeForPlatform(scope, platform, projectPath) {
46
+ function getAgentCapabilities(platform, service) {
47
+ const capabilities = service?.getCapabilities?.();
48
+ if (capabilities && typeof capabilities === 'object') return capabilities;
49
+ return getPlatformCatalog().get(platform)?.agentCapabilities || { projectScope: true, repoOperations: true };
50
+ }
51
+
52
+ function validateScopeForPlatform(scope, _platform, projectPath, capabilities = {}) {
46
53
  if (!['user', 'project'].includes(scope)) {
47
54
  return '无效的 scope,必须是 user 或 project';
48
55
  }
49
56
 
50
- if (platform === 'codex' && scope !== 'user') {
51
- return 'Codex 平台仅支持 user 作用域代理';
57
+ if (scope === 'project' && capabilities.projectScope === false) {
58
+ return '该平台仅支持 user 作用域代理';
52
59
  }
53
60
 
54
61
  if (scope === 'project' && !projectPath) {
@@ -73,11 +80,16 @@ function validateAgentFileName(fileName) {
73
80
  }
74
81
  return null;
75
82
  }
76
-
77
- function isCodexRepoOperationUnsupported(platform) {
78
- return platform === 'codex';
83
+ function rejectUnsupportedRepo(res, platform, service, operation = '') {
84
+ const capabilities = getAgentCapabilities(platform, service);
85
+ if (capabilities.repoOperations !== false) return false;
86
+ return res.status(400).json({
87
+ success: false,
88
+ message: service.getRepoOperationError?.(operation) || `该平台暂不支持远程仓库代理${operation ? ' ' + operation : ''}`
89
+ });
79
90
  }
80
91
 
92
+
81
93
  function validateRepoPath(repoPath) {
82
94
  if (typeof repoPath !== 'string' || !repoPath.trim()) {
83
95
  return '代理仓库路径不能为空';
@@ -211,14 +223,13 @@ function normalizeOptionalProjectPath(projectPath) {
211
223
  }
212
224
 
213
225
  router.use((req, res, next) => {
214
- const rawPlatform = getRawPlatform(req);
215
- if (rawPlatform && !isSupportedPlatform(rawPlatform)) {
216
- return res.status(400).json({
217
- success: false,
218
- message: `不支持的平台: ${rawPlatform}`
219
- });
226
+ try {
227
+ getPlatform(req);
228
+ return next();
229
+ } catch (error) {
230
+ console.error('[Agents API] Platform resolution failed:', error);
231
+ return sendApiError(res, error, error.status || 404);
220
232
  }
221
- next();
222
233
  });
223
234
 
224
235
  /**
@@ -279,7 +290,6 @@ router.get('/stats', (req, res) => {
279
290
  });
280
291
 
281
292
  /**
282
- * 获取单个代理详情
283
293
  * GET /api/agents/:scope/:fileName
284
294
  */
285
295
  router.get('/:scope/:fileName', (req, res) => {
@@ -288,7 +298,7 @@ router.get('/:scope/:fileName', (req, res) => {
288
298
  const { scope, fileName } = req.params;
289
299
  const { projectPath } = req.query;
290
300
 
291
- const scopeError = validateScopeForPlatform(scope, platform, projectPath);
301
+ const scopeError = validateScopeForPlatform(scope, platform, projectPath, getAgentCapabilities(platform, service));
292
302
  if (scopeError) {
293
303
  return res.status(400).json({
294
304
  success: false,
@@ -362,7 +372,6 @@ router.post('/', (req, res) => {
362
372
  message: '代理文件名不能为空'
363
373
  });
364
374
  }
365
-
366
375
  const fileNameError = validateAgentFileName(fileName);
367
376
  if (fileNameError) {
368
377
  return res.status(400).json({
@@ -371,7 +380,7 @@ router.post('/', (req, res) => {
371
380
  });
372
381
  }
373
382
 
374
- const scopeError = validateScopeForPlatform(scope, platform, projectPath);
383
+ const scopeError = validateScopeForPlatform(scope, platform, projectPath, getAgentCapabilities(platform, service));
375
384
  if (scopeError) {
376
385
  return res.status(400).json({
377
386
  success: false,
@@ -437,7 +446,7 @@ router.put('/:scope/:fileName', (req, res) => {
437
446
  configContent
438
447
  } = req.body;
439
448
 
440
- const scopeError = validateScopeForPlatform(scope, platform, projectPath);
449
+ const scopeError = validateScopeForPlatform(scope, platform, projectPath, getAgentCapabilities(platform, service));
441
450
  if (scopeError) {
442
451
  return res.status(400).json({
443
452
  success: false,
@@ -490,7 +499,6 @@ router.put('/:scope/:fileName', (req, res) => {
490
499
  });
491
500
 
492
501
  /**
493
- * 删除代理
494
502
  * DELETE /api/agents/:scope/:fileName
495
503
  */
496
504
  router.delete('/:scope/:fileName', (req, res) => {
@@ -499,7 +507,7 @@ router.delete('/:scope/:fileName', (req, res) => {
499
507
  const { scope, fileName } = req.params;
500
508
  const { projectPath } = req.query;
501
509
 
502
- const scopeError = validateScopeForPlatform(scope, platform, projectPath);
510
+ const scopeError = validateScopeForPlatform(scope, platform, projectPath, getAgentCapabilities(platform, service));
503
511
  if (scopeError) {
504
512
  return res.status(400).json({
505
513
  success: false,
@@ -568,19 +576,10 @@ router.get('/all', async (req, res) => {
568
576
  }
569
577
  });
570
578
 
571
- /**
572
- * 获取仓库列表
573
- * GET /api/agents/repos
574
- */
575
579
  router.get('/repos', (req, res) => {
576
580
  try {
577
581
  const { platform, service } = getAgentsService(req);
578
- if (isCodexRepoOperationUnsupported(platform)) {
579
- return res.status(400).json({
580
- success: false,
581
- message: 'Codex 平台暂不支持远程仓库代理'
582
- });
583
- }
582
+ if (rejectUnsupportedRepo(res, platform, service)) return;
584
583
  const repos = service.getRepos();
585
584
  res.json({
586
585
  success: true,
@@ -601,12 +600,7 @@ router.get('/repos', (req, res) => {
601
600
  router.post('/repos', (req, res) => {
602
601
  try {
603
602
  const { platform, service } = getAgentsService(req);
604
- if (isCodexRepoOperationUnsupported(platform)) {
605
- return res.status(400).json({
606
- success: false,
607
- message: 'Codex 平台暂不支持远程仓库代理'
608
- });
609
- }
603
+ if (rejectUnsupportedRepo(res, platform, service)) return;
610
604
  const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
611
605
 
612
606
  if (!owner || !name) {
@@ -637,12 +631,7 @@ router.post('/repos', (req, res) => {
637
631
  router.delete('/repos/:owner/:name', (req, res) => {
638
632
  try {
639
633
  const { platform, service } = getAgentsService(req);
640
- if (isCodexRepoOperationUnsupported(platform)) {
641
- return res.status(400).json({
642
- success: false,
643
- message: 'Codex 平台暂不支持远程仓库代理'
644
- });
645
- }
634
+ if (rejectUnsupportedRepo(res, platform, service)) return;
646
635
  const { owner, name } = req.params;
647
636
  const { directory = '' } = req.query;
648
637
  const repos = service.removeRepo(owner, name, directory);
@@ -666,12 +655,7 @@ router.delete('/repos/:owner/:name', (req, res) => {
666
655
  router.put('/repos/:owner/:name/toggle', (req, res) => {
667
656
  try {
668
657
  const { platform, service } = getAgentsService(req);
669
- if (isCodexRepoOperationUnsupported(platform)) {
670
- return res.status(400).json({
671
- success: false,
672
- message: 'Codex 平台暂不支持远程仓库代理'
673
- });
674
- }
658
+ if (rejectUnsupportedRepo(res, platform, service)) return;
675
659
  const { owner, name } = req.params;
676
660
  const { enabled, directory = '' } = req.body;
677
661
 
@@ -696,12 +680,7 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
696
680
  router.post('/install', async (req, res) => {
697
681
  try {
698
682
  const { platform, service } = getAgentsService(req);
699
- if (isCodexRepoOperationUnsupported(platform)) {
700
- return res.status(400).json({
701
- success: false,
702
- message: 'Codex 平台暂不支持远程仓库代理安装'
703
- });
704
- }
683
+ if (rejectUnsupportedRepo(res, platform, service, 'install')) return;
705
684
  const agent = req.body;
706
685
 
707
686
  if (!agent || !agent.repoOwner || !agent.repoName) {
@@ -748,12 +727,7 @@ router.post('/install', async (req, res) => {
748
727
  router.post('/uninstall', (req, res) => {
749
728
  try {
750
729
  const { platform, service } = getAgentsService(req);
751
- if (isCodexRepoOperationUnsupported(platform)) {
752
- return res.status(400).json({
753
- success: false,
754
- message: 'Codex 平台暂不支持远程仓库代理卸载'
755
- });
756
- }
730
+ if (rejectUnsupportedRepo(res, platform, service, 'uninstall')) return;
757
731
  const { fileName } = req.body;
758
732
 
759
733
  if (!fileName) {
@@ -5,25 +5,35 @@
5
5
  */
6
6
 
7
7
  const express = require('express');
8
- const { CommandsService } = require('../services/commands-service');
8
+ const { CommandsService } = require('../../platforms/commands-service');
9
+ const { resolvePlatform, createPlatformAccessError } = require('../../platforms/access');
9
10
  const { sendApiError } = require('./validation-errors');
10
11
 
11
12
  const router = express.Router();
12
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
13
13
  const commandServices = new Map();
14
14
 
15
- function resolvePlatform(rawPlatform) {
16
- return SUPPORTED_PLATFORMS.includes(rawPlatform) ? rawPlatform : 'claude';
15
+ function getRawPlatform(req) {
16
+ const queryPlatform = typeof req.query?.platform === 'string' ? req.query.platform.trim() : '';
17
+ const bodyPlatform = typeof req.body?.platform === 'string' ? req.body.platform.trim() : '';
18
+ return queryPlatform || bodyPlatform || '';
17
19
  }
18
20
 
19
21
  function getPlatform(req) {
20
- return resolvePlatform(req.query?.platform || req.body?.platform);
22
+ return resolvePlatform(getRawPlatform(req), { fallback: 'claude' }).key;
21
23
  }
22
24
 
23
25
  function getCommandsService(req) {
24
26
  const platform = getPlatform(req);
25
27
  if (!commandServices.has(platform)) {
26
- commandServices.set(platform, new CommandsService(platform));
28
+ const service = new CommandsService(platform);
29
+ if (!service || typeof service.listCommands !== 'function') {
30
+ throw createPlatformAccessError('unsupported', {
31
+ platform,
32
+ capability: 'commands',
33
+ message: `Commands are not supported for ${platform}`
34
+ });
35
+ }
36
+ commandServices.set(platform, service);
27
37
  }
28
38
  return { platform, service: commandServices.get(platform) };
29
39
  }
@@ -6,8 +6,47 @@
6
6
  */
7
7
 
8
8
  const express = require('express');
9
- const { ConfigRegistryService, CONFIG_TYPES, SUPPORTED_PLATFORMS } = require('../services/config-registry-service');
9
+ const path = require('path');
10
+ const { ConfigRegistryService, CONFIG_TYPES } = require('../services/config-registry-service');
10
11
  const { ConfigSyncManager } = require('../services/config-sync-manager');
12
+ const { ControlManifestStore } = require('../services/control-manifest-store');
13
+ const { EffectiveControlService } = require('../services/effective-control-service');
14
+ const { PATHS } = require('../../config/paths');
15
+ const { SkillProjectionService } = require('../services/skill-projection-service');
16
+ const { validateKnownProjectCwd } = require('../services/project-path-validation');
17
+ const { getPlatformCatalog } = require('../services/platform-catalog');
18
+ const { getPlatformContext } = require('../platform-context');
19
+
20
+ let effectiveControlService;
21
+
22
+ function getEffectiveControlService() {
23
+ if (!effectiveControlService && PATHS.effectiveControlManifest) {
24
+ effectiveControlService = new EffectiveControlService({
25
+ store: new ControlManifestStore({
26
+ userPath: PATHS.effectiveControlManifest,
27
+ projectPathResolver: ({ projectPath }) => require('path').join(projectPath, '.ctx-control.json')
28
+ }),
29
+ projection: new SkillProjectionService({
30
+ registry: getPlatformContext().registry
31
+ })
32
+ });
33
+ }
34
+ return effectiveControlService;
35
+ }
36
+
37
+ async function resolveSkillControlScope(body = {}) {
38
+ const scope = body.scope || 'user';
39
+ if (scope === 'user') return { scope, projectPath: null };
40
+ if (scope !== 'project') throw new Error('Invalid scope: expected "user" or "project"');
41
+ const projectPath = await validateKnownProjectCwd(body.projectPath || body.cwd);
42
+ if (!projectPath) throw new Error('Project scope requires a valid projectPath');
43
+ return { scope, projectPath };
44
+ }
45
+
46
+ function fallbackSkillControlKey(name, platform, scopeOptions) {
47
+ const location = scopeOptions.scope === 'project' ? scopeOptions.projectPath : 'user';
48
+ return `skill:${platform}:${scopeOptions.scope}:${location}:local:${platform}:${name}`;
49
+ }
11
50
 
12
51
  const router = express.Router();
13
52
  const registryService = new ConfigRegistryService();
@@ -16,8 +55,9 @@ const syncManager = new ConfigSyncManager();
16
55
  // Valid config types
17
56
  const VALID_TYPES = CONFIG_TYPES;
18
57
 
19
- // Valid platforms
20
- const VALID_PLATFORMS = SUPPORTED_PLATFORMS;
58
+ function getValidPlatforms() {
59
+ return getPlatformCatalog().keys();
60
+ }
21
61
 
22
62
  /**
23
63
  * Validate config type parameter
@@ -37,46 +77,78 @@ function validateType(type) {
37
77
  * @returns {string|null} Error message or null if valid
38
78
  */
39
79
  function validatePlatform(platform) {
40
- if (!VALID_PLATFORMS.includes(platform)) {
41
- return `Invalid platform: ${platform}. Must be one of: ${VALID_PLATFORMS.join(', ')}`;
80
+ const validPlatforms = getValidPlatforms();
81
+ if (!validPlatforms.includes(String(platform || '').trim().toLowerCase())) {
82
+ return `Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(', ')}`;
42
83
  }
43
84
  return null;
44
85
  }
45
86
 
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
- omp: { sync: 'syncToOmp', remove: 'removeFromOmp' }
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);
87
+ const RESERVED_REGISTRY_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
88
+
89
+ function normalizeRegistryName(name) {
90
+ const raw = String(name || '').replace(/\\/g, '/').trim();
91
+ const normalized = path.posix.normalize(raw).replace(/^(\.\/)+/, '');
92
+ if (
93
+ !raw
94
+ || raw.includes('\0')
95
+ || path.posix.isAbsolute(raw)
96
+ || !normalized
97
+ || normalized === '.'
98
+ || normalized === '..'
99
+ || normalized.startsWith('../')
100
+ || RESERVED_REGISTRY_NAMES.has(normalized)
101
+ ) {
102
+ throw new Error('Invalid config name');
58
103
  }
104
+ return normalized;
59
105
  }
60
106
 
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
- }
107
+ async function syncPlatform(type, name, platform) {
108
+ return syncManager.syncToPlatform(platform, type, name);
66
109
  }
67
110
 
68
- function syncEnabledPlatforms(type, name, platforms = {}) {
69
- for (const platform of VALID_PLATFORMS) {
70
- if (platforms?.[platform]) {
71
- syncPlatform(type, name, platform);
72
- }
73
- }
111
+ async function removePlatform(type, name, platform) {
112
+ return syncManager.removeFromPlatform(platform, type, name);
74
113
  }
75
114
 
76
- function removeAllPlatforms(type, name) {
77
- for (const platform of VALID_PLATFORMS) {
78
- removePlatform(type, name, platform);
79
- }
115
+ async function syncEnabledPlatforms(type, name, platforms = {}) {
116
+ await Promise.all(getValidPlatforms()
117
+ .filter(platform => platforms?.[platform])
118
+ .map(platform => syncPlatform(type, name, platform)));
119
+ }
120
+
121
+ async function removeAllPlatforms(type, name) {
122
+ await Promise.all(getValidPlatforms().map(platform => removePlatform(type, name, platform)));
123
+ }
124
+
125
+ function getEffectiveSkillRegistryItems() {
126
+ registryService.migrateSkillControls?.();
127
+ const items = registryService.listItems('skills');
128
+ const controlService = getEffectiveControlService();
129
+ if (!controlService?.getSkill) return items;
130
+
131
+ return Object.fromEntries(Object.entries(items || {}).map(([rawName, item]) => {
132
+ const name = normalizeRegistryName(rawName);
133
+ const controlKeys = {};
134
+ let hasControl = false;
135
+ for (const platform of getValidPlatforms()) {
136
+ const controlKey = item.controlKey || fallbackSkillControlKey(name, platform, { scope: 'user', projectPath: null });
137
+ const control = controlService.getSkill(controlKey, { scope: 'user' });
138
+ if (!control) continue;
139
+ hasControl = true;
140
+ controlKeys[platform] = control.controlKey || controlKey;
141
+ platforms[platform] = control.enabled === true
142
+ && control.trust === 'approved'
143
+ && control.artifact?.state === 'ready';
144
+ }
145
+ return [
146
+ name,
147
+ hasControl
148
+ ? { ...item, enabled: Object.values(platforms).some(Boolean), platforms, controlKeys }
149
+ : { ...item, platforms }
150
+ ];
151
+ }));
80
152
  }
81
153
 
82
154
  /**
@@ -86,6 +158,19 @@ function removeAllPlatforms(type, name) {
86
158
  router.get('/stats', async (req, res) => {
87
159
  try {
88
160
  const stats = registryService.getStats();
161
+ if (stats?.byType?.skills) {
162
+ const effectiveSkills = Object.values(getEffectiveSkillRegistryItems());
163
+ const skillStats = {
164
+ total: effectiveSkills.length,
165
+ enabled: effectiveSkills.filter(item => item.enabled).length,
166
+ disabled: effectiveSkills.filter(item => !item.enabled).length
167
+ };
168
+ for (const platform of getValidPlatforms()) {
169
+ skillStats[platform] = effectiveSkills.filter(item => item.platforms?.[platform]).length;
170
+ stats.byPlatform[platform] += skillStats[platform] - (stats.byType.skills[platform] || 0);
171
+ }
172
+ stats.byType.skills = skillStats;
173
+ }
89
174
 
90
175
  res.json({
91
176
  success: true,
@@ -117,7 +202,9 @@ router.get('/:type', async (req, res) => {
117
202
  });
118
203
  }
119
204
 
120
- const items = registryService.listItems(type);
205
+ const items = type === 'skills'
206
+ ? getEffectiveSkillRegistryItems()
207
+ : registryService.listItems(type);
121
208
 
122
209
  res.json({
123
210
  success: true,
@@ -163,7 +250,7 @@ router.post('/:type/import', async (req, res) => {
163
250
  for (const name of result.items) {
164
251
  const item = registryService.getItem(type, name);
165
252
  if (item && item.enabled && item.platforms?.claude) {
166
- syncManager.syncToClaude(type, name);
253
+ await syncPlatform(type, name, 'claude');
167
254
  }
168
255
  }
169
256
  }
@@ -196,7 +283,7 @@ router.post('/:type/import', async (req, res) => {
196
283
  router.put('/:type/:name/toggle', async (req, res) => {
197
284
  try {
198
285
  const { type } = req.params;
199
- const name = decodeURIComponent(req.params.name);
286
+ const name = normalizeRegistryName(decodeURIComponent(req.params.name));
200
287
  const { enabled } = req.body;
201
288
 
202
289
  const typeError = validateType(type);
@@ -223,25 +310,43 @@ router.put('/:type/:name/toggle', async (req, res) => {
223
310
  });
224
311
  }
225
312
 
226
- // Update registry
227
- const item = registryService.toggleEnabled(type, name, enabled);
313
+ if (type === 'skills') {
314
+ const platform = String(req.body.platform || 'claude').trim().toLowerCase();
315
+ const platformError = validatePlatform(platform);
316
+ if (platformError) {
317
+ return res.status(400).json({ success: false, message: platformError });
318
+ }
319
+ registryService.migrateSkillControls?.();
320
+ const controlService = getEffectiveControlService();
321
+ if (!controlService) throw new Error('Effective control service is unavailable');
322
+ const scopeOptions = await resolveSkillControlScope(req.body);
323
+ const item = controlService.setSkillEnabled({
324
+ controlKey: req.body.controlKey || existing.controlKey || fallbackSkillControlKey(name, platform, scopeOptions),
325
+ platform,
326
+ ...scopeOptions,
327
+ enabled
328
+ });
329
+ return res.json({ success: true, item });
330
+ }
228
331
 
229
- // Apply sync based on new state
332
+ // Commands, agents and plugins retain the legacy registry behaviour.
333
+ const item = registryService.toggleEnabled(type, name, enabled);
230
334
  if (enabled) {
231
335
  // Sync to platforms where platform=true
232
- syncEnabledPlatforms(type, name, item.platforms);
336
+ await syncEnabledPlatforms(type, name, item.platforms);
233
337
  } else {
234
- // Remove from all platforms
235
- removeAllPlatforms(type, name);
338
+ // Remove from all platforms.
339
+ await removeAllPlatforms(type, name);
236
340
  }
237
341
 
238
- res.json({
342
+ return res.json({
239
343
  success: true,
240
344
  item
241
345
  });
242
346
  } catch (err) {
243
347
  console.error('[ConfigRegistry API] Toggle enabled error:', err);
244
- res.status(500).json({
348
+ const status = /Invalid config name|Absolute path|Invalid platform/i.test(err.message || '') ? 400 : 500;
349
+ res.status(status).json({
245
350
  success: false,
246
351
  message: err.message
247
352
  });
@@ -259,8 +364,9 @@ router.put('/:type/:name/toggle', async (req, res) => {
259
364
  */
260
365
  router.put('/:type/:name/platform/:platform', async (req, res) => {
261
366
  try {
262
- const { type, platform } = req.params;
263
- const name = decodeURIComponent(req.params.name);
367
+ const { type } = req.params;
368
+ const platform = String(req.params.platform || '').trim().toLowerCase();
369
+ const name = normalizeRegistryName(decodeURIComponent(req.params.name));
264
370
  const { enabled } = req.body;
265
371
 
266
372
  const typeError = validateType(type);
@@ -295,25 +401,37 @@ router.put('/:type/:name/platform/:platform', async (req, res) => {
295
401
  });
296
402
  }
297
403
 
298
- // Update registry
299
- const item = registryService.togglePlatform(type, name, platform, enabled);
404
+ if (type === 'skills') {
405
+ registryService.migrateSkillControls?.();
406
+ const controlService = getEffectiveControlService();
407
+ if (!controlService) throw new Error('Effective control service is unavailable');
408
+ const scopeOptions = await resolveSkillControlScope(req.body);
409
+ const item = controlService.setSkillEnabled({
410
+ controlKey: req.body.controlKey || existing.controlKey || fallbackSkillControlKey(name, platform, scopeOptions),
411
+ platform,
412
+ ...scopeOptions,
413
+ enabled
414
+ });
415
+ return res.json({ success: true, item });
416
+ }
300
417
 
301
- // Apply sync based on new state
418
+ const item = registryService.togglePlatform(type, name, platform, enabled);
302
419
  if (enabled && item.enabled) {
303
420
  // Sync to this platform (only if item is enabled)
304
- syncPlatform(type, name, platform);
421
+ await syncPlatform(type, name, platform);
305
422
  } else {
306
- // Remove from this platform
307
- removePlatform(type, name, platform);
423
+ // Remove from this platform.
424
+ await removePlatform(type, name, platform);
308
425
  }
309
426
 
310
- res.json({
427
+ return res.json({
311
428
  success: true,
312
429
  item
313
430
  });
314
431
  } catch (err) {
315
432
  console.error('[ConfigRegistry API] Toggle platform error:', err);
316
- res.status(500).json({
433
+ const status = /Invalid config name|Absolute path|Invalid platform/i.test(err.message || '') ? 400 : 500;
434
+ res.status(status).json({
317
435
  success: false,
318
436
  message: err.message
319
437
  });
@@ -337,13 +455,42 @@ router.post('/:type/sync', async (req, res) => {
337
455
  });
338
456
  }
339
457
 
340
- // Get all items for this type
341
- const items = registryService.listItems(type);
458
+ const items = type === 'skills'
459
+ ? getEffectiveSkillRegistryItems()
460
+ : registryService.listItems(type);
461
+ if (type === 'skills') {
462
+ const controlService = getEffectiveControlService();
463
+ if (!controlService) throw new Error('Effective control service is unavailable');
464
+ const results = [];
465
+ const errors = [];
466
+ for (const [name, item] of Object.entries(items || {})) {
467
+ for (const platform of getValidPlatforms()) {
468
+ if (!item?.platforms?.[platform]) continue;
469
+ try {
470
+ results.push(controlService.setSkillEnabled({
471
+ controlKey: item.controlKeys?.[platform] || item.controlKey || fallbackSkillControlKey(name, platform, { scope: 'user', projectPath: null }),
472
+ platform,
473
+ scope: 'user',
474
+ enabled: true
475
+ }));
476
+ } catch (error) {
477
+ errors.push({ name, platform, message: error.message });
478
+ }
479
+ }
480
+ }
481
+ return res.json({
482
+ success: true,
483
+ type,
484
+ synced: results.filter(result => result.enabled).length,
485
+ removed: 0,
486
+ errors,
487
+ warnings: []
488
+ });
489
+ }
342
490
 
343
491
  // Sync all items based on their registry state
344
- const result = syncManager.syncAll(type, items);
345
-
346
- res.json({
492
+ const result = await syncManager.syncAll(type, items);
493
+ return res.json({
347
494
  success: true,
348
495
  type,
349
496
  synced: result.synced.length,
@@ -352,7 +499,7 @@ router.post('/:type/sync', async (req, res) => {
352
499
  warnings: result.warnings
353
500
  });
354
501
  } catch (err) {
355
- console.error('[ConfigRegistry API] Sync all error:', err);
502
+ console.error('[ConfigRegistry API] Sync error:', err);
356
503
  res.status(500).json({
357
504
  success: false,
358
505
  message: err.message