mulmoclaude 0.5.2 → 0.6.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 (486) hide show
  1. package/Dockerfile.sandbox +100 -0
  2. package/README.md +17 -4
  3. package/bin/mulmoclaude.js +46 -15
  4. package/bin/prepare-dist.js +18 -2
  5. package/client/assets/chunk-CernVdwh.js +1 -0
  6. package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
  7. package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
  8. package/client/assets/index-BbgSjFQ8.js +4968 -0
  9. package/client/assets/index-ECD0lgIv.css +2 -0
  10. package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
  11. package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
  12. package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
  13. package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
  14. package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
  15. package/client/assets/vue-1e_vz2LW.js +1 -0
  16. package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
  17. package/client/index.html +33 -2
  18. package/package.json +20 -18
  19. package/sandbox-entrypoint.sh +106 -0
  20. package/server/accounting/accountNormalize.ts +32 -0
  21. package/server/accounting/defaultAccounts.ts +87 -0
  22. package/server/accounting/eventPublisher.ts +51 -0
  23. package/server/accounting/journal.ts +252 -0
  24. package/server/accounting/openingBalances.ts +114 -0
  25. package/server/accounting/report.ts +237 -0
  26. package/server/accounting/service.ts +718 -0
  27. package/server/accounting/snapshotCache.ts +333 -0
  28. package/server/accounting/timeSeries.ts +265 -0
  29. package/server/accounting/types.ts +148 -0
  30. package/server/agent/activeTools.ts +128 -0
  31. package/server/agent/attachmentConverter.ts +10 -5
  32. package/server/agent/backend/claude-code.ts +8 -2
  33. package/server/agent/backend/types.ts +1 -1
  34. package/server/agent/config.ts +101 -31
  35. package/server/agent/index.ts +45 -33
  36. package/server/agent/mcp-server.ts +146 -69
  37. package/server/agent/mcp-tools/index.ts +1 -5
  38. package/server/agent/mcp-tools/notify.ts +2 -22
  39. package/server/agent/mcp-tools/x.ts +0 -4
  40. package/server/agent/mcpHealth.ts +168 -0
  41. package/server/agent/plugin-names.ts +20 -77
  42. package/server/agent/prompt.ts +259 -51
  43. package/server/agent/resumeFailover.ts +1 -1
  44. package/server/agent/stream.ts +0 -1
  45. package/server/api/auth/bearerAuth.ts +5 -5
  46. package/server/api/csrfGuard.ts +1 -1
  47. package/server/api/routes/accounting.ts +366 -0
  48. package/server/api/routes/agent.ts +509 -46
  49. package/server/api/routes/attachment.ts +104 -0
  50. package/server/api/routes/chart.ts +2 -1
  51. package/server/api/routes/config.ts +12 -12
  52. package/server/api/routes/files.ts +105 -48
  53. package/server/api/routes/image.ts +70 -25
  54. package/server/api/routes/journal.ts +35 -0
  55. package/server/api/routes/mulmo-script.ts +358 -118
  56. package/server/api/routes/mulmoScriptValidate.ts +1 -1
  57. package/server/api/routes/news.ts +1 -1
  58. package/server/api/routes/notifications.ts +92 -22
  59. package/server/api/routes/notifier.ts +98 -0
  60. package/server/api/routes/pdf.ts +188 -48
  61. package/server/api/routes/plugins.ts +34 -14
  62. package/server/api/routes/presentHtml.ts +58 -3
  63. package/server/api/routes/roles.ts +1 -8
  64. package/server/api/routes/runtime-plugin.ts +224 -0
  65. package/server/api/routes/scheduler.ts +7 -5
  66. package/server/api/routes/schedulerHandlers.ts +1 -1
  67. package/server/api/routes/schedulerTasks.ts +8 -7
  68. package/server/api/routes/sessions.ts +234 -121
  69. package/server/api/routes/skills.ts +56 -51
  70. package/server/api/routes/sources.ts +52 -45
  71. package/server/api/routes/translation.ts +44 -0
  72. package/server/api/routes/wiki/frontmatter.ts +13 -65
  73. package/server/api/routes/wiki/history.ts +261 -0
  74. package/server/api/routes/wiki/pageIndex.ts +1 -1
  75. package/server/api/routes/wiki.ts +50 -26
  76. package/server/events/file-change.ts +83 -0
  77. package/server/events/notifications.ts +247 -91
  78. package/server/events/pub-sub/index.ts +1 -1
  79. package/server/events/relay-client.ts +5 -5
  80. package/server/events/scheduler-adapter.ts +2 -2
  81. package/server/events/session-store/index.ts +110 -22
  82. package/server/events/task-manager/index.ts +10 -9
  83. package/server/index.ts +509 -33
  84. package/server/notifier/engine.ts +419 -0
  85. package/server/notifier/legacy-adapters.ts +76 -0
  86. package/server/notifier/runtime-api.ts +74 -0
  87. package/server/notifier/store.ts +70 -0
  88. package/server/notifier/types.ts +121 -0
  89. package/server/plugins/dev-loader.ts +171 -0
  90. package/server/plugins/dev-watcher.ts +150 -0
  91. package/server/plugins/diagnostics.ts +188 -0
  92. package/server/plugins/preset-list.ts +52 -0
  93. package/server/plugins/preset-loader.ts +112 -0
  94. package/server/plugins/runtime-chat-api.ts +38 -0
  95. package/server/plugins/runtime-loader.ts +430 -0
  96. package/server/plugins/runtime-registry.ts +112 -0
  97. package/server/plugins/runtime-tasks-api.ts +50 -0
  98. package/server/plugins/runtime.ts +378 -0
  99. package/server/services/translation/cache.ts +72 -0
  100. package/server/services/translation/index.ts +106 -0
  101. package/server/services/translation/llm.ts +140 -0
  102. package/server/services/translation/types.ts +35 -0
  103. package/server/system/credentials.ts +13 -2
  104. package/server/system/env.ts +6 -1
  105. package/server/system/logger/formatters.ts +46 -4
  106. package/server/system/logger/index.ts +4 -4
  107. package/server/system/logger/sinks.ts +26 -5
  108. package/server/system/logger/types.ts +2 -2
  109. package/server/utils/dev-plugin-args.d.mts +11 -0
  110. package/server/utils/dev-plugin-args.mjs +43 -0
  111. package/server/utils/errors.ts +13 -4
  112. package/server/utils/files/accounting-io.ts +295 -0
  113. package/server/utils/files/atomic.ts +17 -49
  114. package/server/utils/files/attachment-store.ts +182 -0
  115. package/server/utils/files/html-io.ts +1 -7
  116. package/server/utils/files/html-store.ts +19 -0
  117. package/server/utils/files/image-store.ts +20 -22
  118. package/server/utils/files/index.ts +5 -15
  119. package/server/utils/files/journal-io.ts +7 -35
  120. package/server/utils/files/json.ts +2 -29
  121. package/server/utils/files/markdown-image-fill.ts +6 -37
  122. package/server/utils/files/markdown-store.ts +6 -21
  123. package/server/utils/files/naming.ts +3 -39
  124. package/server/utils/files/plugins-io.ts +100 -0
  125. package/server/utils/files/reference-dirs-io.ts +1 -9
  126. package/server/utils/files/roles-io.ts +2 -10
  127. package/server/utils/files/safe.ts +17 -19
  128. package/server/utils/files/scheduler-io.ts +1 -7
  129. package/server/utils/files/scheduler-overrides-io.ts +3 -12
  130. package/server/utils/files/session-io.ts +21 -30
  131. package/server/utils/files/spreadsheet-store.ts +9 -22
  132. package/server/utils/files/translation-io.ts +46 -0
  133. package/server/utils/files/user-tasks-io.ts +1 -7
  134. package/server/utils/files/workspace-io.ts +3 -79
  135. package/server/utils/gemini.ts +33 -11
  136. package/server/utils/html/htmlArtifactSplicer.ts +41 -0
  137. package/server/utils/markdown/frontmatter.ts +112 -0
  138. package/server/utils/regex.ts +56 -0
  139. package/server/utils/router.ts +41 -0
  140. package/server/utils/slug.ts +5 -3
  141. package/server/utils/time.ts +12 -0
  142. package/server/workspace/chat-index/indexer.ts +15 -2
  143. package/server/workspace/chat-index/summarizer.ts +1 -1
  144. package/server/workspace/custom-dirs.ts +1 -1
  145. package/server/workspace/helps/gemini.md +1 -1
  146. package/server/workspace/helps/guide.md +61 -0
  147. package/server/workspace/helps/index.md +4 -0
  148. package/server/workspace/helps/presenthtml.md +80 -0
  149. package/server/workspace/helps/sandbox.md +7 -0
  150. package/server/workspace/helps/storyteller.md +101 -0
  151. package/server/workspace/helps/telegram.md +1 -0
  152. package/server/workspace/helps/wiki.md +9 -7
  153. package/server/workspace/journal/archivist-cli.ts +7 -33
  154. package/server/workspace/journal/archivist-schemas.ts +5 -43
  155. package/server/workspace/journal/dailyPass.ts +34 -187
  156. package/server/workspace/journal/diff.ts +3 -28
  157. package/server/workspace/journal/index.ts +10 -81
  158. package/server/workspace/journal/indexFile.ts +3 -24
  159. package/server/workspace/journal/latestDaily.ts +51 -0
  160. package/server/workspace/journal/memoryExtractor.ts +4 -20
  161. package/server/workspace/journal/optimizationPass.ts +4 -21
  162. package/server/workspace/journal/paths.ts +4 -23
  163. package/server/workspace/journal/state.ts +6 -29
  164. package/server/workspace/memory/io.ts +213 -0
  165. package/server/workspace/memory/llm-classifier.ts +158 -0
  166. package/server/workspace/memory/migrate.ts +263 -0
  167. package/server/workspace/memory/run.ts +84 -0
  168. package/server/workspace/memory/topic-cluster.ts +218 -0
  169. package/server/workspace/memory/topic-detect.ts +67 -0
  170. package/server/workspace/memory/topic-index-hook.ts +128 -0
  171. package/server/workspace/memory/topic-io.ts +180 -0
  172. package/server/workspace/memory/topic-migrate.ts +248 -0
  173. package/server/workspace/memory/topic-run.ts +172 -0
  174. package/server/workspace/memory/topic-swap.ts +135 -0
  175. package/server/workspace/memory/topic-types.ts +142 -0
  176. package/server/workspace/memory/types.ts +83 -0
  177. package/server/workspace/news/reader.ts +4 -5
  178. package/server/workspace/paths.ts +124 -47
  179. package/server/workspace/roles.ts +2 -11
  180. package/server/workspace/skills/parser.ts +38 -55
  181. package/server/workspace/skills/user-tasks.ts +1 -2
  182. package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
  183. package/server/workspace/skills-preset.ts +196 -0
  184. package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
  185. package/server/workspace/sources/fetchers/index.ts +1 -1
  186. package/server/workspace/sources/fetchers/rssParser.ts +1 -1
  187. package/server/workspace/sources/pipeline/index.ts +2 -2
  188. package/server/workspace/sources/pipeline/notify.ts +3 -3
  189. package/server/workspace/sources/pipeline/write.ts +2 -2
  190. package/server/workspace/sources/registry.ts +39 -61
  191. package/server/workspace/sources/robots.ts +1 -1
  192. package/server/workspace/tool-trace/classify.ts +2 -1
  193. package/server/workspace/tool-trace/index.ts +1 -1
  194. package/server/workspace/tool-trace/writeSearch.ts +6 -1
  195. package/server/workspace/wiki-backlinks/index.ts +19 -7
  196. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
  197. package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
  198. package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
  199. package/server/workspace/wiki-history/provision.ts +181 -0
  200. package/server/workspace/wiki-pages/io.ts +217 -0
  201. package/server/workspace/wiki-pages/snapshot.ts +380 -0
  202. package/server/workspace/workspace.ts +75 -13
  203. package/src/App.vue +115 -40
  204. package/src/_runtime/protocol-vue.ts +21 -0
  205. package/src/_runtime/vue.ts +22 -0
  206. package/src/components/ChatInput.vue +14 -10
  207. package/src/components/CopyChatButton.vue +76 -0
  208. package/src/components/FileContentRenderer.vue +67 -14
  209. package/src/components/FileTree.vue +2 -2
  210. package/src/components/FilesView.vue +17 -1
  211. package/src/components/NewsView.vue +16 -2
  212. package/src/components/NotificationBell.vue +320 -93
  213. package/src/components/PageChatComposer.vue +5 -4
  214. package/src/components/PluginLauncher.vue +42 -6
  215. package/src/components/PluginScopedRoot.vue +87 -0
  216. package/src/components/RoleSelector.vue +12 -1
  217. package/src/components/RolesView.vue +562 -0
  218. package/src/components/SentAttachmentChip.vue +102 -0
  219. package/src/components/SessionHistoryPanel.vue +109 -20
  220. package/src/components/SessionRoleIcon.vue +7 -4
  221. package/src/components/SessionSidebar.vue +20 -7
  222. package/src/components/SessionTabBar.vue +1 -1
  223. package/src/components/SettingsMcpTab.vue +4 -4
  224. package/src/components/SettingsModal.vue +2 -0
  225. package/src/components/SidebarHeader.vue +16 -5
  226. package/src/components/SourcesManager.vue +23 -9
  227. package/src/components/SourcesView.vue +1 -1
  228. package/src/components/StackView.vue +102 -6
  229. package/src/components/SuggestionsPanel.vue +105 -16
  230. package/src/components/SystemFileBanner.vue +1 -1
  231. package/src/components/TodoExplorer.vue +4 -5
  232. package/src/components/todo/TodoAddDialog.vue +2 -3
  233. package/src/components/todo/TodoEditDialog.vue +1 -2
  234. package/src/components/todo/TodoEditPanel.vue +2 -3
  235. package/src/components/todo/TodoKanbanView.vue +8 -5
  236. package/src/components/todo/TodoListView.vue +3 -5
  237. package/src/components/todo/TodoTableView.vue +7 -5
  238. package/src/composables/useAccountingChannel.ts +58 -0
  239. package/src/composables/useActiveSession.ts +4 -25
  240. package/src/composables/useAppApi.ts +6 -44
  241. package/src/composables/useClipboardCopy.ts +3 -20
  242. package/src/composables/useContentDisplay.ts +33 -2
  243. package/src/composables/useDevPluginReload.ts +23 -0
  244. package/src/composables/useDynamicFavicon.ts +5 -31
  245. package/src/composables/useEventListeners.ts +0 -20
  246. package/src/composables/useExpandedDirs.ts +4 -15
  247. package/src/composables/useFaviconState.ts +12 -46
  248. package/src/composables/useFileChange.ts +53 -0
  249. package/src/composables/useFreshPluginData.ts +6 -43
  250. package/src/composables/useHealth.ts +14 -43
  251. package/src/composables/useImageErrorRepair.ts +104 -0
  252. package/src/composables/useLatestDaily.ts +40 -0
  253. package/src/composables/useMarkdownDoc.ts +39 -0
  254. package/src/composables/useMarkdownLinkHandler.ts +1 -1
  255. package/src/composables/useMcpTools.ts +3 -16
  256. package/src/composables/useNotifications.ts +138 -112
  257. package/src/composables/usePdfDownload.ts +17 -3
  258. package/src/composables/usePendingCalls.ts +8 -26
  259. package/src/composables/usePluginErrorBoundary.ts +68 -0
  260. package/src/composables/usePubSub.ts +9 -17
  261. package/src/composables/useRunElapsed.ts +5 -22
  262. package/src/composables/useSandboxStatus.ts +4 -20
  263. package/src/composables/useSessionDerived.ts +7 -15
  264. package/src/composables/useSessionHistory.ts +70 -29
  265. package/src/composables/useSessionSync.ts +25 -3
  266. package/src/composables/useSkillsList.ts +59 -0
  267. package/src/composables/useTranslatedQueries.ts +109 -0
  268. package/src/config/apiRoutes.ts +181 -80
  269. package/src/config/historyFilters.ts +5 -3
  270. package/src/config/hostEvents.ts +17 -0
  271. package/src/config/mcpCatalog.ts +277 -5
  272. package/src/config/pubsubChannels.ts +134 -12
  273. package/src/config/roles.ts +212 -147
  274. package/src/config/systemFileDescriptors.ts +5 -5
  275. package/src/config/toolNames.ts +52 -30
  276. package/src/config/workspacePaths.ts +26 -2
  277. package/src/lang/de.ts +483 -27
  278. package/src/lang/en.ts +448 -27
  279. package/src/lang/es.ts +474 -27
  280. package/src/lang/fr.ts +476 -27
  281. package/src/lang/ja.ts +465 -27
  282. package/src/lang/ko.ts +466 -27
  283. package/src/lang/pt-BR.ts +473 -27
  284. package/src/lang/zh.ts +463 -27
  285. package/src/lib/vue-i18n.ts +1 -1
  286. package/src/lib/wiki-page/slug.ts +66 -0
  287. package/src/main.ts +85 -0
  288. package/src/plugins/_extras.ts +58 -0
  289. package/src/plugins/_generated/metas.ts +42 -0
  290. package/src/plugins/_generated/registrations.ts +44 -0
  291. package/src/plugins/_generated/server-bindings.ts +47 -0
  292. package/src/plugins/accounting/Preview.vue +106 -0
  293. package/src/plugins/accounting/View.vue +632 -0
  294. package/src/plugins/accounting/actions.ts +34 -0
  295. package/src/plugins/accounting/api.ts +301 -0
  296. package/src/plugins/accounting/components/AccountEditor.vue +250 -0
  297. package/src/plugins/accounting/components/AccountRow.vue +50 -0
  298. package/src/plugins/accounting/components/AccountsList.vue +102 -0
  299. package/src/plugins/accounting/components/AccountsModal.vue +300 -0
  300. package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
  301. package/src/plugins/accounting/components/BookSettings.vue +284 -0
  302. package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
  303. package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
  304. package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
  305. package/src/plugins/accounting/components/JournalList.vue +553 -0
  306. package/src/plugins/accounting/components/Ledger.vue +206 -0
  307. package/src/plugins/accounting/components/NewBookForm.vue +211 -0
  308. package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
  309. package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
  310. package/src/plugins/accounting/components/accountDraft.ts +13 -0
  311. package/src/plugins/accounting/components/accountNumbering.ts +103 -0
  312. package/src/plugins/accounting/components/accountValidation.ts +75 -0
  313. package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
  314. package/src/plugins/accounting/countries.ts +158 -0
  315. package/src/plugins/accounting/currencies.ts +64 -0
  316. package/src/plugins/accounting/dates.ts +51 -0
  317. package/src/plugins/accounting/definition.ts +199 -0
  318. package/src/plugins/accounting/fiscalYear.ts +136 -0
  319. package/src/plugins/accounting/index.ts +49 -0
  320. package/src/plugins/accounting/meta.ts +91 -0
  321. package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
  322. package/src/plugins/api.ts +125 -0
  323. package/src/plugins/canvas/View.vue +38 -28
  324. package/src/plugins/canvas/definition.ts +10 -8
  325. package/src/plugins/canvas/index.ts +15 -8
  326. package/src/plugins/canvas/meta.ts +12 -0
  327. package/src/plugins/chart/Preview.vue +1 -1
  328. package/src/plugins/chart/View.vue +2 -2
  329. package/src/plugins/chart/definition.ts +12 -2
  330. package/src/plugins/chart/index.ts +15 -7
  331. package/src/plugins/chart/meta.ts +18 -0
  332. package/src/plugins/editImages/definition.ts +44 -0
  333. package/src/plugins/editImages/index.ts +43 -0
  334. package/src/plugins/editImages/meta.ts +5 -0
  335. package/src/plugins/generateImage/View.vue +3 -1
  336. package/src/plugins/generateImage/definition.ts +2 -0
  337. package/src/plugins/generateImage/index.ts +13 -5
  338. package/src/plugins/generateImage/meta.ts +5 -0
  339. package/src/plugins/index.ts +35 -0
  340. package/src/plugins/manageRoles/Preview.vue +7 -4
  341. package/src/plugins/manageRoles/View.vue +12 -8
  342. package/src/plugins/manageRoles/definition.ts +6 -0
  343. package/src/plugins/manageRoles/index.ts +7 -6
  344. package/src/plugins/manageSkills/View.vue +11 -7
  345. package/src/plugins/manageSkills/definition.ts +4 -1
  346. package/src/plugins/manageSkills/index.ts +14 -7
  347. package/src/plugins/manageSkills/meta.ts +21 -0
  348. package/src/plugins/manageSource/definition.ts +4 -1
  349. package/src/plugins/manageSource/index.ts +15 -7
  350. package/src/plugins/manageSource/meta.ts +21 -0
  351. package/src/plugins/markdown/Preview.vue +10 -8
  352. package/src/plugins/markdown/View.vue +84 -17
  353. package/src/plugins/markdown/definition.ts +7 -1
  354. package/src/plugins/markdown/index.ts +15 -8
  355. package/src/plugins/markdown/meta.ts +16 -0
  356. package/src/plugins/meta-types.ts +97 -0
  357. package/src/plugins/metas.ts +224 -0
  358. package/src/plugins/presentForm/Preview.vue +4 -15
  359. package/src/plugins/presentForm/View.vue +35 -78
  360. package/src/plugins/presentForm/definition.ts +7 -6
  361. package/src/plugins/presentForm/index.ts +12 -5
  362. package/src/plugins/presentForm/meta.ts +11 -0
  363. package/src/plugins/presentForm/plugin.ts +8 -9
  364. package/src/plugins/presentForm/types.ts +0 -24
  365. package/src/plugins/presentHtml/Preview.vue +1 -8
  366. package/src/plugins/presentHtml/View.vue +401 -30
  367. package/src/plugins/presentHtml/definition.ts +8 -5
  368. package/src/plugins/presentHtml/index.ts +15 -8
  369. package/src/plugins/presentHtml/meta.ts +14 -0
  370. package/src/plugins/presentMulmoScript/View.vue +327 -107
  371. package/src/plugins/presentMulmoScript/definition.ts +34 -7
  372. package/src/plugins/presentMulmoScript/helpers.ts +4 -5
  373. package/src/plugins/presentMulmoScript/index.ts +20 -7
  374. package/src/plugins/presentMulmoScript/meta.ts +52 -0
  375. package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
  376. package/src/plugins/scheduler/Preview.vue +5 -2
  377. package/src/plugins/scheduler/TasksTab.vue +16 -36
  378. package/src/plugins/scheduler/View.vue +22 -54
  379. package/src/plugins/scheduler/automationsDefinition.ts +14 -9
  380. package/src/plugins/scheduler/automationsMeta.ts +5 -0
  381. package/src/plugins/scheduler/calendarDefinition.ts +4 -7
  382. package/src/plugins/scheduler/calendarMeta.ts +28 -0
  383. package/src/plugins/scheduler/formatSchedule.ts +6 -24
  384. package/src/plugins/scheduler/index.ts +26 -52
  385. package/src/plugins/scope.ts +57 -0
  386. package/src/plugins/server-bindings-types.ts +38 -0
  387. package/src/plugins/server.ts +32 -0
  388. package/src/plugins/skill/Preview.vue +25 -0
  389. package/src/plugins/skill/View.vue +125 -0
  390. package/src/plugins/skill/definition.ts +23 -0
  391. package/src/plugins/skill/index.ts +36 -0
  392. package/src/plugins/skill/plugin.ts +31 -0
  393. package/src/plugins/skill/types.ts +21 -0
  394. package/src/plugins/spreadsheet/Preview.vue +1 -3
  395. package/src/plugins/spreadsheet/View.vue +29 -49
  396. package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
  397. package/src/plugins/spreadsheet/definition.ts +5 -2
  398. package/src/plugins/spreadsheet/index.ts +15 -8
  399. package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
  400. package/src/plugins/spreadsheet/meta.ts +14 -0
  401. package/src/plugins/textResponse/Preview.vue +9 -1
  402. package/src/plugins/textResponse/View.vue +59 -8
  403. package/src/plugins/textResponse/index.ts +11 -3
  404. package/src/plugins/textResponse/plugin.ts +8 -10
  405. package/src/plugins/textResponse/types.ts +28 -0
  406. package/src/plugins/wiki/Preview.vue +6 -4
  407. package/src/plugins/wiki/View.vue +463 -254
  408. package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
  409. package/src/plugins/wiki/helpers.ts +17 -0
  410. package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
  411. package/src/plugins/wiki/history/HistoryTab.vue +167 -0
  412. package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
  413. package/src/plugins/wiki/history/api.ts +52 -0
  414. package/src/plugins/wiki/history/diff.ts +145 -0
  415. package/src/plugins/wiki/index.ts +42 -32
  416. package/src/plugins/wiki/meta.ts +10 -0
  417. package/src/plugins/wiki/pageEditLoader.ts +53 -0
  418. package/src/plugins/wiki/route.ts +8 -0
  419. package/src/router/guards.ts +2 -1
  420. package/src/router/index.ts +19 -0
  421. package/src/router/pageRoutes.ts +1 -0
  422. package/src/tools/index.ts +50 -51
  423. package/src/tools/runtimeLoader.ts +141 -0
  424. package/src/tools/types.ts +44 -1
  425. package/src/types/notification.ts +23 -0
  426. package/src/types/pastedFile.ts +10 -0
  427. package/src/types/session.ts +61 -3
  428. package/src/types/sse.ts +21 -6
  429. package/src/utils/agent/eventDispatch.ts +12 -9
  430. package/src/utils/agent/pastedAttachment.ts +35 -0
  431. package/src/utils/agent/request.ts +32 -3
  432. package/src/utils/agent/toolCalls.ts +7 -1
  433. package/src/utils/api.ts +1 -1
  434. package/src/utils/chat/exportMarkdown.ts +243 -0
  435. package/src/utils/errors.ts +10 -2
  436. package/src/utils/files/expandedDirs.ts +1 -1
  437. package/src/utils/filesPreview/todoPreview.ts +13 -2
  438. package/src/utils/format/date.ts +1 -3
  439. package/src/utils/format/jsonSyntax.ts +5 -0
  440. package/src/utils/html/iframeHeightReporterScript.ts +62 -0
  441. package/src/utils/html/previewCsp.ts +29 -2
  442. package/src/utils/image/htmlSrcAttrs.ts +122 -0
  443. package/src/utils/image/imageRepairInlineScript.ts +115 -0
  444. package/src/utils/image/resolve.ts +17 -3
  445. package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
  446. package/src/utils/markdown/frontmatter.ts +125 -0
  447. package/src/utils/markdown/taskList.ts +7 -2
  448. package/src/utils/plugin/runtime.ts +132 -0
  449. package/src/utils/session/mergeSessions.ts +40 -37
  450. package/src/utils/session/sessionEntries.ts +74 -18
  451. package/src/utils/session/sessionHelpers.ts +54 -10
  452. package/src/utils/tools/result.ts +76 -14
  453. package/src/vite-env.d.ts +6 -0
  454. package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
  455. package/client/assets/index-CY-WpQUm.css +0 -2
  456. package/client/assets/index-DbTz2Mfs.js +0 -4911
  457. package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
  458. package/server/api/routes/html.ts +0 -114
  459. package/server/api/routes/todos.ts +0 -293
  460. package/server/api/routes/todosColumnsHandlers.ts +0 -333
  461. package/server/api/routes/todosHandlers.ts +0 -274
  462. package/server/api/routes/todosItemsHandlers.ts +0 -386
  463. package/server/utils/files/todos-io.ts +0 -29
  464. package/src/components/NotificationToast.vue +0 -75
  465. package/src/plugins/editImage/definition.ts +0 -27
  466. package/src/plugins/editImage/index.ts +0 -37
  467. package/src/plugins/presentHtml/helpers.ts +0 -72
  468. package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
  469. package/src/plugins/scheduler/legacyShape.ts +0 -34
  470. package/src/plugins/todo/Preview.vue +0 -68
  471. package/src/plugins/todo/View.vue +0 -378
  472. package/src/plugins/todo/composables/useTodos.ts +0 -179
  473. package/src/plugins/todo/definition.ts +0 -45
  474. package/src/plugins/todo/index.ts +0 -62
  475. package/src/plugins/todo/labels.ts +0 -163
  476. package/src/plugins/todo/priority.ts +0 -98
  477. package/src/plugins/todo/viewModes.ts +0 -19
  478. package/src/plugins/wiki/definition.ts +0 -25
  479. package/src/tools/legacyPluginNames.ts +0 -13
  480. package/src/utils/format/frontmatter.ts +0 -80
  481. package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
  482. package/src/utils/notification/dispatch.ts +0 -58
  483. /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
  484. /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
  485. /package/src/plugins/{editImage → editImages}/View.vue +0 -0
  486. /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
@@ -1,333 +0,0 @@
1
- // Pure handlers for status columns used by the file-explorer todo
2
- // view. Same shape as todosHandlers / schedulerHandlers: each function
3
- // takes the current state + an input record and returns either an
4
- // error or the next state. The Express route is responsible for
5
- // loading / saving the underlying JSON files.
6
- //
7
- // Storage layout:
8
- // workspace/todos/columns.json ← StatusColumn[]
9
- //
10
- // At least one column must always carry `isDone: true` so completed
11
- // items have somewhere to live and the legacy `completed` boolean has
12
- // something to map to.
13
-
14
- import { slugify as slugifyCanonical, disambiguateSlug } from "../../utils/slug.js";
15
- import { isRecord } from "../../utils/types.js";
16
- import type { TodoItem } from "./todos.js";
17
-
18
- export interface StatusColumn {
19
- id: string;
20
- label: string;
21
- // True for the column whose items are considered "completed".
22
- // Exactly one column should have isDone: true at any given time;
23
- // remove_column / patch_column rules enforce this.
24
- isDone?: boolean;
25
- }
26
-
27
- export const DEFAULT_COLUMNS: StatusColumn[] = [
28
- { id: "backlog", label: "Backlog" },
29
- { id: "todo", label: "Todo" },
30
- { id: "in-progress", label: "In Progress" },
31
- { id: "done", label: "Done", isDone: true },
32
- ];
33
-
34
- // ── Result types ──────────────────────────────────────────────────
35
-
36
- export type ColumnsActionResult =
37
- | { kind: "error"; status: number; error: string }
38
- | {
39
- kind: "success";
40
- columns: StatusColumn[];
41
- // Some operations also need to mutate items (e.g. removing a
42
- // column reassigns its items to another column). When set, the
43
- // route persists this items array as well.
44
- items?: TodoItem[];
45
- };
46
-
47
- // ── id slug generation ────────────────────────────────────────────
48
-
49
- // Convert a free-text label into a URL-safe column id. Thin wrapper
50
- // around the canonical slugify (server/utils/slug.ts) with the
51
- // "column" fallback. Hyphen-separated to match wiki / files / journal
52
- // — the previous underscore-separated rule was the only divergent
53
- // slug producer in the codebase. See #732.
54
- function slugify(label: string): string {
55
- return slugifyCanonical(label, "column");
56
- }
57
-
58
- // Pick an id that doesn't collide with `existingIds`. Thin wrapper
59
- // around the shared `disambiguateSlug` helper so the e2e dispatcher
60
- // (which mocks this server-side flow) can mirror the truncation
61
- // boundary without duplicating the logic — Codex iter-2 #732.
62
- function uniqueId(base: string, existingIds: ReadonlySet<string>): string {
63
- return disambiguateSlug(base, existingIds);
64
- }
65
-
66
- // ── Validation helpers ────────────────────────────────────────────
67
-
68
- function findColumn(columns: StatusColumn[], columnId: string): StatusColumn | undefined {
69
- return columns.find((column) => column.id === columnId);
70
- }
71
-
72
- function ensureColumnsValid(columns: StatusColumn[]): StatusColumn[] {
73
- // Guarantee invariants when reading: at least one column, exactly
74
- // one isDone column, ids are unique. If anything is off, fall back
75
- // to DEFAULT_COLUMNS rather than try to repair partial state.
76
- if (columns.length === 0) return [...DEFAULT_COLUMNS];
77
- const seen = new Set<string>();
78
- for (const column of columns) {
79
- if (seen.has(column.id)) return [...DEFAULT_COLUMNS];
80
- seen.add(column.id);
81
- }
82
- const doneCount = columns.filter((column) => column.isDone).length;
83
- if (doneCount === 0) {
84
- // Promote the last column to done so the invariant holds.
85
- const fixed = columns.map((column, i) => (i === columns.length - 1 ? { ...column, isDone: true } : column));
86
- return fixed;
87
- }
88
- if (doneCount > 1) {
89
- // Keep only the first done flag.
90
- let kept = false;
91
- return columns.map((column) => {
92
- if (!column.isDone) return column;
93
- if (kept) {
94
- const next: StatusColumn = { id: column.id, label: column.label };
95
- return next;
96
- }
97
- kept = true;
98
- return column;
99
- });
100
- }
101
- return columns;
102
- }
103
-
104
- // Public: load-time normaliser. Use this when parsing columns.json so
105
- // the rest of the system never has to think about invalid shapes.
106
- export function normalizeColumns(raw: unknown): StatusColumn[] {
107
- if (!Array.isArray(raw)) return [...DEFAULT_COLUMNS];
108
- const cleaned: StatusColumn[] = [];
109
- for (const entry of raw) {
110
- if (!isRecord(entry)) continue;
111
- if (typeof entry["id"] !== "string" || typeof entry["label"] !== "string") continue;
112
- const col: StatusColumn = { id: entry["id"], label: entry["label"] };
113
- if (entry["isDone"] === true) col.isDone = true;
114
- cleaned.push(col);
115
- }
116
- return ensureColumnsValid(cleaned);
117
- }
118
-
119
- // ── Item helpers tied to columns ──────────────────────────────────
120
-
121
- // id of the first column flagged isDone. Guaranteed to exist after
122
- // normalizeColumns.
123
- export function doneColumnId(columns: StatusColumn[]): string {
124
- const done = columns.find((column) => column.isDone);
125
- return done ? done.id : columns[columns.length - 1]!.id;
126
- }
127
-
128
- // id of the first non-done column, used as the default status when
129
- // adding new items. Falls back to the done column if everything is
130
- // somehow flagged done.
131
- export function defaultStatusId(columns: StatusColumn[]): string {
132
- const open = columns.find((column) => !column.isDone);
133
- return open ? open.id : doneColumnId(columns);
134
- }
135
-
136
- // Reconcile each item's `completed` boolean with the new done-column
137
- // id. Items in the new done column are completed=true, items
138
- // elsewhere are completed=false. Returns [updatedItems, changed]
139
- // where `changed` says whether any item was actually rewritten —
140
- // callers use that to decide whether to persist items along with
141
- // the column change.
142
- //
143
- // This is the *only* place that mass-mutates `completed` based on
144
- // status. The migration on read deliberately does NOT do this any
145
- // more (so the legacy MCP `check` action's plain boolean flips keep
146
- // working). Column operations are explicit user intent so it's safe
147
- // to sync at that point.
148
- export function resyncDoneMembership(items: TodoItem[], newDoneId: string): { items: TodoItem[]; changed: boolean } {
149
- let changed = false;
150
- const next = items.map((item): TodoItem => {
151
- const shouldBeDone = item.status === newDoneId;
152
- if (item.completed === shouldBeDone) return item;
153
- changed = true;
154
- return { ...item, completed: shouldBeDone };
155
- });
156
- return { items: next, changed };
157
- }
158
-
159
- // Re-stripe order values for every item in `columnId`. Items already
160
- // sorted by their existing `order` get reassigned to 1000, 2000, ...
161
- // so two columns being merged together (handleDeleteColumn's refuge
162
- // case) end up with unique, contiguous orders rather than colliding
163
- // 1000s from each side.
164
- function rebuildColumnOrder(items: TodoItem[], columnId: string): TodoItem[] {
165
- const inColumn = items.filter((item) => item.status === columnId).sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
166
- const newOrders = new Map<string, number>();
167
- inColumn.forEach((item, index) => newOrders.set(item.id, (index + 1) * ORDER_STEP));
168
- return items.map((item): TodoItem => {
169
- const newOrder = newOrders.get(item.id);
170
- if (newOrder === undefined) return item;
171
- return { ...item, order: newOrder };
172
- });
173
- }
174
-
175
- const ORDER_STEP = 1000;
176
-
177
- // ── Action handlers ───────────────────────────────────────────────
178
-
179
- export interface AddColumnInput {
180
- label?: string;
181
- isDone?: boolean;
182
- }
183
-
184
- export function handleAddColumn(columns: StatusColumn[], items: TodoItem[], input: AddColumnInput): ColumnsActionResult {
185
- if (!input.label || input.label.trim().length === 0) {
186
- return { kind: "error", status: 400, error: "label required" };
187
- }
188
- const baseId = slugify(input.label);
189
- const columnId = uniqueId(baseId, new Set(columns.map((column) => column.id)));
190
- const columnToAdd: StatusColumn = { id: columnId, label: input.label.trim() };
191
- if (input.isDone === true) columnToAdd.isDone = true;
192
- // If the new column is flagged done, demote any existing done
193
- // columns (only one is allowed at a time) and resync items so the
194
- // old done column's items are no longer marked completed. The new
195
- // column itself is empty so there's nothing on its side to sync.
196
- if (input.isDone === true) {
197
- const nextColumns = [...columns.map((column) => ({ ...column, isDone: false })), columnToAdd];
198
- const { items: nextItems, changed } = resyncDoneMembership(items, columnId);
199
- return {
200
- kind: "success",
201
- columns: nextColumns,
202
- ...(changed ? { items: nextItems } : {}),
203
- };
204
- }
205
- return { kind: "success", columns: [...columns, columnToAdd] };
206
- }
207
-
208
- export interface PatchColumnInput {
209
- label?: string;
210
- isDone?: boolean;
211
- }
212
-
213
- export function handlePatchColumn(columns: StatusColumn[], columnId: string, input: PatchColumnInput, items: TodoItem[]): ColumnsActionResult {
214
- const target = findColumn(columns, columnId);
215
- if (!target) {
216
- return { kind: "error", status: 404, error: `column not found: ${columnId}` };
217
- }
218
- const patched: StatusColumn = { id: target.id, label: target.label };
219
- if (target.isDone) patched.isDone = true;
220
- if (typeof input.label === "string" && input.label.trim().length > 0) {
221
- patched.label = input.label.trim();
222
- }
223
- let nextColumns = columns.map((column) => (column.id === columnId ? patched : column));
224
- // Toggling done flag is non-trivial: only one column may be done.
225
- let itemsChanged = false;
226
- let nextItems = items;
227
- if (input.isDone === true && !target.isDone) {
228
- // Promote this column to done; demote everyone else.
229
- nextColumns = nextColumns.map((column) => (column.id === columnId ? { ...column, isDone: true } : { id: column.id, label: column.label }));
230
- // Resync `completed` across all items: the new done column's
231
- // items become true, the old done column's items become false.
232
- // Doing this with the helper rather than a one-sided pass means
233
- // both ends of the swap stay consistent.
234
- const synced = resyncDoneMembership(items, columnId);
235
- nextItems = synced.items;
236
- itemsChanged = synced.changed;
237
- } else if (input.isDone === false && target.isDone) {
238
- // Refuse to demote the only done column — there must always be one.
239
- return {
240
- kind: "error",
241
- status: 400,
242
- error: "at least one column must be marked as done",
243
- };
244
- }
245
- return {
246
- kind: "success",
247
- columns: nextColumns,
248
- ...(itemsChanged ? { items: nextItems } : {}),
249
- };
250
- }
251
-
252
- export function handleDeleteColumn(columns: StatusColumn[], columnId: string, items: TodoItem[]): ColumnsActionResult {
253
- if (columns.length <= 1) {
254
- return {
255
- kind: "error",
256
- status: 400,
257
- error: "cannot delete the last remaining column",
258
- };
259
- }
260
- const target = findColumn(columns, columnId);
261
- if (!target) {
262
- return { kind: "error", status: 404, error: `column not found: ${columnId}` };
263
- }
264
- const remaining = columns.filter((column) => column.id !== columnId);
265
- // If we just removed the done column, promote the new last column.
266
- let nextColumns = remaining;
267
- if (target.isDone) {
268
- nextColumns = remaining.map((column, i) => (i === remaining.length - 1 ? { ...column, isDone: true } : column));
269
- }
270
- const newDoneId = doneColumnId(nextColumns);
271
- // Reassign orphaned items to the (possibly new) done column if the
272
- // deleted column was done; otherwise to the new default open column.
273
- const refugeId = target.isDone ? newDoneId : defaultStatusId(nextColumns);
274
- let itemsChanged = false;
275
- let nextItems = items.map((item): TodoItem => {
276
- if (item.status !== columnId) return item;
277
- itemsChanged = true;
278
- return { ...item, status: refugeId };
279
- });
280
- if (itemsChanged) {
281
- // The refuge column might have already had items in it; the ones
282
- // we just merged in came with their original order values from
283
- // the deleted column, which can collide with the refuge's
284
- // existing orders. Re-stripe the whole refuge column to 1000,
285
- // 2000, ... so the kanban sort stays unique and stable.
286
- nextItems = rebuildColumnOrder(nextItems, refugeId);
287
- }
288
- // Resync the done flag across the result. Necessary in two
289
- // scenarios: (a) we deleted the done column, so the new last column
290
- // is now done and its existing items should flip to completed=true;
291
- // (b) we deleted any other column whose items happened to be the
292
- // refuge target — already handled by the migration above, but
293
- // running the helper unconditionally keeps the rest of the items
294
- // consistent too.
295
- if (target.isDone) {
296
- const synced = resyncDoneMembership(nextItems, newDoneId);
297
- nextItems = synced.items;
298
- itemsChanged = itemsChanged || synced.changed;
299
- }
300
- return {
301
- kind: "success",
302
- columns: nextColumns,
303
- ...(itemsChanged ? { items: nextItems } : {}),
304
- };
305
- }
306
-
307
- export function handleReorderColumns(columns: StatusColumn[], ids: string[]): ColumnsActionResult {
308
- if (!Array.isArray(ids)) {
309
- return { kind: "error", status: 400, error: "ids array required" };
310
- }
311
- if (ids.length !== columns.length) {
312
- return {
313
- kind: "error",
314
- status: 400,
315
- error: "ids must contain every existing column id exactly once",
316
- };
317
- }
318
- const known = new Set(columns.map((column) => column.id));
319
- const seen = new Set<string>();
320
- for (const columnId of ids) {
321
- if (!known.has(columnId) || seen.has(columnId)) {
322
- return {
323
- kind: "error",
324
- status: 400,
325
- error: "ids must contain every existing column id exactly once",
326
- };
327
- }
328
- seen.add(columnId);
329
- }
330
- const byId = new Map(columns.map((column) => [column.id, column]));
331
- const next = ids.map((columnId) => byId.get(columnId)!);
332
- return { kind: "success", columns: next };
333
- }
@@ -1,274 +0,0 @@
1
- // Pure action handlers for the todos POST route. Same shape as
2
- // schedulerHandlers.ts: each handler takes the current items + the
3
- // relevant body fields and returns a discriminated result describing
4
- // either an HTTP error or the next state. The route handler in
5
- // todos.ts dispatches to one of these and translates the result into
6
- // an HTTP response.
7
- //
8
- // Keeping the action logic pure (no I/O, no globals) makes every
9
- // case unit-testable in isolation, and brings the cognitive
10
- // complexity of the route handler under the lint threshold.
11
-
12
- import type { TodoItem } from "./todos.js";
13
- import { filterByLabels, listLabelsWithCount, mergeLabels, subtractLabels } from "../../../src/plugins/todo/labels.js";
14
- import { makeId } from "../../utils/id.js";
15
-
16
- export interface TodosActionInput {
17
- text?: string;
18
- newText?: string;
19
- note?: string;
20
- // For `add`: labels to tag the new item with.
21
- // For `add_label` / `remove_label`: labels to add to / remove from
22
- // the item matched by `text`.
23
- labels?: string[];
24
- // For `show`: OR-semantics filter that restricts the returned list
25
- // to items carrying at least one of these labels (case-insensitive).
26
- filterLabels?: string[];
27
- }
28
-
29
- export type TodosActionResult =
30
- | { kind: "error"; status: number; error: string }
31
- | {
32
- kind: "success";
33
- items: TodoItem[];
34
- message: string;
35
- jsonData: Record<string, unknown>;
36
- };
37
-
38
- // Substring match (case-insensitive). Used by delete / update /
39
- // check / uncheck / add_label / remove_label — all share the same
40
- // lookup contract.
41
- export function findTodoByText(items: TodoItem[], text: string): TodoItem | undefined {
42
- const needle = text.toLowerCase();
43
- return items.find((i) => i.text.toLowerCase().includes(needle));
44
- }
45
-
46
- export function handleShow(items: TodoItem[], input: TodosActionInput): TodosActionResult {
47
- const filterLabels = input.filterLabels ?? [];
48
- const filtered = filterByLabels(items, filterLabels);
49
- const filtering = filterLabels.length > 0;
50
- const message = filtering
51
- ? `Showing ${filtered.length} of ${items.length} todo item(s) filtered by: ${filterLabels.join(", ")}`
52
- : `Showing ${items.length} todo item(s)`;
53
- return {
54
- kind: "success",
55
- items: filtered,
56
- message,
57
- jsonData: {
58
- items: filtered.map((i) => ({
59
- text: i.text,
60
- completed: i.completed,
61
- ...(i.labels && i.labels.length > 0 && { labels: i.labels }),
62
- })),
63
- },
64
- };
65
- }
66
-
67
- export function handleAdd(items: TodoItem[], input: TodosActionInput): TodosActionResult {
68
- if (!input.text) {
69
- return { kind: "error", status: 400, error: "text required" };
70
- }
71
- // Normalise incoming labels by routing them through
72
- // `mergeLabels([], labels ?? [])` — that handles trim / collapse /
73
- // case-insensitive dedup in one shot.
74
- const normalizedLabels = mergeLabels([], input.labels ?? []);
75
- const item: TodoItem = {
76
- id: makeId("todo"),
77
- text: input.text,
78
- ...(input.note !== undefined && { note: input.note }),
79
- ...(normalizedLabels.length > 0 && { labels: normalizedLabels }),
80
- completed: false,
81
- createdAt: Date.now(),
82
- };
83
- return {
84
- kind: "success",
85
- items: [...items, item],
86
- message: normalizedLabels.length > 0 ? `Added: "${input.text}" [${normalizedLabels.join(", ")}]` : `Added: "${input.text}"`,
87
- jsonData: { added: input.text, labels: normalizedLabels },
88
- };
89
- }
90
-
91
- export function handleDelete(items: TodoItem[], input: TodosActionInput): TodosActionResult {
92
- if (!input.text) {
93
- return { kind: "error", status: 400, error: "text required" };
94
- }
95
- const needle = input.text.toLowerCase();
96
- const next = items.filter((i) => !i.text.toLowerCase().includes(needle));
97
- const found = next.length < items.length;
98
- return {
99
- kind: "success",
100
- items: next,
101
- message: found ? `Deleted: "${input.text}"` : `Item not found: "${input.text}"`,
102
- jsonData: { deleted: input.text },
103
- };
104
- }
105
-
106
- export function handleUpdate(items: TodoItem[], input: TodosActionInput): TodosActionResult {
107
- if (!input.text || !input.newText) {
108
- return { kind: "error", status: 400, error: "text and newText required" };
109
- }
110
- const target = findTodoByText(items, input.text);
111
- if (!target) {
112
- return {
113
- kind: "success",
114
- items,
115
- message: `Item not found: "${input.text}"`,
116
- jsonData: {},
117
- };
118
- }
119
- const oldText = target.text;
120
- const updated: TodoItem = {
121
- ...target,
122
- text: input.newText,
123
- note: input.note !== undefined ? input.note || undefined : target.note,
124
- };
125
- const next = items.map((i) => (i.id === target.id ? updated : i));
126
- return {
127
- kind: "success",
128
- items: next,
129
- message: `Updated: "${oldText}" → "${input.newText}"`,
130
- jsonData: { oldText, newText: input.newText },
131
- };
132
- }
133
-
134
- function setCompleted(
135
- items: TodoItem[],
136
- input: TodosActionInput,
137
- completed: boolean,
138
- verb: "Checked" | "Unchecked",
139
- jsonKey: "checkedItem" | "uncheckedItem",
140
- ): TodosActionResult {
141
- if (!input.text) {
142
- return { kind: "error", status: 400, error: "text required" };
143
- }
144
- const target = findTodoByText(items, input.text);
145
- if (!target) {
146
- return {
147
- kind: "success",
148
- items,
149
- message: `Item not found: "${input.text}"`,
150
- jsonData: {},
151
- };
152
- }
153
- const updated: TodoItem = { ...target, completed };
154
- const next = items.map((i) => (i.id === target.id ? updated : i));
155
- return {
156
- kind: "success",
157
- items: next,
158
- message: `${verb}: "${target.text}"`,
159
- jsonData: { [jsonKey]: target.text },
160
- };
161
- }
162
-
163
- export function handleCheck(items: TodoItem[], input: TodosActionInput): TodosActionResult {
164
- return setCompleted(items, input, true, "Checked", "checkedItem");
165
- }
166
-
167
- export function handleUncheck(items: TodoItem[], input: TodosActionInput): TodosActionResult {
168
- return setCompleted(items, input, false, "Unchecked", "uncheckedItem");
169
- }
170
-
171
- export function handleClearCompleted(items: TodoItem[]): TodosActionResult {
172
- const count = items.filter((i) => i.completed).length;
173
- const next = items.filter((i) => !i.completed);
174
- return {
175
- kind: "success",
176
- items: next,
177
- message: `Cleared ${count} completed item(s)`,
178
- jsonData: { clearedCount: count },
179
- };
180
- }
181
-
182
- export function handleAddLabel(items: TodoItem[], input: TodosActionInput): TodosActionResult {
183
- if (!input.text || !input.labels || input.labels.length === 0) {
184
- return {
185
- kind: "error",
186
- status: 400,
187
- error: "text and a non-empty labels array required",
188
- };
189
- }
190
- const target = findTodoByText(items, input.text);
191
- if (!target) {
192
- return {
193
- kind: "success",
194
- items,
195
- message: `Item not found: "${input.text}"`,
196
- jsonData: { notFound: input.text },
197
- };
198
- }
199
- const merged = mergeLabels(target.labels ?? [], input.labels);
200
- const updated: TodoItem = { ...target, labels: merged };
201
- const next = items.map((i) => (i.id === target.id ? updated : i));
202
- return {
203
- kind: "success",
204
- items: next,
205
- message: `Labels on "${target.text}": ${merged.join(", ")}`,
206
- jsonData: { item: target.text, labels: merged },
207
- };
208
- }
209
-
210
- export function handleRemoveLabel(items: TodoItem[], input: TodosActionInput): TodosActionResult {
211
- if (!input.text || !input.labels || input.labels.length === 0) {
212
- return {
213
- kind: "error",
214
- status: 400,
215
- error: "text and a non-empty labels array required",
216
- };
217
- }
218
- const target = findTodoByText(items, input.text);
219
- if (!target) {
220
- return {
221
- kind: "success",
222
- items,
223
- message: `Item not found: "${input.text}"`,
224
- jsonData: { notFound: input.text },
225
- };
226
- }
227
- const remaining = subtractLabels(target.labels ?? [], input.labels);
228
- const updated: TodoItem = { ...target };
229
- if (remaining.length > 0) {
230
- updated.labels = remaining;
231
- } else {
232
- delete updated.labels;
233
- }
234
- const next = items.map((i) => (i.id === target.id ? updated : i));
235
- return {
236
- kind: "success",
237
- items: next,
238
- message: remaining.length > 0 ? `Labels on "${target.text}": ${remaining.join(", ")}` : `"${target.text}" now has no labels`,
239
- jsonData: { item: target.text, labels: remaining },
240
- };
241
- }
242
-
243
- export function handleListLabels(items: TodoItem[]): TodosActionResult {
244
- const inventory = listLabelsWithCount(items);
245
- const summary = inventory.map((entry) => `${entry.label} (${entry.count})`).join(", ");
246
- const message = inventory.length === 0 ? "No labels in use" : `${inventory.length} label(s) in use: ${summary}`;
247
- return {
248
- kind: "success",
249
- items,
250
- message,
251
- jsonData: { labels: inventory },
252
- };
253
- }
254
-
255
- const HANDLERS: Record<string, (items: TodoItem[], input: TodosActionInput) => TodosActionResult> = {
256
- show: handleShow,
257
- add: handleAdd,
258
- delete: handleDelete,
259
- update: handleUpdate,
260
- check: handleCheck,
261
- uncheck: handleUncheck,
262
- clear_completed: handleClearCompleted,
263
- add_label: handleAddLabel,
264
- remove_label: handleRemoveLabel,
265
- list_labels: handleListLabels,
266
- };
267
-
268
- export function dispatchTodos(action: string, items: TodoItem[], input: TodosActionInput): TodosActionResult {
269
- const handler = HANDLERS[action];
270
- if (!handler) {
271
- return { kind: "error", status: 400, error: `Unknown action: ${action}` };
272
- }
273
- return handler(items, input);
274
- }