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,525 @@
1
+ <template>
2
+ <div class="h-full bg-white flex flex-col">
3
+ <div class="flex items-center justify-between px-6 py-4 border-b border-gray-100">
4
+ <h2 class="text-lg font-semibold text-gray-800">Custom Roles</h2>
5
+ <div class="flex items-center gap-3">
6
+ <span class="text-sm text-gray-500">
7
+ {{ customRoles.length }}
8
+ role{{ customRoles.length !== 1 ? "s" : "" }}
9
+ </span>
10
+ <button v-if="!creating" data-testid="role-add-btn" class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" @click="startCreate">
11
+ + Add
12
+ </button>
13
+ </div>
14
+ </div>
15
+
16
+ <div class="flex-1 overflow-y-auto">
17
+ <!-- New role creation panel -->
18
+ <div v-if="creating" class="m-4 border border-blue-300 bg-blue-50 rounded-lg p-4 space-y-3">
19
+ <div class="text-sm font-semibold text-gray-700">Create new role</div>
20
+
21
+ <!-- ID + Name + Icon row -->
22
+ <div class="flex gap-3">
23
+ <div class="w-40">
24
+ <label class="block text-xs font-medium text-gray-600 mb-1">ID</label>
25
+ <input
26
+ v-model="newForm.id"
27
+ type="text"
28
+ placeholder="unique-id"
29
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
30
+ />
31
+ </div>
32
+ <div class="flex-1">
33
+ <label class="block text-xs font-medium text-gray-600 mb-1">Name</label>
34
+ <input
35
+ v-model="newForm.name"
36
+ type="text"
37
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
38
+ />
39
+ </div>
40
+ <div class="w-32">
41
+ <label class="block text-xs font-medium text-gray-600 mb-1">
42
+ Icon
43
+ <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">?</a>
44
+ </label>
45
+ <input
46
+ v-model="newForm.icon"
47
+ type="text"
48
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
49
+ />
50
+ </div>
51
+ </div>
52
+
53
+ <!-- Prompt -->
54
+ <div>
55
+ <label class="block text-xs font-medium text-gray-600 mb-1">Prompt</label>
56
+ <textarea
57
+ v-model="newForm.prompt"
58
+ rows="6"
59
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono resize-y focus:outline-none focus:border-blue-400"
60
+ spellcheck="false"
61
+ />
62
+ </div>
63
+
64
+ <!-- Plugins -->
65
+ <div>
66
+ <label class="block text-xs font-medium text-gray-600 mb-2">Plugins</label>
67
+ <div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
68
+ <label
69
+ v-for="plugin in availablePlugins"
70
+ :key="plugin.name"
71
+ class="flex items-center gap-2 text-sm cursor-pointer"
72
+ :class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
73
+ :title="plugin.enabled ? '' : `Requires ${plugin.requiredEnv.join(', ')} in .env`"
74
+ >
75
+ <input
76
+ v-model="newForm.selectedPlugins"
77
+ type="checkbox"
78
+ :value="plugin.name"
79
+ :disabled="!plugin.enabled"
80
+ class="cursor-pointer disabled:cursor-not-allowed"
81
+ />
82
+ {{ plugin.name }}
83
+ <span v-if="!plugin.enabled" class="text-xs text-gray-400">(missing {{ plugin.requiredEnv.join(", ") }})</span>
84
+ </label>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Starter queries -->
89
+ <div>
90
+ <label class="block text-xs font-medium text-gray-600 mb-1">
91
+ Starter queries
92
+ <span class="text-gray-400 font-normal">(one per line)</span>
93
+ </label>
94
+ <textarea
95
+ v-model="newForm.queriesText"
96
+ rows="3"
97
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
98
+ />
99
+ </div>
100
+
101
+ <!-- Buttons -->
102
+ <div class="flex gap-2 pt-1">
103
+ <button
104
+ class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
105
+ :disabled="saving || !!newFormError"
106
+ :title="newFormError ?? ''"
107
+ @click="saveNew"
108
+ >
109
+ {{ saving ? "Creating…" : "Create" }}
110
+ </button>
111
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="cancelCreate">Cancel</button>
112
+ </div>
113
+ <div v-if="newFormError" class="text-xs text-gray-500" data-testid="role-form-hint">
114
+ {{ newFormError }}
115
+ </div>
116
+ <div v-if="createError" class="text-xs text-red-500">
117
+ {{ createError }}
118
+ </div>
119
+ </div>
120
+
121
+ <div v-if="!creating && customRoles.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">
122
+ No custom roles yet. Click "+ Add" or ask Claude to create one.
123
+ </div>
124
+
125
+ <ul v-if="customRoles.length > 0" class="p-4 space-y-2">
126
+ <li v-for="role in customRoles" :key="role.id" class="rounded-lg border" :class="selectedId === role.id ? 'border-blue-400' : 'border-gray-200'">
127
+ <!-- Role header row -->
128
+ <div
129
+ class="flex items-center gap-3 p-3 cursor-pointer hover:bg-gray-50 rounded-lg"
130
+ :class="selectedId === role.id ? 'rounded-b-none' : ''"
131
+ @click="selectRole(role)"
132
+ >
133
+ <span class="material-icons text-gray-500">{{ role.icon }}</span>
134
+ <div class="flex-1 min-w-0">
135
+ <div class="font-medium text-sm text-gray-800">
136
+ {{ role.name }}
137
+ <span class="ml-1 text-xs font-mono text-gray-400">({{ role.id }})</span>
138
+ </div>
139
+ <div class="text-xs text-gray-400 truncate">
140
+ {{ role.availablePlugins.join(", ") }}
141
+ </div>
142
+ </div>
143
+ <span class="material-icons text-gray-400 text-sm" :title="selectedId === role.id ? 'Collapse' : 'Expand'">
144
+ {{ selectedId === role.id ? "expand_less" : "expand_more" }}
145
+ </span>
146
+ </div>
147
+
148
+ <!-- Inline editor -->
149
+ <div v-if="selectedId === role.id" class="border-t border-blue-100 bg-blue-50 p-4 space-y-3 rounded-b-lg">
150
+ <!-- ID + Name + Icon row -->
151
+ <div class="flex gap-3">
152
+ <div class="w-40">
153
+ <label class="block text-xs font-medium text-gray-600 mb-1">ID</label>
154
+ <input
155
+ v-model="editForm.id"
156
+ type="text"
157
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
158
+ />
159
+ </div>
160
+ <div class="flex-1">
161
+ <label class="block text-xs font-medium text-gray-600 mb-1">Name</label>
162
+ <input
163
+ v-model="editForm.name"
164
+ type="text"
165
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
166
+ @keydown.enter="saveEdit(role.id)"
167
+ />
168
+ </div>
169
+ <div class="w-32">
170
+ <label class="block text-xs font-medium text-gray-600 mb-1">
171
+ Icon
172
+ <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">?</a>
173
+ </label>
174
+ <input
175
+ v-model="editForm.icon"
176
+ type="text"
177
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
178
+ />
179
+ </div>
180
+ </div>
181
+
182
+ <!-- Prompt -->
183
+ <div>
184
+ <label class="block text-xs font-medium text-gray-600 mb-1">Prompt</label>
185
+ <textarea
186
+ v-model="editForm.prompt"
187
+ rows="6"
188
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono resize-y focus:outline-none focus:border-blue-400"
189
+ spellcheck="false"
190
+ />
191
+ </div>
192
+
193
+ <!-- Plugins -->
194
+ <div>
195
+ <label class="block text-xs font-medium text-gray-600 mb-2">Plugins</label>
196
+ <div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
197
+ <label
198
+ v-for="plugin in availablePlugins"
199
+ :key="plugin.name"
200
+ class="flex items-center gap-2 text-sm cursor-pointer"
201
+ :class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
202
+ :title="plugin.enabled ? '' : `Requires ${plugin.requiredEnv.join(', ')} in .env`"
203
+ >
204
+ <input
205
+ v-model="editForm.selectedPlugins"
206
+ type="checkbox"
207
+ :value="plugin.name"
208
+ :disabled="!plugin.enabled"
209
+ class="cursor-pointer disabled:cursor-not-allowed"
210
+ />
211
+ {{ plugin.name }}
212
+ <span v-if="!plugin.enabled" class="text-xs text-gray-400">(missing {{ plugin.requiredEnv.join(", ") }})</span>
213
+ </label>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- Starter queries -->
218
+ <div>
219
+ <label class="block text-xs font-medium text-gray-600 mb-1">
220
+ Starter queries
221
+ <span class="text-gray-400 font-normal">(one per line)</span>
222
+ </label>
223
+ <textarea
224
+ v-model="editForm.queriesText"
225
+ rows="3"
226
+ class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
227
+ />
228
+ </div>
229
+
230
+ <!-- Buttons -->
231
+ <div class="flex items-center justify-between pt-1">
232
+ <div class="flex gap-2">
233
+ <button
234
+ class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
235
+ :disabled="saving || !!editFormError"
236
+ :title="editFormError ?? ''"
237
+ @click="saveEdit(role.id)"
238
+ >
239
+ {{ saving ? "Updating…" : "Update" }}
240
+ </button>
241
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="selectedId = null">Cancel</button>
242
+ </div>
243
+ <button
244
+ class="px-3 py-1.5 text-sm rounded border border-red-200 text-red-500 hover:bg-red-50 disabled:opacity-50"
245
+ :disabled="saving"
246
+ @click="deleteRole(role.id)"
247
+ >
248
+ Delete
249
+ </button>
250
+ </div>
251
+ <div v-if="editFormError" class="text-xs text-gray-500">
252
+ {{ editFormError }}
253
+ </div>
254
+ <div v-if="saveError" class="text-xs text-red-500">
255
+ {{ saveError }}
256
+ </div>
257
+ </div>
258
+ </li>
259
+ </ul>
260
+ </div>
261
+ </div>
262
+ </template>
263
+
264
+ <script setup lang="ts">
265
+ import { ref, computed, watch, onMounted } from "vue";
266
+ import { useFreshPluginData } from "../../composables/useFreshPluginData";
267
+ import { useAppApi } from "../../composables/useAppApi";
268
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
269
+ import type { CustomRole, ManageRolesData } from "./index";
270
+ import { getAllPluginNames } from "../../tools/index";
271
+ import { apiGet, apiPost } from "../../utils/api";
272
+ import { API_ROUTES } from "../../config/apiRoutes";
273
+
274
+ interface PluginEntry {
275
+ name: string;
276
+ enabled: boolean;
277
+ requiredEnv: string[];
278
+ }
279
+
280
+ // Plugins the user can assign — exclude internal/auto-managed ones
281
+ const EXCLUDED = new Set(["text-response", "switchRole"]);
282
+ const guiPlugins: PluginEntry[] = getAllPluginNames()
283
+ .filter((name) => !EXCLUDED.has(name))
284
+ .map((name) => ({ name, enabled: true, requiredEnv: [] }));
285
+
286
+ const availablePlugins = ref<PluginEntry[]>(guiPlugins);
287
+
288
+ onMounted(async () => {
289
+ const result = await apiGet<PluginEntry[]>(API_ROUTES.mcpTools.list);
290
+ if (result.ok) {
291
+ availablePlugins.value = [...guiPlugins, ...result.data];
292
+ }
293
+ // Non-critical: MCP tools enrich the plugin palette for role editing
294
+ // but the view works fine with GUI plugins alone. No error banner needed.
295
+ });
296
+
297
+ const props = defineProps<{
298
+ selectedResult?: ToolResultComplete<ManageRolesData>;
299
+ }>();
300
+ const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
301
+
302
+ const appApi = useAppApi();
303
+
304
+ const customRoles = ref<CustomRole[]>(props.selectedResult?.data?.customRoles ?? []);
305
+
306
+ const { refresh: refreshCustomRoles } = useFreshPluginData<CustomRole[]>({
307
+ endpoint: () => API_ROUTES.roles.list,
308
+ extract: (json) => (Array.isArray(json) ? (json as CustomRole[]) : null),
309
+ apply: (data) => {
310
+ customRoles.value = data;
311
+ },
312
+ });
313
+
314
+ watch(
315
+ () => props.selectedResult?.uuid,
316
+ () => {
317
+ customRoles.value = props.selectedResult?.data?.customRoles ?? [];
318
+ void refreshCustomRoles();
319
+ },
320
+ );
321
+
322
+ // ── Selection & edit form ─────────────────────────────────────────────────────
323
+
324
+ const selectedId = ref<string | null>(null);
325
+ const saving = ref(false);
326
+ const saveError = ref("");
327
+
328
+ interface EditForm {
329
+ id: string;
330
+ name: string;
331
+ icon: string;
332
+ prompt: string;
333
+ selectedPlugins: string[];
334
+ queriesText: string;
335
+ }
336
+
337
+ const editForm = ref<EditForm>({
338
+ id: "",
339
+ name: "",
340
+ icon: "",
341
+ prompt: "",
342
+ selectedPlugins: [],
343
+ queriesText: "",
344
+ });
345
+
346
+ const creating = ref(false);
347
+ const createError = ref("");
348
+ const newForm = ref<EditForm>({
349
+ id: "",
350
+ name: "",
351
+ icon: "person",
352
+ prompt: "",
353
+ selectedPlugins: [],
354
+ queriesText: "",
355
+ });
356
+
357
+ function startCreate() {
358
+ selectedId.value = null;
359
+ createError.value = "";
360
+ newForm.value = {
361
+ id: "",
362
+ name: "",
363
+ icon: "person",
364
+ prompt: "",
365
+ selectedPlugins: [],
366
+ queriesText: "",
367
+ };
368
+ creating.value = true;
369
+ }
370
+
371
+ function cancelCreate() {
372
+ creating.value = false;
373
+ createError.value = "";
374
+ }
375
+
376
+ function selectRole(role: CustomRole) {
377
+ if (selectedId.value === role.id) {
378
+ selectedId.value = null;
379
+ return;
380
+ }
381
+ selectedId.value = role.id;
382
+ saveError.value = "";
383
+ editForm.value = {
384
+ id: role.id,
385
+ name: role.name,
386
+ icon: role.icon,
387
+ prompt: role.prompt,
388
+ selectedPlugins: role.availablePlugins.filter((plugin) => plugin !== "switchRole"),
389
+ queriesText: (role.queries ?? []).join("\n"),
390
+ };
391
+ }
392
+
393
+ // ── API ───────────────────────────────────────────────────────────────────────
394
+
395
+ interface ManageResult {
396
+ success?: boolean;
397
+ error?: string;
398
+ [key: string]: unknown;
399
+ }
400
+
401
+ async function callManage(body: Record<string, unknown>): Promise<ManageResult> {
402
+ const result = await apiPost<ManageResult>(API_ROUTES.roles.manage, body);
403
+ if (!result.ok) {
404
+ // Prefer the backend's error message (e.g. validation failure
405
+ // details). Fall back to a status code only when the server didn't
406
+ // give us anything useful.
407
+ return {
408
+ success: false,
409
+ error: result.status === 0 ? result.error || "Network error" : result.error || `Server error: ${result.status}`,
410
+ };
411
+ }
412
+ return result.data;
413
+ }
414
+
415
+ async function refreshList() {
416
+ const result = await callManage({ action: "list" });
417
+ if (result.success) {
418
+ const data = result as { data?: { customRoles?: CustomRole[] } };
419
+ customRoles.value = data.data?.customRoles ?? [];
420
+ if (props.selectedResult) {
421
+ emit("updateResult", {
422
+ ...props.selectedResult,
423
+ ...result,
424
+ uuid: props.selectedResult.uuid,
425
+ });
426
+ }
427
+ // Let App.vue know the dropdown needs to refresh.
428
+ await Promise.resolve(appApi.refreshRoles());
429
+ }
430
+ }
431
+
432
+ function validateRoleForm(form: EditForm, excludeId: string | null): string | null {
433
+ const trimmedId = form.id.trim();
434
+ const trimmedName = form.name.trim();
435
+ if (!trimmedId) return "ID is required.";
436
+ if (!/^[a-zA-Z0-9_-]+$/.test(trimmedId)) {
437
+ return "ID may only contain letters, numbers, '-' and '_'.";
438
+ }
439
+ if (!trimmedName) return "Name is required.";
440
+ if (customRoles.value.some((existing) => existing.id === trimmedId && existing.id !== excludeId)) {
441
+ return `A role with ID '${trimmedId}' already exists.`;
442
+ }
443
+ return null;
444
+ }
445
+
446
+ const newFormError = computed<string | null>(() => validateRoleForm(newForm.value, null));
447
+
448
+ const editFormError = computed<string | null>(() => validateRoleForm(editForm.value, selectedId.value));
449
+
450
+ function buildNewRole(): CustomRole {
451
+ return {
452
+ id: newForm.value.id.trim(),
453
+ name: newForm.value.name.trim(),
454
+ icon: newForm.value.icon.trim() || "person",
455
+ prompt: newForm.value.prompt,
456
+ availablePlugins: newForm.value.selectedPlugins,
457
+ queries: newForm.value.queriesText
458
+ .split("\n")
459
+ .map((line) => line.trim())
460
+ .filter(Boolean),
461
+ };
462
+ }
463
+
464
+ async function saveNew() {
465
+ if (newFormError.value) {
466
+ createError.value = newFormError.value;
467
+ return;
468
+ }
469
+ saving.value = true;
470
+ createError.value = "";
471
+ const result = await callManage({ action: "create", role: buildNewRole() });
472
+ if (result.success) {
473
+ creating.value = false;
474
+ await refreshList();
475
+ } else {
476
+ createError.value = result.error ?? "Create failed";
477
+ }
478
+ saving.value = false;
479
+ }
480
+
481
+ async function saveEdit(originalId: string) {
482
+ if (editFormError.value) {
483
+ saveError.value = editFormError.value;
484
+ return;
485
+ }
486
+ saving.value = true;
487
+ saveError.value = "";
488
+ const role: CustomRole = {
489
+ id: editForm.value.id.trim(),
490
+ name: editForm.value.name.trim(),
491
+ icon: editForm.value.icon.trim(),
492
+ prompt: editForm.value.prompt,
493
+ availablePlugins: editForm.value.selectedPlugins,
494
+ queries: editForm.value.queriesText
495
+ .split("\n")
496
+ .map((line) => line.trim())
497
+ .filter(Boolean),
498
+ };
499
+ const result = await callManage({
500
+ action: "update",
501
+ role,
502
+ oldRoleId: originalId,
503
+ });
504
+ if (result.success) {
505
+ selectedId.value = null;
506
+ await refreshList();
507
+ } else {
508
+ saveError.value = result.error ?? "Save failed";
509
+ }
510
+ saving.value = false;
511
+ }
512
+
513
+ async function deleteRole(roleId: string) {
514
+ saving.value = true;
515
+ saveError.value = "";
516
+ const result = await callManage({ action: "delete", roleId });
517
+ if (result.success) {
518
+ selectedId.value = null;
519
+ await refreshList();
520
+ } else {
521
+ saveError.value = result.error ?? "Delete failed";
522
+ }
523
+ saving.value = false;
524
+ }
525
+ </script>
@@ -0,0 +1,43 @@
1
+ import type { ToolDefinition } from "gui-chat-protocol";
2
+
3
+ export const TOOL_NAME = "manageRoles";
4
+
5
+ const toolDefinition: ToolDefinition = {
6
+ type: "function",
7
+ name: TOOL_NAME,
8
+ description: "Create, update, or delete a custom user role stored in ~/mulmoclaude/roles/. After success, the frontend role list refreshes automatically.",
9
+ parameters: {
10
+ type: "object",
11
+ properties: {
12
+ action: {
13
+ type: "string",
14
+ enum: ["create", "update", "delete", "list"],
15
+ description: "The action to perform. Use 'list' to display all custom roles in the canvas.",
16
+ },
17
+ role: {
18
+ type: "object",
19
+ description: "The full role definition (required for create/update)",
20
+ properties: {
21
+ id: { type: "string" },
22
+ name: { type: "string" },
23
+ icon: {
24
+ type: "string",
25
+ description:
26
+ "A Material Icons ligature name (lowercase with underscores, e.g. 'smart_toy', 'science', 'draw', 'translate'). Default to 'smart_toy' if unsure.",
27
+ },
28
+ prompt: { type: "string" },
29
+ availablePlugins: { type: "array", items: { type: "string" } },
30
+ queries: { type: "array", items: { type: "string" } },
31
+ },
32
+ required: ["id", "name", "icon", "prompt", "availablePlugins"],
33
+ },
34
+ roleId: {
35
+ type: "string",
36
+ description: "The role ID to delete (required for delete action)",
37
+ },
38
+ },
39
+ required: ["action"],
40
+ },
41
+ };
42
+
43
+ export default toolDefinition;
@@ -0,0 +1,47 @@
1
+ import type { ToolPlugin } from "../../tools/types";
2
+ import type { ToolResult } from "gui-chat-protocol";
3
+ import toolDefinition from "./definition";
4
+ import { TOOL_NAME } from "./definition";
5
+ import View from "./View.vue";
6
+ import Preview from "./Preview.vue";
7
+ import { apiPost } from "../../utils/api";
8
+ import { API_ROUTES } from "../../config/apiRoutes";
9
+
10
+ export interface CustomRole {
11
+ id: string;
12
+ name: string;
13
+ icon: string;
14
+ prompt: string;
15
+ availablePlugins: string[];
16
+ queries?: string[];
17
+ }
18
+
19
+ export interface ManageRolesData {
20
+ customRoles: CustomRole[];
21
+ }
22
+
23
+ const manageRolesPlugin: ToolPlugin = {
24
+ toolDefinition,
25
+ async execute(_context, args) {
26
+ const result = await apiPost<ToolResult<ManageRolesData>>(API_ROUTES.roles.manage, args);
27
+ if (!result.ok) {
28
+ return {
29
+ toolName: TOOL_NAME,
30
+ uuid: crypto.randomUUID(),
31
+ message: result.error,
32
+ };
33
+ }
34
+ return {
35
+ ...result.data,
36
+ toolName: TOOL_NAME,
37
+ uuid: crypto.randomUUID(),
38
+ };
39
+ },
40
+ isEnabled: () => true,
41
+ generatingMessage: "Managing roles…",
42
+ viewComponent: View,
43
+ previewComponent: Preview,
44
+ };
45
+
46
+ export default manageRolesPlugin;
47
+ export { TOOL_NAME };
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <div class="text-sm">
3
+ <div class="flex items-center gap-1 font-medium text-gray-700 mb-1">
4
+ <span class="material-icons" style="font-size: 14px">auto_awesome</span>
5
+ <span>{{ skills.length }} skill{{ skills.length !== 1 ? "s" : "" }}</span>
6
+ </div>
7
+ <div v-for="skill in skills.slice(0, 6)" :key="skill.name" class="text-xs text-gray-600 truncate">
8
+ {{ skill.name }}
9
+ </div>
10
+ <div v-if="skills.length > 6" class="text-xs text-gray-400 italic">+{{ skills.length - 6 }} more</div>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { computed } from "vue";
16
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
17
+ import type { ManageSkillsData } from "./index";
18
+
19
+ const props = defineProps<{ result: ToolResultComplete<ManageSkillsData> }>();
20
+ const skills = computed(() => props.result.data?.skills ?? []);
21
+ </script>