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,63 @@
1
+ import type { ToolDefinition } from "gui-chat-protocol";
2
+
3
+ export const TOOL_NAME = "manageSource";
4
+
5
+ const toolDefinition: ToolDefinition = {
6
+ type: "function",
7
+ name: TOOL_NAME,
8
+ description:
9
+ "Manage the user's information-source registry stored under <workspace>/sources/. Use this for RSS feeds, GitHub repos (releases or issues), and arXiv search queries. After every action the response carries the full registry so the UI can re-render. Always call action='list' to display the current registry in the canvas.",
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ action: {
14
+ type: "string",
15
+ enum: ["list", "register", "remove", "rebuild"],
16
+ description:
17
+ "What to do. 'list' = show the registry. 'register' = add a new source (requires title + url + fetcherKind, fetcherParams). 'remove' = delete by slug. 'rebuild' = run the daily aggregation pipeline now.",
18
+ },
19
+ slug: {
20
+ type: "string",
21
+ description:
22
+ "Source slug (lowercase letters/digits/hyphens). Required for action='remove'. Optional for action='register' (auto-derived from title when omitted).",
23
+ },
24
+ title: {
25
+ type: "string",
26
+ description: "Human-friendly source name. Required for action='register'.",
27
+ },
28
+ url: {
29
+ type: "string",
30
+ description:
31
+ "Canonical URL: feed URL for RSS, repo URL (https://github.com/<owner>/<name>) for github-*, or the arXiv search/listing URL for arxiv. Required for action='register'.",
32
+ },
33
+ fetcherKind: {
34
+ type: "string",
35
+ enum: ["rss", "github-releases", "github-issues", "arxiv"],
36
+ description:
37
+ "Which fetcher handles this source. Infer from the URL when registering: feed URLs → 'rss'; github.com/<owner>/<name> → 'github-releases' or 'github-issues' depending on user intent; arxiv.org → 'arxiv'.",
38
+ },
39
+ fetcherParams: {
40
+ type: "object",
41
+ description:
42
+ "Fetcher-specific params (flat object of string values). For 'rss' set { rss_url: '<feed URL>' }. For 'github-releases' / 'github-issues' set { github_repo: 'owner/name' }. For 'arxiv' set { arxiv_query: '<query, e.g. cat:cs.CL>' }.",
43
+ },
44
+ schedule: {
45
+ type: "string",
46
+ enum: ["daily", "weekly", "manual"],
47
+ description: "How often the pipeline should fetch this source. Default 'daily'.",
48
+ },
49
+ categories: {
50
+ type: "array",
51
+ items: { type: "string" },
52
+ description: "Optional category override. When omitted the auto-classifier picks from the 25-slug taxonomy.",
53
+ },
54
+ notes: {
55
+ type: "string",
56
+ description: "Free-form notes shown in the UI.",
57
+ },
58
+ },
59
+ required: ["action"],
60
+ },
61
+ };
62
+
63
+ export default toolDefinition;
@@ -0,0 +1,66 @@
1
+ import type { ToolPlugin } from "../../tools/types";
2
+ import type { ToolResult } from "gui-chat-protocol";
3
+ import toolDefinition, { TOOL_NAME } from "./definition";
4
+ import View from "./View.vue";
5
+ import Preview from "./Preview.vue";
6
+ import { apiPost } from "../../utils/api";
7
+ import { API_ROUTES } from "../../config/apiRoutes";
8
+
9
+ // Mirrors server/sources/types.ts#Source. Re-declared here so the
10
+ // frontend doesn't have to import a server package.
11
+ export interface Source {
12
+ slug: string;
13
+ title: string;
14
+ url: string;
15
+ fetcherKind: "rss" | "github-releases" | "github-issues" | "arxiv";
16
+ fetcherParams: Record<string, string>;
17
+ schedule: "daily" | "weekly" | "manual";
18
+ categories: string[];
19
+ maxItemsPerFetch: number;
20
+ addedAt: string;
21
+ notes?: string;
22
+ }
23
+
24
+ export interface RebuildSummary {
25
+ plannedCount: number;
26
+ itemCount: number;
27
+ duplicateCount: number;
28
+ archiveErrors: string[];
29
+ isoDate: string;
30
+ }
31
+
32
+ export interface ManageSourceData {
33
+ sources: Source[];
34
+ // Optional per-action context. Set on register to highlight the
35
+ // newly-added source; set on rebuild so the View can flash the
36
+ // run summary.
37
+ highlightSlug?: string;
38
+ lastRebuild?: RebuildSummary;
39
+ classifyRationale?: string;
40
+ }
41
+
42
+ const manageSourcePlugin: ToolPlugin<ManageSourceData> = {
43
+ toolDefinition,
44
+ async execute(_context, args) {
45
+ const result = await apiPost<ToolResult<ManageSourceData>>(API_ROUTES.sources.manage, args);
46
+ if (!result.ok) {
47
+ return {
48
+ toolName: TOOL_NAME,
49
+ uuid: crypto.randomUUID(),
50
+ message: result.error,
51
+ };
52
+ }
53
+ return {
54
+ ...result.data,
55
+ toolName: TOOL_NAME,
56
+ uuid: crypto.randomUUID(),
57
+ };
58
+ },
59
+ isEnabled: () => true,
60
+ generatingMessage: "Managing sources…",
61
+ viewComponent: View,
62
+ previewComponent: Preview,
63
+ };
64
+
65
+ export default manageSourcePlugin;
66
+ export { TOOL_NAME };
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <div class="p-3 bg-purple-100 rounded overflow-hidden">
3
+ <div class="text-sm text-gray-800 font-medium truncate">
4
+ {{ displayTitle }}
5
+ </div>
6
+ <div v-if="contentPreview" class="text-xs text-gray-500 mt-1 line-clamp-4 whitespace-pre-line">
7
+ {{ contentPreview }}
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed, ref, watch } from "vue";
14
+ import type { ToolResult } from "gui-chat-protocol";
15
+ import { isFilePath, type MarkdownToolData } from "./definition";
16
+ import { extractFirstH1 } from "../../utils/markdown/extractFirstH1";
17
+ import { apiGet } from "../../utils/api";
18
+ import { API_ROUTES } from "../../config/apiRoutes";
19
+
20
+ const props = defineProps<{
21
+ result: ToolResult<MarkdownToolData>;
22
+ }>();
23
+
24
+ const fetchedContent = ref("");
25
+
26
+ async function fetchContent(): Promise<void> {
27
+ const raw = props.result.data?.markdown;
28
+ if (!raw || !isFilePath(raw)) {
29
+ fetchedContent.value = "";
30
+ return;
31
+ }
32
+ const result = await apiGet<{ content?: string }>(API_ROUTES.files.content, {
33
+ path: raw,
34
+ });
35
+ if (!result.ok) {
36
+ fetchedContent.value = "";
37
+ return;
38
+ }
39
+ fetchedContent.value = result.data.content ?? "";
40
+ }
41
+
42
+ fetchContent();
43
+ watch(() => props.result.data?.markdown, fetchContent);
44
+
45
+ const displayTitle = computed(() => {
46
+ if (props.result.title) {
47
+ return props.result.title;
48
+ }
49
+ const markdown = resolvedMarkdown.value;
50
+ if (markdown) {
51
+ const heading = extractFirstH1(markdown);
52
+ if (heading) return heading;
53
+ }
54
+ return "Markdown Document";
55
+ });
56
+
57
+ const resolvedMarkdown = computed(() => {
58
+ const raw = props.result.data?.markdown;
59
+ if (!raw) return "";
60
+ return isFilePath(raw) ? fetchedContent.value : raw;
61
+ });
62
+
63
+ function extractPreview(markdown: string): string {
64
+ const lines = markdown
65
+ .split("\n")
66
+ .filter((line) => !/^#{1,6}\s/.test(line) && line.trim() !== "")
67
+ .map((line) => line.replace(/[*_`~[\]]/g, "").trim())
68
+ .filter(Boolean);
69
+ return lines.slice(0, 6).join("\n");
70
+ }
71
+
72
+ const contentPreview = computed(() => {
73
+ const markdown = resolvedMarkdown.value;
74
+ if (!markdown) return "";
75
+ return extractPreview(markdown);
76
+ });
77
+ </script>
@@ -0,0 +1,476 @@
1
+ <template>
2
+ <div class="markdown-container">
3
+ <div v-if="loading" class="min-h-full p-8 flex items-center justify-center">
4
+ <div class="text-gray-500">Loading document...</div>
5
+ </div>
6
+ <div v-else-if="loadError && !markdownContent" class="min-h-full p-8 flex items-center justify-center">
7
+ <div class="load-error-banner" role="alert">⚠ Failed to load document: {{ loadError }}</div>
8
+ </div>
9
+ <div v-else-if="!markdownContent" class="min-h-full p-8 flex items-center justify-center">
10
+ <div class="text-gray-500">No markdown content available</div>
11
+ </div>
12
+ <template v-else>
13
+ <div v-if="loadError" class="load-error-banner" role="alert">
14
+ ⚠ Failed to refresh document: {{ loadError }} — showing last successfully loaded content.
15
+ </div>
16
+ <div class="markdown-content-wrapper">
17
+ <div class="p-4">
18
+ <div class="header-row">
19
+ <h1 class="document-title">
20
+ {{ selectedResult.title || "Document" }}
21
+ </h1>
22
+ <div class="button-group">
23
+ <button class="download-btn download-btn-green" :disabled="pdfDownloading" @click="downloadPdf">
24
+ <span class="material-icons">{{ pdfDownloading ? "hourglass_empty" : "download" }}</span>
25
+ PDF
26
+ </button>
27
+ </div>
28
+ <span v-if="pdfError" class="text-xs text-red-500 self-center ml-2" :title="pdfError">⚠ PDF failed</span>
29
+ </div>
30
+ <div class="markdown-content prose prose-slate max-w-none" v-html="renderedHtml"></div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="bottom-bar-wrapper">
35
+ <details ref="sourceDetails" class="markdown-source" @toggle="onDetailsToggle">
36
+ <summary>Edit Markdown Source</summary>
37
+ <textarea v-model="editableMarkdown" class="markdown-editor" spellcheck="false"></textarea>
38
+ <div class="editor-actions">
39
+ <button class="apply-btn" :disabled="!hasChanges || saving" @click="applyMarkdown">
40
+ {{ saving ? "Saving..." : "Apply Changes" }}
41
+ </button>
42
+ <button class="cancel-btn" @click="cancelEdit">Cancel</button>
43
+ </div>
44
+ <p v-if="saveError" class="save-error" role="alert">⚠ {{ saveError }}</p>
45
+ </details>
46
+ <button v-show="!editing" class="copy-btn" :title="copied ? 'Copied!' : 'Copy'" @click="copyText">
47
+ <span class="material-icons">{{ copied ? "check" : "content_copy" }}</span>
48
+ </button>
49
+ </div>
50
+ </template>
51
+ </div>
52
+ </template>
53
+
54
+ <script setup lang="ts">
55
+ import { computed, ref, watch, nextTick } from "vue";
56
+ import { marked } from "marked";
57
+ import type { ToolResult } from "gui-chat-protocol";
58
+ import { isFilePath, type MarkdownToolData } from "./definition";
59
+ import { rewriteMarkdownImageRefs } from "../../utils/image/rewriteMarkdownImageRefs";
60
+ import { usePdfDownload } from "../../composables/usePdfDownload";
61
+ import { apiGet, apiPut } from "../../utils/api";
62
+ import { API_ROUTES } from "../../config/apiRoutes";
63
+ import { useClipboardCopy } from "../../composables/useClipboardCopy";
64
+ import { toSafeFilename } from "../../utils/files/filename";
65
+
66
+ const props = defineProps<{
67
+ selectedResult: ToolResult<MarkdownToolData>;
68
+ }>();
69
+
70
+ const emit = defineEmits<{
71
+ updateResult: [result: ToolResult<MarkdownToolData>];
72
+ }>();
73
+
74
+ const loading = ref(false);
75
+ const saving = ref(false);
76
+ // Human-readable message shown next to the Save button when a PUT
77
+ // fails. null while the editor is idle or the last save succeeded.
78
+ const saveError = ref<string | null>(null);
79
+ // Error loading the markdown content from the server. Distinct from an
80
+ // intentionally empty document — we used to wipe `markdownContent` on
81
+ // failure, which made "fetch failed" look like "no content available".
82
+ const loadError = ref<string | null>(null);
83
+ // The actual markdown content (fetched from server or inline)
84
+ const markdownContent = ref("");
85
+ const editableMarkdown = ref("");
86
+
87
+ async function fetchMarkdownContent(): Promise<void> {
88
+ loadError.value = null;
89
+ const raw = props.selectedResult.data?.markdown;
90
+ if (!raw) {
91
+ markdownContent.value = "";
92
+ editableMarkdown.value = "";
93
+ return;
94
+ }
95
+ if (isFilePath(raw)) {
96
+ loading.value = true;
97
+ const result = await apiGet<{ content?: string }>(API_ROUTES.files.content, {
98
+ path: raw,
99
+ });
100
+ if (!result.ok) {
101
+ // Preserve any previously-loaded content instead of wiping it —
102
+ // the user sees the banner AND whatever they were reading, not
103
+ // a blank canvas. editableMarkdown is left in sync so the editor
104
+ // (if open) doesn't flip between states.
105
+ loadError.value = result.error;
106
+ loading.value = false;
107
+ return;
108
+ }
109
+ markdownContent.value = result.data.content ?? "";
110
+ loading.value = false;
111
+ } else {
112
+ // Legacy inline content
113
+ markdownContent.value = raw;
114
+ }
115
+ editableMarkdown.value = markdownContent.value;
116
+ }
117
+
118
+ // Fetch on mount
119
+ fetchMarkdownContent();
120
+
121
+ const hasChanges = computed(() => {
122
+ return editableMarkdown.value !== markdownContent.value;
123
+ });
124
+
125
+ const renderedHtml = computed(() => {
126
+ if (!markdownContent.value) return "";
127
+ // Rewrite workspace-relative image refs BEFORE marked parses them —
128
+ // same approach as wiki/View.vue and FilesView.vue. Markdown files
129
+ // under `markdowns/<year>/foo.md` typically use `../images/x.png`,
130
+ // so the basePath is the directory of the file; for inline legacy
131
+ // content we have no path, so basePath is empty and only rooted
132
+ // references get rewritten.
133
+ const raw = props.selectedResult.data?.markdown;
134
+ const basePath = typeof raw === "string" && isFilePath(raw) ? raw.slice(0, raw.lastIndexOf("/")) : "";
135
+ const withImages = rewriteMarkdownImageRefs(markdownContent.value, basePath);
136
+ return marked(withImages) as string;
137
+ });
138
+
139
+ // Watch for scroll requests from viewState
140
+ watch(
141
+ () => props.selectedResult?.viewState?.scrollToAnchor as string | undefined,
142
+ (anchorId) => {
143
+ if (!anchorId) return;
144
+ nextTick(() => {
145
+ const element = document.getElementById(anchorId);
146
+ if (element) {
147
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
148
+ } else {
149
+ console.warn(`Anchor element with id "${anchorId}" not found`);
150
+ }
151
+ });
152
+ },
153
+ );
154
+
155
+ const sourceDetails = ref<HTMLDetailsElement>();
156
+ const editing = ref(false);
157
+ const { copied, copy } = useClipboardCopy();
158
+
159
+ function onDetailsToggle(e: Event) {
160
+ const open = (e.target as HTMLDetailsElement).open;
161
+ editing.value = open;
162
+ if (!open) {
163
+ editableMarkdown.value = markdownContent.value;
164
+ saveError.value = null;
165
+ }
166
+ }
167
+
168
+ function cancelEdit() {
169
+ if (sourceDetails.value) sourceDetails.value.open = false;
170
+ }
171
+
172
+ async function copyText() {
173
+ await copy(markdownContent.value);
174
+ }
175
+
176
+ const { pdfDownloading, pdfError, downloadPdf: rawDownloadPdf } = usePdfDownload();
177
+
178
+ async function downloadPdf() {
179
+ if (!markdownContent.value) return;
180
+ const prefix = props.selectedResult.data?.filenamePrefix;
181
+ const rawName = prefix || props.selectedResult.title || "document";
182
+ const title = toSafeFilename(rawName, "document");
183
+ await rawDownloadPdf(markdownContent.value, `${title}.pdf`);
184
+ }
185
+
186
+ async function applyMarkdown() {
187
+ const raw = props.selectedResult.data?.markdown;
188
+ if (!raw) return;
189
+
190
+ saveError.value = null;
191
+
192
+ // If file-based, save to server
193
+ if (isFilePath(raw)) {
194
+ saving.value = true;
195
+ const filename = raw.replace(/^(artifacts\/documents|markdowns)\//, "");
196
+ const result = await apiPut<unknown>(API_ROUTES.plugins.updateMarkdown.replace(":filename", filename), {
197
+ markdown: editableMarkdown.value,
198
+ });
199
+ saving.value = false;
200
+ if (!result.ok) {
201
+ saveError.value = `Save failed: ${result.error}`;
202
+ return;
203
+ }
204
+ }
205
+
206
+ // Update local state
207
+ markdownContent.value = editableMarkdown.value;
208
+
209
+ // Emit update to parent (clears pdfPath since content changed)
210
+ const updatedResult: ToolResult<MarkdownToolData> = {
211
+ ...props.selectedResult,
212
+ data: {
213
+ ...props.selectedResult.data,
214
+ markdown: isFilePath(raw) ? raw : editableMarkdown.value,
215
+ pdfPath: undefined,
216
+ },
217
+ };
218
+ emit("updateResult", updatedResult);
219
+
220
+ // Close the edit panel
221
+ if (sourceDetails.value) sourceDetails.value.open = false;
222
+ }
223
+
224
+ // Watch for external changes to selectedResult (when user clicks different result)
225
+ watch(
226
+ () => props.selectedResult.data?.markdown,
227
+ () => {
228
+ fetchMarkdownContent();
229
+ },
230
+ );
231
+ </script>
232
+
233
+ <style scoped>
234
+ .markdown-container {
235
+ width: 100%;
236
+ height: 100%;
237
+ display: flex;
238
+ flex-direction: column;
239
+ background: white;
240
+ }
241
+
242
+ .markdown-content-wrapper {
243
+ flex: 1;
244
+ overflow-y: auto;
245
+ min-height: 0;
246
+ }
247
+
248
+ .header-row {
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: space-between;
252
+ margin-bottom: 1em;
253
+ }
254
+
255
+ .document-title {
256
+ font-size: 2em;
257
+ margin: 0;
258
+ }
259
+
260
+ .button-group {
261
+ display: flex;
262
+ gap: 0.5em;
263
+ }
264
+
265
+ .download-btn {
266
+ padding: 0.5em 1em;
267
+ color: white;
268
+ border: none;
269
+ border-radius: 4px;
270
+ cursor: pointer;
271
+ font-size: 0.9em;
272
+ display: flex;
273
+ align-items: center;
274
+ gap: 0.5em;
275
+ }
276
+
277
+ .download-btn-green {
278
+ background-color: #4caf50;
279
+ }
280
+
281
+ .download-btn .material-icons {
282
+ font-size: 1.2em;
283
+ }
284
+
285
+ .download-btn:disabled {
286
+ opacity: 0.6;
287
+ cursor: not-allowed;
288
+ }
289
+
290
+ .markdown-content :deep(h1) {
291
+ font-size: 2rem;
292
+ font-weight: bold;
293
+ margin-top: 1em;
294
+ margin-bottom: 0.5em;
295
+ }
296
+
297
+ .markdown-content :deep(h2) {
298
+ font-size: 1.75rem;
299
+ font-weight: bold;
300
+ margin-top: 1em;
301
+ margin-bottom: 0.5em;
302
+ }
303
+
304
+ .markdown-content :deep(h3) {
305
+ font-size: 1.5rem;
306
+ font-weight: bold;
307
+ margin-top: 1em;
308
+ margin-bottom: 0.5em;
309
+ }
310
+
311
+ .markdown-content :deep(h4) {
312
+ font-size: 1.25rem;
313
+ font-weight: bold;
314
+ margin-top: 1em;
315
+ margin-bottom: 0.5em;
316
+ }
317
+
318
+ .markdown-content :deep(h5) {
319
+ font-size: 1.125rem;
320
+ font-weight: bold;
321
+ margin-top: 1em;
322
+ margin-bottom: 0.5em;
323
+ }
324
+
325
+ .markdown-content :deep(h6) {
326
+ font-size: 1rem;
327
+ font-weight: bold;
328
+ margin-top: 1em;
329
+ margin-bottom: 0.5em;
330
+ }
331
+
332
+ .bottom-bar-wrapper {
333
+ position: relative;
334
+ flex-shrink: 0;
335
+ }
336
+
337
+ .copy-btn {
338
+ position: absolute;
339
+ bottom: 0.3rem;
340
+ right: 0.65rem;
341
+ padding: 0.4rem;
342
+ background: none;
343
+ border: none;
344
+ color: #333;
345
+ cursor: pointer;
346
+ z-index: 1;
347
+ }
348
+
349
+ .copy-btn:hover {
350
+ color: #000;
351
+ }
352
+
353
+ .copy-btn .material-icons {
354
+ font-size: 1.15rem;
355
+ }
356
+
357
+ .markdown-source {
358
+ padding: 0.5rem;
359
+ background: #f5f5f5;
360
+ border-top: 1px solid #e0e0e0;
361
+ font-family: monospace;
362
+ font-size: 0.85rem;
363
+ flex-shrink: 0;
364
+ }
365
+
366
+ .markdown-source summary {
367
+ cursor: pointer;
368
+ user-select: none;
369
+ padding: 0.5rem;
370
+ background: #e8e8e8;
371
+ border-radius: 4px;
372
+ font-weight: 500;
373
+ color: #333;
374
+ }
375
+
376
+ .markdown-source[open] summary {
377
+ margin-bottom: 0.5rem;
378
+ }
379
+
380
+ .markdown-source summary:hover {
381
+ background: #d8d8d8;
382
+ }
383
+
384
+ .markdown-editor {
385
+ width: 100%;
386
+ height: 40vh;
387
+ padding: 1rem;
388
+ background: #ffffff;
389
+ border: 1px solid #ccc;
390
+ border-radius: 4px;
391
+ color: #333;
392
+ font-family: "Courier New", monospace;
393
+ font-size: 0.9rem;
394
+ resize: vertical;
395
+ margin-bottom: 0.5rem;
396
+ line-height: 1.5;
397
+ }
398
+
399
+ .markdown-editor:focus {
400
+ outline: none;
401
+ border-color: #4caf50;
402
+ box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.1);
403
+ }
404
+
405
+ .apply-btn {
406
+ padding: 0.5rem 1rem;
407
+ background: #4caf50;
408
+ color: white;
409
+ border: none;
410
+ border-radius: 4px;
411
+ cursor: pointer;
412
+ font-size: 0.9rem;
413
+ transition: background 0.2s;
414
+ font-weight: 500;
415
+ }
416
+
417
+ .apply-btn:hover {
418
+ background: #45a049;
419
+ }
420
+
421
+ .apply-btn:active {
422
+ background: #3d8b40;
423
+ }
424
+
425
+ .apply-btn:disabled {
426
+ background: #cccccc;
427
+ color: #666666;
428
+ cursor: not-allowed;
429
+ opacity: 0.6;
430
+ }
431
+
432
+ .apply-btn:disabled:hover {
433
+ background: #cccccc;
434
+ }
435
+
436
+ .editor-actions {
437
+ display: flex;
438
+ justify-content: space-between;
439
+ }
440
+
441
+ .save-error {
442
+ margin: 0.5rem 0 0;
443
+ padding: 0.4rem 0.6rem;
444
+ background: #fdecea;
445
+ color: #b71c1c;
446
+ border: 1px solid #f5c2c7;
447
+ border-radius: 4px;
448
+ font-size: 0.85rem;
449
+ }
450
+
451
+ .load-error-banner {
452
+ margin: 0.75rem 1rem;
453
+ padding: 0.5rem 0.75rem;
454
+ background: #fdecea;
455
+ color: #b71c1c;
456
+ border: 1px solid #f5c2c7;
457
+ border-radius: 4px;
458
+ font-size: 0.875rem;
459
+ }
460
+
461
+ .cancel-btn {
462
+ padding: 0.5rem 1rem;
463
+ background: #e0e0e0;
464
+ color: #333;
465
+ border: none;
466
+ border-radius: 4px;
467
+ cursor: pointer;
468
+ font-size: 0.9rem;
469
+ transition: background 0.2s;
470
+ font-weight: 500;
471
+ }
472
+
473
+ .cancel-btn:hover {
474
+ background: #d0d0d0;
475
+ }
476
+ </style>