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,389 @@
1
+ /**
2
+ * Text Functions
3
+ */
4
+
5
+ import { functionRegistry, toString, type FunctionHandler } from "../registry";
6
+
7
+ const concatenateHandler: FunctionHandler = (args, context) => {
8
+ if (args.length === 0) throw new Error("CONCATENATE requires at least 1 argument");
9
+
10
+ return args
11
+ .map((arg) => {
12
+ const value = context.evaluateFormula(arg.trim());
13
+ return toString(value);
14
+ })
15
+ .join("");
16
+ };
17
+
18
+ const concatHandler: FunctionHandler = concatenateHandler; // Alias
19
+
20
+ const leftHandler: FunctionHandler = (args, context) => {
21
+ if (args.length < 1 || args.length > 2) {
22
+ throw new Error("LEFT requires 1 or 2 arguments");
23
+ }
24
+
25
+ const text = toString(context.evaluateFormula(args[0]));
26
+ const numChars = args.length === 2 ? Number(context.evaluateFormula(args[1])) : 1;
27
+
28
+ return text.substring(0, numChars);
29
+ };
30
+
31
+ const rightHandler: FunctionHandler = (args, context) => {
32
+ if (args.length < 1 || args.length > 2) {
33
+ throw new Error("RIGHT requires 1 or 2 arguments");
34
+ }
35
+
36
+ const text = toString(context.evaluateFormula(args[0]));
37
+ const numChars = args.length === 2 ? Number(context.evaluateFormula(args[1])) : 1;
38
+
39
+ return text.substring(text.length - numChars);
40
+ };
41
+
42
+ const midHandler: FunctionHandler = (args, context) => {
43
+ if (args.length !== 3) throw new Error("MID requires 3 arguments");
44
+
45
+ const text = toString(context.evaluateFormula(args[0]));
46
+ const start = Number(context.evaluateFormula(args[1])) - 1; // 1-indexed to 0-indexed
47
+ const numChars = Number(context.evaluateFormula(args[2]));
48
+
49
+ return text.substring(start, start + numChars);
50
+ };
51
+
52
+ const lenHandler: FunctionHandler = (args, context) => {
53
+ if (args.length !== 1) throw new Error("LEN requires 1 argument");
54
+
55
+ const text = toString(context.evaluateFormula(args[0]));
56
+ return text.length;
57
+ };
58
+
59
+ const upperHandler: FunctionHandler = (args, context) => {
60
+ if (args.length !== 1) throw new Error("UPPER requires 1 argument");
61
+
62
+ const text = toString(context.evaluateFormula(args[0]));
63
+ return text.toUpperCase();
64
+ };
65
+
66
+ const lowerHandler: FunctionHandler = (args, context) => {
67
+ if (args.length !== 1) throw new Error("LOWER requires 1 argument");
68
+
69
+ const text = toString(context.evaluateFormula(args[0]));
70
+ return text.toLowerCase();
71
+ };
72
+
73
+ const properHandler: FunctionHandler = (args, context) => {
74
+ if (args.length !== 1) throw new Error("PROPER requires 1 argument");
75
+
76
+ const text = toString(context.evaluateFormula(args[0]));
77
+ return text
78
+ .toLowerCase()
79
+ .split(" ")
80
+ .map((word) => (word.length > 0 ? word[0].toUpperCase() + word.slice(1) : ""))
81
+ .join(" ");
82
+ };
83
+
84
+ const trimHandler: FunctionHandler = (args, context) => {
85
+ if (args.length !== 1) throw new Error("TRIM requires 1 argument");
86
+
87
+ const text = toString(context.evaluateFormula(args[0]));
88
+ // Trim leading/trailing spaces and replace multiple spaces with single space
89
+ return text.trim().replace(/\s+/g, " ");
90
+ };
91
+
92
+ const substituteHandler: FunctionHandler = (args, context) => {
93
+ if (args.length < 3 || args.length > 4) {
94
+ throw new Error("SUBSTITUTE requires 3 or 4 arguments");
95
+ }
96
+
97
+ const text = toString(context.evaluateFormula(args[0]));
98
+ const oldText = toString(context.evaluateFormula(args[1]));
99
+ const newText = toString(context.evaluateFormula(args[2]));
100
+
101
+ if (args.length === 4) {
102
+ // Replace specific instance
103
+ const instance = Number(context.evaluateFormula(args[3]));
104
+ let count = 0;
105
+ let index = 0;
106
+
107
+ while (index < text.length) {
108
+ const pos = text.indexOf(oldText, index);
109
+ if (pos === -1) break;
110
+
111
+ count++;
112
+ if (count === instance) {
113
+ return text.substring(0, pos) + newText + text.substring(pos + oldText.length);
114
+ }
115
+ index = pos + 1;
116
+ }
117
+ return text; // Instance not found
118
+ } else {
119
+ // Replace all instances
120
+ return text.split(oldText).join(newText);
121
+ }
122
+ };
123
+
124
+ const replaceHandler: FunctionHandler = (args, context) => {
125
+ if (args.length !== 4) throw new Error("REPLACE requires 4 arguments");
126
+
127
+ const oldText = toString(context.evaluateFormula(args[0]));
128
+ const startPos = Number(context.evaluateFormula(args[1])) - 1; // 1-indexed to 0-indexed
129
+ const numChars = Number(context.evaluateFormula(args[2]));
130
+ const newText = toString(context.evaluateFormula(args[3]));
131
+
132
+ return oldText.substring(0, startPos) + newText + oldText.substring(startPos + numChars);
133
+ };
134
+
135
+ const findHandler: FunctionHandler = (args, context) => {
136
+ if (args.length < 2 || args.length > 3) {
137
+ throw new Error("FIND requires 2 or 3 arguments");
138
+ }
139
+
140
+ const findText = toString(context.evaluateFormula(args[0]));
141
+ const withinText = toString(context.evaluateFormula(args[1]));
142
+ const startPos = args.length === 3 ? Number(context.evaluateFormula(args[2])) - 1 : 0;
143
+
144
+ const index = withinText.indexOf(findText, startPos);
145
+ return index === -1 ? "#VALUE!" : index + 1; // Return 1-indexed position
146
+ };
147
+
148
+ const searchHandler: FunctionHandler = (args, context) => {
149
+ if (args.length < 2 || args.length > 3) {
150
+ throw new Error("SEARCH requires 2 or 3 arguments");
151
+ }
152
+
153
+ const findText = toString(context.evaluateFormula(args[0]));
154
+ const withinText = toString(context.evaluateFormula(args[1]));
155
+ const startPos = args.length === 3 ? Number(context.evaluateFormula(args[2])) - 1 : 0;
156
+
157
+ // SEARCH is case-insensitive
158
+ const lowerFind = findText.toLowerCase();
159
+ const lowerWithin = withinText.toLowerCase();
160
+
161
+ const index = lowerWithin.indexOf(lowerFind, startPos);
162
+ return index === -1 ? "#VALUE!" : index + 1; // Return 1-indexed position
163
+ };
164
+
165
+ const textHandler: FunctionHandler = (args, context) => {
166
+ if (args.length !== 2) throw new Error("TEXT requires 2 arguments");
167
+
168
+ const value = context.evaluateFormula(args[0]);
169
+ const format = toString(context.evaluateFormula(args[1])).replace(
170
+ // eslint-disable -- sonarjs/anchor-precedence
171
+ /^["']|["']$/g,
172
+ "",
173
+ );
174
+
175
+ // Simple format code handling
176
+ if (typeof value === "number") {
177
+ // Handle common format codes
178
+ if (format.includes("$")) {
179
+ const decimals = (format.match(/\.0+/) || [""])[0].length - 1;
180
+ return "$" + value.toFixed(decimals >= 0 ? decimals : 2);
181
+ }
182
+ if (format.includes("%")) {
183
+ const decimals = (format.match(/\.0+/) || [""])[0].length - 1;
184
+ return (value * 100).toFixed(decimals >= 0 ? decimals : 2) + "%";
185
+ }
186
+ if (format.includes("0")) {
187
+ const decimals = (format.match(/\.0+/) || [""])[0].length - 1;
188
+ return value.toFixed(decimals >= 0 ? decimals : 0);
189
+ }
190
+ }
191
+
192
+ return toString(value);
193
+ };
194
+
195
+ const valueHandler: FunctionHandler = (args, context) => {
196
+ if (args.length !== 1) throw new Error("VALUE requires 1 argument");
197
+
198
+ const text = toString(context.evaluateFormula(args[0]));
199
+
200
+ // Remove currency symbols and commas
201
+ const cleaned = text.replace(/[$,]/g, "").trim();
202
+
203
+ // Handle percentages
204
+ if (cleaned.includes("%")) {
205
+ const num = parseFloat(cleaned.replace("%", ""));
206
+ return isNaN(num) ? "#VALUE!" : num / 100;
207
+ }
208
+
209
+ const num = parseFloat(cleaned);
210
+ return isNaN(num) ? "#VALUE!" : num;
211
+ };
212
+
213
+ const exactHandler: FunctionHandler = (args, context) => {
214
+ if (args.length !== 2) throw new Error("EXACT requires 2 arguments");
215
+
216
+ const text1 = toString(context.evaluateFormula(args[0]));
217
+ const text2 = toString(context.evaluateFormula(args[1]));
218
+
219
+ return text1 === text2;
220
+ };
221
+
222
+ // Register all text functions
223
+ functionRegistry.register({
224
+ name: "CONCATENATE",
225
+ handler: concatenateHandler,
226
+ minArgs: 1,
227
+ description: "Joins several text strings into one string",
228
+ examples: ['CONCATENATE("Hello", " ", "World")', "CONCATENATE(A1, B1)"],
229
+ category: "Text",
230
+ });
231
+
232
+ functionRegistry.register({
233
+ name: "CONCAT",
234
+ handler: concatHandler,
235
+ minArgs: 1,
236
+ description: "Joins several text strings into one string (same as CONCATENATE)",
237
+ examples: ['CONCAT("Hello", " ", "World")', "CONCAT(A1, B1)"],
238
+ category: "Text",
239
+ });
240
+
241
+ functionRegistry.register({
242
+ name: "LEFT",
243
+ handler: leftHandler,
244
+ minArgs: 1,
245
+ maxArgs: 2,
246
+ description: "Returns the leftmost characters from a text string",
247
+ examples: ['LEFT("Hello", 2)', "LEFT(A1, 3)"],
248
+ category: "Text",
249
+ });
250
+
251
+ functionRegistry.register({
252
+ name: "RIGHT",
253
+ handler: rightHandler,
254
+ minArgs: 1,
255
+ maxArgs: 2,
256
+ description: "Returns the rightmost characters from a text string",
257
+ examples: ['RIGHT("Hello", 2)', "RIGHT(A1, 3)"],
258
+ category: "Text",
259
+ });
260
+
261
+ functionRegistry.register({
262
+ name: "MID",
263
+ handler: midHandler,
264
+ minArgs: 3,
265
+ maxArgs: 3,
266
+ description: "Returns characters from the middle of a text string",
267
+ examples: ['MID("Hello", 2, 3)', "MID(A1, 1, 5)"],
268
+ category: "Text",
269
+ });
270
+
271
+ functionRegistry.register({
272
+ name: "LEN",
273
+ handler: lenHandler,
274
+ minArgs: 1,
275
+ maxArgs: 1,
276
+ description: "Returns the number of characters in a text string",
277
+ examples: ['LEN("Hello")', "LEN(A1)"],
278
+ category: "Text",
279
+ });
280
+
281
+ functionRegistry.register({
282
+ name: "UPPER",
283
+ handler: upperHandler,
284
+ minArgs: 1,
285
+ maxArgs: 1,
286
+ description: "Converts text to uppercase",
287
+ examples: ['UPPER("hello")', "UPPER(A1)"],
288
+ category: "Text",
289
+ });
290
+
291
+ functionRegistry.register({
292
+ name: "LOWER",
293
+ handler: lowerHandler,
294
+ minArgs: 1,
295
+ maxArgs: 1,
296
+ description: "Converts text to lowercase",
297
+ examples: ['LOWER("HELLO")', "LOWER(A1)"],
298
+ category: "Text",
299
+ });
300
+
301
+ functionRegistry.register({
302
+ name: "PROPER",
303
+ handler: properHandler,
304
+ minArgs: 1,
305
+ maxArgs: 1,
306
+ description: "Capitalizes the first letter of each word",
307
+ examples: ['PROPER("hello world")', "PROPER(A1)"],
308
+ category: "Text",
309
+ });
310
+
311
+ functionRegistry.register({
312
+ name: "TRIM",
313
+ handler: trimHandler,
314
+ minArgs: 1,
315
+ maxArgs: 1,
316
+ description: "Removes extra spaces from text",
317
+ examples: ['TRIM(" hello world ")', "TRIM(A1)"],
318
+ category: "Text",
319
+ });
320
+
321
+ functionRegistry.register({
322
+ name: "SUBSTITUTE",
323
+ handler: substituteHandler,
324
+ minArgs: 3,
325
+ maxArgs: 4,
326
+ description: "Replaces old text with new text in a string",
327
+ examples: ['SUBSTITUTE("Hello World", "World", "Earth")', 'SUBSTITUTE(A1, "old", "new", 1)'],
328
+ category: "Text",
329
+ });
330
+
331
+ functionRegistry.register({
332
+ name: "REPLACE",
333
+ handler: replaceHandler,
334
+ minArgs: 4,
335
+ maxArgs: 4,
336
+ description: "Replaces part of a text string with a different text string",
337
+ examples: ['REPLACE("Hello World", 7, 5, "Earth")', 'REPLACE(A1, 1, 3, "New")'],
338
+ category: "Text",
339
+ });
340
+
341
+ functionRegistry.register({
342
+ name: "FIND",
343
+ handler: findHandler,
344
+ minArgs: 2,
345
+ maxArgs: 3,
346
+ description: "Finds one text string within another (case-sensitive)",
347
+ examples: ['FIND("o", "Hello")', 'FIND("World", A1)'],
348
+ category: "Text",
349
+ });
350
+
351
+ functionRegistry.register({
352
+ name: "SEARCH",
353
+ handler: searchHandler,
354
+ minArgs: 2,
355
+ maxArgs: 3,
356
+ description: "Finds one text string within another (case-insensitive)",
357
+ examples: ['SEARCH("O", "Hello")', 'SEARCH("world", A1)'],
358
+ category: "Text",
359
+ });
360
+
361
+ functionRegistry.register({
362
+ name: "TEXT",
363
+ handler: textHandler,
364
+ minArgs: 2,
365
+ maxArgs: 2,
366
+ description: "Formats a number and converts it to text",
367
+ examples: ['TEXT(1234.5, "$#,##0.00")', 'TEXT(0.5, "0%")'],
368
+ category: "Text",
369
+ });
370
+
371
+ functionRegistry.register({
372
+ name: "VALUE",
373
+ handler: valueHandler,
374
+ minArgs: 1,
375
+ maxArgs: 1,
376
+ description: "Converts a text string to a number",
377
+ examples: ['VALUE("123")', 'VALUE("$1,234.56")'],
378
+ category: "Text",
379
+ });
380
+
381
+ functionRegistry.register({
382
+ name: "EXACT",
383
+ handler: exactHandler,
384
+ minArgs: 2,
385
+ maxArgs: 2,
386
+ description: "Checks if two text strings are exactly the same",
387
+ examples: ['EXACT("Hello", "hello")', "EXACT(A1, B1)"],
388
+ category: "Text",
389
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Spreadsheet Engine
3
+ *
4
+ * Framework-agnostic spreadsheet calculation engine
5
+ */
6
+
7
+ // Export types
8
+ export * from "./types";
9
+
10
+ // Export utilities
11
+ export * from "./parser";
12
+ export * from "./formatter";
13
+ export * from "./evaluator";
14
+ export * from "./calculator";
15
+ export * from "./formulaRefs";
16
+ export * from "./cellBuilder";
17
+ export * from "./responseDecoder";
18
+ export * from "./jsonCellLocator";
19
+
20
+ // Export function registry
21
+ export * from "./registry";
22
+
23
+ // Load all built-in functions
24
+ import "./functions";
25
+
26
+ // Export main SpreadsheetEngine class
27
+ export { SpreadsheetEngine } from "./engine";
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Locate the start offset of a specific cell value inside a
3
+ * pretty-printed JSON spreadsheet document. Extracted from
4
+ * `handleTableClick` in `src/plugins/spreadsheet/View.vue` where the
5
+ * inline scanner pushed cognitive complexity to 163.
6
+ *
7
+ * The scanner walks the raw editor text character-by-character,
8
+ * tracking string boundaries, bracket depth, and object depth to
9
+ * find the n-th cell of the m-th row within a named sheet's `data`
10
+ * array. We deliberately do NOT parse the JSON — we need the
11
+ * character offset inside the user's text buffer (which may not be
12
+ * valid JSON mid-edit), so a positional scan is required.
13
+ *
14
+ * Pure — no refs, no DOM. Returns -1 if the cell can't be located.
15
+ * Tested in `test/plugins/spreadsheet/engine/test_jsonCellLocator.ts`.
16
+ */
17
+
18
+ // Advance `pos` through `text` until we reach the `rowIndex`-th
19
+ // opening `[` after `startPos` (counting from -1 so the first `[`
20
+ // encountered is index 0). Returns the position just after that
21
+ // opening bracket, or -1 if we ran off the end.
22
+ function findRowOpenBracket(text: string, startPos: number, rowIndex: number): number {
23
+ let currentRow = -1;
24
+ let inString = false;
25
+ for (let i = startPos; i < text.length; i++) {
26
+ const c = text[i];
27
+ const prevChar = i > 0 ? text[i - 1] : "";
28
+ // Track string literal boundaries so that a `[` inside a cell
29
+ // value like `"has [bracket]"` doesn't get mistaken for a row
30
+ // opener and throw off the row offset.
31
+ if (c === '"' && prevChar !== "\\") {
32
+ inString = !inString;
33
+ continue;
34
+ }
35
+ if (!inString && c === "[") {
36
+ currentRow++;
37
+ if (currentRow === rowIndex) return i + 1;
38
+ }
39
+ }
40
+ return -1;
41
+ }
42
+
43
+ // Starting just inside the row's `[`, scan for the start offset of
44
+ // the `colIndex`-th cell. Tracks string/object/bracket state so
45
+ // commas inside cell objects don't miscounted as cell separators.
46
+ // Returns -1 if the row ends before we reach colIndex.
47
+ function findCellStartWithinRow(text: string, rowStart: number, colIndex: number): number {
48
+ let currentCol = 0;
49
+ let inString = false;
50
+ let inObject = 0;
51
+ let bracketDepth = 1; // we already stepped past one `[`
52
+
53
+ for (let i = rowStart; i < text.length; i++) {
54
+ const c = text[i];
55
+ const prevChar = i > 0 ? text[i - 1] : "";
56
+
57
+ if (c === '"' && prevChar !== "\\") {
58
+ inString = !inString;
59
+ }
60
+
61
+ if (!inString) {
62
+ if (c === "[") bracketDepth++;
63
+ if (c === "]") {
64
+ bracketDepth--;
65
+ if (bracketDepth === 0) return -1; // row ended before colIndex
66
+ }
67
+ if (c === "{") inObject++;
68
+ if (c === "}") inObject--;
69
+ if (c === "," && inObject === 0 && bracketDepth === 1) {
70
+ currentCol++;
71
+ }
72
+ }
73
+
74
+ // Once currentCol matches, skip any structural whitespace /
75
+ // opening bracket / comma and return the first content char.
76
+ if (currentCol === colIndex) {
77
+ if (c !== " " && c !== "\n" && c !== "\t" && c !== "[" && c !== ",") {
78
+ return i;
79
+ }
80
+ }
81
+ }
82
+ return -1;
83
+ }
84
+
85
+ /**
86
+ * Given the pretty-printed JSON editor text, a sheet name, and
87
+ * (rowIndex, colIndex), return the character offset of that cell's
88
+ * JSON token. The returned offset can be fed into
89
+ * `textarea.setSelectionRange(offset, offset + cellJsonLength)` to
90
+ * highlight the cell.
91
+ *
92
+ * Returns -1 if the sheet isn't found or the (row, col) is out of
93
+ * range. Never throws.
94
+ */
95
+ export function findCellJsonPosition(editorText: string, sheetName: string, rowIndex: number, colIndex: number): number {
96
+ // JSON.stringify escapes embedded quotes/backslashes so the marker
97
+ // matches the way the sheet name actually appears in editorText.
98
+ const sheetStartMarker = `"name": ${JSON.stringify(sheetName)}`;
99
+ const dataStartMarker = `"data": [`;
100
+
101
+ const sheetPos = editorText.indexOf(sheetStartMarker);
102
+ if (sheetPos === -1) return -1;
103
+
104
+ const dataPos = editorText.indexOf(dataStartMarker, sheetPos);
105
+ if (dataPos === -1) return -1;
106
+
107
+ const rowStart = findRowOpenBracket(editorText, dataPos + dataStartMarker.length, rowIndex);
108
+ if (rowStart === -1) return -1;
109
+
110
+ return findCellStartWithinRow(editorText, rowStart, colIndex);
111
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Cell and Range Reference Parsing
3
+ *
4
+ * Handles Excel A1 notation parsing and conversion
5
+ */
6
+
7
+ import type { CellRef, RangeRef } from "./types";
8
+
9
+ /**
10
+ * Convert Excel column letters to 0-based index
11
+ * A=0, B=1, ..., Z=25, AA=26, etc.
12
+ *
13
+ * @param col - Column letters (e.g., "A", "Z", "AA")
14
+ * @returns 0-based column index
15
+ */
16
+ export function columnToIndex(col: string): number {
17
+ let result = 0;
18
+ for (let i = 0; i < col.length; i++) {
19
+ result = result * 26 + (col.charCodeAt(i) - 64); // A=1, B=2, etc.
20
+ }
21
+ return result - 1; // Convert to 0-based
22
+ }
23
+
24
+ /**
25
+ * Convert 0-based index to Excel column letters
26
+ * 0=A, 1=B, ..., 25=Z, 26=AA, etc.
27
+ *
28
+ * @param index - 0-based column index
29
+ * @returns Column letters (e.g., "A", "Z", "AA")
30
+ */
31
+ export function indexToColumn(index: number): string {
32
+ let col = "";
33
+ let num = index + 1; // Convert to 1-based
34
+ while (num > 0) {
35
+ const remainder = (num - 1) % 26;
36
+ col = String.fromCharCode(65 + remainder) + col;
37
+ num = Math.floor((num - 1) / 26);
38
+ }
39
+ return col;
40
+ }
41
+
42
+ /**
43
+ * Parse a cell reference to its components
44
+ * Supports: A1, $A$1, $A1, A$1, Sheet1!A1, 'My Sheet'!A1
45
+ *
46
+ * @param ref - Cell reference string
47
+ * @returns Parsed cell reference object
48
+ */
49
+ export function parseCellRef(ref: string): CellRef {
50
+ let cellRef = ref;
51
+ let sheetName: string | undefined;
52
+
53
+ // Check for cross-sheet reference (e.g., 'Sheet Name'!B2 or Sheet1!B2)
54
+ const sheetMatch = ref.match(/^(?:'([^']+)'|([^!]+))!(.+)$/);
55
+ if (sheetMatch) {
56
+ sheetName = sheetMatch[1] || sheetMatch[2]; // Quoted or unquoted sheet name
57
+ cellRef = sheetMatch[3]; // Cell reference part
58
+ }
59
+
60
+ // Parse absolute references ($A$1)
61
+ const absoluteRow = cellRef.includes("$") && cellRef.match(/\$\d+/);
62
+ const absoluteCol = cellRef.includes("$") && cellRef.match(/\$[A-Z]+/);
63
+
64
+ // Remove $ symbols
65
+ const cleanRef = cellRef.replace(/\$/g, "");
66
+ const match = cleanRef.match(/^([A-Z]+)(\d+)$/);
67
+
68
+ if (!match) {
69
+ throw new Error(`Invalid cell reference: ${ref}`);
70
+ }
71
+
72
+ const col = columnToIndex(match[1]);
73
+ const row = parseInt(match[2]) - 1; // 1-indexed to 0-indexed
74
+
75
+ const result: CellRef = { row, col };
76
+
77
+ if (sheetName) {
78
+ result.sheet = sheetName;
79
+ }
80
+
81
+ if (absoluteRow || absoluteCol) {
82
+ result.absolute = {
83
+ row: !!absoluteRow,
84
+ col: !!absoluteCol,
85
+ };
86
+ }
87
+
88
+ return result;
89
+ }
90
+
91
+ /**
92
+ * Parse a range reference to its components
93
+ * Supports: A1:B10, $A$1:$B$10, Sheet1!A1:B10
94
+ *
95
+ * @param range - Range reference string
96
+ * @returns Parsed range reference object
97
+ */
98
+ export function parseRangeRef(range: string): RangeRef {
99
+ // Use non-greedy match to improve performance
100
+ const colonIndex = range.lastIndexOf(":");
101
+ if (colonIndex === -1) {
102
+ throw new Error(`Invalid range reference: ${range}`);
103
+ }
104
+
105
+ const start = parseCellRef(range.substring(0, colonIndex));
106
+ const end = parseCellRef(range.substring(colonIndex + 1));
107
+
108
+ return { start, end };
109
+ }
110
+
111
+ /**
112
+ * Convert a cell reference object back to A1 notation
113
+ *
114
+ * @param ref - Cell reference object
115
+ * @returns A1 notation string
116
+ */
117
+ export function cellRefToA1(ref: CellRef): string {
118
+ const col = indexToColumn(ref.col);
119
+ const row = ref.row + 1; // 0-based to 1-based
120
+
121
+ let result = "";
122
+
123
+ if (ref.absolute?.col) {
124
+ result += "$";
125
+ }
126
+ result += col;
127
+
128
+ if (ref.absolute?.row) {
129
+ result += "$";
130
+ }
131
+ result += row;
132
+
133
+ if (ref.sheet) {
134
+ // Quote sheet name if it contains spaces
135
+ if (ref.sheet.includes(" ")) {
136
+ result = `'${ref.sheet}'!${result}`;
137
+ } else {
138
+ result = `${ref.sheet}!${result}`;
139
+ }
140
+ }
141
+
142
+ return result;
143
+ }