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
@@ -18,12 +18,12 @@
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
20
  const os = require('os');
21
- const toml = require('toml');
22
- const tomlStringify = require('@iarna/toml').stringify;
23
- const { convertSkillToCodex, convertCommandToCodex, convertCommandToGemini } = require('./format-converter');
24
21
  const { PATHS, NATIVE_PATHS, HOME_DIR, ensureStorageDirMigrated } = require('../../config/paths');
22
+ const { getPlatformContext } = require('../platform-context');
23
+ const { assertNoSymlinkComponents } = require('../../shared/project-config');
24
+ const { CONFIG_TYPES } = require('../../shared/driver-factories/resource-sync');
25
25
 
26
- // Paths
26
+ // Paths retained for the compatibility facade and existing consumers.
27
27
  const HOME = HOME_DIR || os.homedir();
28
28
  const CC_TOOL_CONFIGS = PATHS.configs;
29
29
  const CLAUDE_CODE_DIR = NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings) || path.join(HOME, '.claude');
@@ -33,67 +33,11 @@ const OPENCODE_DIR = NATIVE_PATHS.opencode.config;
33
33
  const OMP_AGENT_DIR = NATIVE_PATHS.omp.dir;
34
34
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
35
35
 
36
- // Config type definitions
37
- const CONFIG_TYPES = {
38
- skills: {
39
- isDirectory: true,
40
- markerFile: 'SKILL.md',
41
- claudeTarget: 'skills',
42
- codexTarget: 'skills',
43
- codexSupported: true,
44
- convertForCodex: true,
45
- geminiTarget: 'skills',
46
- geminiSupported: true,
47
- opencodeTarget: 'skills',
48
- opencodeLegacyTarget: 'skill',
49
- opencodeSupported: true,
50
- ompTarget: 'skills',
51
- ompSupported: true
52
- },
53
- commands: {
54
- isDirectory: false,
55
- extension: '.md',
56
- claudeTarget: 'commands',
57
- codexTarget: 'prompts',
58
- codexSupported: true,
59
- convertForCodex: true,
60
- geminiTarget: 'commands',
61
- geminiExtension: '.toml',
62
- geminiSupported: true,
63
- opencodeTarget: 'commands',
64
- opencodeLegacyTarget: 'command',
65
- opencodeSupported: true,
66
- ompTarget: 'commands',
67
- ompSupported: true
68
- },
69
- agents: {
70
- isDirectory: false,
71
- extension: '.md',
72
- claudeTarget: 'agents',
73
- codexSupported: true,
74
- geminiTarget: 'agents',
75
- geminiSupported: true,
76
- opencodeTarget: 'agents',
77
- opencodeLegacyTarget: 'agent',
78
- opencodeSupported: true,
79
- ompSupported: false
80
- },
81
- plugins: {
82
- isDirectory: true,
83
- claudeTarget: 'plugins',
84
- codexSupported: false,
85
- geminiSupported: false,
86
- opencodeTarget: 'plugins',
87
- opencodeLegacyTarget: 'plugin',
88
- opencodeSupported: true,
89
- ompTarget: 'extensions',
90
- ompSupported: true
91
- }
92
- };
93
-
94
36
  class ConfigSyncManager {
95
- constructor() {
37
+ constructor({ registry, runtime } = {}) {
96
38
  ensureStorageDirMigrated();
39
+ this.registry = registry || getPlatformContext().registry;
40
+ this.runtime = runtime || getPlatformContext().runtime;
97
41
  this.ccToolConfigs = CC_TOOL_CONFIGS;
98
42
  this.claudeDir = CLAUDE_CODE_DIR;
99
43
  this.codexDir = CODEX_DIR;
@@ -103,565 +47,150 @@ class ConfigSyncManager {
103
47
  this.configTypes = CONFIG_TYPES;
104
48
  }
105
49
 
106
- /**
107
- * Sync a config item to Claude Code
108
- * @param {string} type - Config type (skills, commands, agents, plugins)
109
- * @param {string} name - Item name (directory name for skills, file path for others)
110
- * @returns {Object} Result with success status
111
- */
112
- syncToClaude(type, name) {
113
- const config = this.configTypes[type];
114
- if (!config) {
115
- console.log(`[ConfigSyncManager] Unknown config type: ${type}`);
116
- return { success: false, error: `Unknown config type: ${type}` };
117
- }
118
-
119
- const safeName = this._normalizeSafeRelativeName(name);
120
- if (!safeName) {
121
- return { success: false, error: 'Invalid config item name' };
122
- }
123
- const sourcePath = path.join(this.ccToolConfigs, type, safeName);
124
- const targetPath = path.join(this.claudeDir, config.claudeTarget, safeName);
125
-
126
- // Check if source exists
127
- if (!fs.existsSync(sourcePath)) {
128
- console.log(`[ConfigSyncManager] Source not found: ${sourcePath}`);
129
- return { success: false, error: 'Source not found' };
130
- }
131
-
50
+ _resolveSourcePath(type, safeName, sourcePathOverride = null) {
51
+ const defaultPath = path.join(this.ccToolConfigs, type, safeName);
52
+ const sourcePath = sourcePathOverride
53
+ ? (() => {
54
+ if (typeof sourcePathOverride !== 'string' || !path.isAbsolute(sourcePathOverride)) {
55
+ throw new Error('Controlled source path must be an absolute path');
56
+ }
57
+ return path.resolve(sourcePathOverride);
58
+ })()
59
+ : defaultPath;
60
+ const allowedRoots = [
61
+ path.join(this.ccToolConfigs, type),
62
+ PATHS.skillArtifacts
63
+ ].filter(Boolean).map(root => path.resolve(root));
64
+ const allowedRoot = allowedRoots.find(root => (
65
+ sourcePath === root || sourcePath.startsWith(`${root}${path.sep}`)
66
+ ));
67
+ if (!allowedRoot) throw new Error('Controlled source path escapes allowed storage');
68
+ assertNoSymlinkComponents(allowedRoot, sourcePath, fs);
69
+ if (sourcePathOverride && !fs.existsSync(sourcePath)) {
70
+ throw new Error('Controlled source path not found');
71
+ }
72
+ return sourcePath;
73
+ }
74
+
75
+ syncToPlatform(platform, type, name, sourcePathOverride = null) {
76
+ const key = String(platform || '').trim().toLowerCase();
132
77
  try {
133
- if (config.isDirectory) {
134
- // Copy entire directory recursively
135
- this._ensureDir(path.dirname(targetPath));
136
- this._copyDirRecursive(sourcePath, targetPath);
137
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Claude Code (directory)`);
138
- } else {
139
- // Copy single file, preserving subdirectory structure
140
- this._ensureDir(path.dirname(targetPath));
141
- this._copyFile(sourcePath, targetPath);
142
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Claude Code (file)`);
78
+ const driver = this.runtime?.getDriver?.(key, 'resourceSync');
79
+ if (!driver || (typeof driver.status === 'string' && driver.status !== 'ok')) {
80
+ return { status: 'unsupported', platform: key, capability: 'resourceSync', operation: 'sync' };
143
81
  }
144
-
145
- return { success: true, target: targetPath };
146
- } catch (err) {
147
- console.error(`[ConfigSyncManager] Sync to Claude failed:`, err.message);
148
- return { success: false, error: err.message };
149
- }
150
- }
151
-
152
- /**
153
- * Remove a config item from Claude Code
154
- * @param {string} type - Config type
155
- * @param {string} name - Item name
156
- * @returns {Object} Result with success status
157
- */
158
- removeFromClaude(type, name) {
159
- const config = this.configTypes[type];
160
- if (!config) {
161
- return { success: false, error: `Unknown config type: ${type}` };
162
- }
163
-
164
- const safeName = this._normalizeSafeRelativeName(name);
165
- if (!safeName) {
166
- return { success: false, error: 'Invalid config item name' };
167
- }
168
- const targetPath = path.join(this.claudeDir, config.claudeTarget, safeName);
169
-
170
- if (!fs.existsSync(targetPath)) {
171
- console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
172
- return { success: true, message: 'Already removed' };
173
- }
174
-
175
- try {
176
- if (config.isDirectory) {
177
- // Remove entire directory
178
- this._removeRecursive(targetPath);
179
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from Claude Code (directory)`);
180
- } else {
181
- // Remove file
182
- fs.unlinkSync(targetPath);
183
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from Claude Code (file)`);
184
-
185
- // Clean up empty parent directories for file-based configs
186
- this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.claudeDir, config.claudeTarget));
82
+ if (typeof driver.sync !== 'function') {
83
+ return { status: 'unsupported', platform: key, capability: 'resourceSync', operation: 'sync' };
187
84
  }
188
-
189
- return { success: true };
190
- } catch (err) {
191
- console.error(`[ConfigSyncManager] Remove from Claude failed:`, err.message);
192
- return { success: false, error: err.message };
85
+ const safeName = this._normalizeSafeRelativeName(name);
86
+ if (!safeName) {
87
+ return { status: 'invalid', platform: key, capability: 'resourceSync', operation: 'sync', error: 'Invalid config item name' };
88
+ }
89
+ const sourcePath = this._resolveSourcePath(type, safeName, sourcePathOverride);
90
+ const syncArgs = sourcePathOverride || driver.sync.length >= 3
91
+ ? [type, safeName, sourcePath]
92
+ : [type, safeName];
93
+ return driver.sync(...syncArgs);
94
+ } catch (error) {
95
+ return {
96
+ status: 'failed',
97
+ platform: key,
98
+ capability: 'resourceSync',
99
+ operation: 'sync',
100
+ error: error.message
101
+ };
193
102
  }
194
103
  }
195
104
 
196
- /**
197
- * Sync a config item to Codex CLI
198
- * Supports skills, commands, agents
199
- * @param {string} type - Config type
200
- * @param {string} name - Item name
201
- * @returns {Object} Result with success status and any warnings
202
- */
203
- syncToCodex(type, name) {
204
- const config = this.configTypes[type];
205
- if (!config) {
206
- return { success: false, error: `Unknown config type: ${type}` };
207
- }
208
-
209
- if (!config.codexSupported) {
210
- console.log(`[ConfigSyncManager] ${type} not supported by Codex, skipping`);
211
- return { success: true, skipped: true, reason: 'Not supported by Codex' };
212
- }
213
-
214
- const safeName = this._normalizeSafeRelativeName(name);
215
- if (!safeName) {
216
- return { success: false, error: 'Invalid config item name' };
217
- }
218
- const sourcePath = path.join(this.ccToolConfigs, type, safeName);
219
-
220
- if (!fs.existsSync(sourcePath)) {
221
- console.log(`[ConfigSyncManager] Source not found: ${sourcePath}`);
222
- return { success: false, error: 'Source not found' };
223
- }
224
-
105
+ removeFromPlatform(platform, type, name) {
106
+ const key = String(platform || '').trim().toLowerCase();
225
107
  try {
226
- const warnings = [];
227
-
228
- if (type === 'agents') {
229
- const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
230
- const { frontmatter } = this._parseFrontmatter(sourceContent);
231
- const fileName = path.basename(safeName, path.extname(safeName));
232
-
233
- const codexConfig = this._readCodexConfigToml();
234
- codexConfig.features = this._isPlainObject(codexConfig.features) ? codexConfig.features : {};
235
- codexConfig.features.multi_agent = true;
236
- codexConfig.agents = this._isPlainObject(codexConfig.agents) ? codexConfig.agents : {};
237
-
238
- const existing = codexConfig.agents[fileName];
239
- if (Object.prototype.hasOwnProperty.call(codexConfig.agents, fileName) &&
240
- !this._isPlainObject(existing)) {
241
- return { success: false, error: `Agent name "${fileName}" conflicts with global [agents] key` };
242
- }
243
-
244
- const entry = this._isPlainObject(existing) ? { ...existing } : {};
245
- entry.description = (frontmatter.description || fileName).trim();
246
-
247
- const model = typeof frontmatter.model === 'string' ? frontmatter.model.trim() : '';
248
- const existingConfigFile = this._normalizeCodexPath(entry.config_file);
249
- const isExistingManagedConfig = this._isManagedCodexAgentConfigPath(existingConfigFile);
250
- if (model) {
251
- const managedConfigPath = isExistingManagedConfig
252
- ? existingConfigFile
253
- : this._getCodexManagedAgentConfigPath(fileName);
254
- const parsedConfigFile = this._readCodexAgentConfigFile(managedConfigPath);
255
- const configData = this._isPlainObject(parsedConfigFile?.data) ? parsedConfigFile.data : {};
256
- configData.model = model;
257
- this._writeCodexAgentConfigFile(managedConfigPath, configData);
258
- entry.config_file = managedConfigPath;
259
- } else if (isExistingManagedConfig) {
260
- const resolvedConfigPath = this._resolveCodexPath(existingConfigFile);
261
- if (resolvedConfigPath && fs.existsSync(resolvedConfigPath)) {
262
- fs.unlinkSync(resolvedConfigPath);
263
- }
264
- delete entry.config_file;
265
- }
266
-
267
- codexConfig.agents[fileName] = entry;
268
-
269
- this._writeCodexConfigToml(codexConfig);
270
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Codex (config.toml agents table)`);
271
- return { success: true, target: CODEX_CONFIG_PATH, warnings };
108
+ const driver = this.runtime?.getDriver?.(key, 'resourceSync');
109
+ if (!driver || (typeof driver.status === 'string' && driver.status !== 'ok')) {
110
+ return { status: 'unsupported', platform: key, capability: 'resourceSync', operation: 'remove' };
272
111
  }
273
-
274
- if (type === 'skills') {
275
- // Skills: copy directory, convert SKILL.md content
276
- const targetPath = path.join(this.codexDir, config.codexTarget, safeName);
277
- this._ensureDir(targetPath);
278
-
279
- // Copy all files, converting SKILL.md
280
- this._copyDirWithConversion(sourcePath, targetPath, (filePath, content) => {
281
- if (path.basename(filePath) === 'SKILL.md') {
282
- const result = convertSkillToCodex(content);
283
- if (result.warnings && result.warnings.length > 0) {
284
- warnings.push(...result.warnings);
285
- }
286
- return result.content;
287
- }
288
- return content;
289
- });
290
-
291
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Codex (skill directory)`);
292
- return { success: true, target: targetPath, warnings };
293
-
294
- } else if (type === 'commands') {
295
- // Commands: convert and write to prompts directory
296
- const content = fs.readFileSync(sourcePath, 'utf-8');
297
- const result = convertCommandToCodex(content);
298
-
299
- if (result.warnings && result.warnings.length > 0) {
300
- warnings.push(...result.warnings);
301
- }
302
-
303
- // Target path in codex prompts (same relative path structure)
304
- const targetPath = path.join(this.codexDir, config.codexTarget, safeName);
305
- this._ensureDir(path.dirname(targetPath));
306
- fs.writeFileSync(targetPath, result.content, 'utf-8');
307
-
308
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Codex (prompt)`);
309
- return { success: true, target: targetPath, warnings };
310
- }
311
-
312
- return { success: false, error: 'Unexpected type' };
313
- } catch (err) {
314
- console.error(`[ConfigSyncManager] Sync to Codex failed:`, err.message);
315
- return { success: false, error: err.message };
316
- }
317
- }
318
-
319
- /**
320
- * Remove a config item from Codex CLI
321
- * @param {string} type - Config type
322
- * @param {string} name - Item name
323
- * @returns {Object} Result with success status
324
- */
325
- removeFromCodex(type, name) {
326
- const config = this.configTypes[type];
327
- if (!config) {
328
- return { success: false, error: `Unknown config type: ${type}` };
329
- }
330
-
331
- const safeName = this._normalizeSafeRelativeName(name);
332
- if (!safeName) {
333
- return { success: false, error: 'Invalid config item name' };
334
- }
335
-
336
- if (!config.codexSupported) {
337
- return { success: true, skipped: true, reason: 'Not supported by Codex' };
338
- }
339
-
340
- if (type === 'agents') {
341
- try {
342
- const configData = this._readCodexConfigToml();
343
- const agentsTable = this._isPlainObject(configData.agents) ? configData.agents : {};
344
- const fileName = path.basename(safeName, path.extname(safeName));
345
-
346
- const existing = agentsTable[fileName];
347
- if (!this._isPlainObject(existing)) {
348
- return { success: true, message: 'Already removed' };
349
- }
350
-
351
- const existingConfigFile = this._normalizeCodexPath(existing.config_file);
352
- if (existingConfigFile && this._isManagedCodexAgentConfigPath(existingConfigFile)) {
353
- const resolvedPath = this._resolveCodexPath(existingConfigFile);
354
- if (resolvedPath && fs.existsSync(resolvedPath)) {
355
- fs.unlinkSync(resolvedPath);
356
- }
357
- }
358
-
359
- delete agentsTable[fileName];
360
- configData.agents = agentsTable;
361
- this._writeCodexConfigToml(configData);
362
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from Codex (config.toml agents table)`);
363
- return { success: true };
364
- } catch (err) {
365
- console.error(`[ConfigSyncManager] Remove from Codex failed:`, err.message);
366
- return { success: false, error: err.message };
112
+ if (typeof driver.remove !== 'function') {
113
+ return { status: 'unsupported', platform: key, capability: 'resourceSync', operation: 'remove' };
367
114
  }
368
- }
369
-
370
- const targetPath = path.join(this.codexDir, config.codexTarget, safeName);
371
-
372
- if (!fs.existsSync(targetPath)) {
373
- console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
374
- return { success: true, message: 'Already removed' };
375
- }
376
-
377
- try {
378
- if (type === 'skills') {
379
- // Remove entire directory
380
- this._removeRecursive(targetPath);
381
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from Codex (skill directory)`);
382
- } else {
383
- // Remove file
384
- fs.unlinkSync(targetPath);
385
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from Codex (prompt)`);
386
-
387
- // Clean up empty parent directories
388
- this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.codexDir, config.codexTarget));
115
+ const safeName = this._normalizeSafeRelativeName(name);
116
+ if (!safeName) {
117
+ return { status: 'invalid', platform: key, capability: 'resourceSync', operation: 'remove', error: 'Invalid config item name' };
389
118
  }
390
-
391
- return { success: true };
392
- } catch (err) {
393
- console.error(`[ConfigSyncManager] Remove from Codex failed:`, err.message);
394
- return { success: false, error: err.message };
119
+ return driver.remove(type, safeName);
120
+ } catch (error) {
121
+ return {
122
+ status: 'failed',
123
+ platform: key,
124
+ capability: 'resourceSync',
125
+ operation: 'remove',
126
+ error: error.message
127
+ };
395
128
  }
396
129
  }
397
130
 
398
- /**
399
- * Sync a config item to Gemini CLI
400
- * Supports skills, commands, and agents.
401
- * @param {string} type - Config type
402
- * @param {string} name - Item name
403
- * @returns {Object} Result with success status
404
- */
405
- syncToGemini(type, name) {
406
- const config = this.configTypes[type];
407
- if (!config) {
408
- return { success: false, error: `Unknown config type: ${type}` };
409
- }
410
-
411
- if (!config.geminiSupported) {
412
- console.log(`[ConfigSyncManager] ${type} not supported by Gemini, skipping`);
413
- return { success: true, skipped: true, reason: 'Not supported by Gemini' };
414
- }
415
-
416
- const safeName = this._normalizeSafeRelativeName(name);
417
- if (!safeName) {
418
- return { success: false, error: 'Invalid config item name' };
419
- }
420
- const sourcePath = path.join(this.ccToolConfigs, type, safeName);
421
- if (!fs.existsSync(sourcePath)) {
422
- console.log(`[ConfigSyncManager] Source not found: ${sourcePath}`);
423
- return { success: false, error: 'Source not found' };
424
- }
425
-
131
+ _runResourceOperation(platform, operation, args) {
132
+ const key = String(platform || '').trim().toLowerCase();
426
133
  try {
427
- const targetPath = this._getGeminiTargetPath(config, safeName);
428
- this._ensureDir(path.dirname(targetPath));
429
- if (config.isDirectory) {
430
- this._copyDirRecursive(sourcePath, targetPath);
431
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Gemini (directory)`);
432
- } else if (type === 'commands') {
433
- const content = fs.readFileSync(sourcePath, 'utf-8');
434
- const result = convertCommandToGemini(content);
435
- fs.writeFileSync(targetPath, result.content, 'utf-8');
436
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Gemini (command TOML)`);
437
- return { success: true, target: targetPath, warnings: result.warnings || [] };
438
- } else {
439
- this._copyFile(sourcePath, targetPath);
440
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to Gemini (file)`);
134
+ const driver = this.runtime?.getDriver?.(key, 'resourceSync');
135
+ if (!driver || typeof driver[operation] !== 'function') {
136
+ return { success: false, error: `Resource sync is not supported by ${key}` };
441
137
  }
442
- return { success: true, target: targetPath };
443
- } catch (err) {
444
- console.error(`[ConfigSyncManager] Sync to Gemini failed:`, err.message);
445
- return { success: false, error: err.message };
138
+ const normalize = result => {
139
+ if (result?.status === 'ok') return result.data;
140
+ return {
141
+ success: false,
142
+ error: result?.error || `Resource sync ${operation} failed for ${key}`
143
+ };
144
+ };
145
+ const result = driver[operation](...args);
146
+ return result && typeof result.then === 'function' ? result.then(normalize) : normalize(result);
147
+ } catch (error) {
148
+ return { success: false, error: error.message };
446
149
  }
447
150
  }
448
151
 
449
152
  /**
450
- * Remove a config item from Gemini CLI
451
- * @param {string} type - Config type
452
- * @param {string} name - Item name
453
- * @returns {Object} Result with success status
153
+ * Compatibility methods for callers that still use the old manager API.
154
+ * All platform behavior is implemented by the resourceSync Driver.
454
155
  */
455
- removeFromGemini(type, name) {
456
- const config = this.configTypes[type];
457
- if (!config) {
458
- return { success: false, error: `Unknown config type: ${type}` };
459
- }
460
-
461
- const safeName = this._normalizeSafeRelativeName(name);
462
- if (!safeName) {
463
- return { success: false, error: 'Invalid config item name' };
464
- }
465
-
466
- if (!config.geminiSupported) {
467
- return { success: true, skipped: true, reason: 'Not supported by Gemini' };
468
- }
469
-
470
- const targetPath = this._getGeminiTargetPath(config, safeName);
471
- if (!fs.existsSync(targetPath)) {
472
- console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
473
- return { success: true, message: 'Already removed' };
474
- }
475
-
476
- try {
477
- if (config.isDirectory) {
478
- this._removeRecursive(targetPath);
479
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from Gemini (directory)`);
480
- } else {
481
- fs.unlinkSync(targetPath);
482
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from Gemini (file)`);
483
- this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.geminiDir, config.geminiTarget));
484
- }
485
- return { success: true };
486
- } catch (err) {
487
- console.error(`[ConfigSyncManager] Remove from Gemini failed:`, err.message);
488
- return { success: false, error: err.message };
489
- }
156
+ syncToClaude(type, name, sourcePathOverride = null) {
157
+ return this._runResourceOperation('claude', 'sync', [type, name, sourcePathOverride]);
490
158
  }
491
159
 
492
- /**
493
- * Sync a config item to OpenCode CLI
494
- * Supports skills, commands, agents, plugins
495
- * @param {string} type - Config type
496
- * @param {string} name - Item name
497
- * @returns {Object} Result with success status
498
- */
499
- syncToOpenCode(type, name) {
500
- const config = this.configTypes[type];
501
- if (!config) {
502
- return { success: false, error: `Unknown config type: ${type}` };
503
- }
160
+ removeFromClaude(type, name) {
161
+ return this._runResourceOperation('claude', 'remove', [type, name]);
162
+ }
504
163
 
505
- if (!config.opencodeSupported) {
506
- console.log(`[ConfigSyncManager] ${type} not supported by OpenCode, skipping`);
507
- return { success: true, skipped: true, reason: 'Not supported by OpenCode' };
508
- }
164
+ syncToCodex(type, name, sourcePathOverride = null) {
165
+ return this._runResourceOperation('codex', 'sync', [type, name, sourcePathOverride]);
166
+ }
509
167
 
510
- const safeName = this._normalizeSafeRelativeName(name);
511
- if (!safeName) {
512
- return { success: false, error: 'Invalid config item name' };
513
- }
514
- const sourcePath = path.join(this.ccToolConfigs, type, safeName);
515
- if (!fs.existsSync(sourcePath)) {
516
- console.log(`[ConfigSyncManager] Source not found: ${sourcePath}`);
517
- return { success: false, error: 'Source not found' };
518
- }
168
+ removeFromCodex(type, name) {
169
+ return this._runResourceOperation('codex', 'remove', [type, name]);
170
+ }
519
171
 
520
- try {
521
- const targetBaseDir = this._getOpenCodeTypeBaseDir(config);
522
- const targetPath = path.join(targetBaseDir, safeName);
172
+ syncToGemini(type, name, sourcePathOverride = null) {
173
+ return this._runResourceOperation('gemini', 'sync', [type, name, sourcePathOverride]);
174
+ }
523
175
 
524
- if (config.isDirectory) {
525
- this._ensureDir(path.dirname(targetPath));
526
- this._copyDirRecursive(sourcePath, targetPath);
527
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to OpenCode (directory)`);
528
- } else {
529
- this._ensureDir(path.dirname(targetPath));
530
- this._copyFile(sourcePath, targetPath);
531
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to OpenCode (file)`);
532
- }
176
+ removeFromGemini(type, name) {
177
+ return this._runResourceOperation('gemini', 'remove', [type, name]);
178
+ }
533
179
 
534
- return { success: true, target: targetPath };
535
- } catch (err) {
536
- console.error(`[ConfigSyncManager] Sync to OpenCode failed:`, err.message);
537
- return { success: false, error: err.message };
538
- }
180
+ syncToOpenCode(type, name, sourcePathOverride = null) {
181
+ return this._runResourceOperation('opencode', 'sync', [type, name, sourcePathOverride]);
539
182
  }
540
183
 
541
- /**
542
- * Remove a config item from OpenCode CLI
543
- * @param {string} type - Config type
544
- * @param {string} name - Item name
545
- * @returns {Object} Result with success status
546
- */
547
184
  removeFromOpenCode(type, name) {
548
- const config = this.configTypes[type];
549
- if (!config) {
550
- return { success: false, error: `Unknown config type: ${type}` };
551
- }
552
-
553
- const safeName = this._normalizeSafeRelativeName(name);
554
- if (!safeName) {
555
- return { success: false, error: 'Invalid config item name' };
556
- }
557
-
558
- if (!config.opencodeSupported) {
559
- return { success: true, skipped: true, reason: 'Not supported by OpenCode' };
560
- }
561
-
562
- const targetBaseDir = this._getOpenCodeTypeBaseDir(config);
563
- const targetPath = path.join(targetBaseDir, safeName);
564
-
565
- if (!fs.existsSync(targetPath)) {
566
- console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
567
- return { success: true, message: 'Already removed' };
568
- }
569
-
570
- try {
571
- if (config.isDirectory) {
572
- this._removeRecursive(targetPath);
573
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from OpenCode (directory)`);
574
- } else {
575
- fs.unlinkSync(targetPath);
576
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from OpenCode (file)`);
577
- this._cleanupEmptyParents(path.dirname(targetPath), targetBaseDir);
578
- }
579
-
580
- return { success: true };
581
- } catch (err) {
582
- console.error(`[ConfigSyncManager] Remove from OpenCode failed:`, err.message);
583
- return { success: false, error: err.message };
584
- }
185
+ return this._runResourceOperation('opencode', 'remove', [type, name]);
585
186
  }
586
187
 
587
- /**
588
- * Sync a config item to OMP.
589
- * OMP treats commands as slash-command files and plugins as extensions/packages.
590
- */
591
- syncToOmp(type, name) {
592
- const config = this.configTypes[type];
593
- if (!config) {
594
- return { success: false, error: `Unknown config type: ${type}` };
595
- }
596
-
597
- if (!config.ompSupported) {
598
- console.log(`[ConfigSyncManager] ${type} not supported natively by OMP, skipping`);
599
- return { success: true, skipped: true, reason: 'Not supported natively by OMP' };
600
- }
601
-
602
- const safeName = this._normalizeSafeRelativeName(name);
603
- if (!safeName) {
604
- return { success: false, error: 'Invalid config item name' };
605
- }
606
-
607
- const sourcePath = path.join(this.ccToolConfigs, type, safeName);
608
- if (!fs.existsSync(sourcePath)) {
609
- console.log(`[ConfigSyncManager] Source not found: ${sourcePath}`);
610
- return { success: false, error: 'Source not found' };
611
- }
612
-
613
- const targetPath = path.join(this.ompDir, config.ompTarget, safeName);
614
- try {
615
- if (config.isDirectory) {
616
- this._ensureDir(path.dirname(targetPath));
617
- this._copyDirRecursive(sourcePath, targetPath);
618
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to OMP (directory)`);
619
- } else {
620
- this._ensureDir(path.dirname(targetPath));
621
- this._copyFile(sourcePath, targetPath);
622
- console.log(`[ConfigSyncManager] Synced ${type}/${name} to OMP (file)`);
623
- }
624
-
625
- return { success: true, target: targetPath };
626
- } catch (err) {
627
- console.error('[ConfigSyncManager] Sync to OMP failed:', err.message);
628
- return { success: false, error: err.message };
629
- }
188
+ syncToOmp(type, name, sourcePathOverride = null) {
189
+ return this._runResourceOperation('omp', 'sync', [type, name, sourcePathOverride]);
630
190
  }
631
191
 
632
192
  removeFromOmp(type, name) {
633
- const config = this.configTypes[type];
634
- if (!config) {
635
- return { success: false, error: `Unknown config type: ${type}` };
636
- }
637
-
638
- const safeName = this._normalizeSafeRelativeName(name);
639
- if (!safeName) {
640
- return { success: false, error: 'Invalid config item name' };
641
- }
642
-
643
- if (!config.ompSupported) {
644
- return { success: true, skipped: true, reason: 'Not supported natively by OMP' };
645
- }
646
-
647
- const targetPath = path.join(this.ompDir, config.ompTarget, safeName);
648
- if (!fs.existsSync(targetPath)) {
649
- return { success: true, message: 'Already removed' };
650
- }
651
-
652
- try {
653
- if (config.isDirectory) {
654
- this._removeRecursive(targetPath);
655
- } else {
656
- fs.unlinkSync(targetPath);
657
- this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.ompDir, config.ompTarget));
658
- }
659
- console.log(`[ConfigSyncManager] Removed ${type}/${name} from OMP`);
660
- return { success: true };
661
- } catch (err) {
662
- console.error('[ConfigSyncManager] Remove from OMP failed:', err.message);
663
- return { success: false, error: err.message };
664
- }
193
+ return this._runResourceOperation('omp', 'remove', [type, name]);
665
194
  }
666
195
 
667
196
  /**
@@ -682,264 +211,74 @@ class ConfigSyncManager {
682
211
  return results;
683
212
  }
684
213
 
685
- for (const [name, item] of Object.entries(registryItems)) {
686
- if (!item || typeof item !== 'object') continue;
687
-
688
- const { enabled, platforms } = item;
689
-
690
- if (enabled && platforms) {
691
- // Sync to enabled platforms
692
- if (platforms.claude) {
693
- const result = this.syncToClaude(type, name);
694
- if (result.success && !result.skipped) {
695
- results.synced.push({ type, name, platform: 'claude' });
696
- } else if (!result.success) {
697
- results.errors.push({ type, name, platform: 'claude', error: result.error });
698
- }
699
- } else {
700
- // Platform disabled, remove
701
- const result = this.removeFromClaude(type, name);
702
- if (result.success && !result.message) {
703
- results.removed.push({ type, name, platform: 'claude' });
704
- }
705
- }
706
-
707
- if (platforms.codex) {
708
- const result = this.syncToCodex(type, name);
709
- if (result.success && !result.skipped) {
710
- results.synced.push({ type, name, platform: 'codex' });
711
- if (result.warnings && result.warnings.length > 0) {
712
- results.warnings.push({ type, name, platform: 'codex', warnings: result.warnings });
713
- }
714
- } else if (!result.success) {
715
- results.errors.push({ type, name, platform: 'codex', error: result.error });
716
- }
717
- } else {
718
- // Platform disabled, remove
719
- const result = this.removeFromCodex(type, name);
720
- if (result.success && !result.message && !result.skipped) {
721
- results.removed.push({ type, name, platform: 'codex' });
722
- }
723
- }
724
-
725
- if (platforms.gemini) {
726
- const result = this.syncToGemini(type, name);
727
- if (result.success && !result.skipped) {
728
- results.synced.push({ type, name, platform: 'gemini' });
729
- if (result.warnings && result.warnings.length > 0) {
730
- results.warnings.push({ type, name, platform: 'gemini', warnings: result.warnings });
731
- }
732
- } else if (!result.success) {
733
- results.errors.push({ type, name, platform: 'gemini', error: result.error });
734
- }
735
- } else {
736
- const result = this.removeFromGemini(type, name);
737
- if (result.success && !result.message && !result.skipped) {
738
- results.removed.push({ type, name, platform: 'gemini' });
739
- }
740
- }
741
-
742
- if (platforms.opencode) {
743
- const result = this.syncToOpenCode(type, name);
744
- if (result.success && !result.skipped) {
745
- results.synced.push({ type, name, platform: 'opencode' });
746
- } else if (!result.success) {
747
- results.errors.push({ type, name, platform: 'opencode', error: result.error });
748
- }
749
- } else {
750
- const result = this.removeFromOpenCode(type, name);
751
- if (result.success && !result.message && !result.skipped) {
752
- results.removed.push({ type, name, platform: 'opencode' });
753
- }
754
- }
214
+ let platforms = [];
215
+ try {
216
+ platforms = this.registry?.list?.() || [];
217
+ } catch (error) {
218
+ results.errors.push({ type, operation: 'list-platforms', error: error.message });
219
+ return results;
220
+ }
755
221
 
756
- if (platforms.omp) {
757
- const result = this.syncToOmp(type, name);
758
- if (result.success && !result.skipped) {
759
- results.synced.push({ type, name, platform: 'omp' });
760
- } else if (!result.success) {
761
- results.errors.push({ type, name, platform: 'omp', error: result.error });
762
- }
763
- } else {
764
- const result = this.removeFromOmp(type, name);
765
- if (result.success && !result.message && !result.skipped) {
766
- results.removed.push({ type, name, platform: 'omp' });
222
+ const definitions = new Map(
223
+ platforms
224
+ .filter(platform => platform && platform.key)
225
+ .map(platform => [String(platform.key).trim().toLowerCase(), platform])
226
+ );
227
+
228
+ const invoke = (platform, operation, name) => (
229
+ operation === 'sync'
230
+ ? this.syncToPlatform(platform, type, name)
231
+ : this.removeFromPlatform(platform, type, name)
232
+ );
233
+
234
+ const isSuccessful = result => result?.success === true || result?.status === 'ok';
235
+ const isSkipped = result => result?.skipped === true || result?.status === 'unsupported';
236
+ const appendResult = (name, platform, operation, result) => {
237
+ if (isSkipped(result)) return;
238
+ if (isSuccessful(result)) {
239
+ if (operation === 'sync') {
240
+ results.synced.push({ type, name, platform });
241
+ if (Array.isArray(result.warnings) && result.warnings.length > 0) {
242
+ results.warnings.push({ type, name, platform, warnings: result.warnings });
767
243
  }
244
+ } else if (!result.message) {
245
+ results.removed.push({ type, name, platform });
768
246
  }
769
247
  } else {
770
- // Item disabled, remove from all platforms
771
- const claudeResult = this.removeFromClaude(type, name);
772
- if (claudeResult.success && !claudeResult.message) {
773
- results.removed.push({ type, name, platform: 'claude' });
774
- }
775
-
776
- const codexResult = this.removeFromCodex(type, name);
777
- if (codexResult.success && !codexResult.message && !codexResult.skipped) {
778
- results.removed.push({ type, name, platform: 'codex' });
779
- }
780
-
781
- const geminiResult = this.removeFromGemini(type, name);
782
- if (geminiResult.success && !geminiResult.message && !geminiResult.skipped) {
783
- results.removed.push({ type, name, platform: 'gemini' });
784
- }
785
-
786
- const opencodeResult = this.removeFromOpenCode(type, name);
787
- if (opencodeResult.success && !opencodeResult.message && !opencodeResult.skipped) {
788
- results.removed.push({ type, name, platform: 'opencode' });
789
- }
790
-
791
- const ompResult = this.removeFromOmp(type, name);
792
- if (ompResult.success && !ompResult.message && !ompResult.skipped) {
793
- results.removed.push({ type, name, platform: 'omp' });
794
- }
795
- }
796
- }
797
-
798
- console.log(`[ConfigSyncManager] syncAll(${type}): synced=${results.synced.length}, removed=${results.removed.length}, errors=${results.errors.length}`);
799
- return results;
800
- }
801
-
802
- // ==================== Helper Methods ====================
803
-
804
- /**
805
- * Ensure a directory exists
806
- */
807
- _ensureDir(dir) {
808
- if (!fs.existsSync(dir)) {
809
- fs.mkdirSync(dir, { recursive: true });
810
- }
811
- }
812
-
813
- /**
814
- * Recursively copy a directory
815
- */
816
- _copyDirRecursive(src, dest) {
817
- this._ensureDir(dest);
818
-
819
- const entries = fs.readdirSync(src, { withFileTypes: true });
820
-
821
- for (const entry of entries) {
822
- const srcPath = path.join(src, entry.name);
823
- const destPath = path.join(dest, entry.name);
824
-
825
- if (entry.isDirectory()) {
826
- this._copyDirRecursive(srcPath, destPath);
827
- } else {
828
- fs.copyFileSync(srcPath, destPath);
248
+ results.errors.push({
249
+ type,
250
+ name,
251
+ platform,
252
+ error: result?.error || `${operation} failed`
253
+ });
829
254
  }
830
- }
831
- }
832
-
833
- /**
834
- * Copy a directory with content transformation
835
- * @param {string} src - Source directory
836
- * @param {string} dest - Destination directory
837
- * @param {Function} transform - Function(filePath, content) => transformedContent
838
- */
839
- _copyDirWithConversion(src, dest, transform) {
840
- this._ensureDir(dest);
841
-
842
- const entries = fs.readdirSync(src, { withFileTypes: true });
843
-
844
- for (const entry of entries) {
845
- const srcPath = path.join(src, entry.name);
846
- const destPath = path.join(dest, entry.name);
847
-
848
- if (entry.isDirectory()) {
849
- this._copyDirWithConversion(srcPath, destPath, transform);
850
- } else {
851
- // Check if it's a text file that should be transformed
852
- const ext = path.extname(entry.name).toLowerCase();
853
- const textExtensions = ['.md', '.txt', '.json', '.js', '.ts', '.py', '.sh', '.yaml', '.yml'];
255
+ };
854
256
 
855
- if (textExtensions.includes(ext)) {
856
- const content = fs.readFileSync(srcPath, 'utf-8');
857
- const transformed = transform(srcPath, content);
858
- fs.writeFileSync(destPath, transformed, 'utf-8');
257
+ const pending = [];
258
+ for (const [name, item] of Object.entries(registryItems)) {
259
+ if (!item || typeof item !== 'object') continue;
260
+ const active = item.enabled === true && item.platforms && typeof item.platforms === 'object';
261
+
262
+ for (const platform of definitions.keys()) {
263
+ const definition = definitions.get(platform);
264
+ if (definition?.resourceTypes?.[type] === false) continue;
265
+ const shouldSync = active && item.platforms[platform] === true;
266
+ const operation = shouldSync ? 'sync' : 'remove';
267
+ const result = invoke(platform, operation, name);
268
+ if (result && typeof result.then === 'function') {
269
+ pending.push(result.then(value => appendResult(name, platform, operation, value)));
859
270
  } else {
860
- // Binary file, copy as-is
861
- fs.copyFileSync(srcPath, destPath);
271
+ appendResult(name, platform, operation, result);
862
272
  }
863
273
  }
864
274
  }
865
- }
866
-
867
- /**
868
- * Copy a single file
869
- */
870
- _copyFile(src, dest) {
871
- fs.copyFileSync(src, dest);
872
- }
873
-
874
- /**
875
- * Resolve OpenCode base target directory for a config type.
876
- * OpenCode supports both plural (new) and singular (legacy) folder names.
877
- */
878
- _getOpenCodeTypeBaseDir(config) {
879
- const modernDir = path.join(this.opencodeDir, config.opencodeTarget);
880
- // 技能目录强制使用 modern/plural 形式,避免 legacy 目录带来的跨平台历史污染
881
- if (config === this.configTypes.skills) {
882
- return modernDir;
883
- }
884
-
885
- if (!config.opencodeLegacyTarget) {
886
- return modernDir;
887
- }
888
-
889
- const legacyDir = path.join(this.opencodeDir, config.opencodeLegacyTarget);
890
- if (fs.existsSync(legacyDir) && !fs.existsSync(modernDir)) {
891
- return legacyDir;
892
- }
893
-
894
- return modernDir;
895
- }
896
-
897
- _getGeminiTargetPath(config, safeName) {
898
- const targetName = config.geminiExtension
899
- ? safeName.replace(new RegExp(`${this._escapeRegExp(path.extname(safeName))}$`, 'i'), config.geminiExtension)
900
- : safeName;
901
- return path.join(this.geminiDir, config.geminiTarget, targetName);
902
- }
903
-
904
- _escapeRegExp(value) {
905
- return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
906
- }
907
-
908
- /**
909
- * Recursively remove a file or directory
910
- */
911
- _removeRecursive(target) {
912
- if (!fs.existsSync(target)) {
913
- return;
914
- }
915
-
916
- fs.rmSync(target, { recursive: true, force: true });
917
- }
918
-
919
- /**
920
- * Clean up empty parent directories up to the base directory
921
- */
922
- _cleanupEmptyParents(dir, baseDir) {
923
- // Normalize paths for comparison
924
- const normalizedDir = path.resolve(dir);
925
- const normalizedBase = path.resolve(baseDir);
926
275
 
927
- // Don't go above base directory
928
- if (!normalizedDir.startsWith(normalizedBase) || normalizedDir === normalizedBase) {
929
- return;
930
- }
276
+ const finish = () => {
277
+ console.log(`[ConfigSyncManager] syncAll(${type}): synced=${results.synced.length}, removed=${results.removed.length}, errors=${results.errors.length}`);
278
+ return results;
279
+ };
931
280
 
932
- try {
933
- const entries = fs.readdirSync(dir);
934
- if (entries.length === 0) {
935
- fs.rmdirSync(dir);
936
- console.log(`[ConfigSyncManager] Removed empty directory: ${dir}`);
937
- // Recurse to parent
938
- this._cleanupEmptyParents(path.dirname(dir), baseDir);
939
- }
940
- } catch (err) {
941
- // Ignore errors (directory might not exist or permission issues)
942
- }
281
+ return pending.length ? Promise.all(pending).then(finish) : finish();
943
282
  }
944
283
 
945
284
  _normalizeSafeRelativeName(name) {
@@ -960,117 +299,6 @@ class ConfigSyncManager {
960
299
  return normalized;
961
300
  }
962
301
 
963
- _parseFrontmatter(content) {
964
- const result = {
965
- frontmatter: {},
966
- body: content
967
- };
968
-
969
- const normalized = content.trim().replace(/^\uFEFF/, '');
970
- const match = normalized.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
971
- if (!match) {
972
- return result;
973
- }
974
-
975
- const frontmatterText = match[1];
976
- result.body = match[2].trim();
977
-
978
- for (const line of frontmatterText.split('\n')) {
979
- const colonIndex = line.indexOf(':');
980
- if (colonIndex === -1) continue;
981
- const key = line.slice(0, colonIndex).trim();
982
- let value = line.slice(colonIndex + 1).trim();
983
- if ((value.startsWith('"') && value.endsWith('"')) ||
984
- (value.startsWith("'") && value.endsWith("'"))) {
985
- value = value.slice(1, -1);
986
- }
987
- result.frontmatter[key] = value;
988
- }
989
-
990
- return result;
991
- }
992
-
993
- _isPlainObject(value) {
994
- return !!value && typeof value === 'object' && !Array.isArray(value);
995
- }
996
-
997
- _normalizeCodexPath(configPath) {
998
- return typeof configPath === 'string' ? configPath.trim() : '';
999
- }
1000
-
1001
- _resolveCodexPath(configPath) {
1002
- const normalized = this._normalizeCodexPath(configPath);
1003
- if (!normalized) return '';
1004
-
1005
- if (normalized.startsWith('~/')) {
1006
- return path.join(HOME, normalized.slice(2));
1007
- }
1008
-
1009
- if (path.isAbsolute(normalized)) {
1010
- return normalized;
1011
- }
1012
-
1013
- return path.resolve(path.dirname(CODEX_CONFIG_PATH), normalized);
1014
- }
1015
-
1016
- _isManagedCodexAgentConfigPath(configPath) {
1017
- const resolved = this._resolveCodexPath(configPath);
1018
- if (!resolved) return false;
1019
-
1020
- const managedRoot = path.resolve(this._getCodexManagedAgentConfigDir()) + path.sep;
1021
- return resolved.startsWith(managedRoot) || resolved === path.resolve(this._getCodexManagedAgentConfigDir());
1022
- }
1023
-
1024
- _getCodexManagedAgentConfigDir() {
1025
- return path.join(this.codexDir, 'agents');
1026
- }
1027
-
1028
- _getCodexManagedAgentConfigPath(fileName) {
1029
- return path.join(this._getCodexManagedAgentConfigDir(), `${fileName}.toml`);
1030
- }
1031
-
1032
- _writeCodexAgentConfigFile(configPath, data) {
1033
- const resolved = this._resolveCodexPath(configPath);
1034
- this._ensureDir(path.dirname(resolved));
1035
- const tempPath = `${resolved}.tmp-${process.pid}-${Date.now()}`;
1036
- fs.writeFileSync(tempPath, tomlStringify(data), 'utf-8');
1037
- fs.renameSync(tempPath, resolved);
1038
- }
1039
-
1040
- _readCodexAgentConfigFile(configPath) {
1041
- const resolved = this._resolveCodexPath(configPath);
1042
- if (!resolved || !fs.existsSync(resolved)) {
1043
- return null;
1044
- }
1045
-
1046
- try {
1047
- const content = fs.readFileSync(resolved, 'utf-8');
1048
- return {
1049
- content,
1050
- data: toml.parse(content)
1051
- };
1052
- } catch (err) {
1053
- return {
1054
- content: fs.readFileSync(resolved, 'utf-8'),
1055
- data: null
1056
- };
1057
- }
1058
- }
1059
-
1060
- _readCodexConfigToml() {
1061
- if (!fs.existsSync(CODEX_CONFIG_PATH)) {
1062
- return {};
1063
- }
1064
- const content = fs.readFileSync(CODEX_CONFIG_PATH, 'utf-8');
1065
- return toml.parse(content);
1066
- }
1067
-
1068
- _writeCodexConfigToml(config) {
1069
- this._ensureDir(path.dirname(CODEX_CONFIG_PATH));
1070
- const tempPath = `${CODEX_CONFIG_PATH}.tmp-${process.pid}-${Date.now()}`;
1071
- fs.writeFileSync(tempPath, tomlStringify(config), 'utf-8');
1072
- fs.renameSync(tempPath, CODEX_CONFIG_PATH);
1073
- }
1074
302
  }
1075
303
 
1076
304
  module.exports = {