@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.
Files changed (337) hide show
  1. package/README.md +197 -0
  2. package/bin/cli-entry.js +55 -0
  3. package/bin/cli-output.js +145 -0
  4. package/bin/cli.js +4887 -0
  5. package/bin/cli.test.js +64 -0
  6. package/dist/apple-touch-icon-120x120.png +0 -0
  7. package/dist/apple-touch-icon-152x152.png +0 -0
  8. package/dist/apple-touch-icon-167x167.png +0 -0
  9. package/dist/apple-touch-icon-180x180.png +0 -0
  10. package/dist/apple-touch-icon.png +0 -0
  11. package/dist/apple-touch-icon.svg +528 -0
  12. package/dist/assets/JsonTreeView-CSm9OzXG.js +1 -0
  13. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  14. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  15. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  19. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  20. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  21. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  25. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  26. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  27. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  30. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  31. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  32. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  33. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  34. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  35. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  37. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  38. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  39. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  40. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  41. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  42. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  43. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  44. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  45. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  52. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  53. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  54. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  55. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  56. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  57. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  58. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  59. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  60. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  61. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  62. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  63. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  64. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  65. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  66. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  67. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  68. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  69. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  70. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  71. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  72. package/dist/assets/MarkdownRendererImpl-DensKOLc.js +6 -0
  73. package/dist/assets/MultiRunWindow-Bo7THayo.js +1 -0
  74. package/dist/assets/OnboardingScreen-BDqmzTVR.js +2 -0
  75. package/dist/assets/SettingsWindow-coz__Ykw.js +1 -0
  76. package/dist/assets/TerminalView-DrZ-i3Dr.js +1 -0
  77. package/dist/assets/ToolOutputDialog-Eglzslt3.js +16 -0
  78. package/dist/assets/es-4o9ciP61.js +15 -0
  79. package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  80. package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  81. package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  82. package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  83. package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  84. package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  85. package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  86. package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  87. package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  88. package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  89. package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  90. package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  91. package/dist/assets/index-DLTDToSP.css +1 -0
  92. package/dist/assets/index-DgiFEKGN.js +1 -0
  93. package/dist/assets/ko-B20imCHE.js +15 -0
  94. package/dist/assets/main-BV3KOtdA.css +1 -0
  95. package/dist/assets/main-CDKJj0sH.js +226 -0
  96. package/dist/assets/main-LC-PSNVM.js +2 -0
  97. package/dist/assets/miniChat-CQUiG_cr.js +2 -0
  98. package/dist/assets/modelPrefsAutoSave-Dm799vzR.js +6986 -0
  99. package/dist/assets/pl-DQJ7LSzj.js +15 -0
  100. package/dist/assets/pt-BR-OmjHUz9y.js +15 -0
  101. package/dist/assets/renderElectronMiniChatApp-CARbeW0G.js +2 -0
  102. package/dist/assets/uk-BNFxOlO4.js +15 -0
  103. package/dist/assets/vendor--DBfsbEis.css +1 -0
  104. package/dist/assets/vendor-.bun-B9l0ZNi2.js +4094 -0
  105. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  106. package/dist/assets/wasmSttWorker-Dtlxac_K.js +1 -0
  107. package/dist/assets/wasmSttWorker-oo7Dm_jy.js +1806 -0
  108. package/dist/assets/worker-CbT6TVo7.js +155 -0
  109. package/dist/assets/zh-CN-C6T-Ac7F.js +15 -0
  110. package/dist/favicon-16.png +0 -0
  111. package/dist/favicon-32.png +0 -0
  112. package/dist/favicon.png +0 -0
  113. package/dist/favicon.svg +528 -0
  114. package/dist/index.html +607 -0
  115. package/dist/logo-dark-192x192.png +0 -0
  116. package/dist/logo-dark-512x512.png +0 -0
  117. package/dist/logo-dark-512x512.svg +528 -0
  118. package/dist/logo-light-192x192.png +0 -0
  119. package/dist/logo-light-512x512.png +0 -0
  120. package/dist/logo-light-512x512.svg +528 -0
  121. package/dist/mini-chat.html +16 -0
  122. package/dist/pwa-192.png +0 -0
  123. package/dist/pwa-512.png +0 -0
  124. package/dist/pwa-maskable-192.png +0 -0
  125. package/dist/pwa-maskable-512.png +0 -0
  126. package/dist/site.webmanifest +21 -0
  127. package/dist/sw.js +1 -0
  128. package/package.json +118 -0
  129. package/public/apple-touch-icon-120x120.png +0 -0
  130. package/public/apple-touch-icon-152x152.png +0 -0
  131. package/public/apple-touch-icon-167x167.png +0 -0
  132. package/public/apple-touch-icon-180x180.png +0 -0
  133. package/public/apple-touch-icon.png +0 -0
  134. package/public/apple-touch-icon.svg +528 -0
  135. package/public/favicon-16.png +0 -0
  136. package/public/favicon-32.png +0 -0
  137. package/public/favicon.png +0 -0
  138. package/public/favicon.svg +528 -0
  139. package/public/logo-dark-192x192.png +0 -0
  140. package/public/logo-dark-512x512.png +0 -0
  141. package/public/logo-dark-512x512.svg +528 -0
  142. package/public/logo-light-192x192.png +0 -0
  143. package/public/logo-light-512x512.png +0 -0
  144. package/public/logo-light-512x512.svg +528 -0
  145. package/public/pwa-192.png +0 -0
  146. package/public/pwa-512.png +0 -0
  147. package/public/pwa-maskable-192.png +0 -0
  148. package/public/pwa-maskable-512.png +0 -0
  149. package/public/site.webmanifest +21 -0
  150. package/server/TERMINAL_WS_PROTOCOL.md +48 -0
  151. package/server/index.d.ts +39 -0
  152. package/server/index.js +1311 -0
  153. package/server/lib/cloudflare-tunnel.js +650 -0
  154. package/server/lib/event-stream/DOCUMENTATION.md +61 -0
  155. package/server/lib/event-stream/directory-ws-bridge.js +185 -0
  156. package/server/lib/event-stream/global-hub.js +158 -0
  157. package/server/lib/event-stream/global-hub.test.js +140 -0
  158. package/server/lib/event-stream/global-ws-bridge.js +206 -0
  159. package/server/lib/event-stream/index.js +25 -0
  160. package/server/lib/event-stream/protocol.js +131 -0
  161. package/server/lib/event-stream/protocol.test.js +182 -0
  162. package/server/lib/event-stream/runtime.js +180 -0
  163. package/server/lib/event-stream/runtime.test.js +512 -0
  164. package/server/lib/event-stream/upstream-reader.js +226 -0
  165. package/server/lib/event-stream/upstream-reader.test.js +276 -0
  166. package/server/lib/fs/DOCUMENTATION.md +36 -0
  167. package/server/lib/fs/routes.js +1040 -0
  168. package/server/lib/fs/search.js +238 -0
  169. package/server/lib/git/DOCUMENTATION.md +152 -0
  170. package/server/lib/git/credentials.js +74 -0
  171. package/server/lib/git/identity-storage.js +112 -0
  172. package/server/lib/git/index.js +6 -0
  173. package/server/lib/git/routes.js +972 -0
  174. package/server/lib/git/service.js +3432 -0
  175. package/server/lib/git/service.test.js +39 -0
  176. package/server/lib/github/DOCUMENTATION.md +171 -0
  177. package/server/lib/github/auth.js +307 -0
  178. package/server/lib/github/device-flow.js +50 -0
  179. package/server/lib/github/index.js +24 -0
  180. package/server/lib/github/octokit.js +10 -0
  181. package/server/lib/github/pr-status.js +519 -0
  182. package/server/lib/github/repo/fork-detection.js +102 -0
  183. package/server/lib/github/repo/index.js +55 -0
  184. package/server/lib/github/routes.js +1560 -0
  185. package/server/lib/magic-prompts/routes.js +63 -0
  186. package/server/lib/magic-prompts/runtime.js +119 -0
  187. package/server/lib/notifications/DOCUMENTATION.md +122 -0
  188. package/server/lib/notifications/emitter-runtime.js +102 -0
  189. package/server/lib/notifications/index.js +4 -0
  190. package/server/lib/notifications/message.js +52 -0
  191. package/server/lib/notifications/message.test.js +34 -0
  192. package/server/lib/notifications/push-runtime.js +304 -0
  193. package/server/lib/notifications/routes.js +315 -0
  194. package/server/lib/notifications/runtime.js +566 -0
  195. package/server/lib/notifications/template-runtime.js +349 -0
  196. package/server/lib/notifications/template-runtime.test.js +26 -0
  197. package/server/lib/opencode/DOCUMENTATION.md +362 -0
  198. package/server/lib/opencode/agents.js +634 -0
  199. package/server/lib/opencode/auth-state-runtime.js +88 -0
  200. package/server/lib/opencode/auth.js +83 -0
  201. package/server/lib/opencode/bootstrap-runtime.js +131 -0
  202. package/server/lib/opencode/cli-entry-runtime.js +43 -0
  203. package/server/lib/opencode/cli-options.js +128 -0
  204. package/server/lib/opencode/commands.js +339 -0
  205. package/server/lib/opencode/config-entity-routes.js +370 -0
  206. package/server/lib/opencode/core-routes.js +500 -0
  207. package/server/lib/opencode/core-routes.test.js +26 -0
  208. package/server/lib/opencode/env-config.js +74 -0
  209. package/server/lib/opencode/env-keys.js +68 -0
  210. package/server/lib/opencode/env-runtime.js +1162 -0
  211. package/server/lib/opencode/env-runtime.test.js +116 -0
  212. package/server/lib/opencode/feature-routes-runtime.js +244 -0
  213. package/server/lib/opencode/hmr-state-runtime.js +85 -0
  214. package/server/lib/opencode/index.js +66 -0
  215. package/server/lib/opencode/lifecycle.js +1019 -0
  216. package/server/lib/opencode/lifecycle.test.js +240 -0
  217. package/server/lib/opencode/mcp.js +278 -0
  218. package/server/lib/opencode/network-runtime.js +104 -0
  219. package/server/lib/opencode/network-runtime.test.js +37 -0
  220. package/server/lib/opencode/opencode-resolution-runtime.js +71 -0
  221. package/server/lib/opencode/path-utils.js +100 -0
  222. package/server/lib/opencode/path-utils.test.js +71 -0
  223. package/server/lib/opencode/project-directory-runtime.js +124 -0
  224. package/server/lib/opencode/project-icon-routes.js +399 -0
  225. package/server/lib/opencode/project-icon-routes.test.js +107 -0
  226. package/server/lib/opencode/providers.js +96 -0
  227. package/server/lib/opencode/proxy.js +445 -0
  228. package/server/lib/opencode/pwa-manifest-routes.js +257 -0
  229. package/server/lib/opencode/pwa-manifest-routes.test.js +133 -0
  230. package/server/lib/opencode/routes.js +541 -0
  231. package/server/lib/opencode/server-startup-runtime.js +156 -0
  232. package/server/lib/opencode/server-utils-runtime.js +168 -0
  233. package/server/lib/opencode/server-utils-runtime.test.js +135 -0
  234. package/server/lib/opencode/session-runtime.js +356 -0
  235. package/server/lib/opencode/session-runtime.test.js +151 -0
  236. package/server/lib/opencode/settings-helpers.js +770 -0
  237. package/server/lib/opencode/settings-helpers.test.js +109 -0
  238. package/server/lib/opencode/settings-normalization-runtime.js +428 -0
  239. package/server/lib/opencode/settings-runtime.js +826 -0
  240. package/server/lib/opencode/settings-runtime.test.js +85 -0
  241. package/server/lib/opencode/shared.js +615 -0
  242. package/server/lib/opencode/shutdown-runtime.js +139 -0
  243. package/server/lib/opencode/shutdown-runtime.test.js +58 -0
  244. package/server/lib/opencode/skill-routes.js +701 -0
  245. package/server/lib/opencode/skills.js +548 -0
  246. package/server/lib/opencode/startup-pipeline-runtime.js +130 -0
  247. package/server/lib/opencode/static-routes-runtime.js +65 -0
  248. package/server/lib/opencode/theme-runtime.js +167 -0
  249. package/server/lib/opencode/tunnel-auth.js +591 -0
  250. package/server/lib/opencode/tunnel-wiring-runtime.js +94 -0
  251. package/server/lib/opencode/vinci-routes.js +76 -0
  252. package/server/lib/opencode/watcher.js +115 -0
  253. package/server/lib/opencode/watcher.test.js +239 -0
  254. package/server/lib/preview/proxy-runtime.js +1333 -0
  255. package/server/lib/preview/proxy-runtime.test.js +144 -0
  256. package/server/lib/projects/project-config.js +567 -0
  257. package/server/lib/projects/project-config.test.js +175 -0
  258. package/server/lib/projects/project-id.js +13 -0
  259. package/server/lib/quota/DOCUMENTATION.md +58 -0
  260. package/server/lib/quota/index.js +25 -0
  261. package/server/lib/quota/providers/claude.js +107 -0
  262. package/server/lib/quota/providers/codex.js +113 -0
  263. package/server/lib/quota/providers/copilot.js +165 -0
  264. package/server/lib/quota/providers/google/api.js +92 -0
  265. package/server/lib/quota/providers/google/auth.js +108 -0
  266. package/server/lib/quota/providers/google/index.js +124 -0
  267. package/server/lib/quota/providers/google/transforms.js +109 -0
  268. package/server/lib/quota/providers/index.js +168 -0
  269. package/server/lib/quota/providers/interface.js +55 -0
  270. package/server/lib/quota/providers/kimi.js +108 -0
  271. package/server/lib/quota/providers/minimax-cn-coding-plan.js +140 -0
  272. package/server/lib/quota/providers/minimax-coding-plan.js +139 -0
  273. package/server/lib/quota/providers/nanogpt.js +124 -0
  274. package/server/lib/quota/providers/ollama-cloud.js +112 -0
  275. package/server/lib/quota/providers/openai.js +91 -0
  276. package/server/lib/quota/providers/openrouter.js +92 -0
  277. package/server/lib/quota/providers/zai.js +91 -0
  278. package/server/lib/quota/providers/zhipuai-coding-plan.js +133 -0
  279. package/server/lib/quota/providers/zhipuai.js +114 -0
  280. package/server/lib/quota/routes.js +27 -0
  281. package/server/lib/quota/utils/auth.js +50 -0
  282. package/server/lib/quota/utils/formatters.js +85 -0
  283. package/server/lib/quota/utils/formatters.test.js +54 -0
  284. package/server/lib/quota/utils/index.js +10 -0
  285. package/server/lib/quota/utils/transformers.js +55 -0
  286. package/server/lib/scheduled-tasks/DOCUMENTATION.md +44 -0
  287. package/server/lib/scheduled-tasks/routes.js +235 -0
  288. package/server/lib/scheduled-tasks/runtime.js +773 -0
  289. package/server/lib/scheduled-tasks/runtime.test.js +100 -0
  290. package/server/lib/security/request-security.js +115 -0
  291. package/server/lib/session-folders/routes.js +63 -0
  292. package/server/lib/session-folders/routes.test.js +102 -0
  293. package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
  294. package/server/lib/skills-catalog/cache.js +29 -0
  295. package/server/lib/skills-catalog/clawdhub/api.js +158 -0
  296. package/server/lib/skills-catalog/clawdhub/index.js +30 -0
  297. package/server/lib/skills-catalog/clawdhub/install.js +238 -0
  298. package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
  299. package/server/lib/skills-catalog/curated-sources.js +21 -0
  300. package/server/lib/skills-catalog/git.js +77 -0
  301. package/server/lib/skills-catalog/index.js +42 -0
  302. package/server/lib/skills-catalog/install.js +294 -0
  303. package/server/lib/skills-catalog/scan.js +221 -0
  304. package/server/lib/skills-catalog/source.js +87 -0
  305. package/server/lib/terminal/DOCUMENTATION.md +76 -0
  306. package/server/lib/terminal/index.js +31 -0
  307. package/server/lib/terminal/output-replay-buffer.js +78 -0
  308. package/server/lib/terminal/output-replay-buffer.test.js +75 -0
  309. package/server/lib/terminal/runtime.js +850 -0
  310. package/server/lib/terminal/runtime.test.js +96 -0
  311. package/server/lib/terminal/terminal-ws-protocol.js +68 -0
  312. package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
  313. package/server/lib/text/DOCUMENTATION.md +35 -0
  314. package/server/lib/text/summarization.js +138 -0
  315. package/server/lib/text/summarization.test.js +34 -0
  316. package/server/lib/tts/DOCUMENTATION.md +146 -0
  317. package/server/lib/tts/base-url.js +62 -0
  318. package/server/lib/tts/capability-runtime.js +31 -0
  319. package/server/lib/tts/index.js +19 -0
  320. package/server/lib/tts/routes.js +261 -0
  321. package/server/lib/tts/routes.test.js +53 -0
  322. package/server/lib/tts/service.js +178 -0
  323. package/server/lib/tts/stt.js +75 -0
  324. package/server/lib/tunnels/DOCUMENTATION.md +18 -0
  325. package/server/lib/tunnels/index.js +166 -0
  326. package/server/lib/tunnels/managed-config.js +201 -0
  327. package/server/lib/tunnels/providers/cloudflare.js +260 -0
  328. package/server/lib/tunnels/registry.js +51 -0
  329. package/server/lib/tunnels/routes.js +605 -0
  330. package/server/lib/tunnels/types.js +219 -0
  331. package/server/lib/ui-auth/DOCUMENTATION.md +38 -0
  332. package/server/lib/ui-auth/ui-auth.js +673 -0
  333. package/server/lib/ui-auth/ui-passkeys.js +545 -0
  334. package/server/opencode-proxy.test.js +151 -0
  335. package/server/proxy-headers.js +61 -0
  336. package/server/proxy-headers.test.js +58 -0
  337. package/server/sse-routes.test.js +152 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * ClawdHub API client
3
+ *
4
+ * ClawdHub is a public skill registry at https://clawdhub.com
5
+ * This client provides methods to fetch skills list and download skill packages.
6
+ */
7
+
8
+ const CLAWDHUB_API_BASE = 'https://clawdhub.com/api/v1';
9
+ const CLAWDHUB_PAGE_LIMIT = 25;
10
+
11
+ // Rate limiting: ClawdHub allows 120 requests/minute
12
+ const RATE_LIMIT_DELAY_MS = 100;
13
+ let lastRequestTime = 0;
14
+
15
+ async function rateLimitedFetch(url, options = {}) {
16
+ const maxAttempts = 10;
17
+
18
+ let lastResponse = null;
19
+
20
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
21
+ const now = Date.now();
22
+ const elapsed = now - lastRequestTime;
23
+ if (elapsed < RATE_LIMIT_DELAY_MS) {
24
+ await new Promise((resolve) => setTimeout(resolve, RATE_LIMIT_DELAY_MS - elapsed));
25
+ }
26
+ lastRequestTime = Date.now();
27
+
28
+ const response = await fetch(url, {
29
+ ...options,
30
+ headers: {
31
+ Accept: 'application/json',
32
+ 'User-Agent': 'Vinci/1.0',
33
+ ...options.headers,
34
+ },
35
+ });
36
+
37
+ lastResponse = response;
38
+
39
+ if (response.status === 429 || response.status >= 500) {
40
+ if (attempt < maxAttempts - 1) {
41
+ const waitMs = 50 * (attempt + 1);
42
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
43
+ continue;
44
+ }
45
+ }
46
+
47
+ return response;
48
+ }
49
+
50
+ return lastResponse;
51
+ }
52
+
53
+ /**
54
+ * Fetch paginated list of skills from ClawdHub
55
+ * @param {Object} options
56
+ * @param {string} [options.cursor] - Pagination cursor from previous response
57
+ * @returns {Promise<{ items: Array, nextCursor?: string }>}
58
+ */
59
+ export async function fetchClawdHubSkills({ cursor } = {}) {
60
+ const url = cursor
61
+ ? `${CLAWDHUB_API_BASE}/skills?cursor=${encodeURIComponent(cursor)}&limit=${CLAWDHUB_PAGE_LIMIT}`
62
+ : `${CLAWDHUB_API_BASE}/skills?limit=${CLAWDHUB_PAGE_LIMIT}`;
63
+
64
+ const response = await rateLimitedFetch(url);
65
+
66
+ if (!response.ok) {
67
+ const text = await response.text().catch(() => '');
68
+ throw new Error(`ClawdHub API error (${response.status}): ${text || response.statusText}`);
69
+ }
70
+
71
+ const data = await response.json();
72
+ const nextCursor =
73
+ (typeof data.nextCursor === 'string' && data.nextCursor) ||
74
+ (typeof data.next_cursor === 'string' && data.next_cursor) ||
75
+ (typeof data.next === 'string' && data.next) ||
76
+ (typeof data.cursor === 'string' && data.cursor) ||
77
+ null;
78
+
79
+ return {
80
+ items: data.items || [],
81
+ nextCursor,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Fetch details for a specific skill version
87
+ * @param {string} slug - Skill slug/identifier
88
+ * @param {string} [version='latest'] - Version string or 'latest'
89
+ * @returns {Promise<{ skill: Object, version: Object }>}
90
+ */
91
+ export async function fetchClawdHubSkillVersion(slug, version = 'latest') {
92
+ // For 'latest', we need to first get the skill metadata to find the latest version
93
+ if (version === 'latest') {
94
+ const skillResponse = await rateLimitedFetch(`${CLAWDHUB_API_BASE}/skills/${encodeURIComponent(slug)}`);
95
+ if (!skillResponse.ok) {
96
+ throw new Error(`ClawdHub skill not found: ${slug}`);
97
+ }
98
+ const skillData = await skillResponse.json();
99
+ const latestVersion = skillData.skill?.tags?.latest || skillData.latestVersion?.version;
100
+ if (!latestVersion) {
101
+ throw new Error(`No latest version found for skill: ${slug}`);
102
+ }
103
+ version = latestVersion;
104
+ }
105
+
106
+ const url = `${CLAWDHUB_API_BASE}/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`;
107
+ const response = await rateLimitedFetch(url);
108
+
109
+ if (!response.ok) {
110
+ const text = await response.text().catch(() => '');
111
+ throw new Error(`ClawdHub version error (${response.status}): ${text || response.statusText}`);
112
+ }
113
+
114
+ return response.json();
115
+ }
116
+
117
+ /**
118
+ * Download a skill package as a ZIP buffer
119
+ * @param {string} slug - Skill slug/identifier
120
+ * @param {string} version - Specific version string
121
+ * @returns {Promise<ArrayBuffer>} - ZIP file contents
122
+ */
123
+ export async function downloadClawdHubSkill(slug, version) {
124
+ const versionParam = typeof version === 'string' && version !== 'latest'
125
+ ? `&version=${encodeURIComponent(version)}`
126
+ : '&tag=latest';
127
+ const url = `${CLAWDHUB_API_BASE}/download?slug=${encodeURIComponent(slug)}${versionParam}`;
128
+
129
+ const response = await rateLimitedFetch(url, {
130
+ headers: {
131
+ Accept: 'application/zip',
132
+ },
133
+ });
134
+
135
+ if (!response.ok) {
136
+ const text = await response.text().catch(() => '');
137
+ throw new Error(`ClawdHub download error (${response.status}): ${text || response.statusText}`);
138
+ }
139
+
140
+ return response.arrayBuffer();
141
+ }
142
+
143
+ /**
144
+ * Get skill metadata without version details
145
+ * @param {string} slug - Skill slug/identifier
146
+ * @returns {Promise<Object>}
147
+ */
148
+ export async function fetchClawdHubSkillInfo(slug) {
149
+ const url = `${CLAWDHUB_API_BASE}/skills/${encodeURIComponent(slug)}`;
150
+ const response = await rateLimitedFetch(url);
151
+
152
+ if (!response.ok) {
153
+ const text = await response.text().catch(() => '');
154
+ throw new Error(`ClawdHub skill error (${response.status}): ${text || response.statusText}`);
155
+ }
156
+
157
+ return response.json();
158
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ClawdHub integration module
3
+ *
4
+ * Provides skill browsing and installation from the ClawdHub registry.
5
+ * https://clawdhub.com
6
+ */
7
+
8
+ export { scanClawdHub, scanClawdHubPage } from './scan.js';
9
+ export { installSkillsFromClawdHub } from './install.js';
10
+ export {
11
+ fetchClawdHubSkills,
12
+ fetchClawdHubSkillVersion,
13
+ fetchClawdHubSkillInfo,
14
+ downloadClawdHubSkill,
15
+ } from './api.js';
16
+
17
+ /**
18
+ * Check if a source string refers to ClawdHub
19
+ * @param {string} source
20
+ * @returns {boolean}
21
+ */
22
+ export function isClawdHubSource(source) {
23
+ return typeof source === 'string' && source.startsWith('clawdhub:');
24
+ }
25
+
26
+ /**
27
+ * ClawdHub source identifier used in curated sources
28
+ */
29
+ export const CLAWDHUB_SOURCE_ID = 'clawdhub';
30
+ export const CLAWDHUB_SOURCE_STRING = 'clawdhub:registry';
@@ -0,0 +1,238 @@
1
+ /**
2
+ * ClawdHub skill installation
3
+ *
4
+ * Downloads skills from ClawdHub as ZIP files and extracts them
5
+ * to the appropriate skill directory.
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import os from 'os';
10
+ import path from 'path';
11
+ import AdmZip from 'adm-zip';
12
+
13
+ import { downloadClawdHubSkill, fetchClawdHubSkillInfo } from './api.js';
14
+
15
+ const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
16
+
17
+ function normalizeUserSkillDir(userSkillDir) {
18
+ if (!userSkillDir) return null;
19
+ const legacySkillDir = path.join(os.homedir(), '.vinci', '.opencode', 'skill');
20
+ const pluralSkillDir = path.join(os.homedir(), '.vinci', '.opencode', 'skills');
21
+ if (userSkillDir === legacySkillDir) {
22
+ if (fs.existsSync(legacySkillDir) && !fs.existsSync(pluralSkillDir)) return legacySkillDir;
23
+ return pluralSkillDir;
24
+ }
25
+ return userSkillDir;
26
+ }
27
+
28
+ function validateSkillName(skillName) {
29
+ if (typeof skillName !== 'string') return false;
30
+ if (skillName.length < 1 || skillName.length > 64) return false;
31
+ return SKILL_NAME_PATTERN.test(skillName);
32
+ }
33
+
34
+ async function safeRm(dir) {
35
+ try {
36
+ await fs.promises.rm(dir, { recursive: true, force: true });
37
+ } catch {
38
+ // ignore
39
+ }
40
+ }
41
+
42
+ async function ensureDir(dirPath) {
43
+ await fs.promises.mkdir(dirPath, { recursive: true });
44
+ }
45
+
46
+ function getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName }) {
47
+ const source = targetSource === 'agents' ? 'agents' : 'opencode';
48
+
49
+ if (scope === 'user') {
50
+ if (source === 'agents') {
51
+ return path.join(os.homedir(), '.agents', 'skills', skillName);
52
+ }
53
+ return path.join(userSkillDir, skillName);
54
+ }
55
+
56
+ if (!workingDirectory) {
57
+ throw new Error('workingDirectory is required for project installs');
58
+ }
59
+
60
+ if (source === 'agents') {
61
+ return path.join(workingDirectory, '.agents', 'skills', skillName);
62
+ }
63
+
64
+ return path.join(workingDirectory, '.opencode', 'skills', skillName);
65
+ }
66
+
67
+ /**
68
+ * Install skills from ClawdHub registry
69
+ * @param {Object} options
70
+ * @param {string} options.scope - 'user' or 'project'
71
+ * @param {string} [options.targetSource] - 'opencode' or 'agents'
72
+ * @param {string} [options.workingDirectory] - Required for project scope
73
+ * @param {string} options.userSkillDir - User skills directory
74
+ * @param {Array} options.selections - Array of { skillDir, clawdhub: { slug, version } }
75
+ * @param {string} [options.conflictPolicy] - 'prompt', 'skipAll', or 'overwriteAll'
76
+ * @param {Object} [options.conflictDecisions] - Per-skill conflict decisions
77
+ * @returns {Promise<{ ok: boolean, installed?: Array, skipped?: Array, error?: Object }>}
78
+ */
79
+ export async function installSkillsFromClawdHub({
80
+ scope,
81
+ targetSource,
82
+ workingDirectory,
83
+ userSkillDir,
84
+ selections,
85
+ conflictPolicy,
86
+ conflictDecisions,
87
+ } = {}) {
88
+ if (scope !== 'user' && scope !== 'project') {
89
+ return { ok: false, error: { kind: 'invalidSource', message: 'Invalid scope' } };
90
+ }
91
+
92
+ if (targetSource !== undefined && targetSource !== 'opencode' && targetSource !== 'agents') {
93
+ return { ok: false, error: { kind: 'invalidSource', message: 'Invalid target source' } };
94
+ }
95
+
96
+ if (!userSkillDir) {
97
+ return { ok: false, error: { kind: 'unknown', message: 'userSkillDir is required' } };
98
+ }
99
+
100
+ const normalizedUserSkillDir = normalizeUserSkillDir(userSkillDir);
101
+ if (normalizedUserSkillDir) {
102
+ userSkillDir = normalizedUserSkillDir;
103
+ }
104
+
105
+ if (scope === 'project' && !workingDirectory) {
106
+ return { ok: false, error: { kind: 'invalidSource', message: 'Project installs require a directory parameter' } };
107
+ }
108
+
109
+ const requestedSkills = Array.isArray(selections) ? selections : [];
110
+ if (requestedSkills.length === 0) {
111
+ return { ok: false, error: { kind: 'invalidSource', message: 'No skills selected for installation' } };
112
+ }
113
+
114
+ // Build installation plans
115
+ const skillPlans = requestedSkills.map((sel) => {
116
+ const slug = sel.clawdhub?.slug || sel.skillDir;
117
+ const version = sel.clawdhub?.version || 'latest';
118
+ return {
119
+ slug,
120
+ version,
121
+ installable: validateSkillName(slug),
122
+ };
123
+ });
124
+
125
+ // Check for conflicts before downloading
126
+ const conflicts = [];
127
+ for (const plan of skillPlans) {
128
+ if (!plan.installable) {
129
+ continue;
130
+ }
131
+
132
+ const targetDir = getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName: plan.slug });
133
+ if (fs.existsSync(targetDir)) {
134
+ const decision = conflictDecisions?.[plan.slug];
135
+ const hasAutoPolicy = conflictPolicy === 'skipAll' || conflictPolicy === 'overwriteAll';
136
+ if (!decision && !hasAutoPolicy) {
137
+ conflicts.push({ skillName: plan.slug, scope, source: targetSource === 'agents' ? 'agents' : 'opencode' });
138
+ }
139
+ }
140
+ }
141
+
142
+ if (conflicts.length > 0) {
143
+ return {
144
+ ok: false,
145
+ error: {
146
+ kind: 'conflicts',
147
+ message: 'Some skills already exist in the selected scope',
148
+ conflicts,
149
+ },
150
+ };
151
+ }
152
+
153
+ const installed = [];
154
+ const skipped = [];
155
+
156
+ for (const plan of skillPlans) {
157
+ if (!plan.installable) {
158
+ skipped.push({ skillName: plan.slug, reason: 'Invalid skill name' });
159
+ continue;
160
+ }
161
+
162
+ try {
163
+ // Resolve 'latest' version if needed
164
+ let resolvedVersion = plan.version;
165
+ if (resolvedVersion === 'latest') {
166
+ try {
167
+ const info = await fetchClawdHubSkillInfo(plan.slug);
168
+ const latest = info.skill?.tags?.latest || info.latestVersion?.version || null;
169
+ if (latest) {
170
+ resolvedVersion = latest;
171
+ }
172
+ } catch {
173
+ // ignore
174
+ }
175
+
176
+ if (resolvedVersion === 'latest') {
177
+ skipped.push({ skillName: plan.slug, reason: 'Unable to resolve latest version' });
178
+ continue;
179
+ }
180
+ }
181
+
182
+ const targetDir = getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName: plan.slug });
183
+ const exists = fs.existsSync(targetDir);
184
+
185
+ // Determine conflict resolution
186
+ let decision = conflictDecisions?.[plan.slug] || null;
187
+ if (!decision) {
188
+ if (exists && conflictPolicy === 'skipAll') decision = 'skip';
189
+ if (exists && conflictPolicy === 'overwriteAll') decision = 'overwrite';
190
+ if (!exists) decision = 'overwrite'; // No conflict, proceed
191
+ }
192
+
193
+ if (exists && decision === 'skip') {
194
+ skipped.push({ skillName: plan.slug, reason: 'Already installed (skipped)' });
195
+ continue;
196
+ }
197
+
198
+ if (exists && decision === 'overwrite') {
199
+ await safeRm(targetDir);
200
+ }
201
+
202
+ // Download the skill ZIP
203
+ const zipBuffer = await downloadClawdHubSkill(plan.slug, resolvedVersion);
204
+
205
+ // Extract to a temp directory first for validation
206
+ const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `clawdhub-${plan.slug}-`));
207
+
208
+ try {
209
+ const zip = new AdmZip(Buffer.from(zipBuffer));
210
+ zip.extractAllTo(tempDir, true);
211
+
212
+ // Verify SKILL.md exists
213
+ const skillMdPath = path.join(tempDir, 'SKILL.md');
214
+ if (!fs.existsSync(skillMdPath)) {
215
+ skipped.push({ skillName: plan.slug, reason: 'SKILL.md not found in downloaded package' });
216
+ continue;
217
+ }
218
+
219
+ // Move to target directory
220
+ await ensureDir(path.dirname(targetDir));
221
+ await fs.promises.rename(tempDir, targetDir);
222
+
223
+ installed.push({ skillName: plan.slug, scope, source: targetSource === 'agents' ? 'agents' : 'opencode' });
224
+ } catch (extractError) {
225
+ await safeRm(tempDir);
226
+ throw extractError;
227
+ }
228
+ } catch (error) {
229
+ console.error(`Failed to install ClawdHub skill "${plan.slug}":`, error);
230
+ skipped.push({
231
+ skillName: plan.slug,
232
+ reason: error instanceof Error ? error.message : 'Failed to download or extract skill',
233
+ });
234
+ }
235
+ }
236
+
237
+ return { ok: true, installed, skipped };
238
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * ClawdHub skill scanning
3
+ *
4
+ * Fetches all available skills from the ClawdHub registry
5
+ * and transforms them into SkillsCatalogItem format.
6
+ */
7
+
8
+ import { fetchClawdHubSkills } from './api.js';
9
+
10
+ const MAX_PAGES = 20; // Safety limit to prevent infinite loops
11
+ const CLAWDHUB_PAGE_LIMIT = 25;
12
+
13
+ const mapClawdHubItem = (item) => {
14
+ const latestVersion = item.tags?.latest || item.latestVersion?.version || '1.0.0';
15
+
16
+ return {
17
+ sourceId: 'clawdhub',
18
+ repoSource: 'clawdhub:registry',
19
+ repoSubpath: null,
20
+ gitIdentityId: null,
21
+ skillDir: item.slug,
22
+ skillName: item.slug,
23
+ frontmatterName: item.displayName || item.slug,
24
+ description: item.summary || null,
25
+ installable: true,
26
+ warnings: [],
27
+ // ClawdHub-specific metadata
28
+ clawdhub: {
29
+ slug: item.slug,
30
+ version: latestVersion,
31
+ displayName: item.displayName,
32
+ owner: item.owner?.handle || null,
33
+ downloads: item.stats?.downloads || 0,
34
+ stars: item.stats?.stars || 0,
35
+ versionsCount: item.stats?.versions || 1,
36
+ createdAt: item.createdAt,
37
+ updatedAt: item.updatedAt,
38
+ },
39
+ };
40
+ };
41
+
42
+ /**
43
+ * Scan ClawdHub registry for all available skills
44
+ * @returns {Promise<{ ok: boolean, items?: Array, error?: Object }>}
45
+ */
46
+ export async function scanClawdHub() {
47
+ try {
48
+ const allItems = [];
49
+ let cursor = null;
50
+
51
+ for (let page = 0; page < MAX_PAGES; page++) {
52
+ let items = [];
53
+ let nextCursor = null;
54
+
55
+ try {
56
+ const pageResult = await fetchClawdHubSkills({ cursor });
57
+ items = pageResult.items || [];
58
+ nextCursor = pageResult.nextCursor || null;
59
+ } catch (error) {
60
+ if (page > 0 && allItems.length > 0) {
61
+ console.warn('ClawdHub pagination failed; returning partial results.');
62
+ break;
63
+ }
64
+ throw error;
65
+ }
66
+
67
+ for (const item of items) {
68
+ allItems.push(mapClawdHubItem(item));
69
+ }
70
+
71
+ if (!nextCursor) {
72
+ break;
73
+ }
74
+ cursor = nextCursor;
75
+ }
76
+
77
+ // Sort by downloads (most popular first)
78
+ allItems.sort((a, b) => (b.clawdhub?.downloads || 0) - (a.clawdhub?.downloads || 0));
79
+
80
+ return { ok: true, items: allItems };
81
+ } catch (error) {
82
+ console.error('ClawdHub scan error:', error);
83
+ return {
84
+ ok: false,
85
+ error: {
86
+ kind: 'networkError',
87
+ message: error instanceof Error ? error.message : 'Failed to fetch skills from ClawdHub',
88
+ },
89
+ };
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Scan a single ClawdHub page (cursor-based)
95
+ * @returns {Promise<{ ok: boolean, items?: Array, nextCursor?: string | null, error?: Object }>}
96
+ */
97
+ export async function scanClawdHubPage({ cursor } = {}) {
98
+ try {
99
+ const { items, nextCursor } = await fetchClawdHubSkills({ cursor });
100
+ const mapped = (items || []).map(mapClawdHubItem).slice(0, CLAWDHUB_PAGE_LIMIT);
101
+ mapped.sort((a, b) => (b.clawdhub?.downloads || 0) - (a.clawdhub?.downloads || 0));
102
+ return { ok: true, items: mapped, nextCursor: nextCursor || null };
103
+ } catch (error) {
104
+ console.error('ClawdHub page scan error:', error);
105
+ return {
106
+ ok: false,
107
+ error: {
108
+ kind: 'networkError',
109
+ message: error instanceof Error ? error.message : 'Failed to fetch skills from ClawdHub',
110
+ },
111
+ };
112
+ }
113
+ }
@@ -0,0 +1,21 @@
1
+ export const CURATED_SKILLS_SOURCES = [
2
+ {
3
+ id: 'anthropic',
4
+ label: 'Anthropic',
5
+ description: "Anthropic's public skills repository",
6
+ source: 'anthropics/skills',
7
+ defaultSubpath: 'skills',
8
+ sourceType: 'github',
9
+ },
10
+ {
11
+ id: 'clawdhub',
12
+ label: 'ClawdHub',
13
+ description: 'Community skill registry with vector search',
14
+ source: 'clawdhub:registry',
15
+ sourceType: 'clawdhub',
16
+ },
17
+ ];
18
+
19
+ export function getCuratedSkillsSources() {
20
+ return CURATED_SKILLS_SOURCES.slice();
21
+ }
@@ -0,0 +1,77 @@
1
+ import { execFile } from 'child_process';
2
+ import { promisify } from 'util';
3
+
4
+ const execFileAsync = promisify(execFile);
5
+
6
+ const DEFAULT_TIMEOUT_MS = 60_000;
7
+ const DEFAULT_MAX_BUFFER = 4 * 1024 * 1024;
8
+
9
+ export function looksLikeAuthError(message) {
10
+ const text = String(message || '');
11
+ return (
12
+ /permission denied/i.test(text) ||
13
+ /publickey/i.test(text) ||
14
+ /could not read from remote repository/i.test(text) ||
15
+ /authentication failed/i.test(text) ||
16
+ /fatal: could not/i.test(text)
17
+ );
18
+ }
19
+
20
+ export async function runGit(args, options = {}) {
21
+ const cwd = options.cwd;
22
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : DEFAULT_TIMEOUT_MS;
23
+ const maxBuffer = Number.isFinite(options.maxBuffer) ? options.maxBuffer : DEFAULT_MAX_BUFFER;
24
+
25
+ const identity = options.identity || null;
26
+ const normalizedArgs = Array.isArray(args) ? args.slice() : [];
27
+
28
+ // Non-interactive git (avoid prompts / hangs)
29
+ const env = {
30
+ ...process.env,
31
+ GIT_TERMINAL_PROMPT: '0',
32
+ };
33
+
34
+ if (identity?.sshKey) {
35
+ const sshKeyPath = String(identity.sshKey).trim();
36
+ if (sshKeyPath) {
37
+ // Avoid interactive host key prompts; still safe against changed keys.
38
+ const sshCommand = `ssh -i ${sshKeyPath} -o BatchMode=yes -o StrictHostKeyChecking=accept-new`;
39
+ normalizedArgs.unshift(`core.sshCommand=${sshCommand}`);
40
+ normalizedArgs.unshift('-c');
41
+ }
42
+ }
43
+
44
+ try {
45
+ const { stdout, stderr } = await execFileAsync('git', normalizedArgs, {
46
+ cwd,
47
+ env,
48
+ windowsHide: true,
49
+ timeout: timeoutMs,
50
+ maxBuffer,
51
+ });
52
+
53
+ return { ok: true, stdout: stdout || '', stderr: stderr || '' };
54
+ } catch (error) {
55
+ const err = error;
56
+ const stdout = typeof err?.stdout === 'string' ? err.stdout : '';
57
+ const stderr = typeof err?.stderr === 'string' ? err.stderr : '';
58
+ const message = err instanceof Error ? err.message : String(err);
59
+
60
+ return {
61
+ ok: false,
62
+ stdout,
63
+ stderr,
64
+ message,
65
+ code: typeof err?.code === 'number' ? err.code : null,
66
+ signal: typeof err?.signal === 'string' ? err.signal : null,
67
+ };
68
+ }
69
+ }
70
+
71
+ export async function assertGitAvailable() {
72
+ const result = await runGit(['--version'], { timeoutMs: 5_000 });
73
+ if (!result.ok) {
74
+ return { ok: false, error: { kind: 'gitUnavailable', message: 'Git is not available in PATH' } };
75
+ }
76
+ return { ok: true };
77
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Skills catalog module
3
+ *
4
+ * Provides skill scanning, installation, and caching from GitHub repositories and ClawdHub.
5
+ */
6
+
7
+ export {
8
+ CURATED_SKILLS_SOURCES,
9
+ getCuratedSkillsSources,
10
+ } from './curated-sources.js';
11
+
12
+ export {
13
+ getCacheKey,
14
+ getCachedScan,
15
+ setCachedScan,
16
+ clearCache,
17
+ } from './cache.js';
18
+
19
+ export {
20
+ parseSkillRepoSource,
21
+ } from './source.js';
22
+
23
+ export {
24
+ scanSkillsRepository,
25
+ } from './scan.js';
26
+
27
+ export {
28
+ installSkillsFromRepository,
29
+ } from './install.js';
30
+
31
+ export {
32
+ scanClawdHub,
33
+ scanClawdHubPage,
34
+ installSkillsFromClawdHub,
35
+ fetchClawdHubSkills,
36
+ fetchClawdHubSkillVersion,
37
+ fetchClawdHubSkillInfo,
38
+ downloadClawdHubSkill,
39
+ isClawdHubSource,
40
+ CLAWDHUB_SOURCE_ID,
41
+ CLAWDHUB_SOURCE_STRING,
42
+ } from './clawdhub/index.js';