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,79 @@
1
+ /**
2
+ * DOM helpers for the mini-editor cell-highlight pass. Extracted from
3
+ * the post-flush watch in `src/plugins/spreadsheet/View.vue`, which
4
+ * had a cognitive complexity of 30 driven by four levels of nested
5
+ * optional chaining + loops.
6
+ *
7
+ * These helpers are side-effectful by nature (they add/remove CSS
8
+ * classes on DOM nodes) but each one is small enough that its
9
+ * behaviour is obvious. Unit-testable with a minimal mock DOM; see
10
+ * `test/plugins/spreadsheet/test_cellHighlights.ts`.
11
+ */
12
+
13
+ /** Minimal DOM surface the helpers need. Defined here so tests can
14
+ * pass plain objects without pulling in jsdom. */
15
+ export interface HighlightableElement {
16
+ classList: { add: (cls: string) => void; remove: (cls: string) => void };
17
+ }
18
+
19
+ export interface HighlightableRow {
20
+ querySelectorAll: (selector: string) => ArrayLike<HighlightableElement>;
21
+ }
22
+
23
+ export interface HighlightableTable {
24
+ querySelectorAll: (selector: string) => ArrayLike<HighlightableRow>;
25
+ }
26
+
27
+ export interface HighlightableContainer {
28
+ // Overload: the spreadsheet root container is known to return a
29
+ // table when asked for the table id, so callers can keep the
30
+ // result strongly typed without casting.
31
+ querySelector(selector: "#spreadsheet-table"): HighlightableTable | null;
32
+ querySelector(selector: string): HighlightableElement | null;
33
+ querySelectorAll(selector: string): ArrayLike<HighlightableElement> & Iterable<HighlightableElement>;
34
+ }
35
+
36
+ export interface CellCoord {
37
+ row: number;
38
+ col: number;
39
+ }
40
+
41
+ const CELL_EDITING = "cell-editing";
42
+ const CELL_REFERENCED = "cell-referenced";
43
+
44
+ /** Remove both kinds of highlight classes from the container. */
45
+ export function clearCellHighlights(container: HighlightableContainer | null | undefined): void {
46
+ if (!container) return;
47
+ container.querySelector(`.${CELL_EDITING}`)?.classList.remove(CELL_EDITING);
48
+ for (const cell of container.querySelectorAll(`.${CELL_REFERENCED}`)) {
49
+ cell.classList.remove(CELL_REFERENCED);
50
+ }
51
+ }
52
+
53
+ /** Add `className` to the <td> at (row, col) of the given table.
54
+ * No-op if the row or cell doesn't exist. */
55
+ export function highlightCell(table: HighlightableTable | null | undefined, coord: CellCoord, className: string): void {
56
+ if (!table) return;
57
+ const rows = table.querySelectorAll("tr");
58
+ const row = rows[coord.row];
59
+ if (!row) return;
60
+ const cells = row.querySelectorAll("td");
61
+ const cell = cells[coord.col];
62
+ if (!cell) return;
63
+ cell.classList.add(className);
64
+ }
65
+
66
+ /** Apply the editing cell + referenced cells highlights. Looks up
67
+ * the #spreadsheet-table inside the container and no-ops if the
68
+ * table hasn't rendered yet. */
69
+ export function applyCellHighlights(
70
+ container: HighlightableContainer | null | undefined,
71
+ editingCell: CellCoord | null,
72
+ references: readonly CellCoord[],
73
+ ): void {
74
+ if (!container) return;
75
+ const table = container.querySelector("#spreadsheet-table");
76
+ if (!table) return;
77
+ if (editingCell) highlightCell(table, editingCell, CELL_EDITING);
78
+ for (const ref of references) highlightCell(table, ref, CELL_REFERENCED);
79
+ }
@@ -0,0 +1,121 @@
1
+ import type { ToolDefinition } from "gui-chat-protocol";
2
+
3
+ export const TOOL_NAME = "presentSpreadsheet";
4
+
5
+ export interface SpreadsheetCell {
6
+ v: string | number;
7
+ f?: string;
8
+ }
9
+
10
+ export interface SpreadsheetSheet {
11
+ name: string;
12
+ data: Array<Array<SpreadsheetCell>>;
13
+ }
14
+
15
+ export interface SpreadsheetToolData {
16
+ sheets: SpreadsheetSheet[] | string;
17
+ }
18
+
19
+ export interface SpreadsheetArgs {
20
+ title: string;
21
+ sheets: SpreadsheetSheet[];
22
+ }
23
+
24
+ const toolDefinition: ToolDefinition = {
25
+ type: "function",
26
+ name: TOOL_NAME,
27
+ description: "Display an Excel-like spreadsheet with formulas and calculations.",
28
+ prompt: `Use ${TOOL_NAME} when the user asks for a spreadsheet, table with calculations, or what-if analysis. Use formulas and cell references instead of pre-calculated values so the spreadsheet stays interactive. For cell format details and available functions, read \`helps/spreadsheet.md\` in the workspace.`,
29
+ parameters: {
30
+ type: "object",
31
+ properties: {
32
+ title: {
33
+ type: "string",
34
+ description: "Title for the spreadsheet",
35
+ },
36
+ sheets: {
37
+ type: "array",
38
+ description: "Sheets to render as spreadsheet tabs. Each sheet includes a name and 2D array of cells (rows x columns).",
39
+ items: {
40
+ type: "object",
41
+ properties: {
42
+ name: {
43
+ type: "string",
44
+ description: "Sheet name (e.g., 'Sales Q1', 'Summary')",
45
+ },
46
+ data: {
47
+ type: "array",
48
+ description:
49
+ 'Rows of cells. Each cell is an object with \'v\' (value) and \'f\' (format). Use Excel-style A1 notation in formulas: columns are letters (A, B, C...), rows are 1-based numbers (1, 2, 3...). Values can be text, numbers, dates, or formulas. Examples: [{"v": "Product"}, {"v": 2024, "f": "#,##0"}, {"v": "01/15/2025", "f": "MM/DD/YYYY"}, {"v": "=B2*1.05", "f": "$#,##0.00"}]. Format codes: \'$#,##0.00\' (currency), \'#,##0\' (integer), \'0.00%\' (percent), \'0.00\' (decimal), \'MM/DD/YYYY\' (date), \'DD-MMM-YYYY\' (date), \'YYYY-MM-DD\' (ISO date).',
50
+ items: {
51
+ type: "array",
52
+ description: "Row of cells. Each cell is an object with value and format.",
53
+ items: {
54
+ type: "object",
55
+ description: "Cell object with value and optional format. If value is a string starting with '=', it's treated as a formula.",
56
+ properties: {
57
+ v: {
58
+ oneOf: [{ type: "string" }, { type: "number" }],
59
+ description:
60
+ "Cell value. Can be text, number, date, or formula (string starting with '='). Examples: 'Revenue', 1500000, '01/15/2025', '=SUM(A1:A10)', '=B2-TODAY()'. Date strings like '01/15/2025' are automatically parsed to date serial numbers.",
61
+ },
62
+ f: {
63
+ type: "string",
64
+ description:
65
+ "Optional format code for displaying the value. Common formats: '$#,##0.00' (currency), '#,##0' (integer), '0.00%' (percent), '0.00' (decimal), 'MM/DD/YYYY' (date), 'DD-MMM-YYYY' (date), 'YYYY-MM-DD' (ISO date)",
66
+ },
67
+ },
68
+ required: ["v"],
69
+ },
70
+ },
71
+ },
72
+ },
73
+ required: ["name", "data"],
74
+ },
75
+ },
76
+ },
77
+ required: ["title", "sheets"],
78
+ },
79
+ };
80
+
81
+ export default toolDefinition;
82
+
83
+ export const executeSpreadsheet = async (
84
+ args: SpreadsheetArgs,
85
+ ): Promise<{
86
+ message: string;
87
+ title: string;
88
+ data: SpreadsheetToolData;
89
+ instructions: string;
90
+ }> => {
91
+ const { title } = args;
92
+ let { sheets } = args;
93
+
94
+ // Handle case where LLM accidentally stringifies the sheets array
95
+ if (typeof sheets === "string") {
96
+ try {
97
+ sheets = JSON.parse(sheets);
98
+ } catch (error) {
99
+ throw new Error(`Invalid sheets format: sheets must be an array, not a string. Parse error: ${error instanceof Error ? error.message : String(error)}`);
100
+ }
101
+ }
102
+
103
+ // Validate that sheets are provided
104
+ if (!Array.isArray(sheets) || sheets.length === 0) {
105
+ throw new Error("At least one sheet is required. Sheets must be an array of sheet objects.");
106
+ }
107
+
108
+ // Validate each sheet has data
109
+ for (const sheet of sheets) {
110
+ if (!sheet.name || !sheet.data || sheet.data.length === 0) {
111
+ throw new Error(`Invalid sheet: ${sheet.name || "unnamed"}. Each sheet must have a name and data array.`);
112
+ }
113
+ }
114
+
115
+ return {
116
+ message: `Created spreadsheet: ${title}`,
117
+ title,
118
+ data: { sheets },
119
+ instructions: "Acknowledge that the spreadsheet has been created and is displayed to the user.",
120
+ };
121
+ };
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Spreadsheet Calculator
3
+ *
4
+ * Core calculation engine with circular reference detection and cross-sheet support
5
+ */
6
+
7
+ import { formatNumber } from "./formatter";
8
+ import { columnToIndex } from "./parser";
9
+ import { evaluateFormula as evaluateFormulaFn } from "./evaluator";
10
+ import { parseDate, getDefaultDateFormat } from "./date-parser";
11
+ import type { SheetData, CellValue, CalculatedSheet, CalculationError, FormulaInfo, SpreadsheetCell } from "./types";
12
+ import { isObj } from "../../../utils/types";
13
+
14
+ /**
15
+ * Normalize malformed data structures
16
+ * Some models generate flat arrays instead of 2D arrays - fix them
17
+ *
18
+ * @param data - Potentially malformed sheet data
19
+ * @returns Normalized 2D array
20
+ */
21
+ function normalizeData(data: any): SpreadsheetCell[][] {
22
+ // Handle null/undefined
23
+ if (!data) {
24
+ return [];
25
+ }
26
+
27
+ // If not an array, wrap in array
28
+ if (!Array.isArray(data)) {
29
+ return [];
30
+ }
31
+
32
+ // Empty array
33
+ if (data.length === 0) {
34
+ return [];
35
+ }
36
+
37
+ // If data is already a 2D array, return as-is
38
+ if (Array.isArray(data[0])) {
39
+ return data as SpreadsheetCell[][];
40
+ }
41
+
42
+ // If data is a flat array of cell objects, convert to 2D by pairing cells
43
+ // Pattern: [cell1, cell2, cell3, cell4] -> [[cell1, cell2], [cell3, cell4]]
44
+ // This handles the case where models output flat arrays instead of rows
45
+ if (isObj(data[0])) {
46
+ const rows: SpreadsheetCell[][] = [];
47
+ for (let i = 0; i < data.length; i += 2) {
48
+ const row = [data[i]];
49
+ if (i + 1 < data.length) {
50
+ row.push(data[i + 1]);
51
+ }
52
+ rows.push(row);
53
+ }
54
+ return rows;
55
+ }
56
+
57
+ // Unknown structure - return empty
58
+ console.warn("Unknown data structure in spreadsheet, returning empty:", data);
59
+ return [];
60
+ }
61
+
62
+ /**
63
+ * Pre-process sheet data to parse date strings into serial numbers
64
+ *
65
+ * @param data - Raw sheet data
66
+ * @returns Processed data with dates converted to serial numbers
67
+ */
68
+ function preprocessDates(data: SpreadsheetCell[][]): SpreadsheetCell[][] {
69
+ return data.map((row) =>
70
+ row.map((cell) => {
71
+ // Skip if not a cell object or if it has a formula
72
+ if (!isObj(cell) || !("v" in cell)) {
73
+ return cell;
74
+ }
75
+
76
+ const value = cell.v;
77
+
78
+ // Only parse strings that aren't formulas
79
+ if (typeof value === "string" && !value.startsWith("=")) {
80
+ const dateSerial = parseDate(value);
81
+
82
+ if (dateSerial !== null) {
83
+ // It's a date! Convert to serial number
84
+ return {
85
+ v: dateSerial,
86
+ f: cell.f || getDefaultDateFormat(value), // Use existing format or detect from input
87
+ };
88
+ }
89
+ }
90
+
91
+ // Not a date, return as-is
92
+ return cell;
93
+ }),
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Calculate formulas in a single sheet
99
+ *
100
+ * @param sheet - Sheet data to calculate
101
+ * @param allSheets - All sheets for cross-sheet references
102
+ * @returns Calculated sheet with formulas evaluated
103
+ */
104
+ export function calculateSheet(sheet: SheetData, allSheets?: SheetData[]): CalculatedSheet {
105
+ // Normalize malformed data structures first
106
+ const normalizedData = normalizeData(sheet.data);
107
+
108
+ // Pre-process dates before calculation
109
+ const processedData = preprocessDates(normalizedData);
110
+
111
+ // Also preprocess all sheets if provided
112
+ const processedAllSheets = allSheets?.map((s) => ({
113
+ ...s,
114
+ data: preprocessDates(normalizeData(s.data)),
115
+ }));
116
+
117
+ const data = processedData;
118
+ const sheetName = sheet.name;
119
+ // Cache stores either SpreadsheetCell[][] (before calculation) or CellValue[][] (after)
120
+ const sheetsCache = new Map<string, (SpreadsheetCell | CellValue)[][]>();
121
+ const errors: CalculationError[] = [];
122
+ const formulas: FormulaInfo[] = [];
123
+
124
+ // Create a copy of the data with calculated values
125
+ const calculated: any[][] = data.map((row) => [...row]);
126
+
127
+ // Add current sheet to cache to prevent infinite loops
128
+ sheetsCache.set(sheetName, calculated);
129
+
130
+ // Track cells being calculated to detect circular references
131
+ const calculating = new Set<string>();
132
+
133
+ // Helper to extract raw value from cell with recursive formula evaluation
134
+ const getRawValue = (cell: any, row?: number, col?: number): CellValue => {
135
+ // Handle null/undefined cells - treat as 0
136
+ if (cell === null || cell === undefined) return 0;
137
+
138
+ if (typeof cell === "number") return cell;
139
+
140
+ // Handle string values (for legacy or calculated cells)
141
+ if (typeof cell === "string") {
142
+ // Handle empty strings as 0
143
+ if (cell.trim() === "") return 0;
144
+
145
+ // Handle percentage strings like "5%" or "0.4167%"
146
+ if (cell.includes("%")) {
147
+ const numericPart = cell.replace("%", "").trim();
148
+ const value = parseFloat(numericPart);
149
+ return isNaN(value) ? 0 : value / 100;
150
+ }
151
+ // Handle currency strings like "$1,000" or "$1,000.00"
152
+ if (cell.includes("$")) {
153
+ const numericPart = cell.replace(/[$,]/g, "").trim();
154
+ const value = parseFloat(numericPart);
155
+ return isNaN(value) ? 0 : value;
156
+ }
157
+ // Handle comma-separated numbers like "1,000"
158
+ if (cell.includes(",")) {
159
+ const numericPart = cell.replace(/,/g, "").trim();
160
+ const value = parseFloat(numericPart);
161
+ return isNaN(value) ? 0 : value;
162
+ }
163
+ // Handle regular numeric strings, but preserve non-numeric strings
164
+ const num = parseFloat(cell);
165
+ return isNaN(num) ? cell : num;
166
+ }
167
+
168
+ // Handle new cell format {v, f}
169
+ if (isObj(cell) && "v" in cell) {
170
+ const value = cell.v;
171
+ // If value is a string starting with "=", it's a formula
172
+ if (typeof value === "string" && value.startsWith("=")) {
173
+ // Check if we have row/col info to evaluate recursively
174
+ if (row !== undefined && col !== undefined) {
175
+ const cellKey = `${row},${col}`;
176
+
177
+ // Check for circular reference
178
+ if (calculating.has(cellKey)) {
179
+ console.warn(`Circular reference detected at row ${row}, col ${col}`);
180
+ errors.push({
181
+ cell: { row, col },
182
+ formula: value,
183
+ error: "Circular reference detected",
184
+ type: "circular",
185
+ });
186
+ return 0;
187
+ }
188
+
189
+ // Check if already calculated (result is cached as a number)
190
+ const calculatedCell = calculated[row][col];
191
+ if (typeof calculatedCell === "number") {
192
+ return calculatedCell;
193
+ }
194
+
195
+ // Recursively evaluate the formula
196
+ calculating.add(cellKey);
197
+ try {
198
+ const formula = value.substring(1); // Remove "=" prefix
199
+ const result = evaluateFormula(formula);
200
+ calculating.delete(cellKey);
201
+
202
+ // Cache the calculated result (preserve strings and numbers)
203
+ calculated[row][col] = result;
204
+
205
+ return result;
206
+ } catch (error) {
207
+ calculating.delete(cellKey);
208
+ console.error(`Error evaluating formula at row ${row}, col ${col}:`, error);
209
+ errors.push({
210
+ cell: { row, col },
211
+ formula: value,
212
+ error: error instanceof Error ? error.message : String(error),
213
+ type: "unknown",
214
+ });
215
+ return 0;
216
+ }
217
+ }
218
+ return 0; // No position info, can't evaluate
219
+ }
220
+ // Try to parse as number, but preserve original type on failure
221
+ if (typeof value === "number") return value;
222
+ if (typeof value === "boolean") return value;
223
+ if (typeof value === "string") {
224
+ const num = parseFloat(value);
225
+ return isNaN(num) ? value : num;
226
+ }
227
+ return String(value);
228
+ }
229
+
230
+ // Try to parse cell as number, but preserve strings
231
+ const num = parseFloat(cell);
232
+ return isNaN(num) ? cell : num;
233
+ };
234
+
235
+ // Helper to get cell value by reference (e.g., "B2", "$B$2", or "'Sheet1'!B2")
236
+ const getCellValue = (ref: string): CellValue => {
237
+ let sheetData: any[][] = calculated;
238
+ let cellRef = ref;
239
+ let isCurrentSheet = true;
240
+
241
+ // Check for cross-sheet reference (e.g., 'Sheet Name'!B2 or Sheet1!B2)
242
+ const sheetMatch = ref.match(/^(?:'([^']+)'|([^!]+))!(.+)$/);
243
+ if (sheetMatch) {
244
+ const targetSheetName = sheetMatch[1] || sheetMatch[2]; // Quoted or unquoted sheet name
245
+ cellRef = sheetMatch[3]; // Cell reference part
246
+ isCurrentSheet = false;
247
+
248
+ // Check cache first to prevent infinite loops
249
+ if (sheetsCache.has(targetSheetName)) {
250
+ sheetData = sheetsCache.get(targetSheetName)!;
251
+ } else {
252
+ // Find the sheet in all sheets
253
+ const targetSheet = processedAllSheets?.find((s) => s.name === targetSheetName);
254
+ if (targetSheet && targetSheet.data) {
255
+ // Calculate formulas for the target sheet with cache
256
+ const targetCalculated = targetSheet.data.map((row) => [...row]);
257
+ sheetsCache.set(targetSheetName, targetCalculated);
258
+
259
+ // Recursively calculate the target sheet
260
+ const targetResult = calculateSheet(targetSheet, processedAllSheets);
261
+ sheetsCache.set(targetSheetName, targetResult.data);
262
+ sheetData = targetResult.data as any[][];
263
+ } else {
264
+ return 0; // Sheet not found
265
+ }
266
+ }
267
+ }
268
+
269
+ // Remove $ symbols for absolute references
270
+ const cleanRef = cellRef.replace(/\$/g, "");
271
+ const match = cleanRef.match(/^([A-Z]+)(\d+)$/);
272
+ if (!match) return 0;
273
+
274
+ const col = columnToIndex(match[1]); // A=0, B=1, ..., Z=25, AA=26, etc.
275
+ const row = parseInt(match[2]) - 1; // 1-indexed to 0-indexed
276
+
277
+ if (row < 0 || row >= sheetData.length || col < 0 || col >= sheetData[row].length) {
278
+ return 0;
279
+ }
280
+
281
+ const cell = sheetData[row][col];
282
+ // Pass row/col only if this is the current sheet (for recursive evaluation)
283
+ return getRawValue(cell, isCurrentSheet ? row : undefined, isCurrentSheet ? col : undefined);
284
+ };
285
+
286
+ const collectRangeValues = (range: string, options: { numericOnly: boolean }): CellValue[] => {
287
+ let sheetData: any[][] = calculated;
288
+ let rangeRef = range;
289
+ let isCurrentSheet = true;
290
+
291
+ // Check for cross-sheet reference
292
+ const sheetMatch = range.match(/^(?:'([^']+)'|([^!]+))!(.+)$/);
293
+ if (sheetMatch) {
294
+ const targetSheetName = sheetMatch[1] || sheetMatch[2];
295
+ rangeRef = sheetMatch[3];
296
+ isCurrentSheet = false;
297
+
298
+ // Check cache first
299
+ if (sheetsCache.has(targetSheetName)) {
300
+ sheetData = sheetsCache.get(targetSheetName)!;
301
+ } else {
302
+ // Find and calculate the target sheet
303
+ const targetSheet = processedAllSheets?.find((s) => s.name === targetSheetName);
304
+ if (targetSheet && targetSheet.data) {
305
+ const targetCalculated = targetSheet.data.map((row) => [...row]);
306
+ sheetsCache.set(targetSheetName, targetCalculated);
307
+
308
+ // Recursively calculate the target sheet
309
+ const targetResult = calculateSheet(targetSheet, processedAllSheets);
310
+ sheetsCache.set(targetSheetName, targetResult.data);
311
+ sheetData = targetResult.data as any[][];
312
+ } else {
313
+ return [];
314
+ }
315
+ }
316
+ }
317
+
318
+ const match = rangeRef.match(/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/);
319
+ if (!match) return [];
320
+
321
+ const startCol = columnToIndex(match[1]);
322
+ const startRow = parseInt(match[2]) - 1;
323
+ const endCol = columnToIndex(match[3]);
324
+ const endRow = parseInt(match[4]) - 1;
325
+
326
+ const values: CellValue[] = [];
327
+ for (let row = startRow; row <= endRow; row++) {
328
+ for (let col = startCol; col <= endCol; col++) {
329
+ if (row >= 0 && row < sheetData.length && col >= 0 && col < sheetData[row].length) {
330
+ const cell = sheetData[row][col];
331
+ // Pass row/col only if current sheet (for recursive evaluation)
332
+ const rawValue = getRawValue(cell, isCurrentSheet ? row : undefined, isCurrentSheet ? col : undefined);
333
+
334
+ if (options.numericOnly) {
335
+ if (!isNaN(rawValue as number)) {
336
+ values.push(rawValue);
337
+ }
338
+ } else {
339
+ values.push(rawValue);
340
+ }
341
+ }
342
+ }
343
+ }
344
+ return values;
345
+ };
346
+
347
+ // Helper to get numeric-only range values (legacy behavior)
348
+ const getRangeValues = (range: string): CellValue[] => collectRangeValues(range, { numericOnly: true });
349
+
350
+ // Helper to get raw range values including text
351
+ const getRangeValuesRaw = (range: string): CellValue[] => collectRangeValues(range, { numericOnly: false });
352
+
353
+ // Evaluate a formula with context
354
+ const evaluateFormula = (formula: string): CellValue => {
355
+ return evaluateFormulaFn(formula, {
356
+ getCellValue,
357
+ getRangeValues,
358
+ getRangeValuesRaw,
359
+ evaluateFormula,
360
+ });
361
+ };
362
+
363
+ // Process all cells and calculate formulas
364
+ for (let rowIdx = 0; rowIdx < data.length; rowIdx++) {
365
+ for (let colIdx = 0; colIdx < data[rowIdx].length; colIdx++) {
366
+ const originalCell = data[rowIdx][colIdx];
367
+ const calculatedCell = calculated[rowIdx][colIdx];
368
+
369
+ // Skip if cell was already calculated recursively
370
+ if (typeof calculatedCell === "number" && isObj(originalCell) && "f" in originalCell) {
371
+ // Cell was already evaluated - keep it as number for now
372
+ // Formatting will be applied at the end
373
+ continue;
374
+ }
375
+
376
+ // Handle cell format {v, f}
377
+ if (isObj(originalCell) && "v" in originalCell) {
378
+ const value = originalCell.v;
379
+
380
+ // Check if value is a formula (string starting with "=")
381
+ if (typeof value === "string" && value.startsWith("=")) {
382
+ // Remove the "=" prefix and evaluate the formula
383
+ const formula = value.substring(1);
384
+
385
+ // Track formula info
386
+ formulas.push({
387
+ cell: { row: rowIdx, col: colIdx },
388
+ formula: value,
389
+ dependencies: [], // TODO: Extract dependencies from formula
390
+ result: 0, // Will be updated below
391
+ });
392
+
393
+ const result = evaluateFormula(formula);
394
+
395
+ // Update formula result
396
+ formulas[formulas.length - 1].result = result;
397
+
398
+ // Store result as-is (formatting will be applied at the end)
399
+ calculated[rowIdx][colIdx] = result;
400
+ } else {
401
+ // Regular value cell (not a formula)
402
+ // Convert to plain value (important for range evaluation)
403
+ calculated[rowIdx][colIdx] = value;
404
+ }
405
+ }
406
+ // If cell is not in {v, f} format, leave it as-is (already a plain value)
407
+ }
408
+ }
409
+
410
+ // Final formatting pass: apply formatting to all cells with format codes
411
+ for (let rowIdx = 0; rowIdx < data.length; rowIdx++) {
412
+ for (let colIdx = 0; colIdx < data[rowIdx].length; colIdx++) {
413
+ const originalCell = data[rowIdx][colIdx];
414
+ const calculatedValue = calculated[rowIdx][colIdx];
415
+
416
+ if (isObj(originalCell) && "v" in originalCell) {
417
+ const isFormula = typeof originalCell.v === "string" && originalCell.v.startsWith("=");
418
+
419
+ // Apply formatting if cell has a format code and calculated value is a number
420
+ if ("f" in originalCell && originalCell.f && typeof calculatedValue === "number") {
421
+ calculated[rowIdx][colIdx] = formatNumber(calculatedValue, originalCell.f);
422
+ }
423
+ // Auto-format date serial numbers from formulas without explicit format
424
+ else if (
425
+ isFormula &&
426
+ typeof calculatedValue === "number" &&
427
+ calculatedValue >= 36000 &&
428
+ calculatedValue <= 63499 &&
429
+ Number.isInteger(calculatedValue) &&
430
+ (!("f" in originalCell) || !originalCell.f)
431
+ ) {
432
+ // Check if this looks like a date serial number
433
+ // 36000 = Jul 1998, 63499 = Dec 2073
434
+ // Must be integer (dates without time component)
435
+ // Avoids formatting calculated averages/sums as dates
436
+ // Apply default date format
437
+ calculated[rowIdx][colIdx] = formatNumber(calculatedValue, "MM/DD/YYYY");
438
+ }
439
+ }
440
+ }
441
+ }
442
+
443
+ return {
444
+ name: sheetName,
445
+ data: calculated,
446
+ formulas,
447
+ errors,
448
+ };
449
+ }
450
+
451
+ /**
452
+ * Calculate all sheets in a workbook
453
+ *
454
+ * @param sheets - Array of sheets to calculate
455
+ * @returns Array of calculated sheets
456
+ */
457
+ export function calculateWorkbook(sheets: SheetData[]): CalculatedSheet[] {
458
+ return sheets.map((sheet) => calculateSheet(sheet, sheets));
459
+ }