mulmoclaude 0.1.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 (408) hide show
  1. package/README.md +44 -0
  2. package/bin/mulmoclaude.js +202 -0
  3. package/bin/prepare-dist.js +93 -0
  4. package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +1 -0
  5. package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +5 -0
  6. package/client/assets/index-D8rhwXLq.js +4906 -0
  7. package/client/assets/index-KNLBjwuh.css +1 -0
  8. package/client/assets/index.es-D4YyL_Dg-BfRHLTZV.js +5 -0
  9. package/client/assets/material-icons-Dr0goTwe.woff +0 -0
  10. package/client/assets/material-icons-kAwBdRge.woff2 +0 -0
  11. package/client/assets/material-icons-outlined-BpWbwl2n.woff +0 -0
  12. package/client/assets/material-icons-outlined-DZhiGvEA.woff2 +0 -0
  13. package/client/assets/material-icons-round-BDlwx-sv.woff +0 -0
  14. package/client/assets/material-icons-round-DrirKXBx.woff2 +0 -0
  15. package/client/assets/material-icons-sharp-CH1KkVu7.woff +0 -0
  16. package/client/assets/material-icons-sharp-gidztirS.woff2 +0 -0
  17. package/client/assets/material-icons-two-tone-B7wz7mED.woff +0 -0
  18. package/client/assets/material-icons-two-tone-DuNIpaEj.woff2 +0 -0
  19. package/client/assets/mulmo_bw-ERmkSv0a.png +0 -0
  20. package/client/assets/purify.es-Fx1Nqyry-PeS5RUhs.js +2 -0
  21. package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +1 -0
  22. package/client/index.html +28 -0
  23. package/package.json +66 -0
  24. package/server/agent/attachmentConverter.ts +270 -0
  25. package/server/agent/config.ts +414 -0
  26. package/server/agent/index.ts +260 -0
  27. package/server/agent/mcp-server.ts +412 -0
  28. package/server/agent/mcp-tools/index.ts +63 -0
  29. package/server/agent/mcp-tools/x.ts +188 -0
  30. package/server/agent/plugin-names.ts +75 -0
  31. package/server/agent/prompt.ts +349 -0
  32. package/server/agent/resumeFailover.ts +129 -0
  33. package/server/agent/sandboxMounts.ts +329 -0
  34. package/server/agent/stream.ts +194 -0
  35. package/server/api/auth/bearerAuth.ts +61 -0
  36. package/server/api/auth/token.ts +98 -0
  37. package/server/api/csrfGuard.ts +85 -0
  38. package/server/api/routes/agent.ts +478 -0
  39. package/server/api/routes/chart.ts +98 -0
  40. package/server/api/routes/chat-index.ts +46 -0
  41. package/server/api/routes/config.ts +258 -0
  42. package/server/api/routes/dispatchResponse.ts +79 -0
  43. package/server/api/routes/files.ts +812 -0
  44. package/server/api/routes/html.ts +101 -0
  45. package/server/api/routes/image.ts +169 -0
  46. package/server/api/routes/mulmo-script.ts +712 -0
  47. package/server/api/routes/mulmoScriptValidate.ts +101 -0
  48. package/server/api/routes/notifications.ts +69 -0
  49. package/server/api/routes/pdf.ts +163 -0
  50. package/server/api/routes/plugins.ts +276 -0
  51. package/server/api/routes/presentHtml.ts +48 -0
  52. package/server/api/routes/roles.ts +125 -0
  53. package/server/api/routes/scheduler.ts +153 -0
  54. package/server/api/routes/schedulerHandlers.ts +151 -0
  55. package/server/api/routes/schedulerTasks.ts +163 -0
  56. package/server/api/routes/sessions.ts +294 -0
  57. package/server/api/routes/sessionsCursor.ts +59 -0
  58. package/server/api/routes/skills.ts +195 -0
  59. package/server/api/routes/sources.ts +540 -0
  60. package/server/api/routes/todos.ts +263 -0
  61. package/server/api/routes/todosColumnsHandlers.ts +347 -0
  62. package/server/api/routes/todosHandlers.ts +274 -0
  63. package/server/api/routes/todosItemsHandlers.ts +386 -0
  64. package/server/api/routes/wiki/pageIndex.ts +53 -0
  65. package/server/api/routes/wiki.ts +363 -0
  66. package/server/api/sandboxStatus.ts +64 -0
  67. package/server/events/notifications.ts +160 -0
  68. package/server/events/pub-sub/index.ts +45 -0
  69. package/server/events/relay-client.ts +288 -0
  70. package/server/events/scheduler-adapter.ts +302 -0
  71. package/server/events/session-store/index.ts +492 -0
  72. package/server/events/task-manager/index.ts +181 -0
  73. package/server/index.ts +572 -0
  74. package/server/system/config.ts +243 -0
  75. package/server/system/credentials.ts +220 -0
  76. package/server/system/docker.ts +97 -0
  77. package/server/system/env.ts +109 -0
  78. package/server/system/logger/config.ts +112 -0
  79. package/server/system/logger/formatters.ts +40 -0
  80. package/server/system/logger/index.ts +53 -0
  81. package/server/system/logger/rotation.ts +37 -0
  82. package/server/system/logger/sinks.ts +101 -0
  83. package/server/system/logger/types.ts +29 -0
  84. package/server/utils/date.ts +57 -0
  85. package/server/utils/errors.ts +7 -0
  86. package/server/utils/fetch.ts +27 -0
  87. package/server/utils/files/atomic.ts +125 -0
  88. package/server/utils/files/html-io.ts +20 -0
  89. package/server/utils/files/image-store.ts +66 -0
  90. package/server/utils/files/index.ts +45 -0
  91. package/server/utils/files/journal-io.ts +213 -0
  92. package/server/utils/files/json.ts +69 -0
  93. package/server/utils/files/markdown-store.ts +33 -0
  94. package/server/utils/files/naming.ts +50 -0
  95. package/server/utils/files/reference-dirs-io.ts +45 -0
  96. package/server/utils/files/roles-io.ts +45 -0
  97. package/server/utils/files/safe.ts +106 -0
  98. package/server/utils/files/scheduler-io.ts +20 -0
  99. package/server/utils/files/scheduler-overrides-io.ts +64 -0
  100. package/server/utils/files/session-io.ts +136 -0
  101. package/server/utils/files/spreadsheet-store.ts +63 -0
  102. package/server/utils/files/todos-io.ts +29 -0
  103. package/server/utils/files/user-tasks-io.ts +25 -0
  104. package/server/utils/files/workspace-io.ts +221 -0
  105. package/server/utils/gemini.ts +59 -0
  106. package/server/utils/gitignore.ts +69 -0
  107. package/server/utils/http.ts +15 -0
  108. package/server/utils/httpError.ts +61 -0
  109. package/server/utils/id.ts +16 -0
  110. package/server/utils/json.ts +83 -0
  111. package/server/utils/logBackgroundError.ts +22 -0
  112. package/server/utils/markdown.ts +82 -0
  113. package/server/utils/request.ts +29 -0
  114. package/server/utils/slug.ts +50 -0
  115. package/server/utils/spawn.ts +62 -0
  116. package/server/utils/time.ts +34 -0
  117. package/server/utils/types.ts +47 -0
  118. package/server/workspace/chat-index/index.ts +153 -0
  119. package/server/workspace/chat-index/indexer.ts +209 -0
  120. package/server/workspace/chat-index/paths.ts +34 -0
  121. package/server/workspace/chat-index/summarizer.ts +247 -0
  122. package/server/workspace/chat-index/types.ts +38 -0
  123. package/server/workspace/custom-dirs.ts +220 -0
  124. package/server/workspace/helps/business.md +104 -0
  125. package/server/workspace/helps/github.md +23 -0
  126. package/server/workspace/helps/index.md +60 -0
  127. package/server/workspace/helps/mulmoscript.md +249 -0
  128. package/server/workspace/helps/sandbox.md +90 -0
  129. package/server/workspace/helps/spreadsheet.md +43 -0
  130. package/server/workspace/helps/telegram.md +135 -0
  131. package/server/workspace/helps/wiki.md +131 -0
  132. package/server/workspace/journal/archivist.ts +386 -0
  133. package/server/workspace/journal/dailyPass.ts +743 -0
  134. package/server/workspace/journal/diff.ts +71 -0
  135. package/server/workspace/journal/index.ts +185 -0
  136. package/server/workspace/journal/indexFile.ts +136 -0
  137. package/server/workspace/journal/linkRewrite.ts +4 -0
  138. package/server/workspace/journal/memoryExtractor.ts +130 -0
  139. package/server/workspace/journal/optimizationPass.ts +160 -0
  140. package/server/workspace/journal/paths.ts +76 -0
  141. package/server/workspace/journal/state.ts +125 -0
  142. package/server/workspace/paths.ts +158 -0
  143. package/server/workspace/reference-dirs.ts +252 -0
  144. package/server/workspace/roles.ts +37 -0
  145. package/server/workspace/skills/discovery.ts +125 -0
  146. package/server/workspace/skills/index.ts +10 -0
  147. package/server/workspace/skills/parser.ts +144 -0
  148. package/server/workspace/skills/paths.ts +41 -0
  149. package/server/workspace/skills/scheduler.ts +149 -0
  150. package/server/workspace/skills/types.ts +30 -0
  151. package/server/workspace/skills/user-tasks.ts +257 -0
  152. package/server/workspace/skills/writer.ts +189 -0
  153. package/server/workspace/sources/arxivDiscovery.ts +182 -0
  154. package/server/workspace/sources/classifier.ts +268 -0
  155. package/server/workspace/sources/fetchers/arxiv.ts +170 -0
  156. package/server/workspace/sources/fetchers/github.ts +106 -0
  157. package/server/workspace/sources/fetchers/githubIssues.ts +208 -0
  158. package/server/workspace/sources/fetchers/githubReleases.ts +186 -0
  159. package/server/workspace/sources/fetchers/index.ts +71 -0
  160. package/server/workspace/sources/fetchers/registerAll.ts +15 -0
  161. package/server/workspace/sources/fetchers/rss.ts +141 -0
  162. package/server/workspace/sources/fetchers/rssParser.ts +295 -0
  163. package/server/workspace/sources/httpFetcher.ts +230 -0
  164. package/server/workspace/sources/interests.ts +120 -0
  165. package/server/workspace/sources/paths.ts +110 -0
  166. package/server/workspace/sources/pipeline/dedup.ts +60 -0
  167. package/server/workspace/sources/pipeline/fetch.ts +136 -0
  168. package/server/workspace/sources/pipeline/index.ts +249 -0
  169. package/server/workspace/sources/pipeline/notify.ts +72 -0
  170. package/server/workspace/sources/pipeline/plan.ts +66 -0
  171. package/server/workspace/sources/pipeline/summarize.ts +189 -0
  172. package/server/workspace/sources/pipeline/write.ts +185 -0
  173. package/server/workspace/sources/rateLimiter.ts +148 -0
  174. package/server/workspace/sources/registry.ts +326 -0
  175. package/server/workspace/sources/robots.ts +271 -0
  176. package/server/workspace/sources/sourceState.ts +135 -0
  177. package/server/workspace/sources/taxonomy.ts +74 -0
  178. package/server/workspace/sources/types.ts +144 -0
  179. package/server/workspace/sources/urls.ts +112 -0
  180. package/server/workspace/tool-trace/classify.ts +114 -0
  181. package/server/workspace/tool-trace/index.ts +250 -0
  182. package/server/workspace/tool-trace/writeSearch.ts +98 -0
  183. package/server/workspace/wiki-backlinks/index.ts +107 -0
  184. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +144 -0
  185. package/server/workspace/workspace.ts +66 -0
  186. package/src/App.vue +720 -0
  187. package/src/assets/mulmo_bw.png +0 -0
  188. package/src/components/CanvasViewToggle.vue +27 -0
  189. package/src/components/ChatAttachmentPreview.vue +45 -0
  190. package/src/components/ChatImagePreview.vue +17 -0
  191. package/src/components/ChatInput.vue +208 -0
  192. package/src/components/FileContentHeader.vue +49 -0
  193. package/src/components/FileContentRenderer.vue +162 -0
  194. package/src/components/FileTree.vue +115 -0
  195. package/src/components/FileTreePane.vue +85 -0
  196. package/src/components/FilesView.vue +206 -0
  197. package/src/components/LockStatusPopup.vue +111 -0
  198. package/src/components/NotificationBell.vue +131 -0
  199. package/src/components/NotificationToast.vue +72 -0
  200. package/src/components/PluginLauncher.vue +138 -0
  201. package/src/components/RightSidebar.vue +113 -0
  202. package/src/components/RoleSelector.vue +64 -0
  203. package/src/components/SessionHistoryPanel.vue +176 -0
  204. package/src/components/SessionTabBar.vue +81 -0
  205. package/src/components/SettingsMcpTab.vue +350 -0
  206. package/src/components/SettingsModal.vue +275 -0
  207. package/src/components/SettingsReferenceDirsTab.vue +173 -0
  208. package/src/components/SettingsWorkspaceDirsTab.vue +174 -0
  209. package/src/components/SidebarHeader.vue +69 -0
  210. package/src/components/StackView.vue +360 -0
  211. package/src/components/SuggestionsPanel.vue +65 -0
  212. package/src/components/TodoExplorer.vue +358 -0
  213. package/src/components/ToolResultsPanel.vue +77 -0
  214. package/src/components/todo/TodoAddDialog.vue +131 -0
  215. package/src/components/todo/TodoEditDialog.vue +47 -0
  216. package/src/components/todo/TodoEditPanel.vue +113 -0
  217. package/src/components/todo/TodoKanbanView.vue +249 -0
  218. package/src/components/todo/TodoListView.vue +79 -0
  219. package/src/components/todo/TodoTableView.vue +177 -0
  220. package/src/composables/useActiveSession.ts +40 -0
  221. package/src/composables/useAppApi.ts +45 -0
  222. package/src/composables/useCanvasViewMode.ts +121 -0
  223. package/src/composables/useChatScroll.ts +47 -0
  224. package/src/composables/useClickOutside.ts +26 -0
  225. package/src/composables/useClipboardCopy.ts +44 -0
  226. package/src/composables/useContentDisplay.ts +52 -0
  227. package/src/composables/useDebugBeat.ts +23 -0
  228. package/src/composables/useDynamicFavicon.ts +115 -0
  229. package/src/composables/useEventListeners.ts +42 -0
  230. package/src/composables/useExpandedDirs.ts +64 -0
  231. package/src/composables/useFaviconState.ts +30 -0
  232. package/src/composables/useFileSelection.ts +115 -0
  233. package/src/composables/useFileSortMode.ts +24 -0
  234. package/src/composables/useFileTree.ts +85 -0
  235. package/src/composables/useFreshPluginData.ts +89 -0
  236. package/src/composables/useHealth.ts +38 -0
  237. package/src/composables/useImeAwareEnter.ts +57 -0
  238. package/src/composables/useKeyNavigation.ts +60 -0
  239. package/src/composables/useMarkdownLinkHandler.ts +46 -0
  240. package/src/composables/useMarkdownMode.ts +17 -0
  241. package/src/composables/useMcpTools.ts +71 -0
  242. package/src/composables/useMergedSessions.ts +27 -0
  243. package/src/composables/useNotifications.ts +90 -0
  244. package/src/composables/usePdfDownload.ts +60 -0
  245. package/src/composables/usePendingCalls.ts +77 -0
  246. package/src/composables/usePubSub.ts +85 -0
  247. package/src/composables/useRightSidebar.ts +23 -0
  248. package/src/composables/useRoles.ts +34 -0
  249. package/src/composables/useSandboxStatus.ts +67 -0
  250. package/src/composables/useSelectedResult.ts +49 -0
  251. package/src/composables/useSessionDerived.ts +51 -0
  252. package/src/composables/useSessionHistory.ts +81 -0
  253. package/src/composables/useSessionSync.ts +57 -0
  254. package/src/composables/useViewLayout.ts +55 -0
  255. package/src/config/apiRoutes.ts +173 -0
  256. package/src/config/pubsubChannels.ts +45 -0
  257. package/src/config/roles.ts +335 -0
  258. package/src/config/schedulerActions.ts +25 -0
  259. package/src/config/toolNames.ts +71 -0
  260. package/src/config/workspacePaths.ts +24 -0
  261. package/src/index.css +107 -0
  262. package/src/main.ts +25 -0
  263. package/src/plugins/canvas/Preview.vue +13 -0
  264. package/src/plugins/canvas/View.vue +333 -0
  265. package/src/plugins/canvas/definition.ts +38 -0
  266. package/src/plugins/canvas/index.ts +36 -0
  267. package/src/plugins/chart/Preview.vue +49 -0
  268. package/src/plugins/chart/View.vue +143 -0
  269. package/src/plugins/chart/definition.ts +58 -0
  270. package/src/plugins/chart/index.ts +52 -0
  271. package/src/plugins/editImage/Preview.vue +13 -0
  272. package/src/plugins/editImage/View.vue +13 -0
  273. package/src/plugins/editImage/definition.ts +27 -0
  274. package/src/plugins/editImage/index.ts +36 -0
  275. package/src/plugins/generateImage/Preview.vue +13 -0
  276. package/src/plugins/generateImage/View.vue +33 -0
  277. package/src/plugins/generateImage/definition.ts +32 -0
  278. package/src/plugins/generateImage/index.ts +56 -0
  279. package/src/plugins/manageRoles/Preview.vue +49 -0
  280. package/src/plugins/manageRoles/View.vue +525 -0
  281. package/src/plugins/manageRoles/definition.ts +43 -0
  282. package/src/plugins/manageRoles/index.ts +47 -0
  283. package/src/plugins/manageSkills/Preview.vue +21 -0
  284. package/src/plugins/manageSkills/View.vue +321 -0
  285. package/src/plugins/manageSkills/definition.ts +49 -0
  286. package/src/plugins/manageSkills/index.ts +49 -0
  287. package/src/plugins/manageSource/Preview.vue +33 -0
  288. package/src/plugins/manageSource/View.vue +697 -0
  289. package/src/plugins/manageSource/definition.ts +63 -0
  290. package/src/plugins/manageSource/index.ts +66 -0
  291. package/src/plugins/markdown/Preview.vue +77 -0
  292. package/src/plugins/markdown/View.vue +476 -0
  293. package/src/plugins/markdown/definition.ts +50 -0
  294. package/src/plugins/markdown/index.ts +36 -0
  295. package/src/plugins/presentHtml/Preview.vue +25 -0
  296. package/src/plugins/presentHtml/View.vue +52 -0
  297. package/src/plugins/presentHtml/definition.ts +27 -0
  298. package/src/plugins/presentHtml/helpers.ts +72 -0
  299. package/src/plugins/presentHtml/index.ts +41 -0
  300. package/src/plugins/presentMulmoScript/Preview.vue +23 -0
  301. package/src/plugins/presentMulmoScript/View.vue +1166 -0
  302. package/src/plugins/presentMulmoScript/definition.ts +95 -0
  303. package/src/plugins/presentMulmoScript/helpers.ts +162 -0
  304. package/src/plugins/presentMulmoScript/index.ts +40 -0
  305. package/src/plugins/scheduler/Preview.vue +67 -0
  306. package/src/plugins/scheduler/TasksTab.vue +205 -0
  307. package/src/plugins/scheduler/View.vue +565 -0
  308. package/src/plugins/scheduler/definition.ts +57 -0
  309. package/src/plugins/scheduler/index.ts +45 -0
  310. package/src/plugins/scheduler/viewModes.ts +26 -0
  311. package/src/plugins/spreadsheet/Preview.vue +29 -0
  312. package/src/plugins/spreadsheet/View.vue +997 -0
  313. package/src/plugins/spreadsheet/cellHighlights.ts +79 -0
  314. package/src/plugins/spreadsheet/definition.ts +121 -0
  315. package/src/plugins/spreadsheet/engine/calculator.ts +459 -0
  316. package/src/plugins/spreadsheet/engine/cellBuilder.ts +81 -0
  317. package/src/plugins/spreadsheet/engine/date-parser.ts +220 -0
  318. package/src/plugins/spreadsheet/engine/date-utils.ts +56 -0
  319. package/src/plugins/spreadsheet/engine/engine.ts +176 -0
  320. package/src/plugins/spreadsheet/engine/evaluator.ts +390 -0
  321. package/src/plugins/spreadsheet/engine/formatter.ts +172 -0
  322. package/src/plugins/spreadsheet/engine/formulaRefs.ts +101 -0
  323. package/src/plugins/spreadsheet/engine/functions/date.ts +299 -0
  324. package/src/plugins/spreadsheet/engine/functions/financial.ts +387 -0
  325. package/src/plugins/spreadsheet/engine/functions/index.ts +16 -0
  326. package/src/plugins/spreadsheet/engine/functions/logical.ts +262 -0
  327. package/src/plugins/spreadsheet/engine/functions/lookup.ts +400 -0
  328. package/src/plugins/spreadsheet/engine/functions/mathematical.ts +297 -0
  329. package/src/plugins/spreadsheet/engine/functions/statistical.ts +338 -0
  330. package/src/plugins/spreadsheet/engine/functions/text.ts +389 -0
  331. package/src/plugins/spreadsheet/engine/index.ts +27 -0
  332. package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +111 -0
  333. package/src/plugins/spreadsheet/engine/parser.ts +143 -0
  334. package/src/plugins/spreadsheet/engine/registry.ts +150 -0
  335. package/src/plugins/spreadsheet/engine/responseDecoder.ts +67 -0
  336. package/src/plugins/spreadsheet/engine/types.ts +64 -0
  337. package/src/plugins/spreadsheet/index.ts +36 -0
  338. package/src/plugins/textResponse/Preview.vue +94 -0
  339. package/src/plugins/textResponse/View.vue +503 -0
  340. package/src/plugins/textResponse/definition.ts +34 -0
  341. package/src/plugins/textResponse/index.ts +27 -0
  342. package/src/plugins/textResponse/plugin.ts +29 -0
  343. package/src/plugins/textResponse/samples.ts +97 -0
  344. package/src/plugins/textResponse/types.ts +11 -0
  345. package/src/plugins/todo/Preview.vue +63 -0
  346. package/src/plugins/todo/View.vue +364 -0
  347. package/src/plugins/todo/composables/useTodos.ts +177 -0
  348. package/src/plugins/todo/definition.ts +45 -0
  349. package/src/plugins/todo/index.ts +61 -0
  350. package/src/plugins/todo/labels.ts +163 -0
  351. package/src/plugins/todo/priority.ts +98 -0
  352. package/src/plugins/todo/viewModes.ts +19 -0
  353. package/src/plugins/ui-image/ImagePreview.vue +23 -0
  354. package/src/plugins/ui-image/ImageView.vue +34 -0
  355. package/src/plugins/ui-image/index.ts +3 -0
  356. package/src/plugins/ui-image/types.ts +4 -0
  357. package/src/plugins/wiki/Preview.vue +65 -0
  358. package/src/plugins/wiki/View.vue +342 -0
  359. package/src/plugins/wiki/definition.ts +25 -0
  360. package/src/plugins/wiki/helpers.ts +59 -0
  361. package/src/plugins/wiki/index.ts +52 -0
  362. package/src/router/guards.ts +61 -0
  363. package/src/router/index.ts +50 -0
  364. package/src/tools/index.ts +52 -0
  365. package/src/tools/types.ts +27 -0
  366. package/src/types/events.ts +16 -0
  367. package/src/types/fileTree.ts +13 -0
  368. package/src/types/notification.ts +67 -0
  369. package/src/types/session.ts +116 -0
  370. package/src/types/sse.ts +90 -0
  371. package/src/types/toolCallHistory.ts +13 -0
  372. package/src/utils/agent/eventDispatch.ts +74 -0
  373. package/src/utils/agent/request.ts +55 -0
  374. package/src/utils/agent/toolCalls.ts +62 -0
  375. package/src/utils/api.ts +218 -0
  376. package/src/utils/canvas/viewMode.ts +46 -0
  377. package/src/utils/dom/authTokenMeta.ts +20 -0
  378. package/src/utils/dom/clickOutside.ts +11 -0
  379. package/src/utils/dom/externalLink.ts +57 -0
  380. package/src/utils/dom/scrollable.ts +24 -0
  381. package/src/utils/errors.ts +11 -0
  382. package/src/utils/files/expandedDirs.ts +25 -0
  383. package/src/utils/files/filename.ts +12 -0
  384. package/src/utils/files/sortChildren.ts +20 -0
  385. package/src/utils/filesPreview/schedulerPreview.ts +38 -0
  386. package/src/utils/filesPreview/todoPreview.ts +40 -0
  387. package/src/utils/format/date.ts +85 -0
  388. package/src/utils/format/frontmatter.ts +80 -0
  389. package/src/utils/format/jsonSyntax.ts +109 -0
  390. package/src/utils/html/previewCsp.ts +65 -0
  391. package/src/utils/image/resolve.ts +8 -0
  392. package/src/utils/image/rewriteMarkdownImageRefs.ts +182 -0
  393. package/src/utils/markdown/extractFirstH1.ts +39 -0
  394. package/src/utils/notification/dispatch.ts +22 -0
  395. package/src/utils/path/relativeLink.ts +130 -0
  396. package/src/utils/role/icon.ts +20 -0
  397. package/src/utils/role/merge.ts +10 -0
  398. package/src/utils/role/plugins.ts +12 -0
  399. package/src/utils/session/mergeSessions.ts +103 -0
  400. package/src/utils/session/seedRoleDefault.ts +35 -0
  401. package/src/utils/session/sessionEntries.ts +121 -0
  402. package/src/utils/session/sessionFactory.ts +22 -0
  403. package/src/utils/session/sessionHelpers.ts +99 -0
  404. package/src/utils/tools/dedup.ts +17 -0
  405. package/src/utils/tools/mcp.ts +33 -0
  406. package/src/utils/tools/pendingCalls.ts +16 -0
  407. package/src/utils/tools/result.ts +40 -0
  408. package/src/utils/types.ts +44 -0
@@ -0,0 +1,125 @@
1
+ // Scan the user's ~/.claude/skills/ and the workspace-level
2
+ // <workspace>/.claude/skills/ for SKILL.md files, parse them, and
3
+ // produce a deduped list. Project-level skills override user-level
4
+ // skills with the same name (mirrors settings precedence in #197).
5
+
6
+ import { readdir, readFile, stat } from "node:fs/promises";
7
+ import { join, resolve } from "node:path";
8
+ import { log } from "../../system/logger/index.js";
9
+ import { parseSkillFrontmatter } from "./parser.js";
10
+ import { SKILL_FILE, USER_SKILLS_DIR, projectSkillsDir } from "./paths.js";
11
+ import type { Skill, SkillSource } from "./types.js";
12
+
13
+ // One directory entry → a parsed Skill, or null when the entry is
14
+ // not a valid skill (no SKILL.md, malformed frontmatter, I/O error).
15
+ // Errors are logged at warn, not thrown — a single broken skill
16
+ // shouldn't take down the whole list.
17
+ async function readSkillDir(skillDir: string, name: string, source: SkillSource): Promise<Skill | null> {
18
+ const skillPath = join(skillDir, SKILL_FILE);
19
+ try {
20
+ // Follow symlinks: stat rather than lstat so a user's
21
+ // `pptx@ → ~/ss/llm/skills/pptx/` reads through the link.
22
+ const fileStat = await stat(skillPath);
23
+ if (!fileStat.isFile()) return null;
24
+ const raw = await readFile(skillPath, "utf-8");
25
+ const parsed = parseSkillFrontmatter(raw);
26
+ if (!parsed) {
27
+ log.warn("skills", "SKILL.md has no usable frontmatter, skipping", {
28
+ name,
29
+ path: skillPath,
30
+ });
31
+ return null;
32
+ }
33
+ return {
34
+ name,
35
+ description: parsed.description,
36
+ body: parsed.body,
37
+ source,
38
+ path: skillPath,
39
+ };
40
+ } catch (err) {
41
+ // ENOENT = SKILL.md missing. Anything else is logged so a
42
+ // permissions issue is findable; we still treat the slot as
43
+ // "not a skill" rather than failing the whole list.
44
+ const error = err as { code?: string };
45
+ if (error.code !== "ENOENT") {
46
+ log.warn("skills", "failed to read SKILL.md, skipping", {
47
+ name,
48
+ path: skillPath,
49
+ error: String(err),
50
+ });
51
+ }
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Scan one skills root (either the user or project location) and
58
+ * return every valid Skill. The root itself is allowed to not exist
59
+ * — we just return an empty list (a workspace with no .claude/skills/
60
+ * is the common case).
61
+ */
62
+ export async function collectSkillsFromDir(root: string, source: SkillSource): Promise<Skill[]> {
63
+ let entries: string[];
64
+ try {
65
+ entries = await readdir(root);
66
+ } catch (err) {
67
+ const error = err as { code?: string };
68
+ if (error.code === "ENOENT") return [];
69
+ log.warn("skills", "failed to list skills dir, returning empty", {
70
+ root,
71
+ error: String(err),
72
+ });
73
+ return [];
74
+ }
75
+
76
+ const results: Skill[] = [];
77
+ for (const name of entries) {
78
+ // Skip hidden entries (.DS_Store, .gitkeep, etc.) up front.
79
+ if (name.startsWith(".")) continue;
80
+ const skillDir = resolve(root, name);
81
+ let dirStat;
82
+ try {
83
+ // stat follows symlinks — supports `ln -s target skills/name`.
84
+ dirStat = await stat(skillDir);
85
+ } catch {
86
+ continue;
87
+ }
88
+ if (!dirStat.isDirectory()) continue;
89
+ const skill = await readSkillDir(skillDir, name, source);
90
+ if (skill) results.push(skill);
91
+ }
92
+ // Stable alphabetical order for the UI.
93
+ results.sort((a, b) => a.name.localeCompare(b.name));
94
+ return results;
95
+ }
96
+
97
+ export interface DiscoverSkillsOptions {
98
+ /** Absolute path to the user's ~/.claude/skills/. Overridable for
99
+ * tests that point at mkdtempSync trees. */
100
+ userDir?: string;
101
+ /** Workspace root; project-level skills live at
102
+ * `<workspaceRoot>/.claude/skills/`. Passing undefined skips the
103
+ * project scope entirely. */
104
+ workspaceRoot?: string;
105
+ }
106
+
107
+ /**
108
+ * Discover every skill available to this workspace. Project-level
109
+ * skills (under `<workspace>/.claude/skills/`) override user-level
110
+ * skills of the same name.
111
+ */
112
+ export async function discoverSkills(opts: DiscoverSkillsOptions = {}): Promise<Skill[]> {
113
+ const userDir = opts.userDir ?? USER_SKILLS_DIR;
114
+ const userSkills = await collectSkillsFromDir(userDir, "user");
115
+
116
+ const projectSkills = opts.workspaceRoot ? await collectSkillsFromDir(projectSkillsDir(opts.workspaceRoot), "project") : [];
117
+
118
+ // Project overrides user on name collision. Merge by building a
119
+ // map keyed by name, starting with user, overwriting with project.
120
+ const merged = new Map<string, Skill>();
121
+ for (const s of userSkills) merged.set(s.name, s);
122
+ for (const s of projectSkills) merged.set(s.name, s);
123
+
124
+ return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
125
+ }
@@ -0,0 +1,10 @@
1
+ // Public API for the skills module. Discovery (read-only) is phase
2
+ // 0; save + delete (project scope only) is phase 1.
3
+
4
+ export { discoverSkills, collectSkillsFromDir } from "./discovery.js";
5
+ export { parseSkillFrontmatter } from "./parser.js";
6
+ export { saveProjectSkill, updateProjectSkill, deleteProjectSkill } from "./writer.js";
7
+ export type { SaveResult, UpdateResult, DeleteResult } from "./writer.js";
8
+ export { isValidSlug } from "../../utils/slug.js";
9
+ export { projectSkillsDir, projectSkillPath, projectSkillDir } from "./paths.js";
10
+ export type { Skill, SkillSource, SkillSummary } from "./types.js";
@@ -0,0 +1,144 @@
1
+ // Pure SKILL.md parser. Given the raw file content, return the
2
+ // `description` (from YAML frontmatter) + body, plus optional
3
+ // `schedule` and `roleId` for auto-scheduling (#357 Phase 2).
4
+ //
5
+ // Minimal YAML: we only care about a few keys, so rather than
6
+ // pulling in a YAML parser we do line-by-line extraction.
7
+
8
+ import { TIME_UNIT_MS, ONE_SECOND_MS } from "../../utils/time.js";
9
+ import { SCHEDULE_TYPES } from "@receptron/task-scheduler";
10
+
11
+ export interface SkillSchedule {
12
+ /** "daily HH:MM" or "interval Ns/Nm/Nh" */
13
+ raw: string;
14
+ /** Parsed into task-manager-compatible shape */
15
+ parsed: { type: typeof SCHEDULE_TYPES.daily; time: string } | { type: typeof SCHEDULE_TYPES.interval; intervalMs: number } | null;
16
+ }
17
+
18
+ export interface ParsedSkill {
19
+ description: string;
20
+ body: string;
21
+ /** If present, this skill should be auto-scheduled */
22
+ schedule?: SkillSchedule;
23
+ /** Role to use when running the scheduled skill (default: "general") */
24
+ roleId?: string;
25
+ }
26
+
27
+ // Match a YAML scalar value on a single line:
28
+ // description: Enable CI for a repository
29
+ // description: "Quoted with colons: inside"
30
+ // Leading/trailing whitespace trimmed. Quoted values have their
31
+ // outer quotes stripped but inner JSON-style escapes are NOT
32
+ // reversed — SKILL.md descriptions in the wild are plain text.
33
+ function parseScalar(raw: string): string {
34
+ const trimmed = raw.trim();
35
+ if (trimmed.length === 0) return "";
36
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
37
+ return trimmed.slice(1, -1);
38
+ }
39
+ return trimmed;
40
+ }
41
+
42
+ /**
43
+ * Parse schedule value from frontmatter.
44
+ * Supported formats:
45
+ * "daily HH:MM" → { type: "daily", time: "HH:MM" }
46
+ * "interval 30m" → { type: "interval", intervalMs: 1800000 }
47
+ * "interval 2h" → { type: "interval", intervalMs: 7200000 }
48
+ * "interval 300s" → { type: "interval", intervalMs: 300000 }
49
+ */
50
+ // Minimum interval to prevent accidental runaway scheduling.
51
+ const MIN_INTERVAL_MS = 10 * ONE_SECOND_MS;
52
+
53
+ function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
54
+ const trimmed = raw.trim();
55
+
56
+ // daily HH:MM — validate range: HH 00-23, MM 00-59
57
+ const dailyMatch = trimmed.match(/^daily\s+(\d{2}):(\d{2})$/);
58
+ if (dailyMatch) {
59
+ const hh = Number(dailyMatch[1]);
60
+ const mm = Number(dailyMatch[2]);
61
+ if (hh > 23 || mm > 59) return null;
62
+ return {
63
+ type: SCHEDULE_TYPES.daily,
64
+ time: `${dailyMatch[1]}:${dailyMatch[2]}`,
65
+ };
66
+ }
67
+
68
+ // interval Ns / Nm / Nh — must be >= MIN_INTERVAL_MS
69
+ const intervalMatch = trimmed.match(/^interval\s+(\d+)([smh])$/);
70
+ if (intervalMatch) {
71
+ const value = Number(intervalMatch[1]);
72
+ const unit = intervalMatch[2];
73
+ const ms = TIME_UNIT_MS[unit];
74
+ if (!ms) return null;
75
+ const intervalMs = value * ms;
76
+ if (intervalMs < MIN_INTERVAL_MS) return null;
77
+ return { type: SCHEDULE_TYPES.interval, intervalMs };
78
+ }
79
+
80
+ return null;
81
+ }
82
+
83
+ /**
84
+ * Parse a SKILL.md file. Returns null when:
85
+ * - the file has no frontmatter (no leading `---` fence)
86
+ * - the frontmatter is unterminated
87
+ * - there is no `description:` key
88
+ *
89
+ * An empty body is allowed (the skill may be just metadata for now).
90
+ */
91
+ // Extract key-value pairs from YAML frontmatter lines. Returns a
92
+ // map of key → scalar value. Keeps parseSkillFrontmatter under the
93
+ // cognitive-complexity threshold.
94
+ function extractFrontmatterFields(lines: string[], startIdx: number, endIdx: number): Map<string, string> {
95
+ const fields = new Map<string, string>();
96
+ for (let i = startIdx; i < endIdx; i++) {
97
+ const line = lines[i];
98
+ const colonIdx = line.indexOf(":");
99
+ if (colonIdx === -1) continue;
100
+ const key = line.slice(0, colonIdx).trim();
101
+ const value = parseScalar(line.slice(colonIdx + 1));
102
+ fields.set(key, value);
103
+ }
104
+ return fields;
105
+ }
106
+
107
+ export function parseSkillFrontmatter(raw: string): ParsedSkill | null {
108
+ const lines = raw.split(/\r?\n/);
109
+ if (lines.length === 0 || lines[0].trim() !== "---") return null;
110
+
111
+ let closeIdx = -1;
112
+ for (let i = 1; i < lines.length; i++) {
113
+ if (lines[i].trim() === "---") {
114
+ closeIdx = i;
115
+ break;
116
+ }
117
+ }
118
+ if (closeIdx === -1) return null;
119
+
120
+ const fields = extractFrontmatterFields(lines, 1, closeIdx);
121
+ const description = fields.get("description") ?? null;
122
+ if (description === null) return null;
123
+
124
+ const scheduleRaw = fields.get("schedule") ?? null;
125
+ const roleId = fields.get("roleId") ?? null;
126
+
127
+ // Body starts after the closing fence. Trim leading blank lines so
128
+ // the UI doesn't render an awkward gap above the first heading.
129
+ const body = lines
130
+ .slice(closeIdx + 1)
131
+ .join("\n")
132
+ .replace(/^(?:\s*\n)+/, "")
133
+ .trimEnd();
134
+
135
+ const result: ParsedSkill = { description, body };
136
+ if (scheduleRaw) {
137
+ result.schedule = {
138
+ raw: scheduleRaw,
139
+ parsed: parseScheduleValue(scheduleRaw),
140
+ };
141
+ }
142
+ if (roleId) result.roleId = roleId;
143
+ return result;
144
+ }
@@ -0,0 +1,41 @@
1
+ // Path helpers and slug validation for the skills module.
2
+ //
3
+ // Skills live in two scopes:
4
+ // - user: ~/.claude/skills/<slug>/SKILL.md (read-only from MulmoClaude)
5
+ // - project: <workspaceRoot>/.claude/skills/<slug>/SKILL.md (MulmoClaude can CRUD)
6
+ //
7
+ // The slug doubles as a filename and appears in Claude CLI slash
8
+ // commands (`/<slug>`), so it has to be strict: no uppercase, no
9
+ // spaces, no path separators, no leading/trailing hyphens, 1-64
10
+ // chars. Same rule as `server/sources/paths.ts#isValidSlug` — keep
11
+ // them in sync manually.
12
+
13
+ import { homedir } from "node:os";
14
+ import { join } from "node:path";
15
+
16
+ export const SKILL_FILE = "SKILL.md";
17
+
18
+ /** `~/.claude/skills/` — user scope, read-only from MulmoClaude. */
19
+ export const USER_SKILLS_DIR = join(homedir(), ".claude", "skills");
20
+
21
+ /** `<workspaceRoot>/.claude/skills/` — project scope, writable. */
22
+ export function projectSkillsDir(workspaceRoot: string): string {
23
+ return join(workspaceRoot, ".claude", "skills");
24
+ }
25
+
26
+ /** Full path to the project-scope SKILL.md for a given slug. */
27
+ export function projectSkillPath(workspaceRoot: string, slug: string): string {
28
+ return join(projectSkillsDir(workspaceRoot), slug, SKILL_FILE);
29
+ }
30
+
31
+ /** Directory holding a project skill (one level above SKILL.md). */
32
+ export function projectSkillDir(workspaceRoot: string, slug: string): string {
33
+ return join(projectSkillsDir(workspaceRoot), slug);
34
+ }
35
+
36
+ /**
37
+ * Strict slug validator. Rejects anything that could surprise the
38
+ * filesystem, the Claude CLI slash-command resolver, or the URL
39
+ * parser. Single source of truth for `save` / `delete` input.
40
+ */
41
+ // isValidSlug moved to server/utils/slug.ts — import from there.
@@ -0,0 +1,149 @@
1
+ // Skill scheduling (#357 Phase 2). Scans all discovered skills for
2
+ // `schedule:` frontmatter and registers matching ones with the
3
+ // task-manager. Each scheduled skill fires `startChat()` with the
4
+ // skill body as the message and the skill's `roleId` (or "general").
5
+ //
6
+ // `refreshScheduledSkills()` can be called at any time to re-scan
7
+ // and update registrations (e.g. after a skill is saved/deleted
8
+ // via the API). It unregisters stale tasks and registers new ones.
9
+
10
+ import { discoverSkills } from "./discovery.js";
11
+ import type { Skill } from "./types.js";
12
+ import type { ITaskManager, TaskSchedule } from "../../events/task-manager/index.js";
13
+ import { parseSkillFrontmatter } from "./parser.js";
14
+ import { log } from "../../system/logger/index.js";
15
+ import { readFileSync } from "fs";
16
+ import { DEFAULT_ROLE_ID } from "../../../src/config/roles.js";
17
+ import { SESSION_ORIGINS, type SessionOrigin } from "../../../src/types/session.js";
18
+
19
+ interface SkillScheduleInfo {
20
+ schedule: TaskSchedule;
21
+ roleId: string;
22
+ }
23
+
24
+ interface StartChatResult {
25
+ kind: string;
26
+ error?: string;
27
+ status?: number;
28
+ }
29
+
30
+ export interface SkillSchedulerDeps {
31
+ taskManager: ITaskManager;
32
+ workspaceRoot: string;
33
+ startChat: (params: { message: string; roleId: string; chatSessionId: string; origin?: SessionOrigin }) => Promise<StartChatResult>;
34
+ }
35
+
36
+ const SKILL_TASK_PREFIX = "skill.";
37
+
38
+ // Track registered skill task IDs so refresh can unregister stale ones.
39
+ let registeredTaskIds = new Set<string>();
40
+ let cachedDeps: SkillSchedulerDeps | null = null;
41
+
42
+ // Mutex: serialize refresh calls so concurrent save/update/delete
43
+ // API calls don't interleave register/unregister and corrupt state.
44
+ let refreshMutex: Promise<number> = Promise.resolve(0);
45
+
46
+ export async function registerScheduledSkills(deps: SkillSchedulerDeps): Promise<number> {
47
+ cachedDeps = deps;
48
+ return serializedRefresh(deps);
49
+ }
50
+
51
+ /**
52
+ * Re-scan skills and update task-manager registrations. Safe to call
53
+ * after a skill is saved, updated, or deleted — removes stale tasks
54
+ * and adds new ones without a server restart. Serialized: concurrent
55
+ * calls queue behind the in-flight one.
56
+ */
57
+ export async function refreshScheduledSkills(): Promise<number> {
58
+ if (!cachedDeps) {
59
+ log.warn("skills", "refreshScheduledSkills called before initial register");
60
+ return 0;
61
+ }
62
+ return serializedRefresh(cachedDeps);
63
+ }
64
+
65
+ function serializedRefresh(deps: SkillSchedulerDeps): Promise<number> {
66
+ refreshMutex = refreshMutex.then(
67
+ () => doRegister(deps),
68
+ () => doRegister(deps),
69
+ );
70
+ return refreshMutex;
71
+ }
72
+
73
+ async function doRegister(deps: SkillSchedulerDeps): Promise<number> {
74
+ const { taskManager, workspaceRoot, startChat } = deps;
75
+
76
+ // Unregister all previously registered skill tasks
77
+ for (const taskId of registeredTaskIds) {
78
+ taskManager.removeTask(taskId);
79
+ }
80
+ const previousCount = registeredTaskIds.size;
81
+ registeredTaskIds = new Set<string>();
82
+
83
+ const skills = await discoverSkills({ workspaceRoot });
84
+ let registered = 0;
85
+
86
+ for (const skill of skills) {
87
+ const info = readSkillScheduleInfo(skill);
88
+ if (!info) continue;
89
+
90
+ const { schedule, roleId } = info;
91
+ const taskId = `${SKILL_TASK_PREFIX}${skill.name}`;
92
+
93
+ taskManager.registerTask({
94
+ id: taskId,
95
+ description: `Scheduled skill: ${skill.name} — ${skill.description}`,
96
+ schedule,
97
+ run: async () => {
98
+ const chatSessionId = crypto.randomUUID();
99
+ log.info("skills", "running scheduled skill", {
100
+ name: skill.name,
101
+ roleId,
102
+ chatSessionId,
103
+ });
104
+ const result = await startChat({
105
+ message: `/${skill.name}`,
106
+ roleId,
107
+ chatSessionId,
108
+ origin: SESSION_ORIGINS.skill,
109
+ });
110
+ if (result.kind === "error") {
111
+ throw new Error(`scheduled skill failed: ${result.error ?? "unknown"}`);
112
+ }
113
+ log.info("skills", "scheduled skill completed", {
114
+ name: skill.name,
115
+ kind: result.kind,
116
+ });
117
+ },
118
+ });
119
+
120
+ registeredTaskIds.add(taskId);
121
+ registered++;
122
+ }
123
+
124
+ if (previousCount > 0 || registered > 0) {
125
+ log.info("skills", "skill schedules refreshed", {
126
+ previous: previousCount,
127
+ current: registered,
128
+ });
129
+ }
130
+
131
+ return registered;
132
+ }
133
+
134
+ // Read schedule + roleId in one file read (avoid reading the same
135
+ // SKILL.md twice). Returns null if no schedule is configured.
136
+ function readSkillScheduleInfo(skill: Skill): SkillScheduleInfo | null {
137
+ try {
138
+ const raw = readFileSync(skill.path, "utf-8");
139
+ const parsed = parseSkillFrontmatter(raw);
140
+ const s = parsed?.schedule?.parsed;
141
+ if (!s) return null;
142
+ return {
143
+ schedule: s,
144
+ roleId: parsed?.roleId ?? DEFAULT_ROLE_ID,
145
+ };
146
+ } catch {
147
+ return null;
148
+ }
149
+ }
@@ -0,0 +1,30 @@
1
+ // Shared Skill type. A Skill is the parsed content of one
2
+ // ~/.claude/skills/<name>/SKILL.md file (or the project-level
3
+ // equivalent under <workspace>/.claude/skills/<name>/SKILL.md).
4
+ //
5
+ // Phase 0 is read-only: the server discovers and exposes skills
6
+ // but never writes to them. Edits happen through the user's file
7
+ // system or other tooling (e.g. their own skills repo).
8
+
9
+ export type SkillSource = "user" | "project";
10
+
11
+ export interface Skill {
12
+ /** Directory name under skills/, e.g. "ci_enable". */
13
+ name: string;
14
+ /** The `description` field from YAML frontmatter. Used as the
15
+ * one-line summary in the UI list. */
16
+ description: string;
17
+ /** Markdown body after the frontmatter. Passed to Claude when the
18
+ * user clicks "Run". */
19
+ body: string;
20
+ /** Which scope this skill was discovered in. Project overrides user
21
+ * when names collide. */
22
+ source: SkillSource;
23
+ /** Absolute path to the SKILL.md file (post-symlink resolve). Used
24
+ * to surface the origin in the detail view. */
25
+ path: string;
26
+ }
27
+
28
+ /** Lightweight summary for list endpoints — omits the full body so
29
+ * GET /api/skills stays small even with many skills. */
30
+ export type SkillSummary = Pick<Skill, "name" | "description" | "source">;