cli-claw-kit 0.0.1

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 (295) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +245 -0
  3. package/config/default-groups.json +1 -0
  4. package/config/global-agents-md.template.md +37 -0
  5. package/config/mount-allowlist.json +11 -0
  6. package/container/Dockerfile +160 -0
  7. package/container/agent-runner/dist/.tsbuildinfo +1 -0
  8. package/container/agent-runner/dist/agent-definitions.js +22 -0
  9. package/container/agent-runner/dist/channel-prefixes.js +16 -0
  10. package/container/agent-runner/dist/codex-config.js +29 -0
  11. package/container/agent-runner/dist/image-detector.js +96 -0
  12. package/container/agent-runner/dist/index.js +2587 -0
  13. package/container/agent-runner/dist/mcp-tools.js +1076 -0
  14. package/container/agent-runner/dist/stream-event.types.js +5 -0
  15. package/container/agent-runner/dist/stream-processor.js +867 -0
  16. package/container/agent-runner/dist/types.js +6 -0
  17. package/container/agent-runner/dist/utils.js +115 -0
  18. package/container/agent-runner/package.json +36 -0
  19. package/container/agent-runner/prompts/security-rules.md +31 -0
  20. package/container/agent-runner/src/agent-definitions.ts +27 -0
  21. package/container/agent-runner/src/channel-prefixes.ts +16 -0
  22. package/container/agent-runner/src/codex-config.ts +40 -0
  23. package/container/agent-runner/src/image-detector.ts +116 -0
  24. package/container/agent-runner/src/index.ts +3107 -0
  25. package/container/agent-runner/src/mcp-tools.ts +1295 -0
  26. package/container/agent-runner/src/stream-event.types.ts +10 -0
  27. package/container/agent-runner/src/stream-processor.ts +932 -0
  28. package/container/agent-runner/src/types.ts +75 -0
  29. package/container/agent-runner/src/utils.ts +114 -0
  30. package/container/agent-runner/tsconfig.json +17 -0
  31. package/container/build.sh +28 -0
  32. package/container/entrypoint.sh +64 -0
  33. package/container/skills/agent-browser/SKILL.md +159 -0
  34. package/container/skills/install-skill/SKILL.md +64 -0
  35. package/container/skills/post-test-cleanup/SKILL.md +121 -0
  36. package/dist/.tsbuildinfo +1 -0
  37. package/dist/agent-output-parser.js +459 -0
  38. package/dist/app-root.js +52 -0
  39. package/dist/assistant-meta-footer.js +1 -0
  40. package/dist/auth.js +91 -0
  41. package/dist/billing.js +694 -0
  42. package/dist/channel-prefixes.js +16 -0
  43. package/dist/cli.js +86 -0
  44. package/dist/commands.js +79 -0
  45. package/dist/config.js +120 -0
  46. package/dist/container-runner.js +981 -0
  47. package/dist/daily-summary.js +210 -0
  48. package/dist/db.js +3683 -0
  49. package/dist/dingtalk.js +1347 -0
  50. package/dist/feishu-markdown-style.js +97 -0
  51. package/dist/feishu-streaming-card.js +1875 -0
  52. package/dist/feishu.js +1628 -0
  53. package/dist/file-manager.js +270 -0
  54. package/dist/group-queue.js +1070 -0
  55. package/dist/group-runtime.js +35 -0
  56. package/dist/host-workspace-cwd.js +85 -0
  57. package/dist/im-channel.js +384 -0
  58. package/dist/im-command-utils.js +142 -0
  59. package/dist/im-downloader.js +45 -0
  60. package/dist/im-manager.js +527 -0
  61. package/dist/im-utils.js +53 -0
  62. package/dist/image-detector.js +96 -0
  63. package/dist/index.js +5828 -0
  64. package/dist/logger.js +22 -0
  65. package/dist/mcp-utils.js +66 -0
  66. package/dist/message-attachments.js +69 -0
  67. package/dist/message-notifier.js +36 -0
  68. package/dist/middleware/auth.js +85 -0
  69. package/dist/mount-security.js +315 -0
  70. package/dist/permissions.js +67 -0
  71. package/dist/project-memory.js +6 -0
  72. package/dist/provider-pool.js +189 -0
  73. package/dist/qq.js +826 -0
  74. package/dist/reset-admin.js +42 -0
  75. package/dist/routes/admin.js +543 -0
  76. package/dist/routes/agent-definitions.js +241 -0
  77. package/dist/routes/agents.js +533 -0
  78. package/dist/routes/auth.js +675 -0
  79. package/dist/routes/billing.js +490 -0
  80. package/dist/routes/browse.js +210 -0
  81. package/dist/routes/bug-report.js +387 -0
  82. package/dist/routes/config.js +1868 -0
  83. package/dist/routes/files.js +671 -0
  84. package/dist/routes/groups.js +1367 -0
  85. package/dist/routes/mcp-servers.js +320 -0
  86. package/dist/routes/memory.js +523 -0
  87. package/dist/routes/monitor.js +307 -0
  88. package/dist/routes/skills.js +777 -0
  89. package/dist/routes/tasks.js +509 -0
  90. package/dist/routes/usage.js +64 -0
  91. package/dist/routes/workspace-config.js +458 -0
  92. package/dist/runtime-build.js +112 -0
  93. package/dist/runtime-command-handler.js +189 -0
  94. package/dist/runtime-command-registry.js +1 -0
  95. package/dist/runtime-config.js +1777 -0
  96. package/dist/runtime-identity.js +52 -0
  97. package/dist/schemas.js +590 -0
  98. package/dist/script-runner.js +64 -0
  99. package/dist/sdk-query.js +82 -0
  100. package/dist/skill-utils.js +145 -0
  101. package/dist/sqlite-compat.js +19 -0
  102. package/dist/stream-event.types.js +5 -0
  103. package/dist/streaming-runtime-meta.js +29 -0
  104. package/dist/task-scheduler.js +695 -0
  105. package/dist/task-utils.js +13 -0
  106. package/dist/telegram-pairing.js +59 -0
  107. package/dist/telegram.js +897 -0
  108. package/dist/terminal-manager.js +307 -0
  109. package/dist/tool-step-display.js +1 -0
  110. package/dist/types.js +1 -0
  111. package/dist/utils.js +85 -0
  112. package/dist/web-context.js +161 -0
  113. package/dist/web.js +1377 -0
  114. package/dist/wechat-crypto.js +182 -0
  115. package/dist/wechat.js +589 -0
  116. package/dist/workspace-runtime-reset.js +35 -0
  117. package/package.json +107 -0
  118. package/shared/assistant-meta-footer.ts +127 -0
  119. package/shared/channel-prefixes.ts +16 -0
  120. package/shared/dist/assistant-meta-footer.d.ts +29 -0
  121. package/shared/dist/assistant-meta-footer.js +85 -0
  122. package/shared/dist/channel-prefixes.d.ts +4 -0
  123. package/shared/dist/channel-prefixes.js +16 -0
  124. package/shared/dist/image-detector.d.ts +20 -0
  125. package/shared/dist/image-detector.js +96 -0
  126. package/shared/dist/runtime-command-registry.d.ts +38 -0
  127. package/shared/dist/runtime-command-registry.js +185 -0
  128. package/shared/dist/stream-event.d.ts +65 -0
  129. package/shared/dist/stream-event.js +8 -0
  130. package/shared/dist/tool-step-display.d.ts +4 -0
  131. package/shared/dist/tool-step-display.js +11 -0
  132. package/shared/image-detector.ts +116 -0
  133. package/shared/runtime-command-registry.ts +252 -0
  134. package/shared/stream-event.ts +67 -0
  135. package/shared/tool-step-display.ts +21 -0
  136. package/shared/tsconfig.json +24 -0
  137. package/web/dist/assets/BillingPage-B1wBR_o-.js +52 -0
  138. package/web/dist/assets/ChatPage-6GBZ9nXN.css +32 -0
  139. package/web/dist/assets/ChatPage-BOJcXtaj.js +161 -0
  140. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  141. package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  142. package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  143. package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  144. package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  145. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  146. package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  147. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  148. package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  149. package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  150. package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  151. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  152. package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  153. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  154. package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  155. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  156. package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  157. package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  158. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  159. package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  160. package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  161. package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  162. package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  163. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  164. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  165. package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  166. package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  167. package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  168. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  169. package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  170. package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  171. package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  172. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  173. package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  174. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  175. package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  176. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  177. package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  178. package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  179. package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  180. package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  181. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  182. package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  183. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  184. package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  185. package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  186. package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  187. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  188. package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  189. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  190. package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  191. package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  192. package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  193. package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  194. package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  195. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  196. package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  197. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  198. package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  199. package/web/dist/assets/SettingsPage-DoY7FoZ_.js +153 -0
  200. package/web/dist/assets/ShareImageDialog-C1ga8b7l.js +22 -0
  201. package/web/dist/assets/TasksPage-CRivnNsx.js +14 -0
  202. package/web/dist/assets/_basePickBy-Bf-bSoS9.js +1 -0
  203. package/web/dist/assets/_baseUniq-zAOaCuKw.js +1 -0
  204. package/web/dist/assets/arc-Dm9mVQ9U.js +1 -0
  205. package/web/dist/assets/architectureDiagram-2XIMDMQ5-BLmzX1wr.js +36 -0
  206. package/web/dist/assets/band-CquvqAHh.js +1 -0
  207. package/web/dist/assets/blockDiagram-WCTKOSBZ-B9pcqm3j.js +132 -0
  208. package/web/dist/assets/c4Diagram-IC4MRINW-Cytx1q3b.js +10 -0
  209. package/web/dist/assets/channel-BOVj73LR.js +1 -0
  210. package/web/dist/assets/channel-meta-CQD0Pei-.js +41 -0
  211. package/web/dist/assets/chunk-4BX2VUAB-0ToDr6RE.js +1 -0
  212. package/web/dist/assets/chunk-55IACEB6-DQDjnXfS.js +1 -0
  213. package/web/dist/assets/chunk-FMBD7UC4-Di8ABm6c.js +15 -0
  214. package/web/dist/assets/chunk-JSJVCQXG-BZQN6rnX.js +1 -0
  215. package/web/dist/assets/chunk-KX2RTZJC-zBbcpaN_.js +1 -0
  216. package/web/dist/assets/chunk-NQ4KR5QH-BCrLoU88.js +220 -0
  217. package/web/dist/assets/chunk-QZHKN3VN-Bqk8juan.js +1 -0
  218. package/web/dist/assets/chunk-WL4C6EOR-D2YX-MHY.js +189 -0
  219. package/web/dist/assets/classDiagram-VBA2DB6C-DUUoMyaK.js +1 -0
  220. package/web/dist/assets/classDiagram-v2-RAHNMMFH-DUUoMyaK.js +1 -0
  221. package/web/dist/assets/clone-BmaCesfa.js +1 -0
  222. package/web/dist/assets/cose-bilkent-S5V4N54A-CTsv6qQA.js +1 -0
  223. package/web/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  224. package/web/dist/assets/dagre-KLK3FWXG-Ci4Jh9nu.js +4 -0
  225. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  226. package/web/dist/assets/diagram-E7M64L7V-BFRnfTI2.js +24 -0
  227. package/web/dist/assets/diagram-IFDJBPK2-B7Zhnp0b.js +43 -0
  228. package/web/dist/assets/diagram-P4PSJMXO-BVyP7nwq.js +24 -0
  229. package/web/dist/assets/erDiagram-INFDFZHY-NorKdTOF.js +70 -0
  230. package/web/dist/assets/error-CGD5mp5f.js +1 -0
  231. package/web/dist/assets/flowDiagram-PKNHOUZH-Ch97nABF.js +162 -0
  232. package/web/dist/assets/ganttDiagram-A5KZAMGK-BQ2pLWsy.js +292 -0
  233. package/web/dist/assets/gitGraphDiagram-K3NZZRJ6-bcvnBsD2.js +65 -0
  234. package/web/dist/assets/graph-CeAEckur.js +1 -0
  235. package/web/dist/assets/index-CPnL1_qC.js +768 -0
  236. package/web/dist/assets/index-DVevCbcO.css +10 -0
  237. package/web/dist/assets/infoDiagram-LFFYTUFH-CcsrFdj-.js +2 -0
  238. package/web/dist/assets/init-Dmth1JHB.js +1 -0
  239. package/web/dist/assets/ishikawaDiagram-PHBUUO56-1upyMfHN.js +70 -0
  240. package/web/dist/assets/journeyDiagram-4ABVD52K-CKUi-V0c.js +139 -0
  241. package/web/dist/assets/kanban-definition-K7BYSVSG-DOnQwXfL.js +89 -0
  242. package/web/dist/assets/layout-BmMMqTnJ.js +1 -0
  243. package/web/dist/assets/linear-DiaJloY5.js +1 -0
  244. package/web/dist/assets/mermaid.core-BWLV1B2v.js +254 -0
  245. package/web/dist/assets/mindmap-definition-YRQLILUH-BeAKHVWP.js +68 -0
  246. package/web/dist/assets/ordinal-DILIJJjt.js +1 -0
  247. package/web/dist/assets/pieDiagram-SKSYHLDU-DfiMSfWo.js +30 -0
  248. package/web/dist/assets/quadrantDiagram-337W2JSQ-wZxZOJxd.js +7 -0
  249. package/web/dist/assets/requirementDiagram-Z7DCOOCP-BK4HHm17.js +73 -0
  250. package/web/dist/assets/sankeyDiagram-WA2Y5GQK-BX6t2avX.js +10 -0
  251. package/web/dist/assets/sequenceDiagram-2WXFIKYE-BPQlkbAa.js +145 -0
  252. package/web/dist/assets/sheet-rI0FfB1g.js +6 -0
  253. package/web/dist/assets/sliders-horizontal-CuijWFNK.js +6 -0
  254. package/web/dist/assets/sparkles-BsMYXJoT.js +11 -0
  255. package/web/dist/assets/square-0CqMX1Q3.js +11 -0
  256. package/web/dist/assets/stateDiagram-RAJIS63D-DxkV0Vwd.js +1 -0
  257. package/web/dist/assets/stateDiagram-v2-FVOUBMTO-qLYoiOPe.js +1 -0
  258. package/web/dist/assets/step-D51IIHGA.js +1 -0
  259. package/web/dist/assets/tasks-D8JjBTwx.js +1 -0
  260. package/web/dist/assets/time-O8zIGux3.js +1 -0
  261. package/web/dist/assets/timeline-definition-YZTLITO2-kNp1DyFc.js +61 -0
  262. package/web/dist/assets/treemap-KZPCXAKY-CkrClVhk.js +162 -0
  263. package/web/dist/assets/utils-KGAn0XTg.js +11 -0
  264. package/web/dist/assets/vennDiagram-LZ73GAT5-CgdzEZz4.js +34 -0
  265. package/web/dist/assets/xychartDiagram-JWTSCODW-DfYGPfNB.js +7 -0
  266. package/web/dist/assets/zap-_hKJYy7J.js +6 -0
  267. package/web/dist/favicon.svg +332 -0
  268. package/web/dist/fonts/AlibabaPuHuiTi-3-55-Regular.woff2 +0 -0
  269. package/web/dist/fonts/AlibabaPuHuiTi-3-65-Medium.woff2 +0 -0
  270. package/web/dist/fonts/AlibabaPuHuiTi-3-75-SemiBold.woff2 +0 -0
  271. package/web/dist/fonts/DMSans-latin-ext.woff2 +0 -0
  272. package/web/dist/fonts/DMSans-latin.woff2 +0 -0
  273. package/web/dist/icons/README.md +20 -0
  274. package/web/dist/icons/apple-touch-icon-180.png +0 -0
  275. package/web/dist/icons/icon-128.png +0 -0
  276. package/web/dist/icons/icon-144.png +0 -0
  277. package/web/dist/icons/icon-152.png +0 -0
  278. package/web/dist/icons/icon-192.png +0 -0
  279. package/web/dist/icons/icon-192.svg +332 -0
  280. package/web/dist/icons/icon-384.png +0 -0
  281. package/web/dist/icons/icon-48.png +0 -0
  282. package/web/dist/icons/icon-512-maskable.png +0 -0
  283. package/web/dist/icons/icon-512.png +0 -0
  284. package/web/dist/icons/icon-512.svg +332 -0
  285. package/web/dist/icons/icon-72.png +0 -0
  286. package/web/dist/icons/icon-96.png +0 -0
  287. package/web/dist/icons/loading-logo.svg +332 -0
  288. package/web/dist/icons/logo-1024.png +0 -0
  289. package/web/dist/icons/logo-icon.svg +332 -0
  290. package/web/dist/icons/logo-text.svg +332 -0
  291. package/web/dist/index.html +30 -0
  292. package/web/dist/manifest.webmanifest +1 -0
  293. package/web/dist/registerSW.js +1 -0
  294. package/web/dist/sw.js +1 -0
  295. package/web/dist/workbox-08d6266a.js +1 -0
@@ -0,0 +1,458 @@
1
+ /**
2
+ * Workspace-level Skills and MCP Servers management routes.
3
+ *
4
+ * Operates on the workspace's `.claude/` directory (project-level config).
5
+ * SDK reads both 'project' and 'user' settingSources, so these configs
6
+ * take effect alongside global (user-level) configs without any changes
7
+ * to agent-runner or container-runner.
8
+ */
9
+ import { Hono } from 'hono';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import os from 'os';
13
+ import { execFile } from 'child_process';
14
+ import { promisify } from 'util';
15
+ import { authMiddleware } from '../middleware/auth.js';
16
+ import { GROUPS_DIR } from '../config.js';
17
+ import { canAccessGroup } from '../web-context.js';
18
+ import { getJidsByFolder, getRegisteredGroup } from '../db.js';
19
+ import { resolveEffectiveHostWorkspaceCwd } from '../host-workspace-cwd.js';
20
+ import { validateSkillId, validateSkillPath, scanSkillDirectory, } from '../skill-utils.js';
21
+ const execFileAsync = promisify(execFile);
22
+ const workspaceConfigRoutes = new Hono();
23
+ // --- Path Resolution ---
24
+ /**
25
+ * Resolve the workspace root directory for a registered group.
26
+ * Host mode uses the effective host cwd contract; container mode keeps storage under ~/.cli-claw/groups/{folder}/.
27
+ */
28
+ export function getWorkspaceRoot(group, homeGroup) {
29
+ if (group.executionMode === 'host') {
30
+ const effectiveCwd = resolveEffectiveHostWorkspaceCwd(group, homeGroup ?? undefined);
31
+ if (!effectiveCwd) {
32
+ throw new Error('Host workspace is missing custom_cwd');
33
+ }
34
+ return effectiveCwd;
35
+ }
36
+ return path.join(GROUPS_DIR, group.folder);
37
+ }
38
+ function findHomeSiblingGroup(group) {
39
+ if (group.is_home)
40
+ return undefined;
41
+ for (const jid of getJidsByFolder(group.folder)) {
42
+ const sibling = getRegisteredGroup(jid);
43
+ if (sibling?.is_home) {
44
+ return sibling;
45
+ }
46
+ }
47
+ return undefined;
48
+ }
49
+ function getWorkspaceClaudeDir(group) {
50
+ return path.join(getWorkspaceRoot(group, findHomeSiblingGroup(group)), '.claude');
51
+ }
52
+ function getWorkspaceSkillsDir(group) {
53
+ return path.join(getWorkspaceClaudeDir(group), 'skills');
54
+ }
55
+ function getWorkspaceSettingsPath(group) {
56
+ return path.join(getWorkspaceClaudeDir(group), 'settings.json');
57
+ }
58
+ /**
59
+ * Metadata file for workspace MCP servers.
60
+ * Stores full config + enabled state so we can remove disabled servers
61
+ * from settings.json (SDK won't see them) while preserving the config
62
+ * for re-enabling.
63
+ */
64
+ function getWorkspaceMcpMetaPath(group) {
65
+ return path.join(getWorkspaceClaudeDir(group), 'cli-claw-workspace.json');
66
+ }
67
+ function readWorkspaceMeta(group) {
68
+ try {
69
+ const data = fs.readFileSync(getWorkspaceMcpMetaPath(group), 'utf-8');
70
+ return JSON.parse(data);
71
+ }
72
+ catch {
73
+ return { mcpServers: {} };
74
+ }
75
+ }
76
+ function writeWorkspaceMeta(group, meta) {
77
+ const metaPath = getWorkspaceMcpMetaPath(group);
78
+ fs.mkdirSync(path.dirname(metaPath), { recursive: true });
79
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
80
+ }
81
+ function readWorkspaceSettings(group) {
82
+ try {
83
+ const data = fs.readFileSync(getWorkspaceSettingsPath(group), 'utf-8');
84
+ return JSON.parse(data);
85
+ }
86
+ catch {
87
+ return {};
88
+ }
89
+ }
90
+ function writeWorkspaceSettings(group, settings) {
91
+ const settingsPath = getWorkspaceSettingsPath(group);
92
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
93
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
94
+ }
95
+ /**
96
+ * Sync enabled MCP servers to settings.json so SDK can discover them.
97
+ * Disabled servers are removed from settings.json but kept in metadata.
98
+ */
99
+ function syncMcpToSettings(group, meta, existingSettings) {
100
+ const settings = existingSettings ?? readWorkspaceSettings(group);
101
+ const mcpServers = {};
102
+ for (const [id, entry] of Object.entries(meta.mcpServers)) {
103
+ if (!entry.enabled)
104
+ continue;
105
+ const isHttpType = entry.type === 'http' || entry.type === 'sse';
106
+ if (isHttpType) {
107
+ if (!entry.url)
108
+ continue;
109
+ const server = {
110
+ type: entry.type,
111
+ url: entry.url,
112
+ };
113
+ if (entry.headers && Object.keys(entry.headers).length > 0) {
114
+ server.headers = entry.headers;
115
+ }
116
+ mcpServers[id] = server;
117
+ }
118
+ else {
119
+ if (!entry.command)
120
+ continue;
121
+ const server = { command: entry.command };
122
+ if (entry.args && entry.args.length > 0)
123
+ server.args = entry.args;
124
+ if (entry.env && Object.keys(entry.env).length > 0)
125
+ server.env = entry.env;
126
+ mcpServers[id] = server;
127
+ }
128
+ }
129
+ if (Object.keys(mcpServers).length > 0) {
130
+ settings.mcpServers = mcpServers;
131
+ }
132
+ else {
133
+ delete settings.mcpServers;
134
+ }
135
+ writeWorkspaceSettings(group, settings);
136
+ }
137
+ // --- Middleware: resolve group + access check ---
138
+ function resolveGroup(c) {
139
+ const jid = c.req.param('jid');
140
+ const authUser = c.get('user');
141
+ const group = getRegisteredGroup(jid);
142
+ if (!group) {
143
+ return null;
144
+ }
145
+ if (!canAccessGroup(authUser, group)) {
146
+ return null;
147
+ }
148
+ return group;
149
+ }
150
+ // ===========================
151
+ // Skills API
152
+ // ===========================
153
+ // GET /workspace-config/skills — list workspace skills
154
+ workspaceConfigRoutes.get('/:jid/workspace-config/skills', authMiddleware, async (c) => {
155
+ const group = resolveGroup(c);
156
+ if (!group)
157
+ return c.json({ error: 'Group not found or access denied' }, 404);
158
+ const skillsDir = getWorkspaceSkillsDir(group);
159
+ const skills = scanSkillDirectory(skillsDir, 'workspace');
160
+ return c.json({ skills });
161
+ });
162
+ // POST /workspace-config/skills/install — install skill to workspace
163
+ workspaceConfigRoutes.post('/:jid/workspace-config/skills/install', authMiddleware, async (c) => {
164
+ const group = resolveGroup(c);
165
+ if (!group)
166
+ return c.json({ error: 'Group not found or access denied' }, 404);
167
+ const body = await c.req.json().catch(() => ({}));
168
+ const pkg = typeof body.package === 'string' ? body.package.trim() : '';
169
+ if (!/^[\w\-]+\/[\w\-.]+(?:[@#][\w\-.\/]+)?$/.test(pkg) &&
170
+ !/^https?:\/\//.test(pkg)) {
171
+ return c.json({ error: 'Invalid package name format' }, 400);
172
+ }
173
+ // Install to a temp HOME, then copy to workspace skills dir
174
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-skill-install-'));
175
+ const tempSkillsDir = path.join(tempHome, '.claude', 'skills');
176
+ fs.mkdirSync(tempSkillsDir, { recursive: true });
177
+ try {
178
+ await execFileAsync('npx', ['-y', 'skills', 'add', pkg, '--global', '--yes', '-a', 'claude-code'], {
179
+ timeout: 60_000,
180
+ env: { ...process.env, HOME: tempHome },
181
+ });
182
+ // Discover installed skill directories
183
+ const installedEntries = [];
184
+ if (fs.existsSync(tempSkillsDir)) {
185
+ for (const entry of fs.readdirSync(tempSkillsDir, {
186
+ withFileTypes: true,
187
+ })) {
188
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
189
+ installedEntries.push(entry.name);
190
+ }
191
+ }
192
+ }
193
+ if (installedEntries.length === 0) {
194
+ return c.json({ error: 'No skills were installed — package may be invalid' }, 500);
195
+ }
196
+ // Copy to workspace skills directory
197
+ const targetDir = getWorkspaceSkillsDir(group);
198
+ fs.mkdirSync(targetDir, { recursive: true });
199
+ for (const name of installedEntries) {
200
+ const src = path.join(tempSkillsDir, name);
201
+ const dest = path.join(targetDir, name);
202
+ if (fs.existsSync(dest)) {
203
+ fs.rmSync(dest, { recursive: true, force: true });
204
+ }
205
+ // Resolve symlinks and copy real content
206
+ let realSrc = src;
207
+ try {
208
+ const lstat = fs.lstatSync(src);
209
+ if (lstat.isSymbolicLink()) {
210
+ realSrc = fs.realpathSync(src);
211
+ }
212
+ }
213
+ catch {
214
+ // use src as-is
215
+ }
216
+ fs.cpSync(realSrc, dest, { recursive: true });
217
+ }
218
+ return c.json({ success: true, installed: installedEntries });
219
+ }
220
+ catch (error) {
221
+ return c.json({
222
+ error: 'Failed to install skill',
223
+ details: error instanceof Error ? error.message : 'Unknown error',
224
+ }, 500);
225
+ }
226
+ finally {
227
+ try {
228
+ fs.rmSync(tempHome, { recursive: true, force: true });
229
+ }
230
+ catch {
231
+ /* ignore */
232
+ }
233
+ }
234
+ });
235
+ // PATCH /workspace-config/skills/:id — enable/disable
236
+ workspaceConfigRoutes.patch('/:jid/workspace-config/skills/:id', authMiddleware, async (c) => {
237
+ const group = resolveGroup(c);
238
+ if (!group)
239
+ return c.json({ error: 'Group not found or access denied' }, 404);
240
+ const id = c.req.param('id');
241
+ if (!validateSkillId(id)) {
242
+ return c.json({ error: 'Invalid skill ID' }, 400);
243
+ }
244
+ const { enabled } = await c.req.json();
245
+ const skillsDir = getWorkspaceSkillsDir(group);
246
+ const skillDir = path.join(skillsDir, id);
247
+ if (!fs.existsSync(skillDir)) {
248
+ return c.json({ error: 'Skill not found' }, 404);
249
+ }
250
+ if (!validateSkillPath(skillsDir, skillDir)) {
251
+ return c.json({ error: 'Invalid skill path' }, 400);
252
+ }
253
+ const srcPath = path.join(skillDir, enabled ? 'SKILL.md.disabled' : 'SKILL.md');
254
+ const dstPath = path.join(skillDir, enabled ? 'SKILL.md' : 'SKILL.md.disabled');
255
+ if (!fs.existsSync(srcPath)) {
256
+ return c.json({ error: 'Skill not found or already in desired state' }, 404);
257
+ }
258
+ fs.renameSync(srcPath, dstPath);
259
+ return c.json({ success: true });
260
+ });
261
+ // DELETE /workspace-config/skills/:id — delete skill
262
+ workspaceConfigRoutes.delete('/:jid/workspace-config/skills/:id', authMiddleware, async (c) => {
263
+ const group = resolveGroup(c);
264
+ if (!group)
265
+ return c.json({ error: 'Group not found or access denied' }, 404);
266
+ const id = c.req.param('id');
267
+ if (!validateSkillId(id)) {
268
+ return c.json({ error: 'Invalid skill ID' }, 400);
269
+ }
270
+ const skillsDir = getWorkspaceSkillsDir(group);
271
+ const skillDir = path.join(skillsDir, id);
272
+ if (!fs.existsSync(skillDir)) {
273
+ return c.json({ error: 'Skill not found' }, 404);
274
+ }
275
+ if (!validateSkillPath(skillsDir, skillDir)) {
276
+ return c.json({ error: 'Invalid skill path' }, 400);
277
+ }
278
+ fs.rmSync(skillDir, { recursive: true, force: true });
279
+ return c.json({ success: true });
280
+ });
281
+ // ===========================
282
+ // MCP Servers API
283
+ // ===========================
284
+ // GET /workspace-config/mcp-servers — list workspace MCP servers
285
+ workspaceConfigRoutes.get('/:jid/workspace-config/mcp-servers', authMiddleware, async (c) => {
286
+ const group = resolveGroup(c);
287
+ if (!group)
288
+ return c.json({ error: 'Group not found or access denied' }, 404);
289
+ const meta = readWorkspaceMeta(group);
290
+ const settings = readWorkspaceSettings(group);
291
+ const settingsMcp = settings.mcpServers || {};
292
+ // Merge: metadata has full info; also discover servers in settings.json
293
+ // that aren't in metadata (e.g. manually added by user)
294
+ const servers = [];
295
+ // From metadata
296
+ for (const [id, entry] of Object.entries(meta.mcpServers)) {
297
+ servers.push({ id, ...entry });
298
+ }
299
+ // From settings.json (not in metadata = externally added)
300
+ for (const [id, entry] of Object.entries(settingsMcp)) {
301
+ if (meta.mcpServers[id])
302
+ continue; // already covered
303
+ const isHttpType = entry.type === 'http' || entry.type === 'sse';
304
+ servers.push({
305
+ id,
306
+ enabled: true, // in settings.json = enabled
307
+ addedAt: '',
308
+ ...(isHttpType
309
+ ? {
310
+ type: entry.type,
311
+ url: entry.url,
312
+ ...(entry.headers
313
+ ? { headers: entry.headers }
314
+ : {}),
315
+ }
316
+ : {
317
+ command: entry.command,
318
+ ...(entry.args ? { args: entry.args } : {}),
319
+ ...(entry.env
320
+ ? { env: entry.env }
321
+ : {}),
322
+ }),
323
+ });
324
+ }
325
+ return c.json({ servers });
326
+ });
327
+ // POST /workspace-config/mcp-servers — add MCP server
328
+ workspaceConfigRoutes.post('/:jid/workspace-config/mcp-servers', authMiddleware, async (c) => {
329
+ const group = resolveGroup(c);
330
+ if (!group)
331
+ return c.json({ error: 'Group not found or access denied' }, 404);
332
+ const body = await c.req.json().catch(() => ({}));
333
+ const { id, command, args, env, description, type, url, headers } = body;
334
+ if (!id || typeof id !== 'string') {
335
+ return c.json({ error: 'id is required' }, 400);
336
+ }
337
+ if (!/^[\w\-]+$/.test(id)) {
338
+ return c.json({ error: 'Invalid server ID' }, 400);
339
+ }
340
+ const isHttpType = type === 'http' || type === 'sse';
341
+ if (isHttpType) {
342
+ if (!url || typeof url !== 'string') {
343
+ return c.json({ error: 'url is required for http/sse type' }, 400);
344
+ }
345
+ }
346
+ else {
347
+ if (!command || typeof command !== 'string') {
348
+ return c.json({ error: 'command is required' }, 400);
349
+ }
350
+ }
351
+ const meta = readWorkspaceMeta(group);
352
+ if (meta.mcpServers[id]) {
353
+ return c.json({ error: `Server "${id}" already exists` }, 409);
354
+ }
355
+ const entry = {
356
+ enabled: true,
357
+ addedAt: new Date().toISOString(),
358
+ ...(description ? { description } : {}),
359
+ };
360
+ if (isHttpType) {
361
+ entry.type = type;
362
+ entry.url = url;
363
+ if (headers && Object.keys(headers).length > 0)
364
+ entry.headers = headers;
365
+ }
366
+ else {
367
+ entry.command = command;
368
+ if (args && args.length > 0)
369
+ entry.args = args;
370
+ if (env && Object.keys(env).length > 0)
371
+ entry.env = env;
372
+ }
373
+ meta.mcpServers[id] = entry;
374
+ writeWorkspaceMeta(group, meta);
375
+ syncMcpToSettings(group, meta);
376
+ return c.json({ success: true, server: { id, ...entry } });
377
+ });
378
+ // PATCH /workspace-config/mcp-servers/:id — update/enable/disable
379
+ workspaceConfigRoutes.patch('/:jid/workspace-config/mcp-servers/:id', authMiddleware, async (c) => {
380
+ const group = resolveGroup(c);
381
+ if (!group)
382
+ return c.json({ error: 'Group not found or access denied' }, 404);
383
+ const id = c.req.param('id');
384
+ if (!/^[\w\-]+$/.test(id)) {
385
+ return c.json({ error: 'Invalid server ID' }, 400);
386
+ }
387
+ const body = await c.req.json().catch(() => ({}));
388
+ const { command, args, env, enabled, description, url, headers } = body;
389
+ const meta = readWorkspaceMeta(group);
390
+ let entry = meta.mcpServers[id];
391
+ // If not in metadata, check settings.json for externally added servers
392
+ if (!entry) {
393
+ const settings = readWorkspaceSettings(group);
394
+ const settingsMcp = settings.mcpServers || {};
395
+ const settingsEntry = settingsMcp[id];
396
+ if (!settingsEntry) {
397
+ return c.json({ error: 'Server not found' }, 404);
398
+ }
399
+ // Import from settings into metadata
400
+ const isHttp = settingsEntry.type === 'http' || settingsEntry.type === 'sse';
401
+ entry = {
402
+ enabled: true,
403
+ addedAt: '',
404
+ ...(isHttp
405
+ ? {
406
+ type: settingsEntry.type,
407
+ url: settingsEntry.url,
408
+ }
409
+ : { command: settingsEntry.command }),
410
+ };
411
+ meta.mcpServers[id] = entry;
412
+ }
413
+ if (command !== undefined)
414
+ entry.command = command;
415
+ if (args !== undefined)
416
+ entry.args = args;
417
+ if (env !== undefined)
418
+ entry.env = env;
419
+ if (url !== undefined)
420
+ entry.url = url;
421
+ if (headers !== undefined)
422
+ entry.headers = headers;
423
+ if (typeof enabled === 'boolean')
424
+ entry.enabled = enabled;
425
+ if (description !== undefined) {
426
+ entry.description =
427
+ typeof description === 'string' ? description : undefined;
428
+ }
429
+ writeWorkspaceMeta(group, meta);
430
+ syncMcpToSettings(group, meta);
431
+ return c.json({ success: true, server: { id, ...entry } });
432
+ });
433
+ // DELETE /workspace-config/mcp-servers/:id — delete MCP server
434
+ workspaceConfigRoutes.delete('/:jid/workspace-config/mcp-servers/:id', authMiddleware, async (c) => {
435
+ const group = resolveGroup(c);
436
+ if (!group)
437
+ return c.json({ error: 'Group not found or access denied' }, 404);
438
+ const id = c.req.param('id');
439
+ if (!/^[\w\-]+$/.test(id)) {
440
+ return c.json({ error: 'Invalid server ID' }, 400);
441
+ }
442
+ const meta = readWorkspaceMeta(group);
443
+ const hadMeta = !!meta.mcpServers[id];
444
+ delete meta.mcpServers[id];
445
+ // Also remove from settings.json directly
446
+ const settings = readWorkspaceSettings(group);
447
+ const settingsMcp = settings.mcpServers || {};
448
+ const hadSettings = id in settingsMcp;
449
+ if (!hadMeta && !hadSettings) {
450
+ return c.json({ error: 'Server not found' }, 404);
451
+ }
452
+ if (hadMeta) {
453
+ writeWorkspaceMeta(group, meta);
454
+ }
455
+ syncMcpToSettings(group, meta, settings);
456
+ return c.json({ success: true });
457
+ });
458
+ export default workspaceConfigRoutes;
@@ -0,0 +1,112 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { APP_ROOT } from './app-root.js';
4
+ export function getRuntimeBuildArtifactPaths(appRoot = APP_ROOT) {
5
+ return {
6
+ backendBuildPath: path.resolve(appRoot, 'dist', 'index.js'),
7
+ backendPackagePath: path.resolve(appRoot, 'package.json'),
8
+ agentRunnerBuildPath: path.resolve(appRoot, 'container', 'agent-runner', 'dist', 'index.js'),
9
+ agentRunnerPackagePath: path.resolve(appRoot, 'container', 'agent-runner', 'package.json'),
10
+ };
11
+ }
12
+ function readPackageVersion(packageJsonPath) {
13
+ try {
14
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
15
+ return typeof parsed.version === 'string' ? parsed.version : null;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ export function readBuildFingerprint(filePath, version = null) {
22
+ const resolvedPath = path.resolve(filePath);
23
+ try {
24
+ const stat = fs.statSync(resolvedPath);
25
+ return {
26
+ path: resolvedPath,
27
+ version,
28
+ exists: true,
29
+ mtimeMs: stat.mtimeMs,
30
+ mtimeIso: stat.mtime.toISOString(),
31
+ };
32
+ }
33
+ catch {
34
+ return {
35
+ path: resolvedPath,
36
+ version,
37
+ exists: false,
38
+ mtimeMs: null,
39
+ mtimeIso: null,
40
+ };
41
+ }
42
+ }
43
+ export function isBuildFingerprintStale(loaded, current) {
44
+ return (loaded.path !== current.path ||
45
+ loaded.exists !== current.exists ||
46
+ loaded.mtimeMs !== current.mtimeMs);
47
+ }
48
+ export function formatBuildFingerprintForLog(fingerprint) {
49
+ const version = fingerprint.version || 'unknown';
50
+ const mtime = fingerprint.mtimeIso || 'missing';
51
+ const suffix = fingerprint.exists ? `mtime=${mtime}` : 'missing';
52
+ return `${version} @ ${fingerprint.path} (${suffix})`;
53
+ }
54
+ export function createRuntimeBuildSnapshot() {
55
+ const artifactPaths = getRuntimeBuildArtifactPaths();
56
+ return {
57
+ pid: process.pid,
58
+ startedAt: new Date().toISOString(),
59
+ backend: readBuildFingerprint(artifactPaths.backendBuildPath, readPackageVersion(artifactPaths.backendPackagePath)),
60
+ agentRunner: readBuildFingerprint(artifactPaths.agentRunnerBuildPath, readPackageVersion(artifactPaths.agentRunnerPackagePath)),
61
+ };
62
+ }
63
+ export function createRuntimeBuildStatus(snapshot, current = {}) {
64
+ const artifactPaths = getRuntimeBuildArtifactPaths();
65
+ const currentBackend = current.backend ||
66
+ readBuildFingerprint(artifactPaths.backendBuildPath, snapshot.backend.version ||
67
+ readPackageVersion(artifactPaths.backendPackagePath));
68
+ const currentAgentRunner = current.agentRunner ||
69
+ readBuildFingerprint(artifactPaths.agentRunnerBuildPath, snapshot.agentRunner.version ||
70
+ readPackageVersion(artifactPaths.agentRunnerPackagePath));
71
+ const backendStale = isBuildFingerprintStale(snapshot.backend, currentBackend);
72
+ const agentRunnerStale = isBuildFingerprintStale(snapshot.agentRunner, currentAgentRunner);
73
+ return {
74
+ pid: snapshot.pid,
75
+ startedAt: snapshot.startedAt,
76
+ backend: {
77
+ loaded: snapshot.backend,
78
+ current: currentBackend,
79
+ stale: backendStale,
80
+ },
81
+ agentRunner: {
82
+ loaded: snapshot.agentRunner,
83
+ current: currentAgentRunner,
84
+ stale: agentRunnerStale,
85
+ },
86
+ stale: backendStale || agentRunnerStale,
87
+ };
88
+ }
89
+ const STARTUP_RUNTIME_BUILD_SNAPSHOT = createRuntimeBuildSnapshot();
90
+ export function getRuntimeBuildStartupSnapshot() {
91
+ return STARTUP_RUNTIME_BUILD_SNAPSHOT;
92
+ }
93
+ export function getRuntimeBuildStatus() {
94
+ return createRuntimeBuildStatus(STARTUP_RUNTIME_BUILD_SNAPSHOT);
95
+ }
96
+ export function isRuntimeBuildStale() {
97
+ return getRuntimeBuildStatus().stale;
98
+ }
99
+ export function getRuntimeBuildLogFields() {
100
+ const status = getRuntimeBuildStatus();
101
+ return {
102
+ staleBuild: status.stale,
103
+ backendPid: status.pid,
104
+ backendStartedAt: status.startedAt,
105
+ backendBuildLoaded: formatBuildFingerprintForLog(status.backend.loaded),
106
+ backendBuildCurrent: formatBuildFingerprintForLog(status.backend.current),
107
+ backendBuildStale: status.backend.stale,
108
+ agentRunnerBuildLoaded: formatBuildFingerprintForLog(status.agentRunner.loaded),
109
+ agentRunnerBuildCurrent: formatBuildFingerprintForLog(status.agentRunner.current),
110
+ agentRunnerBuildStale: status.agentRunner.stale,
111
+ };
112
+ }