@thevinci/web 1.0.0
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/README.md +197 -0
- package/bin/cli-entry.js +55 -0
- package/bin/cli-output.js +145 -0
- package/bin/cli.js +4887 -0
- package/bin/cli.test.js +64 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +528 -0
- package/dist/assets/JsonTreeView-CSm9OzXG.js +1 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/MarkdownRendererImpl-DensKOLc.js +6 -0
- package/dist/assets/MultiRunWindow-Bo7THayo.js +1 -0
- package/dist/assets/OnboardingScreen-BDqmzTVR.js +2 -0
- package/dist/assets/SettingsWindow-coz__Ykw.js +1 -0
- package/dist/assets/TerminalView-DrZ-i3Dr.js +1 -0
- package/dist/assets/ToolOutputDialog-Eglzslt3.js +16 -0
- package/dist/assets/es-4o9ciP61.js +15 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/assets/index-DLTDToSP.css +1 -0
- package/dist/assets/index-DgiFEKGN.js +1 -0
- package/dist/assets/ko-B20imCHE.js +15 -0
- package/dist/assets/main-BV3KOtdA.css +1 -0
- package/dist/assets/main-CDKJj0sH.js +226 -0
- package/dist/assets/main-LC-PSNVM.js +2 -0
- package/dist/assets/miniChat-CQUiG_cr.js +2 -0
- package/dist/assets/modelPrefsAutoSave-Dm799vzR.js +6986 -0
- package/dist/assets/pl-DQJ7LSzj.js +15 -0
- package/dist/assets/pt-BR-OmjHUz9y.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-CARbeW0G.js +2 -0
- package/dist/assets/uk-BNFxOlO4.js +15 -0
- package/dist/assets/vendor--DBfsbEis.css +1 -0
- package/dist/assets/vendor-.bun-B9l0ZNi2.js +4094 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasmSttWorker-Dtlxac_K.js +1 -0
- package/dist/assets/wasmSttWorker-oo7Dm_jy.js +1806 -0
- package/dist/assets/worker-CbT6TVo7.js +155 -0
- package/dist/assets/zh-CN-C6T-Ac7F.js +15 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +528 -0
- package/dist/index.html +607 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.png +0 -0
- package/dist/logo-dark-512x512.svg +528 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.png +0 -0
- package/dist/logo-light-512x512.svg +528 -0
- package/dist/mini-chat.html +16 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +21 -0
- package/dist/sw.js +1 -0
- package/package.json +118 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +528 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +528 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.png +0 -0
- package/public/logo-dark-512x512.svg +528 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.png +0 -0
- package/public/logo-light-512x512.svg +528 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +21 -0
- package/server/TERMINAL_WS_PROTOCOL.md +48 -0
- package/server/index.d.ts +39 -0
- package/server/index.js +1311 -0
- package/server/lib/cloudflare-tunnel.js +650 -0
- package/server/lib/event-stream/DOCUMENTATION.md +61 -0
- package/server/lib/event-stream/directory-ws-bridge.js +185 -0
- package/server/lib/event-stream/global-hub.js +158 -0
- package/server/lib/event-stream/global-hub.test.js +140 -0
- package/server/lib/event-stream/global-ws-bridge.js +206 -0
- package/server/lib/event-stream/index.js +25 -0
- package/server/lib/event-stream/protocol.js +131 -0
- package/server/lib/event-stream/protocol.test.js +182 -0
- package/server/lib/event-stream/runtime.js +180 -0
- package/server/lib/event-stream/runtime.test.js +512 -0
- package/server/lib/event-stream/upstream-reader.js +226 -0
- package/server/lib/event-stream/upstream-reader.test.js +276 -0
- package/server/lib/fs/DOCUMENTATION.md +36 -0
- package/server/lib/fs/routes.js +1040 -0
- package/server/lib/fs/search.js +238 -0
- package/server/lib/git/DOCUMENTATION.md +152 -0
- package/server/lib/git/credentials.js +74 -0
- package/server/lib/git/identity-storage.js +112 -0
- package/server/lib/git/index.js +6 -0
- package/server/lib/git/routes.js +972 -0
- package/server/lib/git/service.js +3432 -0
- package/server/lib/git/service.test.js +39 -0
- package/server/lib/github/DOCUMENTATION.md +171 -0
- package/server/lib/github/auth.js +307 -0
- package/server/lib/github/device-flow.js +50 -0
- package/server/lib/github/index.js +24 -0
- package/server/lib/github/octokit.js +10 -0
- package/server/lib/github/pr-status.js +519 -0
- package/server/lib/github/repo/fork-detection.js +102 -0
- package/server/lib/github/repo/index.js +55 -0
- package/server/lib/github/routes.js +1560 -0
- package/server/lib/magic-prompts/routes.js +63 -0
- package/server/lib/magic-prompts/runtime.js +119 -0
- package/server/lib/notifications/DOCUMENTATION.md +122 -0
- package/server/lib/notifications/emitter-runtime.js +102 -0
- package/server/lib/notifications/index.js +4 -0
- package/server/lib/notifications/message.js +52 -0
- package/server/lib/notifications/message.test.js +34 -0
- package/server/lib/notifications/push-runtime.js +304 -0
- package/server/lib/notifications/routes.js +315 -0
- package/server/lib/notifications/runtime.js +566 -0
- package/server/lib/notifications/template-runtime.js +349 -0
- package/server/lib/notifications/template-runtime.test.js +26 -0
- package/server/lib/opencode/DOCUMENTATION.md +362 -0
- package/server/lib/opencode/agents.js +634 -0
- package/server/lib/opencode/auth-state-runtime.js +88 -0
- package/server/lib/opencode/auth.js +83 -0
- package/server/lib/opencode/bootstrap-runtime.js +131 -0
- package/server/lib/opencode/cli-entry-runtime.js +43 -0
- package/server/lib/opencode/cli-options.js +128 -0
- package/server/lib/opencode/commands.js +339 -0
- package/server/lib/opencode/config-entity-routes.js +370 -0
- package/server/lib/opencode/core-routes.js +500 -0
- package/server/lib/opencode/core-routes.test.js +26 -0
- package/server/lib/opencode/env-config.js +74 -0
- package/server/lib/opencode/env-keys.js +68 -0
- package/server/lib/opencode/env-runtime.js +1162 -0
- package/server/lib/opencode/env-runtime.test.js +116 -0
- package/server/lib/opencode/feature-routes-runtime.js +244 -0
- package/server/lib/opencode/hmr-state-runtime.js +85 -0
- package/server/lib/opencode/index.js +66 -0
- package/server/lib/opencode/lifecycle.js +1019 -0
- package/server/lib/opencode/lifecycle.test.js +240 -0
- package/server/lib/opencode/mcp.js +278 -0
- package/server/lib/opencode/network-runtime.js +104 -0
- package/server/lib/opencode/network-runtime.test.js +37 -0
- package/server/lib/opencode/opencode-resolution-runtime.js +71 -0
- package/server/lib/opencode/path-utils.js +100 -0
- package/server/lib/opencode/path-utils.test.js +71 -0
- package/server/lib/opencode/project-directory-runtime.js +124 -0
- package/server/lib/opencode/project-icon-routes.js +399 -0
- package/server/lib/opencode/project-icon-routes.test.js +107 -0
- package/server/lib/opencode/providers.js +96 -0
- package/server/lib/opencode/proxy.js +445 -0
- package/server/lib/opencode/pwa-manifest-routes.js +257 -0
- package/server/lib/opencode/pwa-manifest-routes.test.js +133 -0
- package/server/lib/opencode/routes.js +541 -0
- package/server/lib/opencode/server-startup-runtime.js +156 -0
- package/server/lib/opencode/server-utils-runtime.js +168 -0
- package/server/lib/opencode/server-utils-runtime.test.js +135 -0
- package/server/lib/opencode/session-runtime.js +356 -0
- package/server/lib/opencode/session-runtime.test.js +151 -0
- package/server/lib/opencode/settings-helpers.js +770 -0
- package/server/lib/opencode/settings-helpers.test.js +109 -0
- package/server/lib/opencode/settings-normalization-runtime.js +428 -0
- package/server/lib/opencode/settings-runtime.js +826 -0
- package/server/lib/opencode/settings-runtime.test.js +85 -0
- package/server/lib/opencode/shared.js +615 -0
- package/server/lib/opencode/shutdown-runtime.js +139 -0
- package/server/lib/opencode/shutdown-runtime.test.js +58 -0
- package/server/lib/opencode/skill-routes.js +701 -0
- package/server/lib/opencode/skills.js +548 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +130 -0
- package/server/lib/opencode/static-routes-runtime.js +65 -0
- package/server/lib/opencode/theme-runtime.js +167 -0
- package/server/lib/opencode/tunnel-auth.js +591 -0
- package/server/lib/opencode/tunnel-wiring-runtime.js +94 -0
- package/server/lib/opencode/vinci-routes.js +76 -0
- package/server/lib/opencode/watcher.js +115 -0
- package/server/lib/opencode/watcher.test.js +239 -0
- package/server/lib/preview/proxy-runtime.js +1333 -0
- package/server/lib/preview/proxy-runtime.test.js +144 -0
- package/server/lib/projects/project-config.js +567 -0
- package/server/lib/projects/project-config.test.js +175 -0
- package/server/lib/projects/project-id.js +13 -0
- package/server/lib/quota/DOCUMENTATION.md +58 -0
- package/server/lib/quota/index.js +25 -0
- package/server/lib/quota/providers/claude.js +107 -0
- package/server/lib/quota/providers/codex.js +113 -0
- package/server/lib/quota/providers/copilot.js +165 -0
- package/server/lib/quota/providers/google/api.js +92 -0
- package/server/lib/quota/providers/google/auth.js +108 -0
- package/server/lib/quota/providers/google/index.js +124 -0
- package/server/lib/quota/providers/google/transforms.js +109 -0
- package/server/lib/quota/providers/index.js +168 -0
- package/server/lib/quota/providers/interface.js +55 -0
- package/server/lib/quota/providers/kimi.js +108 -0
- package/server/lib/quota/providers/minimax-cn-coding-plan.js +140 -0
- package/server/lib/quota/providers/minimax-coding-plan.js +139 -0
- package/server/lib/quota/providers/nanogpt.js +124 -0
- package/server/lib/quota/providers/ollama-cloud.js +112 -0
- package/server/lib/quota/providers/openai.js +91 -0
- package/server/lib/quota/providers/openrouter.js +92 -0
- package/server/lib/quota/providers/zai.js +91 -0
- package/server/lib/quota/providers/zhipuai-coding-plan.js +133 -0
- package/server/lib/quota/providers/zhipuai.js +114 -0
- package/server/lib/quota/routes.js +27 -0
- package/server/lib/quota/utils/auth.js +50 -0
- package/server/lib/quota/utils/formatters.js +85 -0
- package/server/lib/quota/utils/formatters.test.js +54 -0
- package/server/lib/quota/utils/index.js +10 -0
- package/server/lib/quota/utils/transformers.js +55 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +44 -0
- package/server/lib/scheduled-tasks/routes.js +235 -0
- package/server/lib/scheduled-tasks/runtime.js +773 -0
- package/server/lib/scheduled-tasks/runtime.test.js +100 -0
- package/server/lib/security/request-security.js +115 -0
- package/server/lib/session-folders/routes.js +63 -0
- package/server/lib/session-folders/routes.test.js +102 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
- package/server/lib/skills-catalog/cache.js +29 -0
- package/server/lib/skills-catalog/clawdhub/api.js +158 -0
- package/server/lib/skills-catalog/clawdhub/index.js +30 -0
- package/server/lib/skills-catalog/clawdhub/install.js +238 -0
- package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
- package/server/lib/skills-catalog/curated-sources.js +21 -0
- package/server/lib/skills-catalog/git.js +77 -0
- package/server/lib/skills-catalog/index.js +42 -0
- package/server/lib/skills-catalog/install.js +294 -0
- package/server/lib/skills-catalog/scan.js +221 -0
- package/server/lib/skills-catalog/source.js +87 -0
- package/server/lib/terminal/DOCUMENTATION.md +76 -0
- package/server/lib/terminal/index.js +31 -0
- package/server/lib/terminal/output-replay-buffer.js +78 -0
- package/server/lib/terminal/output-replay-buffer.test.js +75 -0
- package/server/lib/terminal/runtime.js +850 -0
- package/server/lib/terminal/runtime.test.js +96 -0
- package/server/lib/terminal/terminal-ws-protocol.js +68 -0
- package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
- package/server/lib/text/DOCUMENTATION.md +35 -0
- package/server/lib/text/summarization.js +138 -0
- package/server/lib/text/summarization.test.js +34 -0
- package/server/lib/tts/DOCUMENTATION.md +146 -0
- package/server/lib/tts/base-url.js +62 -0
- package/server/lib/tts/capability-runtime.js +31 -0
- package/server/lib/tts/index.js +19 -0
- package/server/lib/tts/routes.js +261 -0
- package/server/lib/tts/routes.test.js +53 -0
- package/server/lib/tts/service.js +178 -0
- package/server/lib/tts/stt.js +75 -0
- package/server/lib/tunnels/DOCUMENTATION.md +18 -0
- package/server/lib/tunnels/index.js +166 -0
- package/server/lib/tunnels/managed-config.js +201 -0
- package/server/lib/tunnels/providers/cloudflare.js +260 -0
- package/server/lib/tunnels/registry.js +51 -0
- package/server/lib/tunnels/routes.js +605 -0
- package/server/lib/tunnels/types.js +219 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +38 -0
- package/server/lib/ui-auth/ui-auth.js +673 -0
- package/server/lib/ui-auth/ui-passkeys.js +545 -0
- package/server/opencode-proxy.test.js +151 -0
- package/server/proxy-headers.js +61 -0
- package/server/proxy-headers.test.js +58 -0
- package/server/sse-routes.test.js +152 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import fsPromises from 'fs/promises';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { createProjectIdFromPath } from '../projects/project-id.js';
|
|
7
|
+
import { createSettingsRuntime } from './settings-runtime.js';
|
|
8
|
+
|
|
9
|
+
const createRuntime = async () => {
|
|
10
|
+
const tempRoot = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-settings-runtime-'));
|
|
11
|
+
const settingsFilePath = path.join(tempRoot, 'settings.json');
|
|
12
|
+
const runtime = createSettingsRuntime({
|
|
13
|
+
fsPromises,
|
|
14
|
+
path,
|
|
15
|
+
crypto,
|
|
16
|
+
SETTINGS_FILE_PATH: settingsFilePath,
|
|
17
|
+
sanitizeProjects: (projects) => Array.isArray(projects) ? projects : [],
|
|
18
|
+
sanitizeSettingsUpdate: (settings) => settings,
|
|
19
|
+
mergePersistedSettings: (_current, changes) => changes,
|
|
20
|
+
normalizeSettingsPaths: (settings) => ({ settings, changed: false }),
|
|
21
|
+
normalizeStringArray: (values) => Array.isArray(values) ? values.filter((value) => typeof value === 'string') : [],
|
|
22
|
+
formatSettingsResponse: (settings) => settings,
|
|
23
|
+
resolveDirectoryCandidate: (value) => value,
|
|
24
|
+
normalizeManagedRemoteTunnelHostname: (value) => value,
|
|
25
|
+
normalizeManagedRemoteTunnelPresets: (value) => value,
|
|
26
|
+
normalizeManagedRemoteTunnelPresetTokens: (value) => value,
|
|
27
|
+
syncManagedRemoteTunnelConfigWithPresets: async () => {},
|
|
28
|
+
upsertManagedRemoteTunnelToken: async () => {},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
runtime,
|
|
33
|
+
settingsFilePath,
|
|
34
|
+
tempRoot,
|
|
35
|
+
cleanup: async () => {
|
|
36
|
+
await fsPromises.rm(tempRoot, { recursive: true, force: true });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe('settings runtime', () => {
|
|
42
|
+
it('only remaps project plan paths within the migrated storage directory', async () => {
|
|
43
|
+
const { runtime, settingsFilePath, tempRoot, cleanup } = await createRuntime();
|
|
44
|
+
try {
|
|
45
|
+
const projectPath = path.join(tempRoot, 'project');
|
|
46
|
+
const oldProjectId = 'legacy-project-id';
|
|
47
|
+
const newProjectId = createProjectIdFromPath(projectPath);
|
|
48
|
+
const projectsRoot = path.join(path.dirname(settingsFilePath), 'projects');
|
|
49
|
+
const oldStorageDir = path.join(projectsRoot, oldProjectId);
|
|
50
|
+
const newStorageDir = path.join(projectsRoot, newProjectId);
|
|
51
|
+
const siblingStorageDir = `${oldStorageDir}-sibling`;
|
|
52
|
+
|
|
53
|
+
await fsPromises.mkdir(projectPath, { recursive: true });
|
|
54
|
+
await fsPromises.mkdir(projectsRoot, { recursive: true });
|
|
55
|
+
await fsPromises.writeFile(
|
|
56
|
+
settingsFilePath,
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
projects: [{ id: oldProjectId, path: projectPath, addedAt: 1, lastOpenedAt: 1 }],
|
|
59
|
+
activeProjectId: oldProjectId,
|
|
60
|
+
}, null, 2),
|
|
61
|
+
'utf8',
|
|
62
|
+
);
|
|
63
|
+
await fsPromises.writeFile(
|
|
64
|
+
path.join(projectsRoot, `${oldProjectId}.json`),
|
|
65
|
+
JSON.stringify({
|
|
66
|
+
projectPlanFiles: [
|
|
67
|
+
{ id: 'inside', path: path.join(oldStorageDir, 'plans', 'inside.md') },
|
|
68
|
+
{ id: 'sibling', path: path.join(siblingStorageDir, 'plans', 'outside.md') },
|
|
69
|
+
],
|
|
70
|
+
}, null, 2),
|
|
71
|
+
'utf8',
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await runtime.readSettingsFromDiskMigrated();
|
|
75
|
+
|
|
76
|
+
const migratedConfig = JSON.parse(await fsPromises.readFile(path.join(projectsRoot, `${newProjectId}.json`), 'utf8'));
|
|
77
|
+
expect(migratedConfig.projectPlanFiles).toEqual([
|
|
78
|
+
{ id: 'inside', path: path.join(newStorageDir, 'plans', 'inside.md') },
|
|
79
|
+
{ id: 'sibling', path: path.join(siblingStorageDir, 'plans', 'outside.md') },
|
|
80
|
+
]);
|
|
81
|
+
} finally {
|
|
82
|
+
await cleanup();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
import { parse as parseJsonc } from 'jsonc-parser';
|
|
6
|
+
|
|
7
|
+
// ============== PATH CONSTANTS ==============
|
|
8
|
+
|
|
9
|
+
const DEFAULT_OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.vinci', 'opencode');
|
|
10
|
+
const OPENCODE_CONFIG_DIR = process.env.OPENCODE_CONFIG_DIR
|
|
11
|
+
? path.resolve(process.env.OPENCODE_CONFIG_DIR)
|
|
12
|
+
: DEFAULT_OPENCODE_CONFIG_DIR;
|
|
13
|
+
const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agents');
|
|
14
|
+
const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'commands');
|
|
15
|
+
const SKILL_DIR = path.join(OPENCODE_CONFIG_DIR, 'skills');
|
|
16
|
+
const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'config.json');
|
|
17
|
+
const CUSTOM_CONFIG_FILE = process.env.OPENCODE_CONFIG
|
|
18
|
+
? path.resolve(process.env.OPENCODE_CONFIG)
|
|
19
|
+
: null;
|
|
20
|
+
const PROMPT_FILE_PATTERN = /^\{file:(.+)\}$/i;
|
|
21
|
+
|
|
22
|
+
console.log('[shared] OPENCODE_CONFIG_DIR:', OPENCODE_CONFIG_DIR);
|
|
23
|
+
console.log('[shared] CONFIG_FILE:', CONFIG_FILE);
|
|
24
|
+
console.log('[shared] opencode.json exists:', fs.existsSync(path.join(OPENCODE_CONFIG_DIR, 'opencode.json')));
|
|
25
|
+
console.log('[shared] config.json exists:', fs.existsSync(CONFIG_FILE));
|
|
26
|
+
|
|
27
|
+
// Bootstrap: copy bundled opencode-tools into OPENCODE_CONFIG_DIR if opencode.json is missing.
|
|
28
|
+
// This runs at module init so the config is ready before OpenCode starts.
|
|
29
|
+
(function bootstrapOpencodeConfig() {
|
|
30
|
+
const opencodeJson = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
|
|
31
|
+
if (fs.existsSync(opencodeJson)) {
|
|
32
|
+
console.log('[shared] Bootstrap: opencode.json already exists, skipping');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const distDir = process.env.VINCI_DIST_DIR;
|
|
36
|
+
if (!distDir) {
|
|
37
|
+
console.warn('[shared] Bootstrap: VINCI_DIST_DIR not set, cannot locate bundled opencode-tools');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const toolsSrc = path.resolve(distDir, '..', 'opencode-tools');
|
|
41
|
+
if (!fs.existsSync(toolsSrc) || !fs.existsSync(path.join(toolsSrc, 'opencode.json'))) {
|
|
42
|
+
console.warn(`[shared] Bootstrap: tools source ${toolsSrc} not found or missing opencode.json`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(`[shared] Bootstrap: copying ${toolsSrc} -> ${OPENCODE_CONFIG_DIR}`);
|
|
46
|
+
copyDirRecursiveSync(toolsSrc, OPENCODE_CONFIG_DIR);
|
|
47
|
+
if (fs.existsSync(opencodeJson)) {
|
|
48
|
+
console.log(`[shared] Bootstrap OK: opencode.json now at ${opencodeJson}`);
|
|
49
|
+
} else {
|
|
50
|
+
console.error(`[shared] Bootstrap FAILED: opencode.json still missing after copy`);
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
|
|
54
|
+
// ============== SCOPE TYPE CONSTANTS ==============
|
|
55
|
+
|
|
56
|
+
const AGENT_SCOPE = {
|
|
57
|
+
USER: 'user',
|
|
58
|
+
PROJECT: 'project'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const COMMAND_SCOPE = {
|
|
62
|
+
USER: 'user',
|
|
63
|
+
PROJECT: 'project'
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const SKILL_SCOPE = {
|
|
67
|
+
USER: 'user',
|
|
68
|
+
PROJECT: 'project'
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ============== DIRECTORY OPERATIONS ==============
|
|
72
|
+
|
|
73
|
+
function copyDirRecursiveSync(src, dst) {
|
|
74
|
+
if (!fs.existsSync(src)) return;
|
|
75
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
76
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
77
|
+
const srcPath = path.join(src, entry.name);
|
|
78
|
+
const dstPath = path.join(dst, entry.name);
|
|
79
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
80
|
+
if (entry.isDirectory()) {
|
|
81
|
+
copyDirRecursiveSync(srcPath, dstPath);
|
|
82
|
+
} else {
|
|
83
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function ensureDirs() {
|
|
89
|
+
if (!fs.existsSync(OPENCODE_CONFIG_DIR)) {
|
|
90
|
+
fs.mkdirSync(OPENCODE_CONFIG_DIR, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
if (!fs.existsSync(AGENT_DIR)) {
|
|
93
|
+
fs.mkdirSync(AGENT_DIR, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
if (!fs.existsSync(COMMAND_DIR)) {
|
|
96
|
+
fs.mkdirSync(COMMAND_DIR, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
if (!fs.existsSync(SKILL_DIR)) {
|
|
99
|
+
fs.mkdirSync(SKILL_DIR, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Bootstrap: copy bundled opencode-tools into OPENCODE_CONFIG_DIR if opencode.json is missing.
|
|
103
|
+
// This ensures the sidecar has config even when started directly (not via Tauri).
|
|
104
|
+
const opencodeJson = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
|
|
105
|
+
if (!fs.existsSync(opencodeJson)) {
|
|
106
|
+
const distDir = process.env.VINCI_DIST_DIR;
|
|
107
|
+
if (distDir) {
|
|
108
|
+
const toolsSrc = path.resolve(distDir, '..', 'opencode-tools');
|
|
109
|
+
if (fs.existsSync(toolsSrc) && fs.existsSync(path.join(toolsSrc, 'opencode.json'))) {
|
|
110
|
+
console.log(`[shared] Bootstrapping opencode config: copying ${toolsSrc} -> ${OPENCODE_CONFIG_DIR}`);
|
|
111
|
+
copyDirRecursiveSync(toolsSrc, OPENCODE_CONFIG_DIR);
|
|
112
|
+
if (fs.existsSync(opencodeJson)) {
|
|
113
|
+
console.log(`[shared] Bootstrap OK: opencode.json now exists at ${opencodeJson}`);
|
|
114
|
+
} else {
|
|
115
|
+
console.error(`[shared] Bootstrap FAILED: opencode.json still missing after copy`);
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
console.warn(`[shared] Bootstrap skipped: tools source ${toolsSrc} not found or missing opencode.json`);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
console.warn('[shared] Bootstrap skipped: VINCI_DIST_DIR not set');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============== MARKDOWN FILE OPERATIONS ==============
|
|
127
|
+
|
|
128
|
+
function parseMdFile(filePath) {
|
|
129
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
130
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
131
|
+
|
|
132
|
+
if (!match) {
|
|
133
|
+
return { frontmatter: {}, body: content.trim() };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let frontmatter = {};
|
|
137
|
+
try {
|
|
138
|
+
frontmatter = yaml.parse(match[1]) || {};
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.warn(`Failed to parse markdown frontmatter ${filePath}, treating as empty:`, error);
|
|
141
|
+
frontmatter = {};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const body = match[2].trim();
|
|
145
|
+
return { frontmatter, body };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function writeMdFile(filePath, frontmatter, body) {
|
|
149
|
+
try {
|
|
150
|
+
const cleanedFrontmatter = Object.fromEntries(
|
|
151
|
+
Object.entries(frontmatter).filter(([, value]) => value != null)
|
|
152
|
+
);
|
|
153
|
+
const yamlStr = yaml.stringify(cleanedFrontmatter);
|
|
154
|
+
const content = `---\n${yamlStr}---\n\n${body}`;
|
|
155
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
156
|
+
console.log(`Successfully wrote markdown file: ${filePath}`);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`Failed to write markdown file ${filePath}:`, error);
|
|
159
|
+
throw new Error('Failed to write agent markdown file');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============== CONFIG FILE OPERATIONS ==============
|
|
164
|
+
|
|
165
|
+
function getProjectConfigCandidates(workingDirectory) {
|
|
166
|
+
if (!workingDirectory) return [];
|
|
167
|
+
return [
|
|
168
|
+
path.join(workingDirectory, 'opencode.json'),
|
|
169
|
+
path.join(workingDirectory, 'opencode.jsonc'),
|
|
170
|
+
path.join(workingDirectory, '.opencode', 'opencode.json'),
|
|
171
|
+
path.join(workingDirectory, '.opencode', 'opencode.jsonc'),
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getProjectConfigPath(workingDirectory) {
|
|
176
|
+
if (!workingDirectory) return null;
|
|
177
|
+
|
|
178
|
+
const candidates = getProjectConfigCandidates(workingDirectory);
|
|
179
|
+
|
|
180
|
+
for (const candidate of candidates) {
|
|
181
|
+
if (fs.existsSync(candidate)) {
|
|
182
|
+
return candidate;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return candidates[0];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getConfigPaths(workingDirectory) {
|
|
190
|
+
return {
|
|
191
|
+
userPaths: [
|
|
192
|
+
path.join(OPENCODE_CONFIG_DIR, 'config.json'),
|
|
193
|
+
path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
|
|
194
|
+
path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
|
|
195
|
+
],
|
|
196
|
+
projectPath: getProjectConfigPath(workingDirectory),
|
|
197
|
+
customPath: CUSTOM_CONFIG_FILE
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getPrimaryUserConfigPath(userPaths) {
|
|
202
|
+
for (const userPath of userPaths) {
|
|
203
|
+
if (fs.existsSync(userPath)) {
|
|
204
|
+
return userPath;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return CONFIG_FILE;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function readConfigFile(filePath) {
|
|
212
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
213
|
+
return {};
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
217
|
+
const normalized = content.trim();
|
|
218
|
+
if (!normalized) {
|
|
219
|
+
return {};
|
|
220
|
+
}
|
|
221
|
+
return parseJsonc(normalized, [], { allowTrailingComma: true });
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error(`Failed to read config file: ${filePath}`, error);
|
|
224
|
+
throw new Error('Failed to read OpenCode configuration');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isPlainObject(value) {
|
|
229
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function mergeConfigs(base, override) {
|
|
233
|
+
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
234
|
+
return override;
|
|
235
|
+
}
|
|
236
|
+
const result = { ...base };
|
|
237
|
+
for (const [key, value] of Object.entries(override)) {
|
|
238
|
+
if (key in result) {
|
|
239
|
+
const baseValue = result[key];
|
|
240
|
+
if (isPlainObject(baseValue) && isPlainObject(value)) {
|
|
241
|
+
result[key] = mergeConfigs(baseValue, value);
|
|
242
|
+
} else {
|
|
243
|
+
result[key] = value;
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
result[key] = value;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function readConfigLayers(workingDirectory) {
|
|
253
|
+
const { userPaths, projectPath, customPath } = getConfigPaths(workingDirectory);
|
|
254
|
+
const userPath = getPrimaryUserConfigPath(userPaths);
|
|
255
|
+
const userConfig = readConfigFile(userPath);
|
|
256
|
+
const projectConfig = readConfigFile(projectPath);
|
|
257
|
+
const customConfig = readConfigFile(customPath);
|
|
258
|
+
const mergedConfig = mergeConfigs(mergeConfigs(userConfig, projectConfig), customConfig);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
userConfig,
|
|
262
|
+
projectConfig,
|
|
263
|
+
customConfig,
|
|
264
|
+
mergedConfig,
|
|
265
|
+
paths: { userPath, projectPath, customPath }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function readConfig(workingDirectory) {
|
|
270
|
+
return readConfigLayers(workingDirectory).mergedConfig;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getConfigForPath(layers, targetPath) {
|
|
274
|
+
if (!targetPath) {
|
|
275
|
+
return layers.userConfig;
|
|
276
|
+
}
|
|
277
|
+
if (layers.paths.customPath && targetPath === layers.paths.customPath) {
|
|
278
|
+
return layers.customConfig;
|
|
279
|
+
}
|
|
280
|
+
if (layers.paths.projectPath && targetPath === layers.paths.projectPath) {
|
|
281
|
+
return layers.projectConfig;
|
|
282
|
+
}
|
|
283
|
+
return layers.userConfig;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function writeConfig(config, filePath = CONFIG_FILE) {
|
|
287
|
+
try {
|
|
288
|
+
if (fs.existsSync(filePath)) {
|
|
289
|
+
const backupFile = `${filePath}.vinci.backup`;
|
|
290
|
+
fs.copyFileSync(filePath, backupFile);
|
|
291
|
+
console.log(`Created config backup: ${backupFile}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
295
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf8');
|
|
296
|
+
console.log(`Successfully wrote config file: ${filePath}`);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error(`Failed to write config file: ${filePath}`, error);
|
|
299
|
+
throw new Error('Failed to write OpenCode configuration');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getJsonEntrySource(layers, sectionKey, entryName) {
|
|
304
|
+
const { userConfig, projectConfig, customConfig, paths } = layers;
|
|
305
|
+
const customSection = customConfig?.[sectionKey]?.[entryName];
|
|
306
|
+
if (customSection !== undefined) {
|
|
307
|
+
return { section: customSection, config: customConfig, path: paths.customPath, exists: true };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const projectSection = projectConfig?.[sectionKey]?.[entryName];
|
|
311
|
+
if (projectSection !== undefined) {
|
|
312
|
+
return { section: projectSection, config: projectConfig, path: paths.projectPath, exists: true };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const userSection = userConfig?.[sectionKey]?.[entryName];
|
|
316
|
+
if (userSection !== undefined) {
|
|
317
|
+
return { section: userSection, config: userConfig, path: paths.userPath, exists: true };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { section: null, config: null, path: null, exists: false };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getJsonWriteTarget(layers, preferredScope) {
|
|
324
|
+
const { userConfig, projectConfig, customConfig, paths } = layers;
|
|
325
|
+
if (paths.customPath) {
|
|
326
|
+
return { config: customConfig, path: paths.customPath };
|
|
327
|
+
}
|
|
328
|
+
if (preferredScope === AGENT_SCOPE.PROJECT && paths.projectPath) {
|
|
329
|
+
return { config: projectConfig, path: paths.projectPath };
|
|
330
|
+
}
|
|
331
|
+
return { config: userConfig, path: paths.userPath };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ============== GIT/WORKTREE HELPERS ==============
|
|
335
|
+
|
|
336
|
+
function getAncestors(startDir, stopDir) {
|
|
337
|
+
if (!startDir) return [];
|
|
338
|
+
const result = [];
|
|
339
|
+
let current = path.resolve(startDir);
|
|
340
|
+
const resolvedStop = stopDir ? path.resolve(stopDir) : null;
|
|
341
|
+
|
|
342
|
+
while (true) {
|
|
343
|
+
result.push(current);
|
|
344
|
+
if (resolvedStop && current === resolvedStop) {
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
const parent = path.dirname(current);
|
|
348
|
+
if (parent === current) {
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
current = parent;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function findWorktreeRoot(startDir) {
|
|
358
|
+
if (!startDir) return null;
|
|
359
|
+
let current = path.resolve(startDir);
|
|
360
|
+
|
|
361
|
+
while (true) {
|
|
362
|
+
if (fs.existsSync(path.join(current, '.git'))) {
|
|
363
|
+
return current;
|
|
364
|
+
}
|
|
365
|
+
const parent = path.dirname(current);
|
|
366
|
+
if (parent === current) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
current = parent;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ============== PROMPT FILE HELPERS ==============
|
|
374
|
+
|
|
375
|
+
function isPromptFileReference(value) {
|
|
376
|
+
if (typeof value !== 'string') {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
return PROMPT_FILE_PATTERN.test(value.trim());
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function resolvePromptFilePath(reference) {
|
|
383
|
+
const match = typeof reference === 'string' ? reference.trim().match(PROMPT_FILE_PATTERN) : null;
|
|
384
|
+
if (!match) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
let target = match[1].trim();
|
|
388
|
+
if (!target) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (target.startsWith('./')) {
|
|
393
|
+
target = target.slice(2);
|
|
394
|
+
target = path.join(OPENCODE_CONFIG_DIR, target);
|
|
395
|
+
} else if (!path.isAbsolute(target)) {
|
|
396
|
+
target = path.join(OPENCODE_CONFIG_DIR, target);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return target;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function writePromptFile(filePath, content) {
|
|
403
|
+
const dir = path.dirname(filePath);
|
|
404
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
405
|
+
fs.writeFileSync(filePath, content ?? '', 'utf8');
|
|
406
|
+
console.log(`Updated prompt file: ${filePath}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ============== SKILL FILE OPERATIONS ==============
|
|
410
|
+
|
|
411
|
+
function walkSkillMdFiles(rootDir) {
|
|
412
|
+
if (!rootDir || !fs.existsSync(rootDir)) return [];
|
|
413
|
+
|
|
414
|
+
const results = [];
|
|
415
|
+
const walk = (dir) => {
|
|
416
|
+
let entries = [];
|
|
417
|
+
try {
|
|
418
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
419
|
+
} catch {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for (const entry of entries) {
|
|
424
|
+
const fullPath = path.join(dir, entry.name);
|
|
425
|
+
if (entry.isDirectory()) {
|
|
426
|
+
walk(fullPath);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (entry.isFile() && entry.name === 'SKILL.md') {
|
|
430
|
+
results.push(fullPath);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
walk(rootDir);
|
|
436
|
+
return results;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function addSkillFromMdFile(skillsMap, skillMdPath, scope, source) {
|
|
440
|
+
let parsed;
|
|
441
|
+
try {
|
|
442
|
+
parsed = parseMdFile(skillMdPath);
|
|
443
|
+
} catch {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const name = typeof parsed.frontmatter?.name === 'string'
|
|
448
|
+
? parsed.frontmatter.name.trim()
|
|
449
|
+
: '';
|
|
450
|
+
const description = typeof parsed.frontmatter?.description === 'string'
|
|
451
|
+
? parsed.frontmatter.description
|
|
452
|
+
: '';
|
|
453
|
+
|
|
454
|
+
if (!name) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
skillsMap.set(name, {
|
|
459
|
+
name,
|
|
460
|
+
path: skillMdPath,
|
|
461
|
+
scope,
|
|
462
|
+
source,
|
|
463
|
+
description,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function resolveSkillSearchDirectories(workingDirectory) {
|
|
468
|
+
const directories = [];
|
|
469
|
+
const pushDir = (dir) => {
|
|
470
|
+
if (!dir) return;
|
|
471
|
+
const resolved = path.resolve(dir);
|
|
472
|
+
if (!directories.includes(resolved)) {
|
|
473
|
+
directories.push(resolved);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
pushDir(OPENCODE_CONFIG_DIR);
|
|
478
|
+
|
|
479
|
+
if (workingDirectory) {
|
|
480
|
+
const worktreeRoot = findWorktreeRoot(workingDirectory) || path.resolve(workingDirectory);
|
|
481
|
+
const projectDirs = getAncestors(workingDirectory, worktreeRoot)
|
|
482
|
+
.map((dir) => path.join(dir, '.opencode'));
|
|
483
|
+
projectDirs.forEach(pushDir);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
pushDir(path.join(os.homedir(), '.opencode'));
|
|
487
|
+
|
|
488
|
+
const customConfigDir = process.env.OPENCODE_CONFIG_DIR
|
|
489
|
+
? path.resolve(process.env.OPENCODE_CONFIG_DIR)
|
|
490
|
+
: null;
|
|
491
|
+
pushDir(customConfigDir);
|
|
492
|
+
|
|
493
|
+
return directories;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function listSkillSupportingFiles(skillDir) {
|
|
497
|
+
if (!fs.existsSync(skillDir)) {
|
|
498
|
+
return [];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const files = [];
|
|
502
|
+
|
|
503
|
+
function walkDir(dir, relativePath = '') {
|
|
504
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
505
|
+
for (const entry of entries) {
|
|
506
|
+
const fullPath = path.join(dir, entry.name);
|
|
507
|
+
const relPath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
508
|
+
|
|
509
|
+
if (entry.isDirectory()) {
|
|
510
|
+
walkDir(fullPath, relPath);
|
|
511
|
+
} else if (entry.name !== 'SKILL.md') {
|
|
512
|
+
files.push({
|
|
513
|
+
name: entry.name,
|
|
514
|
+
path: relPath,
|
|
515
|
+
fullPath: fullPath
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
walkDir(skillDir);
|
|
522
|
+
return files;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function assertPathWithinSkillDir(skillDir, relativePath) {
|
|
526
|
+
const root = fs.realpathSync(skillDir);
|
|
527
|
+
const target = path.resolve(root, relativePath);
|
|
528
|
+
const relative = path.relative(root, target);
|
|
529
|
+
const isWithin = relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
530
|
+
|
|
531
|
+
if (!isWithin) {
|
|
532
|
+
const error = new Error('Access to file denied');
|
|
533
|
+
error.code = 'EACCES';
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return target;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function readSkillSupportingFile(skillDir, relativePath) {
|
|
541
|
+
const fullPath = assertPathWithinSkillDir(skillDir, relativePath);
|
|
542
|
+
if (!fs.existsSync(fullPath)) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
return fs.readFileSync(fullPath, 'utf8');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function writeSkillSupportingFile(skillDir, relativePath, content) {
|
|
549
|
+
const fullPath = assertPathWithinSkillDir(skillDir, relativePath);
|
|
550
|
+
const dir = path.dirname(fullPath);
|
|
551
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
552
|
+
fs.writeFileSync(fullPath, content, 'utf8');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function deleteSkillSupportingFile(skillDir, relativePath) {
|
|
556
|
+
const root = fs.realpathSync(skillDir);
|
|
557
|
+
const fullPath = assertPathWithinSkillDir(skillDir, relativePath);
|
|
558
|
+
if (fs.existsSync(fullPath)) {
|
|
559
|
+
fs.unlinkSync(fullPath);
|
|
560
|
+
let parentDir = path.dirname(fullPath);
|
|
561
|
+
while (parentDir !== root) {
|
|
562
|
+
try {
|
|
563
|
+
const entries = fs.readdirSync(parentDir);
|
|
564
|
+
if (entries.length === 0) {
|
|
565
|
+
fs.rmdirSync(parentDir);
|
|
566
|
+
parentDir = path.dirname(parentDir);
|
|
567
|
+
} else {
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
} catch {
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export {
|
|
578
|
+
OPENCODE_CONFIG_DIR,
|
|
579
|
+
AGENT_DIR,
|
|
580
|
+
COMMAND_DIR,
|
|
581
|
+
SKILL_DIR,
|
|
582
|
+
CONFIG_FILE,
|
|
583
|
+
CUSTOM_CONFIG_FILE,
|
|
584
|
+
PROMPT_FILE_PATTERN,
|
|
585
|
+
AGENT_SCOPE,
|
|
586
|
+
COMMAND_SCOPE,
|
|
587
|
+
SKILL_SCOPE,
|
|
588
|
+
ensureDirs,
|
|
589
|
+
parseMdFile,
|
|
590
|
+
writeMdFile,
|
|
591
|
+
getProjectConfigCandidates,
|
|
592
|
+
getProjectConfigPath,
|
|
593
|
+
getConfigPaths,
|
|
594
|
+
readConfigFile,
|
|
595
|
+
isPlainObject,
|
|
596
|
+
mergeConfigs,
|
|
597
|
+
readConfigLayers,
|
|
598
|
+
readConfig,
|
|
599
|
+
getConfigForPath,
|
|
600
|
+
writeConfig,
|
|
601
|
+
getJsonEntrySource,
|
|
602
|
+
getJsonWriteTarget,
|
|
603
|
+
getAncestors,
|
|
604
|
+
findWorktreeRoot,
|
|
605
|
+
isPromptFileReference,
|
|
606
|
+
resolvePromptFilePath,
|
|
607
|
+
writePromptFile,
|
|
608
|
+
walkSkillMdFiles,
|
|
609
|
+
addSkillFromMdFile,
|
|
610
|
+
resolveSkillSearchDirectories,
|
|
611
|
+
listSkillSupportingFiles,
|
|
612
|
+
readSkillSupportingFile,
|
|
613
|
+
writeSkillSupportingFile,
|
|
614
|
+
deleteSkillSupportingFile,
|
|
615
|
+
};
|