coding-tool-x 3.8.3 → 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.
- package/CHANGELOG.md +18 -0
- package/README.md +86 -15
- package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
- package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
- package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
- package/dist/web/assets/Analytics-CyHhR126.css +1 -0
- package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
- package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
- package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
- package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
- package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
- package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
- package/dist/web/assets/Divider-D28g1gwj.js +1 -0
- package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
- package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
- package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
- package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
- package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
- package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
- package/dist/web/assets/Home-D7CCOFQC.js +1 -0
- package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
- package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
- package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
- package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
- package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
- package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
- package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
- package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
- package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
- package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
- package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
- package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
- package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
- package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
- package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
- package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
- package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
- package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
- package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
- package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
- package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
- package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
- package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
- package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
- package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
- package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
- package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
- package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
- package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
- package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
- package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
- package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
- package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
- package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
- package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
- package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
- package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
- package/dist/web/assets/client-UDwXBP9i.js +1 -0
- package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
- package/dist/web/assets/headers-cf-QapUq.js +1 -0
- package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
- package/dist/web/assets/index-C0EHRym0.css +1 -0
- package/dist/web/assets/index-DnEocV0M.js +2 -0
- package/dist/web/assets/light-Cp0t7v32.js +1 -0
- package/dist/web/assets/markdown-u082bbtj.js +1 -0
- package/dist/web/assets/platforms-CBkB45JP.js +1 -0
- package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
- package/dist/web/assets/project-config-B2baPzYL.js +1 -0
- package/dist/web/assets/sessions-Cmy31slD.js +1 -0
- package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
- package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
- package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
- package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
- package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
- package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
- package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
- package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
- package/dist/web/assets/xml-CgY3RMlX.js +1 -0
- package/dist/web/index.html +12 -8
- package/docs/native-log-memory-2026-09-16.md +43 -0
- package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
- package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
- package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
- package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
- package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
- package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
- package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
- package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
- package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
- package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
- package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
- package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
- package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
- package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
- package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
- package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
- package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
- package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
- package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
- package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
- package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
- package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
- package/package.json +13 -8
- package/src/commands/channels.js +88 -191
- package/src/commands/cli-type.js +19 -11
- package/src/commands/daemon.js +94 -32
- package/src/commands/doctor.js +16 -9
- package/src/commands/export-config.js +23 -5
- package/src/commands/list.js +130 -52
- package/src/commands/logs.js +44 -27
- package/src/commands/platform-command-registry.js +78 -0
- package/src/commands/plugin.js +11 -5
- package/src/commands/port-config.js +34 -52
- package/src/commands/proxy-control.js +207 -145
- package/src/commands/proxy.js +2 -2
- package/src/commands/resume.js +87 -91
- package/src/commands/search.js +137 -67
- package/src/commands/stats.js +30 -22
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +94 -111
- package/src/commands/ui.js +67 -6
- package/src/commands/workspace.js +90 -8
- package/src/config/default.js +6 -53
- package/src/config/loader.js +61 -24
- package/src/config/model-metadata.js +49 -59
- package/src/config/model-metadata.json +4095 -99
- package/src/config/paths.js +181 -8
- package/src/index.js +134 -71
- package/src/platforms/access.js +196 -0
- package/src/{server/services → platforms}/agents-service.js +354 -197
- package/src/platforms/channel-auth-registry.js +162 -0
- package/src/platforms/channel-auth-service.js +82 -0
- package/src/{server/services → platforms}/commands-service.js +195 -115
- package/src/platforms/driver-registry.js +59 -0
- package/src/platforms/drivers/claude/api-operations.js +62 -0
- package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
- package/src/platforms/drivers/claude/channels.js +39 -0
- package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
- package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
- package/src/platforms/drivers/claude/health.js +22 -0
- package/src/platforms/drivers/claude/mcp.js +9 -0
- package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
- package/src/platforms/drivers/claude/native-config.js +46 -0
- package/src/platforms/drivers/claude/native-logs.js +72 -0
- package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
- package/src/platforms/drivers/claude/project-config.js +21 -0
- package/src/platforms/drivers/claude/projects.js +62 -0
- package/src/platforms/drivers/claude/prompts.js +9 -0
- package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
- package/src/platforms/drivers/claude/proxy.js +16 -0
- package/src/platforms/drivers/claude/resource-sync.js +9 -0
- package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
- package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
- package/src/platforms/drivers/claude/sessions.js +76 -0
- package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/claude/statistics.js +16 -0
- package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
- package/src/platforms/drivers/codex/api-operations.js +9 -0
- package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
- package/src/platforms/drivers/codex/channels.js +45 -0
- package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
- package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
- package/src/platforms/drivers/codex/mcp.js +9 -0
- package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
- package/src/platforms/drivers/codex/native-config.js +47 -0
- package/src/platforms/drivers/codex/native-logs.js +166 -0
- package/src/platforms/drivers/codex/project-config.js +21 -0
- package/src/platforms/drivers/codex/projects.js +26 -0
- package/src/platforms/drivers/codex/prompts.js +9 -0
- package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
- package/src/platforms/drivers/codex/proxy.js +16 -0
- package/src/platforms/drivers/codex/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
- package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
- package/src/platforms/drivers/codex/sessions.js +36 -0
- package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/codex/statistics.js +16 -0
- package/src/platforms/drivers/gemini/api-operations.js +9 -0
- package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
- package/src/platforms/drivers/gemini/channels.js +39 -0
- package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
- package/src/platforms/drivers/gemini/mcp.js +9 -0
- package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
- package/src/platforms/drivers/gemini/native-config.js +39 -0
- package/src/platforms/drivers/gemini/native-logs.js +180 -0
- package/src/platforms/drivers/gemini/project-config.js +21 -0
- package/src/platforms/drivers/gemini/projects.js +26 -0
- package/src/platforms/drivers/gemini/prompts.js +9 -0
- package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
- package/src/platforms/drivers/gemini/proxy.js +16 -0
- package/src/platforms/drivers/gemini/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
- package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
- package/src/platforms/drivers/gemini/sessions.js +47 -0
- package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/gemini/statistics.js +16 -0
- package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
- package/src/platforms/drivers/generic-filesystem.js +166 -0
- package/src/platforms/drivers/generic-jsonl.js +141 -0
- package/src/platforms/drivers/generic-mcp.js +44 -0
- package/src/platforms/drivers/generic-openai-compatible.js +45 -0
- package/src/platforms/drivers/generic-prompt.js +41 -0
- package/src/platforms/drivers/legacy.js +677 -0
- package/src/platforms/drivers/native-config-snapshot.js +125 -0
- package/src/platforms/drivers/native-log-utils.js +1292 -0
- package/src/platforms/drivers/notification-hooks.js +578 -0
- package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
- package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
- package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
- package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
- package/src/platforms/drivers/omp/channels.js +58 -0
- package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
- package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
- package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
- package/src/platforms/drivers/omp/mcp.js +9 -0
- package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
- package/src/platforms/drivers/omp/native-config.js +46 -0
- package/src/platforms/drivers/omp/native-logs.js +52 -0
- package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
- package/src/platforms/drivers/omp/project-config.js +29 -0
- package/src/platforms/drivers/omp/projects.js +20 -0
- package/src/platforms/drivers/omp/prompts.js +9 -0
- package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
- package/src/platforms/drivers/omp/proxy.js +16 -0
- package/src/platforms/drivers/omp/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
- package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
- package/src/platforms/drivers/omp/sessions.js +36 -0
- package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
- package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
- package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
- package/src/platforms/drivers/omp/statistics.js +16 -0
- package/src/platforms/drivers/opencode/api-operations.js +9 -0
- package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
- package/src/platforms/drivers/opencode/channels.js +40 -0
- package/src/platforms/drivers/opencode/conversion.js +41 -0
- package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
- package/src/platforms/drivers/opencode/mcp.js +9 -0
- package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
- package/src/platforms/drivers/opencode/native-config.js +26 -0
- package/src/platforms/drivers/opencode/native-logs.js +133 -0
- package/src/platforms/drivers/opencode/project-config.js +98 -0
- package/src/platforms/drivers/opencode/projects.js +20 -0
- package/src/platforms/drivers/opencode/prompts.js +9 -0
- package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
- package/src/platforms/drivers/opencode/proxy.js +16 -0
- package/src/platforms/drivers/opencode/resource-sync.js +9 -0
- package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
- package/src/platforms/drivers/opencode/sessions.js +48 -0
- package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/opencode/statistics.js +16 -0
- package/src/platforms/drivers/secure-file-driver.js +272 -0
- package/src/platforms/drivers/unsupported.js +7 -0
- package/src/platforms/manifest-schema.js +398 -0
- package/src/platforms/manifests/claude.json +489 -0
- package/src/platforms/manifests/codex.json +448 -0
- package/src/platforms/manifests/gemini.json +447 -0
- package/src/platforms/manifests/omp.json +496 -0
- package/src/platforms/manifests/opencode.json +422 -0
- package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
- package/src/{server/services → platforms}/notification-hooks.js +140 -1023
- package/src/platforms/oauth-credentials-service.js +506 -0
- package/src/platforms/path-resolver.js +179 -0
- package/src/platforms/platform-path-context.js +305 -0
- package/src/platforms/registry.js +248 -0
- package/src/platforms/remote-notification-providers.js +678 -0
- package/src/platforms/runtime.js +259 -0
- package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
- package/src/plugins/plugin-loader.js +2 -2
- package/src/plugins/plugin-manager.js +2 -2
- package/src/plugins/registry.js +27 -10
- package/src/reset-config.js +21 -5
- package/src/server/api/agents.js +46 -72
- package/src/server/api/commands.js +16 -6
- package/src/server/api/config-registry.js +206 -59
- package/src/server/api/config-sync.js +3 -2
- package/src/server/api/config-templates.js +20 -4
- package/src/server/api/config.js +111 -102
- package/src/server/api/convert.js +54 -178
- package/src/server/api/dashboard.js +168 -102
- package/src/server/api/hooks.js +1 -1
- package/src/server/api/mcp.js +224 -84
- package/src/server/api/platform-api-config.js +102 -0
- package/src/server/api/platform-route-factory.js +215 -0
- package/src/server/api/platform-routes.js +26 -0
- package/src/server/api/platforms.js +110 -0
- package/src/server/api/plugins.js +57 -57
- package/src/server/api/project-config.js +168 -0
- package/src/server/api/prompts.js +58 -45
- package/src/server/api/settings.js +55 -8
- package/src/server/api/skills.js +262 -133
- package/src/server/api/ui-config.js +2 -2
- package/src/server/api/validation-errors.js +18 -4
- package/src/server/api/workspaces.js +47 -12
- package/src/server/index.js +164 -213
- package/src/server/platform-context.js +81 -0
- package/src/server/services/channel-balance.js +11 -20
- package/src/server/services/channel-health.js +21 -6
- package/src/server/services/channel-scheduler.js +29 -39
- package/src/server/services/channel-sync-utils.js +23 -13
- package/src/server/services/config-export-service.js +350 -319
- package/src/server/services/config-registry-service.js +280 -54
- package/src/server/services/config-sync-manager.js +172 -944
- package/src/server/services/config-templates-service.js +419 -353
- package/src/server/services/control-manifest-store.js +206 -0
- package/src/server/services/dashboard-snapshot-worker.js +656 -100
- package/src/server/services/effective-control-service.js +557 -0
- package/src/server/services/local-resource-index.js +314 -0
- package/src/server/services/mcp-service.js +607 -977
- package/src/server/services/model-detector.js +15 -10
- package/src/server/services/native-log-channel-resolver.js +214 -0
- package/src/server/services/native-log-observer.js +291 -0
- package/src/server/services/network-access.js +1 -1
- package/src/server/services/platform-catalog.js +82 -0
- package/src/server/services/platform-resolution.js +32 -29
- package/src/server/services/plugins-service.js +376 -122
- package/src/server/services/project-config-service.js +550 -0
- package/src/server/services/project-path-validation.js +31 -0
- package/src/server/services/prompts-service.js +267 -233
- package/src/server/services/proxy-runtime.js +7 -4
- package/src/server/services/remote-credential-cache.js +43 -0
- package/src/server/services/repo-scanner-base.js +182 -75
- package/src/server/services/request-logger.js +7 -4
- package/src/server/services/session-history-index.js +780 -213
- package/src/server/services/session-history-worker.js +228 -33
- package/src/server/services/session-snapshot-worker.js +124 -34
- package/src/server/services/session-snapshots.js +4 -2
- package/src/server/services/skill-artifact-store.js +355 -0
- package/src/server/services/skill-format-adapters.js +65 -0
- package/src/server/services/skill-projection-service.js +323 -0
- package/src/server/services/skill-refresh-task-service.js +275 -0
- package/src/server/services/skill-service.js +2020 -677
- package/src/server/services/snapshot-cache.js +30 -8
- package/src/server/services/speed-test.js +8 -8
- package/src/server/services/sqlite-connection.js +19 -3
- package/src/server/services/statistics-service.js +240 -57
- package/src/server/services/ui-config.js +107 -76
- package/src/server/services/usage-log-utils.js +98 -0
- package/src/server/services/web-build.js +1 -1
- package/src/server/services/workspace-service.js +59 -29
- package/src/server/utils/pricing.js +10 -19
- package/src/server/websocket-server.js +153 -28
- package/src/{server/services/base → shared}/base-channel-service.js +156 -28
- package/src/shared/capability-driver.js +104 -0
- package/src/shared/channel-auth-policy.js +27 -0
- package/src/shared/channel-driver.js +161 -0
- package/src/shared/driver-factories/api.js +180 -0
- package/src/shared/driver-factories/mcp.js +244 -0
- package/src/shared/driver-factories/projects.js +121 -0
- package/src/shared/driver-factories/prompts.js +58 -0
- package/src/shared/driver-factories/resource-sync.js +375 -0
- package/src/shared/driver-result.js +78 -0
- package/src/shared/mcp-format.js +189 -0
- package/src/shared/platforms.js +104 -167
- package/src/shared/project-config.js +512 -0
- package/src/shared/proxy-driver.js +57 -0
- package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
- package/src/ui/menu.js +91 -67
- package/src/utils/port-helper.js +35 -19
- package/src/utils/session.js +1 -1
- package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
- package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
- package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
- package/dist/web/assets/Home-DCEyLzwX.js +0 -1
- package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
- package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
- package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
- package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
- package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
- package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
- package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
- package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
- package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
- package/dist/web/assets/icons-nGdSKQn2.js +0 -1
- package/dist/web/assets/index-D5hyHPj7.js +0 -2
- package/dist/web/assets/index-bJRG3RXH.css +0 -1
- package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
- package/dist/web/assets/markdown-BfC0goYb.css +0 -10
- package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
- package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
- package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
- package/src/config/model-pricing.js +0 -35
- package/src/server/api/channels.js +0 -439
- package/src/server/api/claude-hooks.js +0 -56
- package/src/server/api/claude-statistics.js +0 -57
- package/src/server/api/codex-channels.js +0 -491
- package/src/server/api/codex-projects.js +0 -117
- package/src/server/api/codex-proxy.js +0 -293
- package/src/server/api/codex-sessions.js +0 -652
- package/src/server/api/codex-statistics.js +0 -57
- package/src/server/api/gemini-channels.js +0 -392
- package/src/server/api/gemini-projects.js +0 -103
- package/src/server/api/gemini-proxy.js +0 -231
- package/src/server/api/gemini-sessions.js +0 -550
- package/src/server/api/gemini-statistics.js +0 -57
- package/src/server/api/health-check.js +0 -31
- package/src/server/api/oauth-credentials.js +0 -198
- package/src/server/api/omp-channels.js +0 -575
- package/src/server/api/omp-projects.js +0 -88
- package/src/server/api/omp-proxy.js +0 -125
- package/src/server/api/omp-sessions.js +0 -386
- package/src/server/api/omp-statistics.js +0 -40
- package/src/server/api/opencode-channels.js +0 -471
- package/src/server/api/opencode-projects.js +0 -110
- package/src/server/api/opencode-proxy.js +0 -354
- package/src/server/api/opencode-sessions.js +0 -466
- package/src/server/api/opencode-statistics.js +0 -57
- package/src/server/api/projects.js +0 -120
- package/src/server/api/proxy.js +0 -330
- package/src/server/api/sessions.js +0 -810
- package/src/server/services/oauth-credentials-service.js +0 -945
- package/src/server/services/omp-session-log-observer.js +0 -138
- package/src/server/services/opencode-gateway-adapters.js +0 -43
- package/src/server/services/project-snapshots.js +0 -59
- package/src/server/services/proxy-log-helper.js +0 -298
- package/src/server/services/session-cache.js +0 -127
- package/src/server/services/session-converter.js +0 -582
- package/src/server/services/session-history-adapters/claude.js +0 -235
- /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
- /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
- /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
- /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
- /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
- /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
- /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
- /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
|
@@ -7,34 +7,70 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const { randomUUID } = require('crypto');
|
|
10
11
|
const yaml = require('js-yaml');
|
|
11
12
|
const { PATHS } = require('../../config/paths');
|
|
12
|
-
const {
|
|
13
|
-
const
|
|
13
|
+
const { getPlatformContext } = require('../platform-context');
|
|
14
|
+
const TEMPLATES_FILE = path.join(PATHS.config, 'config-templates.json');
|
|
15
|
+
const { AgentsService } = require('../../platforms/agents-service');
|
|
16
|
+
const { CommandsService } = require('../../platforms/commands-service');
|
|
14
17
|
const { SkillService } = require('./skill-service');
|
|
15
18
|
const { PluginsService } = require('./plugins-service');
|
|
16
19
|
const { convertCommandToCodex, convertCommandToGemini } = require('./format-converter');
|
|
20
|
+
const { assertNoSymlinkComponents, resolveProjectTarget } = require('../../shared/project-config');
|
|
17
21
|
const mcpService = require('./mcp-service');
|
|
18
22
|
const promptsService = require('./prompts-service');
|
|
19
|
-
const pluginsService = new PluginsService();
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
function getAiConfigMap(registry = getPlatformContext().registry) {
|
|
25
|
+
if (!registry || typeof registry.list !== 'function') return {};
|
|
26
|
+
return Object.fromEntries(
|
|
27
|
+
registry.list({ enabledOnly: true })
|
|
28
|
+
.filter(platform => platform && platform.key)
|
|
29
|
+
.map(platform => [platform.key, {
|
|
30
|
+
fileName: platform.promptFile || null,
|
|
31
|
+
name: platform.promptLabel
|
|
32
|
+
|| (platform.promptFile
|
|
33
|
+
? (platform.label || platform.title || platform.key)
|
|
34
|
+
: `${platform.label || platform.title || platform.key} command templates`)
|
|
35
|
+
}])
|
|
36
|
+
);
|
|
37
|
+
}
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
39
|
+
function getTemplateRegistry(options = {}) {
|
|
40
|
+
return options.registry || getPlatformContext().registry;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getTemplatePlatforms(registry = getPlatformContext().registry) {
|
|
44
|
+
if (!registry || typeof registry.list !== 'function') return [];
|
|
45
|
+
return registry.list({ enabledOnly: true })
|
|
46
|
+
.filter(platform => platform && platform.key);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getProjectResourceDescriptor(platformKey, resourceType, registry = getPlatformContext().registry) {
|
|
50
|
+
if (!registry || typeof registry.resolve !== 'function') return null;
|
|
51
|
+
const platform = registry.resolve(platformKey);
|
|
52
|
+
if (!platform || platform.resourceTypes?.[resourceType] === false) return null;
|
|
53
|
+
|
|
54
|
+
const resource = platform.projectResources?.[resourceType];
|
|
55
|
+
if (!resource) return null;
|
|
56
|
+
|
|
57
|
+
const relativePath = resource.canonicalRoot || resource.path || null;
|
|
58
|
+
if (!relativePath) return null;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
platform: platform.key,
|
|
62
|
+
prefix: relativePath,
|
|
63
|
+
format: resource.format || resourceType,
|
|
64
|
+
sortOrder: platform.sortOrder || 0
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getProjectResourceTargets(platformKeys, resourceType, registry = getPlatformContext().registry) {
|
|
69
|
+
return (platformKeys || [])
|
|
70
|
+
.map(platformKey => getProjectResourceDescriptor(platformKey, resourceType, registry))
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
73
|
+
}
|
|
38
74
|
|
|
39
75
|
/**
|
|
40
76
|
* 确保目录存在
|
|
@@ -49,17 +85,19 @@ function pushSkipped(list, type, item, reason) {
|
|
|
49
85
|
list.push({ type, item, reason });
|
|
50
86
|
}
|
|
51
87
|
|
|
52
|
-
function getTemplateDefaultAiType(template) {
|
|
53
|
-
|
|
54
|
-
|
|
88
|
+
function getTemplateDefaultAiType(template, registry = getPlatformContext().registry) {
|
|
89
|
+
const aiConfigMap = getAiConfigMap(registry);
|
|
90
|
+
if (template?.cliType && aiConfigMap[template.cliType]) {
|
|
91
|
+
return template.cliType;
|
|
55
92
|
}
|
|
56
|
-
return 'claude';
|
|
93
|
+
return Object.keys(aiConfigMap)[0] || 'claude';
|
|
57
94
|
}
|
|
58
95
|
|
|
59
|
-
function normalizeRequestedAiConfigTypes(options = {}, template = null, skipped = []) {
|
|
96
|
+
function normalizeRequestedAiConfigTypes(options = {}, template = null, skipped = [], registry = getPlatformContext().registry) {
|
|
97
|
+
const aiConfigMap = getAiConfigMap(registry);
|
|
60
98
|
let aiConfigTypes = options.aiConfigTypes;
|
|
61
99
|
if (!aiConfigTypes) {
|
|
62
|
-
aiConfigTypes = options.aiConfigType ? [options.aiConfigType] : [getTemplateDefaultAiType(template)];
|
|
100
|
+
aiConfigTypes = options.aiConfigType ? [options.aiConfigType] : [getTemplateDefaultAiType(template, registry)];
|
|
63
101
|
}
|
|
64
102
|
if (!Array.isArray(aiConfigTypes)) {
|
|
65
103
|
aiConfigTypes = [aiConfigTypes];
|
|
@@ -74,7 +112,7 @@ function normalizeRequestedAiConfigTypes(options = {}, template = null, skipped
|
|
|
74
112
|
}
|
|
75
113
|
const aiType = rawType.trim().toLowerCase();
|
|
76
114
|
if (!aiType) continue;
|
|
77
|
-
if (!
|
|
115
|
+
if (!aiConfigMap[aiType]) {
|
|
78
116
|
pushSkipped(skipped, 'aiConfigType', aiType, `不支持的 AI 配置类型: ${aiType}`);
|
|
79
117
|
continue;
|
|
80
118
|
}
|
|
@@ -85,7 +123,7 @@ function normalizeRequestedAiConfigTypes(options = {}, template = null, skipped
|
|
|
85
123
|
}
|
|
86
124
|
|
|
87
125
|
if (normalized.length === 0) {
|
|
88
|
-
const fallbackType = getTemplateDefaultAiType(template);
|
|
126
|
+
const fallbackType = getTemplateDefaultAiType(template, registry);
|
|
89
127
|
normalized.push(fallbackType);
|
|
90
128
|
pushSkipped(skipped, 'aiConfigType', fallbackType, `未提供有效 AI 配置类型,已回退到默认类型: ${fallbackType}`);
|
|
91
129
|
}
|
|
@@ -109,14 +147,14 @@ function resolveItemName(primary, fallback, defaultPrefix) {
|
|
|
109
147
|
return `${defaultPrefix}-${Date.now()}`;
|
|
110
148
|
}
|
|
111
149
|
|
|
112
|
-
function normalizeAiConfigs(aiConfigs = {}, claudeMd = null) {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
150
|
+
function normalizeAiConfigs(aiConfigs = {}, claudeMd = null, registry = getPlatformContext().registry) {
|
|
151
|
+
const keys = new Set([
|
|
152
|
+
...Object.keys(getAiConfigMap(registry)),
|
|
153
|
+
...Object.keys(aiConfigs || {})
|
|
154
|
+
]);
|
|
155
|
+
const normalized = Object.fromEntries(
|
|
156
|
+
[...keys].map(key => [key, { enabled: false, content: '' }])
|
|
157
|
+
);
|
|
120
158
|
|
|
121
159
|
for (const key of Object.keys(normalized)) {
|
|
122
160
|
const cfg = aiConfigs?.[key];
|
|
@@ -128,7 +166,7 @@ function normalizeAiConfigs(aiConfigs = {}, claudeMd = null) {
|
|
|
128
166
|
}
|
|
129
167
|
}
|
|
130
168
|
|
|
131
|
-
if (claudeMd?.enabled && claudeMd?.content && !normalized.claude.content) {
|
|
169
|
+
if (claudeMd?.enabled && claudeMd?.content && normalized.claude && !normalized.claude.content) {
|
|
132
170
|
normalized.claude = {
|
|
133
171
|
enabled: true,
|
|
134
172
|
content: claudeMd.content
|
|
@@ -136,7 +174,7 @@ function normalizeAiConfigs(aiConfigs = {}, claudeMd = null) {
|
|
|
136
174
|
}
|
|
137
175
|
|
|
138
176
|
// OpenCode defaults to Codex profile if not explicitly configured.
|
|
139
|
-
if (!normalized.opencode.content) {
|
|
177
|
+
if (normalized.opencode && !normalized.opencode.content) {
|
|
140
178
|
const fallback = normalized.codex.content ? normalized.codex : normalized.claude;
|
|
141
179
|
normalized.opencode = {
|
|
142
180
|
enabled: !!fallback.enabled,
|
|
@@ -293,56 +331,34 @@ function deleteCustomTemplate(id) {
|
|
|
293
331
|
throw new Error('模板不存在或不可删除');
|
|
294
332
|
}
|
|
295
333
|
|
|
334
|
+
const projectConfigServiceCache = new Map();
|
|
335
|
+
|
|
336
|
+
function getProjectConfigService() {
|
|
337
|
+
if (!projectConfigServiceCache.has('default')) {
|
|
338
|
+
const { ProjectConfigService } = require('./project-config-service');
|
|
339
|
+
projectConfigServiceCache.set('default', new ProjectConfigService());
|
|
340
|
+
}
|
|
341
|
+
return projectConfigServiceCache.get('default');
|
|
342
|
+
}
|
|
343
|
+
|
|
296
344
|
/**
|
|
297
345
|
* 应用模板到指定目录
|
|
298
346
|
* @param {string} targetDir - 目标目录
|
|
299
347
|
* @param {string} templateId - 模板 ID
|
|
300
348
|
*/
|
|
301
|
-
function applyTemplate(targetDir, templateId) {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
if (!template) {
|
|
305
|
-
throw new Error('模板不存在');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// 确保目标目录存在
|
|
309
|
-
ensureDir(targetDir);
|
|
310
|
-
|
|
311
|
-
const results = {
|
|
312
|
-
claudeMd: false,
|
|
313
|
-
skills: 0,
|
|
314
|
-
commands: 0,
|
|
315
|
-
agents: 0,
|
|
316
|
-
plugins: 0,
|
|
317
|
-
mcpServers: 0
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
// 1. 应用 CLAUDE.md
|
|
321
|
-
if (template.claudeMd && template.claudeMd.enabled && template.claudeMd.content) {
|
|
322
|
-
const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
|
|
323
|
-
fs.writeFileSync(claudeMdPath, template.claudeMd.content, 'utf8');
|
|
324
|
-
results.claudeMd = true;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// 2. 创建配置记录文件(记录应用了哪个模板)
|
|
328
|
-
const configRecord = {
|
|
329
|
-
templateId: template.id,
|
|
330
|
-
templateName: template.name,
|
|
331
|
-
appliedAt: new Date().toISOString(),
|
|
332
|
-
skills: template.skills,
|
|
333
|
-
commands: template.commands,
|
|
334
|
-
agents: template.agents,
|
|
335
|
-
plugins: template.plugins,
|
|
336
|
-
mcpServers: template.mcpServers
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
const recordPath = path.join(targetDir, '.ctx-config.json');
|
|
340
|
-
fs.writeFileSync(recordPath, JSON.stringify(configRecord, null, 2), 'utf8');
|
|
341
|
-
|
|
349
|
+
async function applyTemplate(targetDir, templateId) {
|
|
350
|
+
const result = await applyTemplateToProject(targetDir, templateId);
|
|
342
351
|
return {
|
|
343
|
-
success:
|
|
344
|
-
results
|
|
345
|
-
|
|
352
|
+
success: result.success,
|
|
353
|
+
results: {
|
|
354
|
+
claudeMd: result.results.aiConfigs.some(config => config.key === 'claude'),
|
|
355
|
+
skills: result.results.skills.applied,
|
|
356
|
+
commands: result.results.commands.applied,
|
|
357
|
+
agents: result.results.agents.applied,
|
|
358
|
+
plugins: result.results.plugins.applied,
|
|
359
|
+
mcpServers: result.results.mcpServers.applied
|
|
360
|
+
},
|
|
361
|
+
template: result.template
|
|
346
362
|
};
|
|
347
363
|
}
|
|
348
364
|
|
|
@@ -372,76 +388,84 @@ function readCurrentConfig(targetDir) {
|
|
|
372
388
|
* 获取所有可用配置(用于模板编辑器选择)
|
|
373
389
|
* 返回用户级的 agents, commands, plugins + MCP 服务器列表
|
|
374
390
|
*/
|
|
375
|
-
function getAvailableConfigs() {
|
|
376
|
-
const
|
|
377
|
-
const
|
|
391
|
+
function getAvailableConfigs(options = {}) {
|
|
392
|
+
const registry = getTemplateRegistry(options);
|
|
393
|
+
const platforms = getTemplatePlatforms(registry);
|
|
394
|
+
const agentPlatforms = platforms
|
|
395
|
+
.filter(platform => getProjectResourceDescriptor(platform.key, 'agents', registry))
|
|
396
|
+
.map(platform => platform.key);
|
|
397
|
+
const commandPlatforms = platforms
|
|
398
|
+
.filter(platform => getProjectResourceDescriptor(platform.key, 'commands', registry))
|
|
399
|
+
.map(platform => platform.key);
|
|
378
400
|
const agentMap = new Map();
|
|
379
401
|
const commandMap = new Map();
|
|
380
402
|
const agentsByPlatform = {};
|
|
381
403
|
const commandsByPlatform = {};
|
|
382
404
|
|
|
383
405
|
for (const platform of agentPlatforms) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
description: agent.description,
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
systemPrompt: agent.systemPrompt
|
|
397
|
-
};
|
|
398
|
-
agentsByPlatform[platform] = agentsByPlatform[platform] || [];
|
|
399
|
-
agentsByPlatform[platform].push(normalizedAgent);
|
|
400
|
-
const key = `${agent.fileName || agent.name}|${agent.model || ''}|${agent.description || ''}`;
|
|
401
|
-
if (!agentMap.has(key)) {
|
|
402
|
-
agentMap.set(key, normalizedAgent);
|
|
406
|
+
let service;
|
|
407
|
+
try {
|
|
408
|
+
service = new AgentsService(platform, { registry });
|
|
409
|
+
const { agents } = service.listAgents();
|
|
410
|
+
for (const agent of agents || []) {
|
|
411
|
+
if (agent.scope !== 'user') continue;
|
|
412
|
+
const detail = typeof service.getAgent === 'function' ? (service.getAgent(agent.fileName, 'user') || agent) : agent;
|
|
413
|
+
const normalizedAgent = { fileName: agent.fileName, name: agent.name, description: agent.description, tools: agent.tools, model: agent.model, permissionMode: agent.permissionMode, skills: agent.skills, systemPrompt: detail.systemPrompt };
|
|
414
|
+
agentsByPlatform[platform] = agentsByPlatform[platform] || [];
|
|
415
|
+
agentsByPlatform[platform].push(normalizedAgent);
|
|
416
|
+
const key = `${agent.fileName || agent.name}|${agent.model || ''}|${agent.description || ''}`;
|
|
417
|
+
if (!agentMap.has(key)) agentMap.set(key, normalizedAgent);
|
|
403
418
|
}
|
|
419
|
+
} finally {
|
|
420
|
+
service?.dispose?.();
|
|
404
421
|
}
|
|
405
422
|
}
|
|
406
423
|
|
|
407
424
|
for (const platform of commandPlatforms) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
namespace: command
|
|
415
|
-
description: command.description,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
commandsByPlatform[platform] = commandsByPlatform[platform] || [];
|
|
421
|
-
commandsByPlatform[platform].push(normalizedCommand);
|
|
422
|
-
const key = command.namespace ? `${command.namespace}/${command.name}` : command.name;
|
|
423
|
-
if (!commandMap.has(key)) {
|
|
424
|
-
commandMap.set(key, normalizedCommand);
|
|
425
|
+
let service;
|
|
426
|
+
try {
|
|
427
|
+
service = new CommandsService(platform, { registry });
|
|
428
|
+
const { commands } = service.listCommands();
|
|
429
|
+
for (const command of commands || []) {
|
|
430
|
+
if (command.scope !== 'user') continue;
|
|
431
|
+
const detail = typeof service.getCommand === 'function' ? (service.getCommand(command.name, 'user', null, command.namespace) || command) : command;
|
|
432
|
+
const normalizedCommand = { name: command.name, namespace: command.namespace, description: command.description, allowedTools: command.allowedTools, argumentHint: command.argumentHint, body: detail.body };
|
|
433
|
+
commandsByPlatform[platform] = commandsByPlatform[platform] || [];
|
|
434
|
+
commandsByPlatform[platform].push(normalizedCommand);
|
|
435
|
+
const key = command.namespace ? `${command.namespace}/${command.name}` : command.name;
|
|
436
|
+
if (!commandMap.has(key)) commandMap.set(key, normalizedCommand);
|
|
425
437
|
}
|
|
438
|
+
} finally {
|
|
439
|
+
service?.dispose?.();
|
|
426
440
|
}
|
|
427
441
|
}
|
|
428
|
-
|
|
429
442
|
// 按平台分别获取 skills(每个平台有独立的安装目录)
|
|
430
443
|
const skillsByPlatform = {};
|
|
431
|
-
for (const platform of
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
444
|
+
for (const platform of platforms
|
|
445
|
+
.filter(item => getProjectResourceDescriptor(item.key, 'skills', registry))
|
|
446
|
+
.map(item => item.key)) {
|
|
447
|
+
const service = new SkillService(platform, { registry });
|
|
448
|
+
skillsByPlatform[platform] = service.getInstalledSkills().map(skill => {
|
|
449
|
+
const normalized = {
|
|
450
|
+
directory: skill.directory,
|
|
451
|
+
name: skill.name || skill.directory,
|
|
452
|
+
description: skill.description || '',
|
|
453
|
+
repoOwner: skill.repoOwner || null,
|
|
454
|
+
repoName: skill.repoName || null,
|
|
455
|
+
repoBranch: skill.repoBranch || null
|
|
456
|
+
};
|
|
457
|
+
for (const key of ['fullDirectory', 'repoProvider', 'repoHost', 'repoId', 'repoDirectory', 'repoProjectPath', 'repoLocalPath', 'repoUrl']) {
|
|
458
|
+
if (skill[key] !== undefined && skill[key] !== null && skill[key] !== '') {
|
|
459
|
+
normalized[key] = skill[key];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return normalized;
|
|
463
|
+
});
|
|
441
464
|
}
|
|
442
465
|
|
|
443
466
|
// 获取已安装的插件和市场插件
|
|
444
|
-
const
|
|
467
|
+
const configuredPluginsService = new PluginsService({ registry });
|
|
468
|
+
const { plugins: installedPlugins } = configuredPluginsService.listPlugins();
|
|
445
469
|
|
|
446
470
|
// 获取 MCP 服务器
|
|
447
471
|
const mcpServers = mcpService.getAllServers();
|
|
@@ -469,6 +493,15 @@ function getAvailableConfigs() {
|
|
|
469
493
|
}));
|
|
470
494
|
|
|
471
495
|
return {
|
|
496
|
+
platforms: platforms.map(platform => ({
|
|
497
|
+
key: platform.key,
|
|
498
|
+
label: platform.label || platform.title || platform.key,
|
|
499
|
+
title: platform.title || platform.label || platform.key,
|
|
500
|
+
promptFile: platform.promptFile || null,
|
|
501
|
+
promptLabel: platform.promptLabel || null,
|
|
502
|
+
resourceTypes: platform.resourceTypes || {},
|
|
503
|
+
projectResources: platform.projectResources || {}
|
|
504
|
+
})),
|
|
472
505
|
skillsByPlatform,
|
|
473
506
|
agents: Array.from(agentMap.values()),
|
|
474
507
|
agentsByPlatform,
|
|
@@ -519,110 +552,91 @@ function buildTemplateCommandFileName(commandName, format) {
|
|
|
519
552
|
return `${commandName}.${format === 'gemini' ? 'toml' : 'md'}`;
|
|
520
553
|
}
|
|
521
554
|
|
|
522
|
-
function getCommandPreviewExtension(
|
|
523
|
-
return
|
|
555
|
+
function getCommandPreviewExtension(format) {
|
|
556
|
+
return format === 'gemini' ? 'toml' : 'md';
|
|
524
557
|
}
|
|
525
558
|
|
|
526
|
-
function
|
|
559
|
+
function writeAtomicFile(filePath, content, rootDir = null) {
|
|
560
|
+
if (rootDir) {
|
|
561
|
+
assertNoSymlinkComponents(path.resolve(rootDir), path.dirname(filePath), fs);
|
|
562
|
+
}
|
|
527
563
|
ensureDir(path.dirname(filePath));
|
|
528
|
-
|
|
564
|
+
if (rootDir) {
|
|
565
|
+
assertNoSymlinkComponents(path.resolve(rootDir), path.dirname(filePath), fs);
|
|
566
|
+
}
|
|
567
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`;
|
|
568
|
+
let fileDescriptor;
|
|
569
|
+
try {
|
|
570
|
+
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | (fs.constants.O_NOFOLLOW || 0);
|
|
571
|
+
fileDescriptor = fs.openSync(tempPath, flags, 0o600);
|
|
572
|
+
if (rootDir) {
|
|
573
|
+
const tempRealPath = fs.realpathSync(tempPath);
|
|
574
|
+
const rootRealPath = fs.realpathSync(path.resolve(rootDir));
|
|
575
|
+
const relative = path.relative(rootRealPath, tempRealPath);
|
|
576
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
577
|
+
throw new Error('Template atomic write escaped project root');
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
fs.writeFileSync(fileDescriptor, content, 'utf-8');
|
|
581
|
+
fs.closeSync(fileDescriptor);
|
|
582
|
+
fileDescriptor = undefined;
|
|
583
|
+
if (rootDir) {
|
|
584
|
+
assertNoSymlinkComponents(path.resolve(rootDir), path.dirname(filePath), fs);
|
|
585
|
+
}
|
|
586
|
+
fs.renameSync(tempPath, filePath);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (fileDescriptor !== undefined) {
|
|
589
|
+
try {
|
|
590
|
+
fs.closeSync(fileDescriptor);
|
|
591
|
+
} catch (_) {}
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
|
595
|
+
} catch (_) {}
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
529
598
|
}
|
|
530
599
|
|
|
531
|
-
function
|
|
532
|
-
|
|
533
|
-
|
|
600
|
+
function writeJsonFile(filePath, data, rootDir = null) {
|
|
601
|
+
writeAtomicFile(filePath, JSON.stringify(data, null, 2), rootDir);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function writeYamlFile(filePath, data, rootDir = null) {
|
|
605
|
+
writeAtomicFile(filePath, yaml.dump(data || {}, {
|
|
534
606
|
lineWidth: 120,
|
|
535
607
|
noRefs: true,
|
|
536
608
|
sortKeys: false
|
|
537
|
-
}),
|
|
609
|
+
}), rootDir);
|
|
538
610
|
}
|
|
539
611
|
|
|
540
|
-
function mergeOmpProjectPackages(targetDir, plugins = []) {
|
|
612
|
+
function mergeOmpProjectPackages(targetDir, plugins = [], relativePath) {
|
|
541
613
|
const packages = plugins.map(plugin => plugin.name).filter(Boolean);
|
|
542
|
-
if (packages.length === 0) {
|
|
614
|
+
if (packages.length === 0 || !relativePath) {
|
|
543
615
|
return false;
|
|
544
616
|
}
|
|
545
617
|
|
|
546
|
-
const
|
|
547
|
-
const settingsPath = path.join(ompDir, 'config.yml');
|
|
618
|
+
const settingsPath = resolveProjectTarget(targetDir, relativePath, 'template OMP config');
|
|
548
619
|
let settings = {};
|
|
549
620
|
if (fs.existsSync(settingsPath)) {
|
|
621
|
+
let parsed;
|
|
550
622
|
try {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
623
|
+
parsed = yaml.load(fs.readFileSync(settingsPath, 'utf-8'));
|
|
624
|
+
} catch (error) {
|
|
625
|
+
throw new Error(`无法解析 OMP 项目配置: ${error.message}`);
|
|
626
|
+
}
|
|
627
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
628
|
+
settings = parsed;
|
|
629
|
+
} else {
|
|
630
|
+
throw new Error('无法解析 OMP 项目配置: 配置必须是对象');
|
|
555
631
|
}
|
|
556
632
|
}
|
|
557
633
|
|
|
558
634
|
const existingPackages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
559
635
|
settings.packages = Array.from(new Set([...existingPackages, ...packages]));
|
|
560
|
-
writeYamlFile(settingsPath, settings);
|
|
636
|
+
writeYamlFile(settingsPath, settings, targetDir);
|
|
561
637
|
return true;
|
|
562
638
|
}
|
|
563
639
|
|
|
564
|
-
/**
|
|
565
|
-
* 转换为 OpenCode MCP 结构(local/remote)
|
|
566
|
-
*/
|
|
567
|
-
function convertToOpenCodeMcpSpec(spec = {}) {
|
|
568
|
-
const type = spec.type || 'stdio';
|
|
569
|
-
|
|
570
|
-
if (type === 'local' || type === 'remote') {
|
|
571
|
-
return { ...spec };
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
if (type === 'stdio') {
|
|
575
|
-
const command = [];
|
|
576
|
-
if (spec.command) command.push(spec.command);
|
|
577
|
-
if (Array.isArray(spec.args)) command.push(...spec.args);
|
|
578
|
-
|
|
579
|
-
const result = {
|
|
580
|
-
type: 'local',
|
|
581
|
-
command
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
if (spec.env && typeof spec.env === 'object') {
|
|
585
|
-
result.environment = spec.env;
|
|
586
|
-
}
|
|
587
|
-
if (spec.cwd) {
|
|
588
|
-
result.cwd = spec.cwd;
|
|
589
|
-
}
|
|
590
|
-
return result;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const result = {
|
|
594
|
-
type: 'remote',
|
|
595
|
-
url: spec.url || ''
|
|
596
|
-
};
|
|
597
|
-
if (spec.headers && typeof spec.headers === 'object') {
|
|
598
|
-
result.headers = spec.headers;
|
|
599
|
-
}
|
|
600
|
-
return result;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
function convertToOmpMcpSpec(spec = {}) {
|
|
604
|
-
const type = spec.type || 'stdio';
|
|
605
|
-
const copyCommonFields = (target) => {
|
|
606
|
-
for (const key of ['timeout', 'auth', 'oauth']) {
|
|
607
|
-
if (spec[key] !== undefined) {
|
|
608
|
-
target[key] = spec[key];
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return target;
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
if (type === 'streamable_http') {
|
|
615
|
-
const result = {
|
|
616
|
-
type: 'http',
|
|
617
|
-
url: spec.url || ''
|
|
618
|
-
};
|
|
619
|
-
if (spec.headers && typeof spec.headers === 'object') {
|
|
620
|
-
result.headers = spec.headers;
|
|
621
|
-
}
|
|
622
|
-
return copyCommonFields(result);
|
|
623
|
-
}
|
|
624
|
-
return copyCommonFields({ ...spec });
|
|
625
|
-
}
|
|
626
640
|
|
|
627
641
|
/**
|
|
628
642
|
* 应用模板到项目目录(完整应用,写入实际文件)
|
|
@@ -632,17 +646,16 @@ function convertToOmpMcpSpec(spec = {}) {
|
|
|
632
646
|
* @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini', 'opencode', 'omp']
|
|
633
647
|
* @param {string} options.aiConfigType - (兼容旧版) 单个 AI 配置类型
|
|
634
648
|
*/
|
|
635
|
-
function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
649
|
+
async function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
636
650
|
const template = getTemplateById(templateId);
|
|
637
651
|
if (!template) {
|
|
638
652
|
throw new Error('模板不存在');
|
|
639
653
|
}
|
|
640
654
|
|
|
641
|
-
|
|
642
|
-
|
|
655
|
+
const projectConfigService = getProjectConfigService();
|
|
643
656
|
const results = {
|
|
644
657
|
aiConfigs: [],
|
|
645
|
-
skills: { applied:
|
|
658
|
+
skills: { applied: 0, items: [] },
|
|
646
659
|
agents: { applied: 0, files: [] },
|
|
647
660
|
commands: { applied: 0, files: [] },
|
|
648
661
|
plugins: { applied: 0, items: [] },
|
|
@@ -650,48 +663,109 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
650
663
|
skipped: []
|
|
651
664
|
};
|
|
652
665
|
|
|
653
|
-
const
|
|
666
|
+
const registry = getTemplateRegistry(options);
|
|
667
|
+
const aiConfigMap = getAiConfigMap(registry);
|
|
668
|
+
const aiConfigTypes = normalizeRequestedAiConfigTypes(options, template, results.skipped, registry);
|
|
654
669
|
|
|
655
670
|
for (const aiConfigType of aiConfigTypes) {
|
|
656
671
|
const aiConfig = resolveAiConfig(template, aiConfigType);
|
|
657
|
-
const configInfo =
|
|
672
|
+
const configInfo = aiConfigMap[aiConfigType];
|
|
658
673
|
if (!configInfo?.fileName) {
|
|
659
|
-
|
|
674
|
+
const reason = aiConfigType === 'omp'
|
|
675
|
+
? 'OMP 项目级命令模板通过 .omp/commands 写入,未生成单独 AI 配置文件'
|
|
676
|
+
: `平台 ${configInfo?.name || aiConfigType} 未声明项目级配置文件,已跳过`;
|
|
677
|
+
pushSkipped(results.skipped, 'aiConfig', configInfo?.name || aiConfigType, reason);
|
|
660
678
|
} else if (aiConfig?.enabled && aiConfig?.content) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
679
|
+
try {
|
|
680
|
+
const written = await projectConfigService.writeInstruction(targetDir, aiConfigType, aiConfig.content);
|
|
681
|
+
if (written?.supported === false) {
|
|
682
|
+
pushSkipped(results.skipped, 'aiConfig', configInfo.fileName, `平台 ${configInfo.name} 不支持项目级指令文件,已跳过`);
|
|
683
|
+
} else {
|
|
684
|
+
results.aiConfigs.push({
|
|
685
|
+
applied: true,
|
|
686
|
+
path: written?.path || configInfo.fileName,
|
|
687
|
+
type: configInfo.name,
|
|
688
|
+
key: aiConfigType
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
} catch (error) {
|
|
692
|
+
pushSkipped(results.skipped, 'aiConfig', configInfo.fileName, `写入项目级指令失败: ${error.message}`);
|
|
693
|
+
}
|
|
665
694
|
} else {
|
|
666
|
-
const fileName =
|
|
695
|
+
const fileName = aiConfigMap[aiConfigType]?.fileName || aiConfigType;
|
|
667
696
|
pushSkipped(results.skipped, 'aiConfig', fileName, `模板未启用 ${fileName},已跳过`);
|
|
668
697
|
}
|
|
669
698
|
}
|
|
670
699
|
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
if (aiConfigTypes.includes('gemini')) {
|
|
680
|
-
agentTargets.push({ baseDir: path.join(targetDir, '.gemini', 'agents'), prefix: '.gemini/agents' });
|
|
700
|
+
for (const skill of template.skills || []) {
|
|
701
|
+
const skillPlatform = skill?.platform || template.cliType || aiConfigTypes[0];
|
|
702
|
+
const skillName = typeof skill === 'string' ? skill : (skill?.directory || skill?.name);
|
|
703
|
+
if (!skillName) continue;
|
|
704
|
+
if (!skillPlatform || !aiConfigTypes.includes(skillPlatform)) {
|
|
705
|
+
pushSkipped(results.skipped, 'skill', skillName, `未选择 ${skillPlatform || '目标平台'},已跳过`);
|
|
706
|
+
continue;
|
|
681
707
|
}
|
|
682
708
|
|
|
683
|
-
|
|
684
|
-
|
|
709
|
+
const skillInput = typeof skill === 'string'
|
|
710
|
+
? { directory: skill, name: skill }
|
|
711
|
+
: {
|
|
712
|
+
directory: skill.directory || skill.name,
|
|
713
|
+
name: skill.name || skill.directory,
|
|
714
|
+
...(skill.fullDirectory ? { fullDirectory: skill.fullDirectory } : {}),
|
|
715
|
+
...(Array.isArray(skill.files) ? { files: skill.files } : {}),
|
|
716
|
+
...(skill.repo
|
|
717
|
+
? { repo: skill.repo }
|
|
718
|
+
: ((skill.repoProvider || skill.repoId || skill.repoLocalPath || skill.repoProjectPath)
|
|
719
|
+
? {
|
|
720
|
+
repo: {
|
|
721
|
+
provider: skill.repoProvider,
|
|
722
|
+
owner: skill.repoOwner || null,
|
|
723
|
+
name: skill.repoName || null,
|
|
724
|
+
branch: skill.repoBranch || null,
|
|
725
|
+
directory: skill.repoDirectory || '',
|
|
726
|
+
host: skill.repoHost || null,
|
|
727
|
+
projectPath: skill.repoProjectPath || null,
|
|
728
|
+
localPath: skill.repoLocalPath || null,
|
|
729
|
+
id: skill.repoId || null,
|
|
730
|
+
repoUrl: skill.repoUrl || null
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
: {}))
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
try {
|
|
737
|
+
const skillService = new SkillService(skillPlatform);
|
|
738
|
+
const registration = skillService.registerTemplateSkill({
|
|
739
|
+
...skillInput,
|
|
740
|
+
scope: 'project',
|
|
741
|
+
cwd: targetDir
|
|
742
|
+
});
|
|
743
|
+
if (registration?.status === 'pending_refresh') {
|
|
744
|
+
results.skills.pending = (results.skills.pending || 0) + 1;
|
|
745
|
+
results.skills.pendingItems = [...(results.skills.pendingItems || []), skillName];
|
|
746
|
+
} else if (registration?.status === 'failed') {
|
|
747
|
+
pushSkipped(results.skipped, 'skill', skillName, '项目级 Skill 注册失败,已跳过');
|
|
748
|
+
} else {
|
|
749
|
+
results.skills.applied++;
|
|
750
|
+
}
|
|
751
|
+
results.skills.items.push(skillName);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
pushSkipped(results.skipped, 'skill', skillName, `注册项目级 Skill 失败: ${error.message}`);
|
|
685
754
|
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (template.agents?.length > 0) {
|
|
758
|
+
const agentTargets = getProjectResourceTargets(aiConfigTypes, 'agents', registry);
|
|
686
759
|
|
|
687
760
|
for (const agent of template.agents) {
|
|
688
761
|
const fileName = resolveItemName(agent.fileName, agent.name, 'agent').toLowerCase().replace(/\s+/g, '-');
|
|
689
762
|
const content = generateAgentContent(agent);
|
|
690
763
|
let written = false;
|
|
691
764
|
for (const target of agentTargets) {
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
765
|
+
const relativePath = `${target.prefix}/${fileName}.md`;
|
|
766
|
+
const filePath = resolveProjectTarget(targetDir, relativePath, 'template agent file');
|
|
767
|
+
writeAtomicFile(filePath, content, targetDir);
|
|
768
|
+
results.agents.files.push(relativePath);
|
|
695
769
|
written = true;
|
|
696
770
|
}
|
|
697
771
|
if (aiConfigTypes.includes('codex')) {
|
|
@@ -707,26 +781,7 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
707
781
|
}
|
|
708
782
|
|
|
709
783
|
if (template.commands?.length > 0) {
|
|
710
|
-
const commandTargets =
|
|
711
|
-
if (aiConfigTypes.includes('claude')) {
|
|
712
|
-
commandTargets.push({ baseDir: path.join(targetDir, '.claude', 'commands'), prefix: '.claude/commands', format: 'claude' });
|
|
713
|
-
}
|
|
714
|
-
if (aiConfigTypes.includes('codex')) {
|
|
715
|
-
commandTargets.push({ baseDir: path.join(targetDir, '.codex', 'prompts'), prefix: '.codex/prompts', format: 'codex' });
|
|
716
|
-
}
|
|
717
|
-
if (aiConfigTypes.includes('opencode')) {
|
|
718
|
-
commandTargets.push({ baseDir: path.join(targetDir, '.opencode', 'commands'), prefix: '.opencode/commands', format: 'claude' });
|
|
719
|
-
}
|
|
720
|
-
if (aiConfigTypes.includes('gemini')) {
|
|
721
|
-
commandTargets.push({ baseDir: path.join(targetDir, '.gemini', 'commands'), prefix: '.gemini/commands', format: 'gemini' });
|
|
722
|
-
}
|
|
723
|
-
if (aiConfigTypes.includes('omp')) {
|
|
724
|
-
commandTargets.push({ baseDir: path.join(targetDir, '.omp', 'commands'), prefix: '.omp/commands', format: 'omp' });
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
for (const target of commandTargets) {
|
|
728
|
-
ensureDir(target.baseDir);
|
|
729
|
-
}
|
|
784
|
+
const commandTargets = getProjectResourceTargets(aiConfigTypes, 'commands', registry);
|
|
730
785
|
|
|
731
786
|
for (const command of template.commands) {
|
|
732
787
|
const commandName = resolveItemName(command.name, null, 'command');
|
|
@@ -738,15 +793,12 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
738
793
|
} else if (target.format === 'gemini') {
|
|
739
794
|
content = convertCommandToGemini(content).content;
|
|
740
795
|
}
|
|
741
|
-
const
|
|
742
|
-
? path.join(target.baseDir, command.namespace)
|
|
743
|
-
: target.baseDir;
|
|
744
|
-
ensureDir(targetCmdDir);
|
|
745
|
-
const filePath = path.join(targetCmdDir, buildTemplateCommandFileName(commandName, target.format));
|
|
746
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
796
|
+
const commandFile = buildTemplateCommandFileName(commandName, target.format);
|
|
747
797
|
const relativePath = command.namespace
|
|
748
|
-
? `${target.prefix}/${command.namespace}/${
|
|
749
|
-
: `${target.prefix}/${
|
|
798
|
+
? `${target.prefix}/${command.namespace}/${commandFile}`
|
|
799
|
+
: `${target.prefix}/${commandFile}`;
|
|
800
|
+
const filePath = resolveProjectTarget(targetDir, relativePath, 'template command file');
|
|
801
|
+
writeAtomicFile(filePath, content, targetDir);
|
|
750
802
|
results.commands.files.push(relativePath);
|
|
751
803
|
written = true;
|
|
752
804
|
}
|
|
@@ -768,66 +820,71 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
768
820
|
}
|
|
769
821
|
|
|
770
822
|
const hasMcp = template.mcpServers?.length > 0;
|
|
771
|
-
const
|
|
772
|
-
const
|
|
773
|
-
const hasPluginsForOpenCode = aiConfigTypes.includes('opencode') && template.plugins?.length > 0;
|
|
774
|
-
const hasPluginsForOmp = aiConfigTypes.includes('omp') && template.plugins?.length > 0;
|
|
775
|
-
if (hasMcp
|
|
776
|
-
const mcpConfig = { mcpServers: {} };
|
|
777
|
-
const ompMcpConfig = { mcpServers: {} };
|
|
778
|
-
const opencodeConfig = { mcp: {}, plugin: [] };
|
|
823
|
+
const openCodePluginTarget = getProjectResourceDescriptor('opencode', 'plugins', registry);
|
|
824
|
+
const ompPluginTarget = getProjectResourceDescriptor('omp', 'plugins', registry);
|
|
825
|
+
const hasPluginsForOpenCode = Boolean(openCodePluginTarget && aiConfigTypes.includes('opencode') && template.plugins?.length > 0);
|
|
826
|
+
const hasPluginsForOmp = Boolean(ompPluginTarget && aiConfigTypes.includes('omp') && template.plugins?.length > 0);
|
|
827
|
+
if (hasMcp) {
|
|
779
828
|
const allServers = mcpService.getAllServers();
|
|
780
829
|
const presets = mcpService.getPresets();
|
|
781
830
|
|
|
782
831
|
for (const serverId of template.mcpServers || []) {
|
|
783
|
-
// 先从已配置的服务器中查找
|
|
784
832
|
let serverSpec = allServers[serverId]?.server;
|
|
785
|
-
// 如果没有,从预设中查找
|
|
786
833
|
if (!serverSpec) {
|
|
787
834
|
const preset = presets.find(p => p.id === serverId);
|
|
788
|
-
if (preset)
|
|
789
|
-
serverSpec = preset.server;
|
|
790
|
-
}
|
|
835
|
+
if (preset) serverSpec = preset.server;
|
|
791
836
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
mcpConfig.mcpServers[serverId] = serverSpec;
|
|
795
|
-
}
|
|
796
|
-
if (writesOmpMcpConfig) {
|
|
797
|
-
ompMcpConfig.mcpServers[serverId] = convertToOmpMcpSpec(serverSpec);
|
|
798
|
-
}
|
|
799
|
-
opencodeConfig.mcp[serverId] = convertToOpenCodeMcpSpec(serverSpec);
|
|
800
|
-
if (writesGenericMcpConfig || writesOmpMcpConfig) {
|
|
801
|
-
results.mcpServers.applied++;
|
|
802
|
-
}
|
|
803
|
-
} else {
|
|
837
|
+
|
|
838
|
+
if (!serverSpec) {
|
|
804
839
|
pushSkipped(results.skipped, 'mcpServer', serverId, '未找到对应 MCP 服务配置,已跳过');
|
|
840
|
+
continue;
|
|
805
841
|
}
|
|
806
|
-
}
|
|
807
842
|
|
|
808
|
-
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
843
|
+
let applied = false;
|
|
844
|
+
for (const aiConfigType of aiConfigTypes) {
|
|
845
|
+
try {
|
|
846
|
+
const written = await projectConfigService.upsertProjectMcp(
|
|
847
|
+
targetDir,
|
|
848
|
+
aiConfigType,
|
|
849
|
+
serverId,
|
|
850
|
+
serverSpec
|
|
851
|
+
);
|
|
852
|
+
if (written?.success !== false) applied = true;
|
|
853
|
+
} catch (error) {
|
|
854
|
+
pushSkipped(
|
|
855
|
+
results.skipped,
|
|
856
|
+
'mcpServer',
|
|
857
|
+
`${serverId} (${aiConfigType})`,
|
|
858
|
+
`写入项目级 MCP 失败: ${error.message}`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (applied) results.mcpServers.applied++;
|
|
816
863
|
}
|
|
864
|
+
}
|
|
817
865
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
if (
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
866
|
+
if (hasPluginsForOpenCode) {
|
|
867
|
+
const opencodePath = resolveProjectTarget(targetDir, openCodePluginTarget.prefix, 'template OpenCode config');
|
|
868
|
+
let opencodeConfig = {};
|
|
869
|
+
if (fs.existsSync(opencodePath)) {
|
|
870
|
+
let parsed;
|
|
871
|
+
try {
|
|
872
|
+
parsed = JSON.parse(fs.readFileSync(opencodePath, 'utf-8'));
|
|
873
|
+
} catch (error) {
|
|
874
|
+
throw new Error(`无法解析 OpenCode 项目配置: ${error.message}`);
|
|
875
|
+
}
|
|
876
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
877
|
+
opencodeConfig = parsed;
|
|
878
|
+
} else {
|
|
879
|
+
throw new Error('无法解析 OpenCode 项目配置: 配置必须是对象');
|
|
880
|
+
}
|
|
826
881
|
}
|
|
882
|
+
opencodeConfig.plugin = (template.plugins || []).map(p => p.name).filter(Boolean);
|
|
883
|
+
writeJsonFile(opencodePath, opencodeConfig, targetDir);
|
|
884
|
+
}
|
|
827
885
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
}
|
|
886
|
+
if (hasPluginsForOmp) {
|
|
887
|
+
mergeOmpProjectPackages(targetDir, template.plugins || [], ompPluginTarget.prefix);
|
|
831
888
|
}
|
|
832
889
|
|
|
833
890
|
const configRecord = {
|
|
@@ -843,8 +900,8 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
843
900
|
mcpServers: template.mcpServers || [],
|
|
844
901
|
skipped: results.skipped
|
|
845
902
|
};
|
|
846
|
-
const recordPath =
|
|
847
|
-
|
|
903
|
+
const recordPath = resolveProjectTarget(targetDir, '.ctx-config.json', 'template provenance file');
|
|
904
|
+
writeAtomicFile(recordPath, JSON.stringify(configRecord, null, 2), targetDir);
|
|
848
905
|
|
|
849
906
|
return {
|
|
850
907
|
success: true,
|
|
@@ -882,15 +939,20 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
882
939
|
}
|
|
883
940
|
};
|
|
884
941
|
|
|
885
|
-
const
|
|
942
|
+
const registry = getTemplateRegistry(options);
|
|
943
|
+
const aiConfigMap = getAiConfigMap(registry);
|
|
944
|
+
const aiConfigTypes = normalizeRequestedAiConfigTypes(options, template, preview.skipped, registry);
|
|
886
945
|
|
|
887
946
|
for (const aiConfigType of aiConfigTypes) {
|
|
888
947
|
const aiConfig = resolveAiConfig(template, aiConfigType);
|
|
889
|
-
const configInfo =
|
|
948
|
+
const configInfo = aiConfigMap[aiConfigType];
|
|
890
949
|
if (!configInfo?.fileName) {
|
|
891
|
-
|
|
950
|
+
const reason = aiConfigType === 'omp'
|
|
951
|
+
? 'OMP 项目级命令模板通过 .omp/commands 写入,预览不生成单独 AI 配置文件'
|
|
952
|
+
: `平台 ${configInfo?.name || aiConfigType} 未声明项目级配置文件,预览已跳过`;
|
|
953
|
+
pushSkipped(preview.skipped, 'aiConfig', configInfo?.name || aiConfigType, reason);
|
|
892
954
|
} else if (aiConfig?.enabled && aiConfig?.content) {
|
|
893
|
-
const configPath =
|
|
955
|
+
const configPath = resolveProjectTarget(targetDir, configInfo.fileName, 'template instruction file');
|
|
894
956
|
if (fs.existsSync(configPath)) {
|
|
895
957
|
preview.willOverwrite.push(configInfo.fileName);
|
|
896
958
|
} else {
|
|
@@ -898,7 +960,7 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
898
960
|
}
|
|
899
961
|
preview.summary.aiConfigs.push({ type: aiConfigType, fileName: configInfo.fileName, name: configInfo.name });
|
|
900
962
|
} else {
|
|
901
|
-
const fileName =
|
|
963
|
+
const fileName = aiConfigMap[aiConfigType]?.fileName || aiConfigType;
|
|
902
964
|
pushSkipped(preview.skipped, 'aiConfig', fileName, `模板未启用 ${fileName},已跳过`);
|
|
903
965
|
}
|
|
904
966
|
}
|
|
@@ -909,17 +971,14 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
909
971
|
}
|
|
910
972
|
|
|
911
973
|
if (template.agents?.length > 0) {
|
|
912
|
-
const
|
|
913
|
-
if (aiConfigTypes.includes('claude')) agentPrefixes.push('.claude/agents');
|
|
914
|
-
if (aiConfigTypes.includes('opencode')) agentPrefixes.push('.opencode/agents');
|
|
915
|
-
if (aiConfigTypes.includes('gemini')) agentPrefixes.push('.gemini/agents');
|
|
974
|
+
const agentTargets = getProjectResourceTargets(aiConfigTypes, 'agents', registry);
|
|
916
975
|
|
|
917
976
|
for (const agent of template.agents) {
|
|
918
977
|
const fileName = resolveItemName(agent.fileName, agent.name, 'agent').toLowerCase().replace(/\s+/g, '-');
|
|
919
978
|
let applicable = false;
|
|
920
|
-
for (const
|
|
921
|
-
const relativePath = `${prefix}/${fileName}.md`;
|
|
922
|
-
const fullPath =
|
|
979
|
+
for (const target of agentTargets) {
|
|
980
|
+
const relativePath = `${target.prefix}/${fileName}.md`;
|
|
981
|
+
const fullPath = resolveProjectTarget(targetDir, relativePath, 'template agent file');
|
|
923
982
|
if (fs.existsSync(fullPath)) {
|
|
924
983
|
preview.willOverwrite.push(relativePath);
|
|
925
984
|
} else {
|
|
@@ -940,22 +999,17 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
940
999
|
}
|
|
941
1000
|
|
|
942
1001
|
if (template.commands?.length > 0) {
|
|
943
|
-
const
|
|
944
|
-
if (aiConfigTypes.includes('claude')) commandPrefixes.push('.claude/commands');
|
|
945
|
-
if (aiConfigTypes.includes('codex')) commandPrefixes.push('.codex/prompts');
|
|
946
|
-
if (aiConfigTypes.includes('opencode')) commandPrefixes.push('.opencode/commands');
|
|
947
|
-
if (aiConfigTypes.includes('gemini')) commandPrefixes.push('.gemini/commands');
|
|
948
|
-
if (aiConfigTypes.includes('omp')) commandPrefixes.push('.omp/commands');
|
|
1002
|
+
const commandTargets = getProjectResourceTargets(aiConfigTypes, 'commands', registry);
|
|
949
1003
|
|
|
950
1004
|
for (const command of template.commands) {
|
|
951
1005
|
const commandName = resolveItemName(command.name, null, 'command');
|
|
952
1006
|
let applicable = false;
|
|
953
|
-
for (const
|
|
954
|
-
const extension = getCommandPreviewExtension(
|
|
1007
|
+
for (const target of commandTargets) {
|
|
1008
|
+
const extension = getCommandPreviewExtension(target.format);
|
|
955
1009
|
const relativePath = command.namespace
|
|
956
|
-
? `${prefix}/${command.namespace}/${commandName}.${extension}`
|
|
957
|
-
: `${prefix}/${commandName}.${extension}`;
|
|
958
|
-
const fullPath =
|
|
1010
|
+
? `${target.prefix}/${command.namespace}/${commandName}.${extension}`
|
|
1011
|
+
: `${target.prefix}/${commandName}.${extension}`;
|
|
1012
|
+
const fullPath = resolveProjectTarget(targetDir, relativePath, 'template command file');
|
|
959
1013
|
if (fs.existsSync(fullPath)) {
|
|
960
1014
|
preview.willOverwrite.push(relativePath);
|
|
961
1015
|
} else {
|
|
@@ -969,8 +1023,16 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
969
1023
|
}
|
|
970
1024
|
}
|
|
971
1025
|
|
|
972
|
-
const
|
|
973
|
-
|
|
1026
|
+
const genericMcpTarget = getProjectResourceTargets(
|
|
1027
|
+
aiConfigTypes.filter(type => type !== 'omp'),
|
|
1028
|
+
'mcp',
|
|
1029
|
+
registry
|
|
1030
|
+
)[0];
|
|
1031
|
+
const writesGenericMcpConfig = Boolean(
|
|
1032
|
+
template.mcpServers?.length > 0 && aiConfigTypes.some(type => type !== 'omp') && genericMcpTarget
|
|
1033
|
+
);
|
|
1034
|
+
const ompMcpTarget = getProjectResourceDescriptor('omp', 'mcp', registry);
|
|
1035
|
+
const writesOmpMcpConfig = Boolean(template.mcpServers?.length > 0 && aiConfigTypes.includes('omp') && ompMcpTarget);
|
|
974
1036
|
const allServers = mcpService.getAllServers();
|
|
975
1037
|
const presets = mcpService.getPresets();
|
|
976
1038
|
let resolvableMcpCount = 0;
|
|
@@ -992,43 +1054,46 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
992
1054
|
}
|
|
993
1055
|
|
|
994
1056
|
if (writesGenericMcpConfig && resolvableMcpCount > 0) {
|
|
995
|
-
const
|
|
1057
|
+
const mcpRelativePath = genericMcpTarget.prefix;
|
|
1058
|
+
const mcpPath = resolveProjectTarget(targetDir, mcpRelativePath, 'template MCP file');
|
|
996
1059
|
if (fs.existsSync(mcpPath)) {
|
|
997
|
-
preview.willOverwrite.push(
|
|
1060
|
+
preview.willOverwrite.push(mcpRelativePath);
|
|
998
1061
|
} else {
|
|
999
|
-
preview.willCreate.push(
|
|
1062
|
+
preview.willCreate.push(mcpRelativePath);
|
|
1000
1063
|
}
|
|
1001
1064
|
preview.summary.mcpServers = resolvableMcpCount;
|
|
1002
1065
|
}
|
|
1003
1066
|
|
|
1004
1067
|
if (writesOmpMcpConfig && resolvableMcpCount > 0) {
|
|
1005
|
-
const ompMcpPath =
|
|
1068
|
+
const ompMcpPath = resolveProjectTarget(targetDir, ompMcpTarget.prefix, 'template OMP MCP file');
|
|
1006
1069
|
if (fs.existsSync(ompMcpPath)) {
|
|
1007
|
-
preview.willOverwrite.push(
|
|
1070
|
+
preview.willOverwrite.push(ompMcpTarget.prefix);
|
|
1008
1071
|
} else {
|
|
1009
|
-
preview.willCreate.push(
|
|
1072
|
+
preview.willCreate.push(ompMcpTarget.prefix);
|
|
1010
1073
|
}
|
|
1011
1074
|
preview.summary.mcpServers = resolvableMcpCount;
|
|
1012
1075
|
}
|
|
1013
1076
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1077
|
+
const openCodePluginTarget = getProjectResourceDescriptor('opencode', 'plugins', registry);
|
|
1078
|
+
if (aiConfigTypes.includes('opencode') && openCodePluginTarget && (resolvableMcpCount > 0 || template.plugins?.length > 0)) {
|
|
1079
|
+
const opencodeConfigPath = resolveProjectTarget(targetDir, openCodePluginTarget.prefix, 'template OpenCode config');
|
|
1016
1080
|
if (fs.existsSync(opencodeConfigPath)) {
|
|
1017
|
-
preview.willOverwrite.push(
|
|
1081
|
+
preview.willOverwrite.push(openCodePluginTarget.prefix);
|
|
1018
1082
|
} else {
|
|
1019
|
-
preview.willCreate.push(
|
|
1083
|
+
preview.willCreate.push(openCodePluginTarget.prefix);
|
|
1020
1084
|
}
|
|
1021
1085
|
}
|
|
1022
1086
|
|
|
1023
1087
|
if (template.plugins?.length > 0) {
|
|
1024
1088
|
if (aiConfigTypes.includes('opencode') || aiConfigTypes.includes('omp')) {
|
|
1025
1089
|
preview.summary.plugins = template.plugins.length;
|
|
1026
|
-
|
|
1027
|
-
|
|
1090
|
+
const ompPluginTarget = getProjectResourceDescriptor('omp', 'plugins', registry);
|
|
1091
|
+
if (aiConfigTypes.includes('omp') && ompPluginTarget) {
|
|
1092
|
+
const ompSettingsPath = resolveProjectTarget(targetDir, ompPluginTarget.prefix, 'template OMP config');
|
|
1028
1093
|
if (fs.existsSync(ompSettingsPath)) {
|
|
1029
|
-
preview.willOverwrite.push(
|
|
1094
|
+
preview.willOverwrite.push(ompPluginTarget.prefix);
|
|
1030
1095
|
} else {
|
|
1031
|
-
preview.willCreate.push(
|
|
1096
|
+
preview.willCreate.push(ompPluginTarget.prefix);
|
|
1032
1097
|
}
|
|
1033
1098
|
}
|
|
1034
1099
|
} else {
|
|
@@ -1055,5 +1120,6 @@ module.exports = {
|
|
|
1055
1120
|
readCurrentConfig,
|
|
1056
1121
|
getAvailableConfigs,
|
|
1057
1122
|
applyTemplateToProject,
|
|
1058
|
-
previewTemplateApplication
|
|
1123
|
+
previewTemplateApplication,
|
|
1124
|
+
getAiConfigMap
|
|
1059
1125
|
};
|