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
package/dist/logger.js ADDED
@@ -0,0 +1,22 @@
1
+ import pino from 'pino';
2
+ export const logger = pino({
3
+ level: process.env.LOG_LEVEL || 'info',
4
+ transport: {
5
+ target: 'pino-pretty',
6
+ options: {
7
+ colorize: true,
8
+ translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l',
9
+ },
10
+ },
11
+ });
12
+ // Route uncaught errors through pino so they get timestamps in stderr
13
+ process.on('uncaughtException', (err) => {
14
+ logger.fatal({ err }, 'Uncaught exception');
15
+ process.exit(1);
16
+ });
17
+ process.on('unhandledRejection', (reason) => {
18
+ logger.error({ err: reason }, 'Unhandled rejection');
19
+ // 不立即退出:unhandled rejection 通常非致命(如 API 超时未 catch),
20
+ // 立即 exit 会导致长期运行服务丢失正在处理的消息和容器管理状态。
21
+ // uncaughtException 仍保持 exit(1),因为异常会破坏进程状态。
22
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Shared MCP server loading utilities.
3
+ * Used by container-runner (Docker + Host modes) and routes/mcp-servers.
4
+ */
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { DATA_DIR } from './config.js';
8
+ /**
9
+ * Load enabled MCP server configs from a servers.json file.
10
+ * Returns only enabled servers with fields needed for settings.json.
11
+ * Supports both stdio (command/args/env) and http/sse (type/url/headers) server types.
12
+ */
13
+ function loadMcpServersFromFile(serversFile) {
14
+ try {
15
+ if (!fs.existsSync(serversFile))
16
+ return {};
17
+ const file = JSON.parse(fs.readFileSync(serversFile, 'utf8'));
18
+ const raw = file.servers || {};
19
+ const result = {};
20
+ for (const [name, server] of Object.entries(raw)) {
21
+ if (!server.enabled)
22
+ continue;
23
+ const isHttpType = server.type === 'http' || server.type === 'sse';
24
+ if (isHttpType) {
25
+ if (!server.url)
26
+ continue;
27
+ const entry = {
28
+ type: server.type,
29
+ url: server.url,
30
+ };
31
+ if (server.headers &&
32
+ typeof server.headers === 'object' &&
33
+ Object.keys(server.headers).length > 0) {
34
+ entry.headers = server.headers;
35
+ }
36
+ result[name] = entry;
37
+ }
38
+ else {
39
+ if (!server.command)
40
+ continue;
41
+ const entry = { command: server.command };
42
+ if (server.args)
43
+ entry.args = server.args;
44
+ if (server.env &&
45
+ typeof server.env === 'object' &&
46
+ Object.keys(server.env).length > 0) {
47
+ entry.env = server.env;
48
+ }
49
+ result[name] = entry;
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ catch {
55
+ return {};
56
+ }
57
+ }
58
+ /**
59
+ * Load enabled MCP server configs for a user.
60
+ * Reads ~/.cli-claw/mcp-servers/{userId}/servers.json.
61
+ * All workspaces owned by this user share the same MCP server set.
62
+ */
63
+ export function loadUserMcpServers(userId) {
64
+ const serversFile = path.join(DATA_DIR, 'mcp-servers', userId, 'servers.json');
65
+ return loadMcpServersFromFile(serversFile);
66
+ }
@@ -0,0 +1,69 @@
1
+ import { detectImageMimeTypeFromBase64Strict } from './image-detector.js';
2
+ const DATA_URL_BASE64_RE = /^\s*data:([^;,]+);base64,(.*)\s*$/is;
3
+ function normalizeImageMimeType(raw) {
4
+ if (typeof raw !== 'string')
5
+ return undefined;
6
+ const lowered = raw.trim().toLowerCase();
7
+ if (!lowered.startsWith('image/'))
8
+ return undefined;
9
+ return lowered;
10
+ }
11
+ function unwrapBase64Payload(raw) {
12
+ const match = DATA_URL_BASE64_RE.exec(raw);
13
+ if (!match)
14
+ return { base64: raw.replace(/\s+/g, '') };
15
+ return {
16
+ hintedMime: normalizeImageMimeType(match[1]),
17
+ base64: match[2].replace(/\s+/g, ''),
18
+ };
19
+ }
20
+ function resolveImageMimeType(declaredMime, detectedMime, options) {
21
+ if (declaredMime && detectedMime && declaredMime !== detectedMime) {
22
+ options?.onMimeMismatch?.({ declaredMime, detectedMime });
23
+ return detectedMime;
24
+ }
25
+ if (declaredMime)
26
+ return declaredMime;
27
+ if (detectedMime)
28
+ return detectedMime;
29
+ return 'image/jpeg';
30
+ }
31
+ export function normalizeImageAttachment(input, options) {
32
+ // 历史附件数据可能缺少 type 字段,缺失时默认视为 image
33
+ if ((input.type ?? 'image') !== 'image')
34
+ return null;
35
+ if (typeof input.data !== 'string' || input.data.length === 0)
36
+ return null;
37
+ const { base64, hintedMime } = unwrapBase64Payload(input.data);
38
+ if (base64.length === 0)
39
+ return null;
40
+ const declared = normalizeImageMimeType(input.mimeType) || hintedMime;
41
+ const detected = detectImageMimeTypeFromBase64Strict(base64);
42
+ const mimeType = resolveImageMimeType(declared, detected, options);
43
+ return {
44
+ type: 'image',
45
+ data: base64,
46
+ mimeType,
47
+ };
48
+ }
49
+ export function normalizeImageAttachments(inputs, options) {
50
+ if (!Array.isArray(inputs))
51
+ return [];
52
+ const normalized = [];
53
+ for (const item of inputs) {
54
+ if (!item || typeof item !== 'object')
55
+ continue;
56
+ const out = normalizeImageAttachment(item, options);
57
+ if (out)
58
+ normalized.push(out);
59
+ }
60
+ return normalized;
61
+ }
62
+ export function toAgentImages(attachments) {
63
+ if (!attachments || attachments.length === 0)
64
+ return undefined;
65
+ return attachments.map((att) => ({
66
+ data: att.data,
67
+ mimeType: att.mimeType,
68
+ }));
69
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Lightweight notification mechanism for IM messages.
3
+ *
4
+ * The message polling loop in index.ts sleeps for POLL_INTERVAL (2s) between
5
+ * iterations. When a Feishu / Telegram / QQ handler stores a new message it
6
+ * calls `notifyNewImMessage()` which wakes the loop immediately so the message
7
+ * is picked up without waiting for the remaining sleep time.
8
+ *
9
+ * Web messages are NOT routed through this notifier — they already bypass the
10
+ * polling loop via direct IPC injection + `enqueueMessageCheck()`.
11
+ */
12
+ let wakeup = null;
13
+ /**
14
+ * Returns a Promise that resolves after `ms` milliseconds **or** as soon as
15
+ * `notifyNewImMessage()` is called — whichever comes first.
16
+ */
17
+ export function interruptibleSleep(ms) {
18
+ return new Promise((resolve) => {
19
+ const timer = setTimeout(() => {
20
+ wakeup = null;
21
+ resolve();
22
+ }, ms);
23
+ wakeup = () => {
24
+ clearTimeout(timer);
25
+ wakeup = null;
26
+ resolve();
27
+ };
28
+ });
29
+ }
30
+ /**
31
+ * Wake the message loop immediately. Safe to call at any time — if the loop
32
+ * is not sleeping this is a no-op.
33
+ */
34
+ export function notifyNewImMessage() {
35
+ wakeup?.();
36
+ }
@@ -0,0 +1,85 @@
1
+ // Authentication and authorization middleware
2
+ import { lastActiveCache, LAST_ACTIVE_DEBOUNCE_MS, parseCookie, getCachedSessionWithUser, invalidateSessionCache, } from '../web-context.js';
3
+ import { updateSessionLastActive, deleteUserSession } from '../db.js';
4
+ import { isSessionExpired } from '../auth.js';
5
+ import { hasPermission } from '../permissions.js';
6
+ import { SESSION_COOKIE_NAME_SECURE, SESSION_COOKIE_NAME_PLAIN, } from '../config.js';
7
+ export const authMiddleware = async (c, next) => {
8
+ const cookies = parseCookie(c.req.header('cookie'));
9
+ // Accept either cookie name — the browser will send whichever was set
10
+ const token = cookies[SESSION_COOKIE_NAME_SECURE] || cookies[SESSION_COOKIE_NAME_PLAIN];
11
+ if (!token) {
12
+ return c.json({ error: 'Unauthorized' }, 401);
13
+ }
14
+ const session = getCachedSessionWithUser(token);
15
+ if (!session) {
16
+ invalidateSessionCache(token);
17
+ return c.json({ error: 'Unauthorized' }, 401);
18
+ }
19
+ if (isSessionExpired(session.expires_at)) {
20
+ deleteUserSession(token);
21
+ invalidateSessionCache(token);
22
+ return c.json({ error: 'Session expired' }, 401);
23
+ }
24
+ if (session.status === 'disabled') {
25
+ return c.json({ error: 'Account disabled' }, 403);
26
+ }
27
+ if (session.status === 'deleted') {
28
+ return c.json({ error: 'Account deleted' }, 403);
29
+ }
30
+ c.set('user', {
31
+ id: session.user_id,
32
+ username: session.username,
33
+ role: session.role,
34
+ status: session.status,
35
+ display_name: session.display_name,
36
+ permissions: session.permissions,
37
+ must_change_password: session.must_change_password,
38
+ });
39
+ c.set('sessionId', token);
40
+ const requestPath = c.req.path;
41
+ const canBypassForcedChange = requestPath === '/api/auth/me' ||
42
+ requestPath === '/api/auth/password' ||
43
+ requestPath === '/api/auth/logout' ||
44
+ requestPath === '/api/auth/profile' ||
45
+ requestPath.startsWith('/api/auth/sessions');
46
+ if (session.must_change_password && !canBypassForcedChange) {
47
+ return c.json({ error: 'Password change required', code: 'PASSWORD_CHANGE_REQUIRED' }, 403);
48
+ }
49
+ // Low-frequency last_active_at update (every 5 min)
50
+ const now = Date.now();
51
+ const lastUpdate = lastActiveCache.get(token) || 0;
52
+ if (now - lastUpdate > LAST_ACTIVE_DEBOUNCE_MS) {
53
+ lastActiveCache.set(token, now);
54
+ try {
55
+ updateSessionLastActive(token);
56
+ }
57
+ catch {
58
+ /* best effort */
59
+ }
60
+ }
61
+ await next();
62
+ };
63
+ export const requirePermission = (permission) => async (c, next) => {
64
+ const user = c.get('user');
65
+ if (!hasPermission(user, permission)) {
66
+ return c.json({ error: `Forbidden: ${permission} required` }, 403);
67
+ }
68
+ await next();
69
+ };
70
+ export const requireAnyPermission = (permissions) => async (c, next) => {
71
+ const user = c.get('user');
72
+ const ok = permissions.some((permission) => hasPermission(user, permission));
73
+ if (!ok) {
74
+ return c.json({ error: `Forbidden: one of [${permissions.join(', ')}] required` }, 403);
75
+ }
76
+ await next();
77
+ };
78
+ export const systemConfigMiddleware = requirePermission('manage_system_config');
79
+ export const groupEnvMiddleware = requireAnyPermission([
80
+ 'manage_group_env',
81
+ 'manage_system_config',
82
+ ]);
83
+ export const usersManageMiddleware = requirePermission('manage_users');
84
+ export const inviteManageMiddleware = requirePermission('manage_invites');
85
+ export const auditViewMiddleware = requirePermission('view_audit_log');
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Mount Security Module for cli-claw
3
+ *
4
+ * Validates additional mounts against an allowlist stored in the project config/ directory.
5
+ *
6
+ * Allowlist location: config/mount-allowlist.json
7
+ */
8
+ import fs from 'fs';
9
+ import os from 'os';
10
+ import path from 'path';
11
+ import { MOUNT_ALLOWLIST_PATH } from './config.js';
12
+ import { logger } from './logger.js';
13
+ // Cache the allowlist in memory - only reloads on process restart
14
+ let cachedAllowlist = null;
15
+ let allowlistLoadError = null;
16
+ /**
17
+ * Default blocked patterns - paths that should never be mounted
18
+ */
19
+ const DEFAULT_BLOCKED_PATTERNS = [
20
+ '.ssh',
21
+ '.gnupg',
22
+ '.gpg',
23
+ '.aws',
24
+ '.azure',
25
+ '.gcloud',
26
+ '.kube',
27
+ '.docker',
28
+ 'credentials',
29
+ '.env',
30
+ '.netrc',
31
+ '.npmrc',
32
+ '.pypirc',
33
+ 'id_rsa',
34
+ 'id_ed25519',
35
+ 'private_key',
36
+ '.secret',
37
+ ];
38
+ /**
39
+ * Load the mount allowlist from the external config location.
40
+ * Returns null if the file doesn't exist or is invalid.
41
+ * Result is cached in memory for the lifetime of the process.
42
+ */
43
+ export function loadMountAllowlist() {
44
+ if (cachedAllowlist !== null) {
45
+ return cachedAllowlist;
46
+ }
47
+ if (allowlistLoadError !== null) {
48
+ // Already tried and failed, don't spam logs
49
+ return null;
50
+ }
51
+ try {
52
+ if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) {
53
+ allowlistLoadError = `Mount allowlist not found at ${MOUNT_ALLOWLIST_PATH}`;
54
+ logger.warn({ path: MOUNT_ALLOWLIST_PATH }, 'Mount allowlist not found - additional mounts will be BLOCKED. ' +
55
+ 'Create the file to enable additional mounts.');
56
+ return null;
57
+ }
58
+ const content = fs.readFileSync(MOUNT_ALLOWLIST_PATH, 'utf-8');
59
+ const allowlist = JSON.parse(content);
60
+ // Validate structure
61
+ if (!Array.isArray(allowlist.allowedRoots)) {
62
+ throw new Error('allowedRoots must be an array');
63
+ }
64
+ if (!Array.isArray(allowlist.blockedPatterns)) {
65
+ throw new Error('blockedPatterns must be an array');
66
+ }
67
+ if (typeof allowlist.nonMainReadOnly !== 'boolean') {
68
+ throw new Error('nonMainReadOnly must be a boolean');
69
+ }
70
+ // Merge with default blocked patterns
71
+ const mergedBlockedPatterns = [
72
+ ...new Set([...DEFAULT_BLOCKED_PATTERNS, ...allowlist.blockedPatterns]),
73
+ ];
74
+ allowlist.blockedPatterns = mergedBlockedPatterns;
75
+ cachedAllowlist = allowlist;
76
+ logger.info({
77
+ path: MOUNT_ALLOWLIST_PATH,
78
+ allowedRoots: allowlist.allowedRoots.length,
79
+ blockedPatterns: allowlist.blockedPatterns.length,
80
+ }, 'Mount allowlist loaded successfully');
81
+ return cachedAllowlist;
82
+ }
83
+ catch (err) {
84
+ allowlistLoadError = err instanceof Error ? err.message : String(err);
85
+ logger.error({
86
+ path: MOUNT_ALLOWLIST_PATH,
87
+ error: allowlistLoadError,
88
+ }, 'Failed to load mount allowlist - additional mounts will be BLOCKED');
89
+ return null;
90
+ }
91
+ }
92
+ /**
93
+ * Expand ~ to home directory and resolve to absolute path
94
+ */
95
+ export function expandPath(p) {
96
+ const homeDir = os.homedir();
97
+ if (p.startsWith('~/')) {
98
+ return path.join(homeDir, p.slice(2));
99
+ }
100
+ if (p === '~') {
101
+ return homeDir;
102
+ }
103
+ return path.resolve(p);
104
+ }
105
+ /**
106
+ * Get the real path, resolving symlinks.
107
+ * Returns null if the path doesn't exist.
108
+ */
109
+ function getRealPath(p) {
110
+ try {
111
+ return fs.realpathSync(p);
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
117
+ /**
118
+ * Check if a path matches any blocked pattern
119
+ */
120
+ export function matchesBlockedPattern(realPath, blockedPatterns) {
121
+ const pathParts = realPath.split(path.sep);
122
+ for (const pattern of blockedPatterns) {
123
+ // Check if any path component exactly matches the pattern
124
+ for (const part of pathParts) {
125
+ if (part === pattern) {
126
+ return pattern;
127
+ }
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Check if a real path is under an allowed root
134
+ */
135
+ export function findAllowedRoot(realPath, allowedRoots) {
136
+ for (const root of allowedRoots) {
137
+ const expandedRoot = expandPath(root.path);
138
+ const realRoot = getRealPath(expandedRoot);
139
+ if (realRoot === null) {
140
+ // Allowed root doesn't exist, skip it
141
+ continue;
142
+ }
143
+ // Check if realPath is under realRoot
144
+ const relative = path.relative(realRoot, realPath);
145
+ if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
146
+ return root;
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+ /**
152
+ * Validate the container path to prevent escaping /workspace/extra/
153
+ */
154
+ function isValidContainerPath(containerPath) {
155
+ // Must not contain .. to prevent path traversal
156
+ if (containerPath.includes('..')) {
157
+ return false;
158
+ }
159
+ // Must not be absolute (it will be prefixed with /workspace/extra/)
160
+ if (containerPath.startsWith('/')) {
161
+ return false;
162
+ }
163
+ // Must not be empty
164
+ if (!containerPath || containerPath.trim() === '') {
165
+ return false;
166
+ }
167
+ return true;
168
+ }
169
+ /**
170
+ * Validate a single additional mount against the allowlist.
171
+ * Returns validation result with reason.
172
+ */
173
+ export function validateMount(mount, isMain) {
174
+ const allowlist = loadMountAllowlist();
175
+ // If no allowlist, block all additional mounts
176
+ if (allowlist === null) {
177
+ return {
178
+ allowed: false,
179
+ reason: `No mount allowlist configured at ${MOUNT_ALLOWLIST_PATH}`,
180
+ };
181
+ }
182
+ // Derive containerPath from hostPath basename if not specified
183
+ const containerPath = mount.containerPath || path.basename(mount.hostPath);
184
+ // Validate container path (cheap check)
185
+ if (!isValidContainerPath(containerPath)) {
186
+ return {
187
+ allowed: false,
188
+ reason: `Invalid container path: "${containerPath}" - must be relative, non-empty, and not contain ".."`,
189
+ };
190
+ }
191
+ // Expand and resolve the host path
192
+ const expandedPath = expandPath(mount.hostPath);
193
+ const realPath = getRealPath(expandedPath);
194
+ if (realPath === null) {
195
+ return {
196
+ allowed: false,
197
+ reason: `Host path does not exist: "${mount.hostPath}" (expanded: "${expandedPath}")`,
198
+ };
199
+ }
200
+ // Check against blocked patterns
201
+ const blockedMatch = matchesBlockedPattern(realPath, allowlist.blockedPatterns);
202
+ if (blockedMatch !== null) {
203
+ return {
204
+ allowed: false,
205
+ reason: `Path matches blocked pattern "${blockedMatch}": "${realPath}"`,
206
+ };
207
+ }
208
+ // Check if under an allowed root
209
+ const allowedRoot = findAllowedRoot(realPath, allowlist.allowedRoots);
210
+ if (allowedRoot === null) {
211
+ return {
212
+ allowed: false,
213
+ reason: `Path "${realPath}" is not under any allowed root. Allowed roots: ${allowlist.allowedRoots
214
+ .map((r) => expandPath(r.path))
215
+ .join(', ')}`,
216
+ };
217
+ }
218
+ // Determine effective readonly status
219
+ const requestedReadWrite = mount.readonly === false;
220
+ let effectiveReadonly = true; // Default to readonly
221
+ if (requestedReadWrite) {
222
+ if (!isMain && allowlist.nonMainReadOnly) {
223
+ // Non-main groups forced to read-only
224
+ effectiveReadonly = true;
225
+ logger.info({
226
+ mount: mount.hostPath,
227
+ }, 'Mount forced to read-only for non-main group');
228
+ }
229
+ else if (!allowedRoot.allowReadWrite) {
230
+ // Root doesn't allow read-write
231
+ effectiveReadonly = true;
232
+ logger.info({
233
+ mount: mount.hostPath,
234
+ root: allowedRoot.path,
235
+ }, 'Mount forced to read-only - root does not allow read-write');
236
+ }
237
+ else {
238
+ // Read-write allowed
239
+ effectiveReadonly = false;
240
+ }
241
+ }
242
+ return {
243
+ allowed: true,
244
+ reason: `Allowed under root "${allowedRoot.path}"${allowedRoot.description ? ` (${allowedRoot.description})` : ''}`,
245
+ realHostPath: realPath,
246
+ resolvedContainerPath: containerPath,
247
+ effectiveReadonly,
248
+ };
249
+ }
250
+ /**
251
+ * Validate all additional mounts for a group.
252
+ * Returns array of validated mounts (only those that passed validation).
253
+ * Logs warnings for rejected mounts.
254
+ */
255
+ export function validateAdditionalMounts(mounts, groupName, isMain) {
256
+ const validatedMounts = [];
257
+ for (const mount of mounts) {
258
+ const result = validateMount(mount, isMain);
259
+ if (result.allowed) {
260
+ validatedMounts.push({
261
+ hostPath: result.realHostPath,
262
+ containerPath: `/workspace/extra/${result.resolvedContainerPath}`,
263
+ readonly: result.effectiveReadonly,
264
+ });
265
+ logger.debug({
266
+ group: groupName,
267
+ hostPath: result.realHostPath,
268
+ containerPath: result.resolvedContainerPath,
269
+ readonly: result.effectiveReadonly,
270
+ reason: result.reason,
271
+ }, 'Mount validated successfully');
272
+ }
273
+ else {
274
+ logger.warn({
275
+ group: groupName,
276
+ requestedPath: mount.hostPath,
277
+ containerPath: mount.containerPath,
278
+ reason: result.reason,
279
+ }, 'Additional mount REJECTED');
280
+ }
281
+ }
282
+ return validatedMounts;
283
+ }
284
+ /**
285
+ * Generate a template allowlist file for users to customize
286
+ */
287
+ export function generateAllowlistTemplate() {
288
+ const template = {
289
+ allowedRoots: [
290
+ {
291
+ path: '~/projects',
292
+ allowReadWrite: true,
293
+ description: 'Development projects',
294
+ },
295
+ {
296
+ path: '~/repos',
297
+ allowReadWrite: true,
298
+ description: 'Git repositories',
299
+ },
300
+ {
301
+ path: '~/Documents/work',
302
+ allowReadWrite: false,
303
+ description: 'Work documents (read-only)',
304
+ },
305
+ ],
306
+ blockedPatterns: [
307
+ // Additional patterns beyond defaults
308
+ 'password',
309
+ 'secret',
310
+ 'token',
311
+ ],
312
+ nonMainReadOnly: true,
313
+ };
314
+ return JSON.stringify(template, null, 2);
315
+ }
@@ -0,0 +1,67 @@
1
+ export const ALL_PERMISSIONS = [
2
+ 'manage_system_config',
3
+ 'manage_group_env',
4
+ 'manage_users',
5
+ 'manage_invites',
6
+ 'view_audit_log',
7
+ 'manage_billing',
8
+ ];
9
+ export const PERMISSION_TEMPLATES = {
10
+ admin_full: {
11
+ key: 'admin_full',
12
+ label: '管理员(全权限)',
13
+ role: 'admin',
14
+ permissions: [...ALL_PERMISSIONS],
15
+ },
16
+ member_basic: {
17
+ key: 'member_basic',
18
+ label: '普通成员(基础权限)',
19
+ role: 'member',
20
+ permissions: [],
21
+ },
22
+ ops_manager: {
23
+ key: 'ops_manager',
24
+ label: '运维管理员(配置+工作区环境)',
25
+ role: 'member',
26
+ permissions: ['manage_system_config', 'manage_group_env'],
27
+ },
28
+ user_admin: {
29
+ key: 'user_admin',
30
+ label: '用户管理员(用户+邀请码+审计)',
31
+ role: 'member',
32
+ permissions: ['manage_users', 'manage_invites', 'view_audit_log'],
33
+ },
34
+ };
35
+ export const ROLE_DEFAULT_PERMISSIONS = {
36
+ admin: [...ALL_PERMISSIONS],
37
+ member: [],
38
+ };
39
+ export function normalizePermissions(input) {
40
+ if (!Array.isArray(input))
41
+ return [];
42
+ const set = new Set();
43
+ for (const value of input) {
44
+ if (typeof value !== 'string')
45
+ continue;
46
+ if (ALL_PERMISSIONS.includes(value)) {
47
+ set.add(value);
48
+ }
49
+ }
50
+ return Array.from(set);
51
+ }
52
+ export function getDefaultPermissions(role) {
53
+ return [...(ROLE_DEFAULT_PERMISSIONS[role] || [])];
54
+ }
55
+ export function resolveTemplate(template) {
56
+ if (!template)
57
+ return null;
58
+ const item = PERMISSION_TEMPLATES[template];
59
+ if (!item)
60
+ return null;
61
+ return { role: item.role, permissions: [...item.permissions] };
62
+ }
63
+ export function hasPermission(user, permission) {
64
+ if (user.role === 'admin')
65
+ return true;
66
+ return user.permissions.includes(permission);
67
+ }
@@ -0,0 +1,6 @@
1
+ import path from 'node:path';
2
+ export const AGENT_MEMORY_FILENAME = 'AGENTS.md';
3
+ export const AGENT_MEMORY_TEMPLATE_FILENAME = 'global-agents-md.template.md';
4
+ export function getAgentMemoryPath(dir) {
5
+ return path.join(dir, AGENT_MEMORY_FILENAME);
6
+ }