whale-code 6.5.5 → 6.5.6

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 (847) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +17 -2
  3. package/dist/cli/app.js +148 -72
  4. package/dist/cli/app.js.map +1 -0
  5. package/dist/cli/chat/AgentSelector.js +105 -10
  6. package/dist/cli/chat/AgentSelector.js.map +1 -0
  7. package/dist/cli/chat/ChatApp.d.ts +31 -0
  8. package/dist/cli/chat/ChatApp.js +539 -286
  9. package/dist/cli/chat/ChatApp.js.map +1 -0
  10. package/dist/cli/chat/ChatInput.js +1088 -770
  11. package/dist/cli/chat/ChatInput.js.map +1 -0
  12. package/dist/cli/chat/MarkdownText.js +39 -14
  13. package/dist/cli/chat/MarkdownText.js.map +1 -0
  14. package/dist/cli/chat/MemoryManager.js +181 -46
  15. package/dist/cli/chat/MemoryManager.js.map +1 -0
  16. package/dist/cli/chat/MessageList.d.ts +2 -3
  17. package/dist/cli/chat/MessageList.js +186 -45
  18. package/dist/cli/chat/MessageList.js.map +1 -0
  19. package/dist/cli/chat/ModelSelector.js +282 -63
  20. package/dist/cli/chat/ModelSelector.js.map +1 -0
  21. package/dist/cli/chat/NodeManager.js +165 -75
  22. package/dist/cli/chat/NodeManager.js.map +1 -0
  23. package/dist/cli/chat/NodeSelector.js +171 -30
  24. package/dist/cli/chat/NodeSelector.js.map +1 -0
  25. package/dist/cli/chat/PlanApproval.js +281 -57
  26. package/dist/cli/chat/PlanApproval.js.map +1 -0
  27. package/dist/cli/chat/RewindViewer.js +559 -144
  28. package/dist/cli/chat/RewindViewer.js.map +1 -0
  29. package/dist/cli/chat/SessionManager.js +137 -30
  30. package/dist/cli/chat/SessionManager.js.map +1 -0
  31. package/dist/cli/chat/SlashMenu.js +293 -164
  32. package/dist/cli/chat/SlashMenu.js.map +1 -0
  33. package/dist/cli/chat/StatusBar.js +172 -9
  34. package/dist/cli/chat/StatusBar.js.map +1 -0
  35. package/dist/cli/chat/StoreSelector.js +147 -18
  36. package/dist/cli/chat/StoreSelector.js.map +1 -0
  37. package/dist/cli/chat/StreamingText.d.ts +1 -5
  38. package/dist/cli/chat/StreamingText.js +22 -7
  39. package/dist/cli/chat/StreamingText.js.map +1 -0
  40. package/dist/cli/chat/SubagentPanel.d.ts +1 -2
  41. package/dist/cli/chat/SubagentPanel.js +612 -72
  42. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  43. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  44. package/dist/cli/chat/TeamPanel.js +230 -30
  45. package/dist/cli/chat/TeamPanel.js.map +1 -0
  46. package/dist/cli/chat/ThemeSelector.js +84 -24
  47. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  48. package/dist/cli/chat/ToolIndicator.js +1476 -371
  49. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  50. package/dist/cli/chat/hooks/useAgentLoop.d.ts +1 -0
  51. package/dist/cli/chat/hooks/useAgentLoop.js +481 -367
  52. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  53. package/dist/cli/chat/hooks/useSlashCommands.d.ts +3 -14
  54. package/dist/cli/chat/hooks/useSlashCommands.js +744 -572
  55. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  56. package/dist/cli/commands/config-cmd.js +56 -57
  57. package/dist/cli/commands/config-cmd.js.map +1 -0
  58. package/dist/cli/commands/db.js +184 -169
  59. package/dist/cli/commands/db.js.map +1 -0
  60. package/dist/cli/commands/doctor.js +212 -122
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/init.js +211 -244
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/mcp.js +127 -122
  65. package/dist/cli/commands/mcp.js.map +1 -0
  66. package/dist/cli/login/LoginApp.js +355 -141
  67. package/dist/cli/login/LoginApp.js.map +1 -0
  68. package/dist/cli/print-mode.js +196 -177
  69. package/dist/cli/print-mode.js.map +1 -0
  70. package/dist/cli/serve-mode.js +615 -530
  71. package/dist/cli/serve-mode.js.map +1 -0
  72. package/dist/cli/services/agent-config.d.ts +5 -1
  73. package/dist/cli/services/agent-config.js +66 -36
  74. package/dist/cli/services/agent-config.js.map +1 -0
  75. package/dist/cli/services/agent-definitions.d.ts +4 -1
  76. package/dist/cli/services/agent-definitions.js +97 -56
  77. package/dist/cli/services/agent-definitions.js.map +1 -0
  78. package/dist/cli/services/agent-events.js +225 -162
  79. package/dist/cli/services/agent-events.js.map +1 -0
  80. package/dist/cli/services/agent-loop.js +976 -688
  81. package/dist/cli/services/agent-loop.js.map +1 -0
  82. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  83. package/dist/cli/services/agent-worker-base.js +337 -153
  84. package/dist/cli/services/agent-worker-base.js.map +1 -0
  85. package/dist/cli/services/api-retry.js +69 -64
  86. package/dist/cli/services/api-retry.js.map +1 -0
  87. package/dist/cli/services/auth-service.d.ts +3 -3
  88. package/dist/cli/services/auth-service.js +209 -132
  89. package/dist/cli/services/auth-service.js.map +1 -0
  90. package/dist/cli/services/background-processes.js +343 -267
  91. package/dist/cli/services/background-processes.js.map +1 -0
  92. package/dist/cli/services/browser-auth.d.ts +2 -2
  93. package/dist/cli/services/browser-auth.js +159 -118
  94. package/dist/cli/services/browser-auth.js.map +1 -0
  95. package/dist/cli/services/claude-md-loader.js +40 -36
  96. package/dist/cli/services/claude-md-loader.js.map +1 -0
  97. package/dist/cli/services/config-store.d.ts +9 -4
  98. package/dist/cli/services/config-store.js +164 -117
  99. package/dist/cli/services/config-store.js.map +1 -0
  100. package/dist/cli/services/debug-log.d.ts +1 -1
  101. package/dist/cli/services/debug-log.js +34 -35
  102. package/dist/cli/services/debug-log.js.map +1 -0
  103. package/dist/cli/services/env-detect.d.ts +7 -0
  104. package/dist/cli/services/env-detect.js +9 -0
  105. package/dist/cli/services/env-detect.js.map +1 -0
  106. package/dist/cli/services/error-logger.js +187 -169
  107. package/dist/cli/services/error-logger.js.map +1 -0
  108. package/dist/cli/services/file-history.d.ts +1 -1
  109. package/dist/cli/services/file-history.js +50 -54
  110. package/dist/cli/services/file-history.js.map +1 -0
  111. package/dist/cli/services/format-server-response.js +332 -372
  112. package/dist/cli/services/format-server-response.js.map +1 -0
  113. package/dist/cli/services/git-context.js +61 -45
  114. package/dist/cli/services/git-context.js.map +1 -0
  115. package/dist/cli/services/hooks.d.ts +2 -2
  116. package/dist/cli/services/hooks.js +195 -180
  117. package/dist/cli/services/hooks.js.map +1 -0
  118. package/dist/cli/services/ink-incremental.d.ts +19 -0
  119. package/dist/cli/services/ink-incremental.js +59 -0
  120. package/dist/cli/services/ink-incremental.js.map +1 -0
  121. package/dist/cli/services/ink-resize-fix.js +54 -44
  122. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  123. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  124. package/dist/cli/services/ink-sync-output.js +16 -0
  125. package/dist/cli/services/ink-sync-output.js.map +1 -0
  126. package/dist/cli/services/interactive-tools.js +268 -212
  127. package/dist/cli/services/interactive-tools.js.map +1 -0
  128. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  129. package/dist/cli/services/keybinding-manager.js +126 -63
  130. package/dist/cli/services/keybinding-manager.js.map +1 -0
  131. package/dist/cli/services/local-tools.d.ts +1 -1
  132. package/dist/cli/services/local-tools.js +939 -656
  133. package/dist/cli/services/local-tools.js.map +1 -0
  134. package/dist/cli/services/lsp-manager.js +757 -594
  135. package/dist/cli/services/lsp-manager.js.map +1 -0
  136. package/dist/cli/services/mcp-client.d.ts +1 -1
  137. package/dist/cli/services/mcp-client.js +173 -134
  138. package/dist/cli/services/mcp-client.js.map +1 -0
  139. package/dist/cli/services/memory-manager.js +53 -40
  140. package/dist/cli/services/memory-manager.js.map +1 -0
  141. package/dist/cli/services/model-manager.js +55 -40
  142. package/dist/cli/services/model-manager.js.map +1 -0
  143. package/dist/cli/services/model-router.js +115 -85
  144. package/dist/cli/services/model-router.js.map +1 -0
  145. package/dist/cli/services/paths.d.ts +30 -0
  146. package/dist/cli/services/paths.js +81 -0
  147. package/dist/cli/services/paths.js.map +1 -0
  148. package/dist/cli/services/permission-modes.js +32 -25
  149. package/dist/cli/services/permission-modes.js.map +1 -0
  150. package/dist/cli/services/rewind.js +182 -168
  151. package/dist/cli/services/rewind.js.map +1 -0
  152. package/dist/cli/services/ripgrep.js +115 -115
  153. package/dist/cli/services/ripgrep.js.map +1 -0
  154. package/dist/cli/services/sandbox.d.ts +1 -1
  155. package/dist/cli/services/sandbox.js +58 -37
  156. package/dist/cli/services/sandbox.js.map +1 -0
  157. package/dist/cli/services/server-tools.js +738 -565
  158. package/dist/cli/services/server-tools.js.map +1 -0
  159. package/dist/cli/services/session-persistence.js +69 -74
  160. package/dist/cli/services/session-persistence.js.map +1 -0
  161. package/dist/cli/services/subagent-worker.js +42 -27
  162. package/dist/cli/services/subagent-worker.js.map +1 -0
  163. package/dist/cli/services/subagent.d.ts +2 -0
  164. package/dist/cli/services/subagent.js +605 -433
  165. package/dist/cli/services/subagent.js.map +1 -0
  166. package/dist/cli/services/system-prompt.js +86 -78
  167. package/dist/cli/services/system-prompt.js.map +1 -0
  168. package/dist/cli/services/task-decomposer.d.ts +1 -1
  169. package/dist/cli/services/task-decomposer.js +172 -139
  170. package/dist/cli/services/task-decomposer.js.map +1 -0
  171. package/dist/cli/services/team-lead.d.ts +2 -2
  172. package/dist/cli/services/team-lead.js +727 -529
  173. package/dist/cli/services/team-lead.js.map +1 -0
  174. package/dist/cli/services/team-state.js +319 -319
  175. package/dist/cli/services/team-state.js.map +1 -0
  176. package/dist/cli/services/teammate.d.ts +8 -2
  177. package/dist/cli/services/teammate.js +857 -569
  178. package/dist/cli/services/teammate.js.map +1 -0
  179. package/dist/cli/services/telemetry.d.ts +6 -1
  180. package/dist/cli/services/telemetry.js +180 -157
  181. package/dist/cli/services/telemetry.js.map +1 -0
  182. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  183. package/dist/cli/services/tools/agent-tools.js +480 -322
  184. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  185. package/dist/cli/services/tools/file-ops.js +563 -450
  186. package/dist/cli/services/tools/file-ops.js.map +1 -0
  187. package/dist/cli/services/tools/search-tools.js +231 -162
  188. package/dist/cli/services/tools/search-tools.js.map +1 -0
  189. package/dist/cli/services/tools/shell-exec.js +197 -151
  190. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  191. package/dist/cli/services/tools/task-manager.js +206 -173
  192. package/dist/cli/services/tools/task-manager.js.map +1 -0
  193. package/dist/cli/services/tools/web-tools.js +388 -341
  194. package/dist/cli/services/tools/web-tools.js.map +1 -0
  195. package/dist/cli/setup/SetupApp.d.ts +2 -2
  196. package/dist/cli/setup/SetupApp.js +608 -160
  197. package/dist/cli/setup/SetupApp.js.map +1 -0
  198. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  199. package/dist/cli/shared/ErrorBoundary.js +73 -0
  200. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  201. package/dist/cli/shared/MatrixIntro.js +66 -69
  202. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  203. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  204. package/dist/cli/shared/SpinnerSlot.js +63 -0
  205. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  206. package/dist/cli/shared/Theme.d.ts +1 -1
  207. package/dist/cli/shared/Theme.js +136 -92
  208. package/dist/cli/shared/Theme.js.map +1 -0
  209. package/dist/cli/shared/WhaleBanner.js +99 -11
  210. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  211. package/dist/cli/shared/markdown.d.ts +3 -1
  212. package/dist/cli/shared/markdown.js +736 -674
  213. package/dist/cli/shared/markdown.js.map +1 -0
  214. package/dist/cli/shared/marked-terminal.d.js +2 -0
  215. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  216. package/dist/cli/shared/theme-manager.js +99 -90
  217. package/dist/cli/shared/theme-manager.js.map +1 -0
  218. package/dist/cli/shared/theme-presets.js +256 -254
  219. package/dist/cli/shared/theme-presets.js.map +1 -0
  220. package/dist/cli/status/StatusApp.js +235 -86
  221. package/dist/cli/status/StatusApp.js.map +1 -0
  222. package/dist/cli/stores/StoreApp.js +275 -65
  223. package/dist/cli/stores/StoreApp.js.map +1 -0
  224. package/dist/index.d.ts +2 -2
  225. package/dist/index.js +509 -396
  226. package/dist/index.js.map +1 -0
  227. package/dist/local-agent/connection.d.ts +2 -2
  228. package/dist/local-agent/connection.js +352 -293
  229. package/dist/local-agent/connection.js.map +1 -0
  230. package/dist/local-agent/discovery.js +259 -122
  231. package/dist/local-agent/discovery.js.map +1 -0
  232. package/dist/local-agent/executor.js +216 -193
  233. package/dist/local-agent/executor.js.map +1 -0
  234. package/dist/local-agent/index.d.ts +2 -2
  235. package/dist/local-agent/index.js +156 -156
  236. package/dist/local-agent/index.js.map +1 -0
  237. package/dist/node/adapters/base.js +18 -8
  238. package/dist/node/adapters/base.js.map +1 -0
  239. package/dist/node/adapters/discord.js +286 -275
  240. package/dist/node/adapters/discord.js.map +1 -0
  241. package/dist/node/adapters/email.js +189 -202
  242. package/dist/node/adapters/email.js.map +1 -0
  243. package/dist/node/adapters/imessage.js +145 -142
  244. package/dist/node/adapters/imessage.js.map +1 -0
  245. package/dist/node/adapters/slack.js +237 -236
  246. package/dist/node/adapters/slack.js.map +1 -0
  247. package/dist/node/adapters/sms.js +149 -151
  248. package/dist/node/adapters/sms.js.map +1 -0
  249. package/dist/node/adapters/telegram.js +88 -92
  250. package/dist/node/adapters/telegram.js.map +1 -0
  251. package/dist/node/adapters/webchat.js +160 -136
  252. package/dist/node/adapters/webchat.js.map +1 -0
  253. package/dist/node/adapters/whatsapp.js +212 -215
  254. package/dist/node/adapters/whatsapp.js.map +1 -0
  255. package/dist/node/cli.js +884 -653
  256. package/dist/node/cli.js.map +1 -0
  257. package/dist/node/config.js +20 -18
  258. package/dist/node/config.js.map +1 -0
  259. package/dist/node/gateway-client.js +191 -181
  260. package/dist/node/gateway-client.js.map +1 -0
  261. package/dist/node/portal/clipboard.js +161 -130
  262. package/dist/node/portal/clipboard.js.map +1 -0
  263. package/dist/node/portal/discovery.js +51 -45
  264. package/dist/node/portal/discovery.js.map +1 -0
  265. package/dist/node/portal/forward.js +64 -58
  266. package/dist/node/portal/forward.js.map +1 -0
  267. package/dist/node/portal/index.js +246 -221
  268. package/dist/node/portal/index.js.map +1 -0
  269. package/dist/node/portal/multiplexer.js +192 -182
  270. package/dist/node/portal/multiplexer.js.map +1 -0
  271. package/dist/node/portal/permissions.js +102 -70
  272. package/dist/node/portal/permissions.js.map +1 -0
  273. package/dist/node/portal/protocol.js +153 -116
  274. package/dist/node/portal/protocol.js.map +1 -0
  275. package/dist/node/portal/screen.js +80 -69
  276. package/dist/node/portal/screen.js.map +1 -0
  277. package/dist/node/portal/session.js +124 -117
  278. package/dist/node/portal/session.js.map +1 -0
  279. package/dist/node/portal/shell.js +140 -113
  280. package/dist/node/portal/shell.js.map +1 -0
  281. package/dist/node/portal/stream.js +77 -75
  282. package/dist/node/portal/stream.js.map +1 -0
  283. package/dist/node/portal/transfer.js +190 -167
  284. package/dist/node/portal/transfer.js.map +1 -0
  285. package/dist/node/portal/ui.js +124 -99
  286. package/dist/node/portal/ui.js.map +1 -0
  287. package/dist/node/remote-desktop/compile-helper.js +50 -45
  288. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  289. package/dist/node/remote-desktop/index.js +215 -187
  290. package/dist/node/remote-desktop/index.js.map +1 -0
  291. package/dist/node/remote-desktop/protocol.js +45 -29
  292. package/dist/node/remote-desktop/protocol.js.map +1 -0
  293. package/dist/node/runtime.js +493 -410
  294. package/dist/node/runtime.js.map +1 -0
  295. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  296. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  297. package/dist/server/handlers/analytics.js +467 -261
  298. package/dist/server/handlers/analytics.js.map +1 -0
  299. package/dist/server/handlers/api-docs.js +1030 -895
  300. package/dist/server/handlers/api-docs.js.map +1 -0
  301. package/dist/server/handlers/api-keys.js +291 -242
  302. package/dist/server/handlers/api-keys.js.map +1 -0
  303. package/dist/server/handlers/billing.js +330 -239
  304. package/dist/server/handlers/billing.js.map +1 -0
  305. package/dist/server/handlers/browser.js +468 -395
  306. package/dist/server/handlers/browser.js.map +1 -0
  307. package/dist/server/handlers/catalog.js +1377 -978
  308. package/dist/server/handlers/catalog.js.map +1 -0
  309. package/dist/server/handlers/clickhouse.js +157 -109
  310. package/dist/server/handlers/clickhouse.js.map +1 -0
  311. package/dist/server/handlers/comms.js +1439 -984
  312. package/dist/server/handlers/comms.js.map +1 -0
  313. package/dist/server/handlers/creations.js +461 -394
  314. package/dist/server/handlers/creations.js.map +1 -0
  315. package/dist/server/handlers/crm.js +1082 -791
  316. package/dist/server/handlers/crm.js.map +1 -0
  317. package/dist/server/handlers/discovery.js +251 -232
  318. package/dist/server/handlers/discovery.js.map +1 -0
  319. package/dist/server/handlers/embeddings.js +241 -164
  320. package/dist/server/handlers/embeddings.js.map +1 -0
  321. package/dist/server/handlers/enrichment.js +887 -718
  322. package/dist/server/handlers/enrichment.js.map +1 -0
  323. package/dist/server/handlers/image-gen.js +467 -376
  324. package/dist/server/handlers/image-gen.js.map +1 -0
  325. package/dist/server/handlers/inventory.js +797 -424
  326. package/dist/server/handlers/inventory.js.map +1 -0
  327. package/dist/server/handlers/kali.js +272 -230
  328. package/dist/server/handlers/kali.js.map +1 -0
  329. package/dist/server/handlers/llm-providers.js +803 -580
  330. package/dist/server/handlers/llm-providers.js.map +1 -0
  331. package/dist/server/handlers/local-agent.js +133 -105
  332. package/dist/server/handlers/local-agent.js.map +1 -0
  333. package/dist/server/handlers/media.js +1179 -857
  334. package/dist/server/handlers/media.js.map +1 -0
  335. package/dist/server/handlers/meta-ads.js +2669 -2093
  336. package/dist/server/handlers/meta-ads.js.map +1 -0
  337. package/dist/server/handlers/nodes.js +1321 -913
  338. package/dist/server/handlers/nodes.js.map +1 -0
  339. package/dist/server/handlers/operations.js +183 -157
  340. package/dist/server/handlers/operations.js.map +1 -0
  341. package/dist/server/handlers/platform.js +346 -210
  342. package/dist/server/handlers/platform.js.map +1 -0
  343. package/dist/server/handlers/remove-bg.js +118 -86
  344. package/dist/server/handlers/remove-bg.js.map +1 -0
  345. package/dist/server/handlers/storefront.js +586 -446
  346. package/dist/server/handlers/storefront.js.map +1 -0
  347. package/dist/server/handlers/supply-chain.js +546 -326
  348. package/dist/server/handlers/supply-chain.js.map +1 -0
  349. package/dist/server/handlers/transcription.js +106 -97
  350. package/dist/server/handlers/transcription.js.map +1 -0
  351. package/dist/server/handlers/video-gen.js +593 -424
  352. package/dist/server/handlers/video-gen.js.map +1 -0
  353. package/dist/server/handlers/voice.js +1458 -1039
  354. package/dist/server/handlers/voice.js.map +1 -0
  355. package/dist/server/handlers/workflow-steps.js +2837 -2116
  356. package/dist/server/handlers/workflow-steps.js.map +1 -0
  357. package/dist/server/handlers/workflows.js +1630 -933
  358. package/dist/server/handlers/workflows.js.map +1 -0
  359. package/dist/server/index.js +3167 -2422
  360. package/dist/server/index.js.map +1 -0
  361. package/dist/server/lib/batch-client.js +471 -409
  362. package/dist/server/lib/batch-client.js.map +1 -0
  363. package/dist/server/lib/clickhouse-buffer.js +118 -104
  364. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  365. package/dist/server/lib/clickhouse-client.js +107 -107
  366. package/dist/server/lib/clickhouse-client.js.map +1 -0
  367. package/dist/server/lib/coa-renderer.js +1786 -356
  368. package/dist/server/lib/coa-renderer.js.map +1 -0
  369. package/dist/server/lib/code-worker-pool.js +227 -177
  370. package/dist/server/lib/code-worker-pool.js.map +1 -0
  371. package/dist/server/lib/code-worker.js +174 -164
  372. package/dist/server/lib/code-worker.js.map +1 -0
  373. package/dist/server/lib/compaction-service.d.ts +2 -12
  374. package/dist/server/lib/compaction-service.js +74 -184
  375. package/dist/server/lib/compaction-service.js.map +1 -0
  376. package/dist/server/lib/logger.js +36 -24
  377. package/dist/server/lib/logger.js.map +1 -0
  378. package/dist/server/lib/otel.js +101 -80
  379. package/dist/server/lib/otel.js.map +1 -0
  380. package/dist/server/lib/pdf-renderer.js +952 -788
  381. package/dist/server/lib/pdf-renderer.js.map +1 -0
  382. package/dist/server/lib/prompt-sanitizer.js +188 -108
  383. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  384. package/dist/server/lib/provider-capabilities.js +136 -138
  385. package/dist/server/lib/provider-capabilities.js.map +1 -0
  386. package/dist/server/lib/provider-failover.js +190 -168
  387. package/dist/server/lib/provider-failover.js.map +1 -0
  388. package/dist/server/lib/rate-limiter.js +186 -117
  389. package/dist/server/lib/rate-limiter.js.map +1 -0
  390. package/dist/server/lib/react-pdf-layout.js +551 -382
  391. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  392. package/dist/server/lib/server-agent-loop.d.ts +4 -1
  393. package/dist/server/lib/server-agent-loop.js +906 -634
  394. package/dist/server/lib/server-agent-loop.js.map +1 -0
  395. package/dist/server/lib/server-subagent.js +260 -164
  396. package/dist/server/lib/server-subagent.js.map +1 -0
  397. package/dist/server/lib/session-checkpoint.js +105 -96
  398. package/dist/server/lib/session-checkpoint.js.map +1 -0
  399. package/dist/server/lib/ssrf-guard.js +193 -184
  400. package/dist/server/lib/ssrf-guard.js.map +1 -0
  401. package/dist/server/lib/supabase-client.js +94 -82
  402. package/dist/server/lib/supabase-client.js.map +1 -0
  403. package/dist/server/lib/template-resolver.js +154 -176
  404. package/dist/server/lib/template-resolver.js.map +1 -0
  405. package/dist/server/lib/utils.js +242 -133
  406. package/dist/server/lib/utils.js.map +1 -0
  407. package/dist/server/local-agent-gateway.d.ts +2 -2
  408. package/dist/server/local-agent-gateway.js +785 -627
  409. package/dist/server/local-agent-gateway.js.map +1 -0
  410. package/dist/server/providers/anthropic.js +250 -172
  411. package/dist/server/providers/anthropic.js.map +1 -0
  412. package/dist/server/providers/bedrock.js +217 -158
  413. package/dist/server/providers/bedrock.js.map +1 -0
  414. package/dist/server/providers/gemini.js +548 -418
  415. package/dist/server/providers/gemini.js.map +1 -0
  416. package/dist/server/providers/openai.js +571 -437
  417. package/dist/server/providers/openai.js.map +1 -0
  418. package/dist/server/providers/registry.js +23 -18
  419. package/dist/server/providers/registry.js.map +1 -0
  420. package/dist/server/providers/shared.js +123 -95
  421. package/dist/server/providers/shared.js.map +1 -0
  422. package/dist/server/providers/types.js +1 -11
  423. package/dist/server/providers/types.js.map +1 -0
  424. package/dist/server/proxy-handlers.js +209 -165
  425. package/dist/server/proxy-handlers.js.map +1 -0
  426. package/dist/server/tool-router.js +959 -599
  427. package/dist/server/tool-router.js.map +1 -0
  428. package/dist/server/validation.js +248 -188
  429. package/dist/server/validation.js.map +1 -0
  430. package/dist/server/worker.js +202 -133
  431. package/dist/server/worker.js.map +1 -0
  432. package/dist/setup.d.ts +2 -2
  433. package/dist/setup.js +151 -147
  434. package/dist/setup.js.map +1 -0
  435. package/dist/shared/agent-core.d.ts +115 -26
  436. package/dist/shared/agent-core.js +956 -522
  437. package/dist/shared/agent-core.js.map +1 -0
  438. package/dist/shared/anthropic-types.js +1 -6
  439. package/dist/shared/anthropic-types.js.map +1 -0
  440. package/dist/shared/api-client.d.ts +16 -9
  441. package/dist/shared/api-client.js +419 -327
  442. package/dist/shared/api-client.js.map +1 -0
  443. package/dist/shared/compaction.d.ts +36 -0
  444. package/dist/shared/compaction.js +138 -0
  445. package/dist/shared/compaction.js.map +1 -0
  446. package/dist/shared/constants.js +67 -64
  447. package/dist/shared/constants.js.map +1 -0
  448. package/dist/shared/sse-parser.js +221 -219
  449. package/dist/shared/sse-parser.js.map +1 -0
  450. package/dist/shared/tool-dispatch.d.ts +4 -0
  451. package/dist/shared/tool-dispatch.js +226 -165
  452. package/dist/shared/tool-dispatch.js.map +1 -0
  453. package/dist/shared/types.js +1 -6
  454. package/dist/shared/types.js.map +1 -0
  455. package/dist/types/cli-highlight.d.js +2 -0
  456. package/dist/types/cli-highlight.d.js.map +1 -0
  457. package/dist/types/diff.d.js +2 -0
  458. package/dist/types/diff.d.js.map +1 -0
  459. package/dist/types/pdf-parse.d.js +2 -0
  460. package/dist/types/pdf-parse.d.js.map +1 -0
  461. package/dist/updater.d.ts +1 -1
  462. package/dist/updater.js +118 -92
  463. package/dist/updater.js.map +1 -0
  464. package/dist/webchat/widget.js +227 -380
  465. package/dist/webchat/widget.js.map +1 -0
  466. package/package.json +22 -10
  467. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  468. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  469. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  470. package/vendor/ink/build/apply-styles.js +175 -0
  471. package/vendor/ink/build/build-layout.js +77 -0
  472. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  473. package/vendor/ink/build/colorize.d.ts +3 -0
  474. package/vendor/ink/build/colorize.js +48 -0
  475. package/vendor/ink/build/colorize.js.map +1 -0
  476. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  477. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  478. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  479. package/vendor/ink/build/components/App.d.ts +18 -0
  480. package/vendor/ink/build/components/App.js +351 -0
  481. package/vendor/ink/build/components/App.js.map +1 -0
  482. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  483. package/vendor/ink/build/components/AppContext.js +11 -0
  484. package/vendor/ink/build/components/AppContext.js.map +1 -0
  485. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  486. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  487. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  488. package/vendor/ink/build/components/Box.d.ts +117 -0
  489. package/vendor/ink/build/components/Box.js +34 -0
  490. package/vendor/ink/build/components/Box.js.map +1 -0
  491. package/vendor/ink/build/components/Color.js +62 -0
  492. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  493. package/vendor/ink/build/components/Cursor.js +53 -0
  494. package/vendor/ink/build/components/Cursor.js.map +1 -0
  495. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  496. package/vendor/ink/build/components/CursorContext.js +8 -0
  497. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  498. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  499. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  500. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  501. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  502. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  503. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  504. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  505. package/vendor/ink/build/components/FocusContext.js +17 -0
  506. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  507. package/vendor/ink/build/components/Newline.d.ts +13 -0
  508. package/vendor/ink/build/components/Newline.js +8 -0
  509. package/vendor/ink/build/components/Newline.js.map +1 -0
  510. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  511. package/vendor/ink/build/components/Spacer.js +11 -0
  512. package/vendor/ink/build/components/Spacer.js.map +1 -0
  513. package/vendor/ink/build/components/Static.d.ts +24 -0
  514. package/vendor/ink/build/components/Static.js +28 -0
  515. package/vendor/ink/build/components/Static.js.map +1 -0
  516. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  517. package/vendor/ink/build/components/StderrContext.js +13 -0
  518. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  519. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  520. package/vendor/ink/build/components/StdinContext.js +19 -0
  521. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  522. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StdoutContext.js +13 -0
  524. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  525. package/vendor/ink/build/components/Text.d.ts +55 -0
  526. package/vendor/ink/build/components/Text.js +50 -0
  527. package/vendor/ink/build/components/Text.js.map +1 -0
  528. package/vendor/ink/build/components/Transform.d.ts +16 -0
  529. package/vendor/ink/build/components/Transform.js +15 -0
  530. package/vendor/ink/build/components/Transform.js.map +1 -0
  531. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  532. package/vendor/ink/build/cursor-helpers.js +56 -0
  533. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  534. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  535. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  536. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  537. package/vendor/ink/build/devtools.d.ts +1 -0
  538. package/vendor/ink/build/devtools.js +11 -0
  539. package/vendor/ink/build/devtools.js.map +1 -0
  540. package/vendor/ink/build/dom.d.ts +56 -0
  541. package/vendor/ink/build/dom.js +124 -0
  542. package/vendor/ink/build/dom.js.map +1 -0
  543. package/vendor/ink/build/experimental/apply-style.js +140 -0
  544. package/vendor/ink/build/experimental/dom.js +123 -0
  545. package/vendor/ink/build/experimental/output.js +91 -0
  546. package/vendor/ink/build/experimental/reconciler.js +141 -0
  547. package/vendor/ink/build/experimental/renderer.js +81 -0
  548. package/vendor/ink/build/get-max-width.d.ts +3 -0
  549. package/vendor/ink/build/get-max-width.js +10 -0
  550. package/vendor/ink/build/get-max-width.js.map +1 -0
  551. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  552. package/vendor/ink/build/hooks/use-app.js +8 -0
  553. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  554. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  555. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  556. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  558. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  559. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  561. package/vendor/ink/build/hooks/use-focus.js +42 -0
  562. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  564. package/vendor/ink/build/hooks/use-input.js +124 -0
  565. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  567. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  568. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  570. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  571. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  574. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  577. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  578. package/vendor/ink/build/hooks/useInput.js +38 -0
  579. package/vendor/ink/build/index.d.ts +34 -0
  580. package/vendor/ink/build/index.js +20 -0
  581. package/vendor/ink/build/index.js.map +1 -0
  582. package/vendor/ink/build/ink.d.ts +90 -0
  583. package/vendor/ink/build/ink.js +654 -0
  584. package/vendor/ink/build/ink.js.map +1 -0
  585. package/vendor/ink/build/input-parser.d.ts +7 -0
  586. package/vendor/ink/build/input-parser.js +154 -0
  587. package/vendor/ink/build/input-parser.js.map +1 -0
  588. package/vendor/ink/build/instance.js +205 -0
  589. package/vendor/ink/build/instances.d.ts +3 -0
  590. package/vendor/ink/build/instances.js +8 -0
  591. package/vendor/ink/build/instances.js.map +1 -0
  592. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  593. package/vendor/ink/build/kitty-keyboard.js +32 -0
  594. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  595. package/vendor/ink/build/layout.d.ts +7 -0
  596. package/vendor/ink/build/layout.js +33 -0
  597. package/vendor/ink/build/layout.js.map +1 -0
  598. package/vendor/ink/build/log-update.d.ts +19 -0
  599. package/vendor/ink/build/log-update.js +243 -0
  600. package/vendor/ink/build/log-update.js.map +1 -0
  601. package/vendor/ink/build/measure-element.d.ts +16 -0
  602. package/vendor/ink/build/measure-element.js +9 -0
  603. package/vendor/ink/build/measure-element.js.map +1 -0
  604. package/vendor/ink/build/measure-text.d.ts +6 -0
  605. package/vendor/ink/build/measure-text.js +21 -0
  606. package/vendor/ink/build/measure-text.js.map +1 -0
  607. package/vendor/ink/build/options.d.ts +52 -0
  608. package/vendor/ink/build/options.js +2 -0
  609. package/vendor/ink/build/options.js.map +1 -0
  610. package/vendor/ink/build/output.d.ts +35 -0
  611. package/vendor/ink/build/output.js +183 -0
  612. package/vendor/ink/build/output.js.map +1 -0
  613. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  614. package/vendor/ink/build/parse-keypress.js +493 -0
  615. package/vendor/ink/build/parse-keypress.js.map +1 -0
  616. package/vendor/ink/build/reconciler.d.ts +4 -0
  617. package/vendor/ink/build/reconciler.js +274 -0
  618. package/vendor/ink/build/reconciler.js.map +1 -0
  619. package/vendor/ink/build/render-background.d.ts +4 -0
  620. package/vendor/ink/build/render-background.js +25 -0
  621. package/vendor/ink/build/render-background.js.map +1 -0
  622. package/vendor/ink/build/render-border.d.ts +4 -0
  623. package/vendor/ink/build/render-border.js +73 -0
  624. package/vendor/ink/build/render-border.js.map +1 -0
  625. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  626. package/vendor/ink/build/render-node-to-output.js +147 -0
  627. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  628. package/vendor/ink/build/render-to-string.d.ts +38 -0
  629. package/vendor/ink/build/render-to-string.js +115 -0
  630. package/vendor/ink/build/render-to-string.js.map +1 -0
  631. package/vendor/ink/build/render.d.ts +121 -0
  632. package/vendor/ink/build/render.js +55 -0
  633. package/vendor/ink/build/render.js.map +1 -0
  634. package/vendor/ink/build/renderer.d.ts +8 -0
  635. package/vendor/ink/build/renderer.js +55 -0
  636. package/vendor/ink/build/renderer.js.map +1 -0
  637. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  638. package/vendor/ink/build/sanitize-ansi.js +27 -0
  639. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  640. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  641. package/vendor/ink/build/screen-reader-update.js +38 -0
  642. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  643. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  644. package/vendor/ink/build/squash-text-nodes.js +36 -0
  645. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  646. package/vendor/ink/build/styles.d.ts +240 -0
  647. package/vendor/ink/build/styles.js +232 -0
  648. package/vendor/ink/build/styles.js.map +1 -0
  649. package/vendor/ink/build/utils.d.ts +2 -0
  650. package/vendor/ink/build/utils.js +4 -0
  651. package/vendor/ink/build/utils.js.map +1 -0
  652. package/vendor/ink/build/wrap-text.d.ts +3 -0
  653. package/vendor/ink/build/wrap-text.js +31 -0
  654. package/vendor/ink/build/wrap-text.js.map +1 -0
  655. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  656. package/vendor/ink/build/write-synchronized.js +7 -0
  657. package/vendor/ink/build/write-synchronized.js.map +1 -0
  658. package/vendor/ink/license +10 -0
  659. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  660. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  661. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  662. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  663. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  664. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  665. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  666. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  667. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  668. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  669. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  670. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  671. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  672. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  673. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  674. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  675. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  676. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  677. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  678. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  679. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  680. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  681. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  682. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  683. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  684. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  685. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  686. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  687. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  688. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  689. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  690. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  691. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  692. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  693. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  694. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  695. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  696. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  697. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  698. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  699. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  700. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  701. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  702. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  703. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  704. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  705. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  706. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  707. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  708. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  709. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  710. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  711. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  712. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  713. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  714. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  715. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  716. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  717. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  718. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  719. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  720. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  721. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  722. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  723. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  724. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  725. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  726. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  727. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  728. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  729. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  730. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  731. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  732. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  733. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  734. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  735. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  746. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  747. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  748. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  749. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  750. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  751. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  752. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  753. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  754. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  755. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  756. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  757. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  758. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  759. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  760. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  761. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  762. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  763. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  764. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  765. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  766. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  767. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  768. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  769. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  770. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  784. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  785. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  786. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  787. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  788. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  789. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  790. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  791. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  792. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  793. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  794. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  795. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  796. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  797. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  798. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  799. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  800. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  801. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  802. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  803. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  804. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  805. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  806. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  807. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  808. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  809. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  810. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  811. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  812. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  813. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  814. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  815. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  816. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  817. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  818. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  819. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  820. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  821. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  822. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  823. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  824. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  825. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  826. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  827. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  828. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  829. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  830. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  831. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  832. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  833. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  834. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  835. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  836. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  837. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  838. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  839. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  840. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  841. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  842. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  843. package/vendor/ink/package.json +201 -0
  844. package/vendor/ink/readme.md +2636 -0
  845. package/bin/swag-agent.js +0 -9
  846. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  847. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -1,6 +1,6 @@
1
1
  import { sanitizeFilterValue, escapeCSV, fillTemplate, groupBy } from "../lib/utils.js";
2
2
  import { validateUrl } from "../lib/ssrf-guard.js";
3
- import { applyGenerationRules, generateCannabinoidData, applyCalculations, runValidation, generateFullPanelData, } from "../lib/pdf-renderer.js";
3
+ import { applyGenerationRules, generateCannabinoidData, applyCalculations, runValidation, generateFullPanelData } from "../lib/pdf-renderer.js";
4
4
  import { renderLayoutToPdf, renderHtmlToPdf, renderLabelToPdf } from "../lib/react-pdf-layout.js";
5
5
  import { renderCOAToPdf } from "../lib/coa-renderer.js";
6
6
  import QRCode from "qrcode";
@@ -8,1032 +8,1487 @@ import { handleBrowser } from "./browser.js";
8
8
  const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024; // 10MB per attachment
9
9
  const MAX_TOTAL_ATTACHMENT_BYTES = 25 * 1024 * 1024; // 25MB total
10
10
  const MAX_ATTACHMENT_COUNT = 10;
11
+
11
12
  /** Generate a PNG thumbnail for a PDF document via Playwright screenshot and store it in Supabase. */
12
13
  async function generateThumbnail(sb, docId, pdfUrl) {
13
- // Wrap in Google Docs viewer — headless Chromium downloads raw PDFs instead of rendering them
14
- const viewerUrl = `https://docs.google.com/gview?url=${encodeURIComponent(pdfUrl)}&embedded=true`;
15
- const result = await handleBrowser(sb, { action: "screenshot", url: viewerUrl });
16
- const b64 = result.data?.screenshot_base64;
17
- if (!b64)
18
- return;
19
- const thumbBuffer = Buffer.from(b64, 'base64');
20
- const thumbPath = `thumbs/${docId}.png`;
21
- await sb.storage.from('screenshots').upload(thumbPath, thumbBuffer, {
22
- contentType: 'image/png', upsert: true,
23
- });
24
- const { data: thumbUrlData } = sb.storage.from('screenshots').getPublicUrl(thumbPath);
25
- await sb.from('store_documents').update({ thumbnail_url: thumbUrlData.publicUrl }).eq('id', docId);
14
+ // Wrap in Google Docs viewer — headless Chromium downloads raw PDFs instead of rendering them
15
+ const viewerUrl = `https://docs.google.com/gview?url=${encodeURIComponent(pdfUrl)}&embedded=true`;
16
+ const result = await handleBrowser(sb, {
17
+ action: "screenshot",
18
+ url: viewerUrl
19
+ });
20
+ const b64 = result.data?.screenshot_base64;
21
+ if (!b64) return;
22
+ const thumbBuffer = Buffer.from(b64, 'base64');
23
+ const thumbPath = `thumbs/${docId}.png`;
24
+ await sb.storage.from('screenshots').upload(thumbPath, thumbBuffer, {
25
+ contentType: 'image/png',
26
+ upsert: true
27
+ });
28
+ const {
29
+ data: thumbUrlData
30
+ } = sb.storage.from('screenshots').getPublicUrl(thumbPath);
31
+ await sb.from('store_documents').update({
32
+ thumbnail_url: thumbUrlData.publicUrl
33
+ }).eq('id', docId);
26
34
  }
27
35
  export async function handleEmail(sb, args, storeId) {
28
- if (!storeId)
29
- return { success: false, error: "store_id required" };
30
- const sid = storeId;
31
- switch (args.action) {
32
- case "inbox": {
33
- let q = sb.from("email_threads").select("*, latest_message:email_inbox(subject, from_email, created_at)")
34
- .eq("store_id", sid).order("updated_at", { ascending: false }).limit(args.limit || 25);
35
- if (args.status)
36
- q = q.eq("status", args.status);
37
- if (args.mailbox)
38
- q = q.eq("mailbox", args.mailbox);
39
- if (args.priority)
40
- q = q.eq("priority", args.priority);
41
- const { data, error } = await q;
42
- if (error)
43
- return { success: false, error: error.message };
44
- // Flatten latest_message join so subject/from appear as table columns
45
- const flattened = (data || []).map((row) => {
46
- const { latest_message, ...rest } = row;
47
- return { ...rest, subject: latest_message?.subject || null, from_email: latest_message?.from_email || null };
48
- });
49
- return { success: true, data: flattened };
36
+ if (!storeId) return {
37
+ success: false,
38
+ error: "store_id required"
39
+ };
40
+ const sid = storeId;
41
+ switch (args.action) {
42
+ case "inbox":
43
+ {
44
+ let q = sb.from("email_threads").select("*, latest_message:email_inbox(subject, from_email, created_at)").eq("store_id", sid).order("updated_at", {
45
+ ascending: false
46
+ }).limit(args.limit || 25);
47
+ if (args.status) q = q.eq("status", args.status);
48
+ if (args.mailbox) q = q.eq("mailbox", args.mailbox);
49
+ if (args.priority) q = q.eq("priority", args.priority);
50
+ const {
51
+ data,
52
+ error
53
+ } = await q;
54
+ if (error) return {
55
+ success: false,
56
+ error: error.message
57
+ };
58
+ // Flatten latest_message join so subject/from appear as table columns
59
+ const flattened = (data || []).map(row => {
60
+ const {
61
+ latest_message,
62
+ ...rest
63
+ } = row;
64
+ return {
65
+ ...rest,
66
+ subject: latest_message?.subject || null,
67
+ from_email: latest_message?.from_email || null
68
+ };
69
+ });
70
+ return {
71
+ success: true,
72
+ data: flattened
73
+ };
74
+ }
75
+ case "inbox_get":
76
+ {
77
+ const {
78
+ data,
79
+ error
80
+ } = await sb.from("email_threads").select("*, messages:email_inbox(id, subject, from_email, to_email, body_text, created_at)").eq("id", args.thread_id).eq("store_id", sid).single();
81
+ if (error) return {
82
+ success: false,
83
+ error: error.message
84
+ };
85
+ // Pre-format so messages are visible (formatter handles sub-tables but body gets truncated)
86
+ const {
87
+ messages,
88
+ ...thread
89
+ } = data;
90
+ const lines = [`## Email Thread`, `**Status**: ${thread.status || "—"} | **Mailbox**: ${thread.mailbox || "—"} | **Priority**: ${thread.priority || "—"}`, ""];
91
+ for (const msg of (messages || []).sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime())) {
92
+ lines.push(`### ${msg.from_email} → ${msg.to_email || "—"} (${msg.created_at?.slice(0, 16) || "—"})`);
93
+ lines.push(`**Subject**: ${msg.subject || "(no subject)"}`);
94
+ const body = (msg.body_text || "").slice(0, 500);
95
+ lines.push(body + (msg.body_text?.length > 500 ? "..." : ""));
96
+ lines.push("");
50
97
  }
51
- case "inbox_get": {
52
- const { data, error } = await sb.from("email_threads")
53
- .select("*, messages:email_inbox(id, subject, from_email, to_email, body_text, created_at)").eq("id", args.thread_id).eq("store_id", sid).single();
54
- if (error)
55
- return { success: false, error: error.message };
56
- // Pre-format so messages are visible (formatter handles sub-tables but body gets truncated)
57
- const { messages, ...thread } = data;
58
- const lines = [
59
- `## Email Thread`,
60
- `**Status**: ${thread.status || "—"} | **Mailbox**: ${thread.mailbox || "—"} | **Priority**: ${thread.priority || "—"}`,
61
- "",
62
- ];
63
- for (const msg of (messages || []).sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime())) {
64
- lines.push(`### ${msg.from_email} → ${msg.to_email || "—"} (${msg.created_at?.slice(0, 16) || "—"})`);
65
- lines.push(`**Subject**: ${msg.subject || "(no subject)"}`);
66
- const body = (msg.body_text || "").slice(0, 500);
67
- lines.push(body + (msg.body_text?.length > 500 ? "..." : ""));
68
- lines.push("");
69
- }
70
- return { success: true, data: lines.join("\n") };
98
+ return {
99
+ success: true,
100
+ data: lines.join("\n")
101
+ };
102
+ }
103
+ case "send":
104
+ {
105
+ // P0 FIX: Per-store email rate limit (100 emails/hour)
106
+ const oneHourAgo = new Date(Date.now() - 3600_000).toISOString();
107
+ const {
108
+ count: recentSends
109
+ } = await sb.from("email_sends").select("id", {
110
+ count: "exact",
111
+ head: true
112
+ }).eq("store_id", sid).gte("created_at", oneHourAgo);
113
+ if ((recentSends || 0) >= 100) {
114
+ return {
115
+ success: false,
116
+ error: "Email rate limit exceeded (100/hour). Try again later."
117
+ };
71
118
  }
72
- case "send": {
73
- // P0 FIX: Per-store email rate limit (100 emails/hour)
74
- const oneHourAgo = new Date(Date.now() - 3600_000).toISOString();
75
- const { count: recentSends } = await sb.from("email_sends")
76
- .select("id", { count: "exact", head: true })
77
- .eq("store_id", sid)
78
- .gte("created_at", oneHourAgo);
79
- if ((recentSends || 0) >= 100) {
80
- return { success: false, error: "Email rate limit exceeded (100/hour). Try again later." };
119
+
120
+ // Invoke send-email edge function
121
+ const sbUrl = process.env["SUPABASE_URL"];
122
+ const sbKey = process.env["SUPABASE_SERVICE_ROLE_KEY"];
123
+ try {
124
+ // Resolve URL-based attachments to base64 for Resend API
125
+ let attachments;
126
+ const rawAttachments = args.attachments;
127
+ if (rawAttachments && rawAttachments.length > 0) {
128
+ // P0 FIX: Enforce attachment count limit
129
+ if (rawAttachments.length > MAX_ATTACHMENT_COUNT) {
130
+ throw new Error(`Too many attachments: ${rawAttachments.length} (max ${MAX_ATTACHMENT_COUNT})`);
81
131
  }
82
- // Invoke send-email edge function
83
- const sbUrl = process.env["SUPABASE_URL"];
84
- const sbKey = process.env["SUPABASE_SERVICE_ROLE_KEY"];
85
- try {
86
- // Resolve URL-based attachments to base64 for Resend API
87
- let attachments;
88
- const rawAttachments = args.attachments;
89
- if (rawAttachments && rawAttachments.length > 0) {
90
- // P0 FIX: Enforce attachment count limit
91
- if (rawAttachments.length > MAX_ATTACHMENT_COUNT) {
92
- throw new Error(`Too many attachments: ${rawAttachments.length} (max ${MAX_ATTACHMENT_COUNT})`);
93
- }
94
- attachments = [];
95
- let totalBytes = 0;
96
- for (const att of rawAttachments) {
97
- if (att.url) {
98
- // P0 FIX: Validate URL to prevent SSRF via attachment URLs
99
- const ssrfErr = await validateUrl(att.url);
100
- if (ssrfErr)
101
- throw new Error(`Attachment URL blocked: ${ssrfErr}`);
102
- // Fetch the file and convert to base64
103
- const fileResp = await fetch(att.url);
104
- if (!fileResp.ok)
105
- throw new Error(`Failed to fetch attachment: ${att.url} (${fileResp.status})`);
106
- const buffer = await fileResp.arrayBuffer();
107
- // P0 FIX: Enforce 10MB per-attachment size limit
108
- if (buffer.byteLength > MAX_ATTACHMENT_BYTES) {
109
- throw new Error(`Attachment too large: ${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB (max 10MB)`);
110
- }
111
- // P0 FIX: Enforce 25MB total attachment size limit
112
- totalBytes += buffer.byteLength;
113
- if (totalBytes > MAX_TOTAL_ATTACHMENT_BYTES) {
114
- throw new Error(`Total attachment size exceeds limit: ${(totalBytes / 1024 / 1024).toFixed(1)}MB (max 25MB)`);
115
- }
116
- const base64 = Buffer.from(buffer).toString("base64");
117
- const filename = att.filename || att.url.split("/").pop() || "attachment";
118
- attachments.push({ filename, content: base64 });
119
- }
120
- else if (att.content && att.filename) {
121
- // Already base64 — decode length to check size
122
- const contentBytes = Math.ceil((att.content.length * 3) / 4);
123
- if (contentBytes > MAX_ATTACHMENT_BYTES) {
124
- throw new Error(`Attachment too large: ${(contentBytes / 1024 / 1024).toFixed(1)}MB (max 10MB)`);
125
- }
126
- totalBytes += contentBytes;
127
- if (totalBytes > MAX_TOTAL_ATTACHMENT_BYTES) {
128
- throw new Error(`Total attachment size exceeds limit: ${(totalBytes / 1024 / 1024).toFixed(1)}MB (max 25MB)`);
129
- }
130
- attachments.push({ filename: att.filename, content: att.content });
131
- }
132
- }
132
+ attachments = [];
133
+ let totalBytes = 0;
134
+ for (const att of rawAttachments) {
135
+ if (att.url) {
136
+ // P0 FIX: Validate URL to prevent SSRF via attachment URLs
137
+ const ssrfErr = await validateUrl(att.url);
138
+ if (ssrfErr) throw new Error(`Attachment URL blocked: ${ssrfErr}`);
139
+ // Fetch the file and convert to base64
140
+ const fileResp = await fetch(att.url);
141
+ if (!fileResp.ok) throw new Error(`Failed to fetch attachment: ${att.url} (${fileResp.status})`);
142
+ const buffer = await fileResp.arrayBuffer();
143
+ // P0 FIX: Enforce 10MB per-attachment size limit
144
+ if (buffer.byteLength > MAX_ATTACHMENT_BYTES) {
145
+ throw new Error(`Attachment too large: ${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB (max 10MB)`);
133
146
  }
134
- // Resolve {{variable}} placeholders in email fields against template_data
135
- let emailHtml = args.html;
136
- let emailSubject = args.subject;
137
- let emailText = args.text;
138
- if (args.template_data && typeof args.template_data === "object") {
139
- const tplData = args.template_data;
140
- if (emailHtml)
141
- emailHtml = fillTemplate(emailHtml, tplData);
142
- if (emailSubject)
143
- emailSubject = fillTemplate(emailSubject, tplData);
144
- if (emailText)
145
- emailText = fillTemplate(emailText, tplData);
147
+ // P0 FIX: Enforce 25MB total attachment size limit
148
+ totalBytes += buffer.byteLength;
149
+ if (totalBytes > MAX_TOTAL_ATTACHMENT_BYTES) {
150
+ throw new Error(`Total attachment size exceeds limit: ${(totalBytes / 1024 / 1024).toFixed(1)}MB (max 25MB)`);
146
151
  }
147
- const payload = { to: args.to, subject: emailSubject, html: emailHtml, text: emailText, storeId: sid };
148
- if (attachments && attachments.length > 0)
149
- payload.attachments = attachments;
150
- const resp = await fetch(`${sbUrl}/functions/v1/send-email`, {
151
- method: "POST",
152
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${sbKey}` },
153
- body: JSON.stringify(payload)
152
+ const base64 = Buffer.from(buffer).toString("base64");
153
+ const filename = att.filename || att.url.split("/").pop() || "attachment";
154
+ attachments.push({
155
+ filename,
156
+ content: base64
154
157
  });
155
- const result = await resp.json();
156
- return resp.ok ? { success: true, data: result } : { success: false, error: result.error || "Send failed" };
157
- }
158
- catch (err) {
159
- return { success: false, error: `Email send failed: ${err}` };
160
- }
161
- }
162
- case "send_template": {
163
- const sbUrl = process.env["SUPABASE_URL"];
164
- const sbKey = process.env["SUPABASE_SERVICE_ROLE_KEY"];
165
- try {
166
- const resp = await fetch(`${sbUrl}/functions/v1/send-email`, {
167
- method: "POST",
168
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${sbKey}` },
169
- body: JSON.stringify({ to: args.to, template: args.template, template_data: args.template_data, storeId: sid })
158
+ } else if (att.content && att.filename) {
159
+ // Already base64 decode length to check size
160
+ const contentBytes = Math.ceil(att.content.length * 3 / 4);
161
+ if (contentBytes > MAX_ATTACHMENT_BYTES) {
162
+ throw new Error(`Attachment too large: ${(contentBytes / 1024 / 1024).toFixed(1)}MB (max 10MB)`);
163
+ }
164
+ totalBytes += contentBytes;
165
+ if (totalBytes > MAX_TOTAL_ATTACHMENT_BYTES) {
166
+ throw new Error(`Total attachment size exceeds limit: ${(totalBytes / 1024 / 1024).toFixed(1)}MB (max 25MB)`);
167
+ }
168
+ attachments.push({
169
+ filename: att.filename,
170
+ content: att.content
170
171
  });
171
- const result = await resp.json();
172
- return resp.ok ? { success: true, data: result } : { success: false, error: result.error || "Send failed" };
173
- }
174
- catch (err) {
175
- return { success: false, error: `Template send failed: ${err}` };
172
+ }
176
173
  }
174
+ }
175
+
176
+ // Resolve {{variable}} placeholders in email fields against template_data
177
+ let emailHtml = args.html;
178
+ let emailSubject = args.subject;
179
+ let emailText = args.text;
180
+ if (args.template_data && typeof args.template_data === "object") {
181
+ const tplData = args.template_data;
182
+ if (emailHtml) emailHtml = fillTemplate(emailHtml, tplData);
183
+ if (emailSubject) emailSubject = fillTemplate(emailSubject, tplData);
184
+ if (emailText) emailText = fillTemplate(emailText, tplData);
185
+ }
186
+ const payload = {
187
+ to: args.to,
188
+ subject: emailSubject,
189
+ html: emailHtml,
190
+ text: emailText,
191
+ storeId: sid
192
+ };
193
+ if (attachments && attachments.length > 0) payload.attachments = attachments;
194
+ const resp = await fetch(`${sbUrl}/functions/v1/send-email`, {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ "Authorization": `Bearer ${sbKey}`
199
+ },
200
+ body: JSON.stringify(payload)
201
+ });
202
+ const result = await resp.json();
203
+ return resp.ok ? {
204
+ success: true,
205
+ data: result
206
+ } : {
207
+ success: false,
208
+ error: result.error || "Send failed"
209
+ };
210
+ } catch (err) {
211
+ return {
212
+ success: false,
213
+ error: `Email send failed: ${err}`
214
+ };
177
215
  }
178
- case "list": {
179
- const { data, error } = await sb.from("email_sends").select("*")
180
- .eq("store_id", sid).order("created_at", { ascending: false }).limit(args.limit || 50);
181
- return error ? { success: false, error: error.message } : { success: true, data };
182
- }
183
- case "get": {
184
- const { data, error } = await sb.from("email_sends")
185
- .select("*").eq("id", args.email_id).eq("store_id", sid).single();
186
- return error ? { success: false, error: error.message } : { success: true, data };
216
+ }
217
+ case "send_template":
218
+ {
219
+ const sbUrl = process.env["SUPABASE_URL"];
220
+ const sbKey = process.env["SUPABASE_SERVICE_ROLE_KEY"];
221
+ try {
222
+ const resp = await fetch(`${sbUrl}/functions/v1/send-email`, {
223
+ method: "POST",
224
+ headers: {
225
+ "Content-Type": "application/json",
226
+ "Authorization": `Bearer ${sbKey}`
227
+ },
228
+ body: JSON.stringify({
229
+ to: args.to,
230
+ template: args.template,
231
+ template_data: args.template_data,
232
+ storeId: sid
233
+ })
234
+ });
235
+ const result = await resp.json();
236
+ return resp.ok ? {
237
+ success: true,
238
+ data: result
239
+ } : {
240
+ success: false,
241
+ error: result.error || "Send failed"
242
+ };
243
+ } catch (err) {
244
+ return {
245
+ success: false,
246
+ error: `Template send failed: ${err}`
247
+ };
187
248
  }
188
- case "templates": {
189
- const { data, error } = await sb.from("email_templates").select("*")
190
- .eq("store_id", sid).eq("is_active", true).limit(100);
191
- return error ? { success: false, error: error.message } : { success: true, data };
249
+ }
250
+ case "list":
251
+ {
252
+ const {
253
+ data,
254
+ error
255
+ } = await sb.from("email_sends").select("*").eq("store_id", sid).order("created_at", {
256
+ ascending: false
257
+ }).limit(args.limit || 50);
258
+ return error ? {
259
+ success: false,
260
+ error: error.message
261
+ } : {
262
+ success: true,
263
+ data
264
+ };
265
+ }
266
+ case "get":
267
+ {
268
+ const {
269
+ data,
270
+ error
271
+ } = await sb.from("email_sends").select("*").eq("id", args.email_id).eq("store_id", sid).single();
272
+ return error ? {
273
+ success: false,
274
+ error: error.message
275
+ } : {
276
+ success: true,
277
+ data
278
+ };
279
+ }
280
+ case "templates":
281
+ {
282
+ const {
283
+ data,
284
+ error
285
+ } = await sb.from("email_templates").select("*").eq("store_id", sid).eq("is_active", true).limit(100);
286
+ return error ? {
287
+ success: false,
288
+ error: error.message
289
+ } : {
290
+ success: true,
291
+ data
292
+ };
293
+ }
294
+ case "inbox_reply":
295
+ {
296
+ const sbUrl = process.env["SUPABASE_URL"];
297
+ const sbKey = process.env["SUPABASE_SERVICE_ROLE_KEY"];
298
+ try {
299
+ const resp = await fetch(`${sbUrl}/functions/v1/send-email`, {
300
+ method: "POST",
301
+ headers: {
302
+ "Content-Type": "application/json",
303
+ "Authorization": `Bearer ${sbKey}`
304
+ },
305
+ body: JSON.stringify({
306
+ to: args.to,
307
+ subject: args.subject,
308
+ html: args.html,
309
+ text: args.text,
310
+ thread_id: args.thread_id,
311
+ storeId: sid
312
+ })
313
+ });
314
+ const result = await resp.json();
315
+ return resp.ok ? {
316
+ success: true,
317
+ data: result
318
+ } : {
319
+ success: false,
320
+ error: result.error || "Reply failed"
321
+ };
322
+ } catch (err) {
323
+ return {
324
+ success: false,
325
+ error: `Reply failed: ${err}`
326
+ };
192
327
  }
193
- case "inbox_reply": {
194
- const sbUrl = process.env["SUPABASE_URL"];
195
- const sbKey = process.env["SUPABASE_SERVICE_ROLE_KEY"];
196
- try {
197
- const resp = await fetch(`${sbUrl}/functions/v1/send-email`, {
198
- method: "POST",
199
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${sbKey}` },
200
- body: JSON.stringify({ to: args.to, subject: args.subject, html: args.html, text: args.text, thread_id: args.thread_id, storeId: sid })
201
- });
202
- const result = await resp.json();
203
- return resp.ok ? { success: true, data: result } : { success: false, error: result.error || "Reply failed" };
204
- }
205
- catch (err) {
206
- return { success: false, error: `Reply failed: ${err}` };
328
+ }
329
+ case "inbox_update":
330
+ {
331
+ const updates = {};
332
+ if (args.status) updates.status = args.status;
333
+ if (args.priority) updates.priority = args.priority;
334
+ if (args.intent) updates.ai_intent = args.intent;
335
+ if (args.ai_summary) updates.ai_summary = args.ai_summary;
336
+ const {
337
+ data,
338
+ error
339
+ } = await sb.from("email_threads").update(updates).eq("id", args.thread_id).eq("store_id", sid).select().single();
340
+ return error ? {
341
+ success: false,
342
+ error: error.message
343
+ } : {
344
+ success: true,
345
+ data
346
+ };
347
+ }
348
+ case "inbox_stats":
349
+ {
350
+ const {
351
+ data,
352
+ error
353
+ } = await sb.from("email_threads").select("status, mailbox, priority").eq("store_id", sid).limit(1000);
354
+ if (error) return {
355
+ success: false,
356
+ error: error.message
357
+ };
358
+ const stats = {
359
+ total: data.length,
360
+ by_status: groupBy(data, "status"),
361
+ by_mailbox: groupBy(data, "mailbox"),
362
+ by_priority: groupBy(data, "priority")
363
+ };
364
+ return {
365
+ success: true,
366
+ data: stats
367
+ };
368
+ }
369
+ case "create_template":
370
+ {
371
+ if (!args.name) return {
372
+ success: false,
373
+ error: "name is required"
374
+ };
375
+ if (!args.subject) return {
376
+ success: false,
377
+ error: "subject is required"
378
+ };
379
+ const slug = args.slug || args.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
380
+ const {
381
+ data,
382
+ error
383
+ } = await sb.from("email_templates").insert({
384
+ store_id: sid,
385
+ name: args.name,
386
+ slug,
387
+ subject: args.subject,
388
+ category: args.category || "general",
389
+ description: args.description || null,
390
+ html_content: args.html_content || null,
391
+ preview_text: args.preview_text || null,
392
+ text_content: args.text_content || null,
393
+ is_active: true
394
+ }).select("id, name, slug, subject, category, created_at").single();
395
+ if (error) return {
396
+ success: false,
397
+ error: error.message
398
+ };
399
+ return {
400
+ success: true,
401
+ data
402
+ };
403
+ }
404
+ case "update_template":
405
+ {
406
+ if (!args.template_id) return {
407
+ success: false,
408
+ error: "template_id is required"
409
+ };
410
+ const updates = {};
411
+ if (args.name !== undefined) updates.name = args.name;
412
+ if (args.slug !== undefined) updates.slug = args.slug;
413
+ if (args.subject !== undefined) updates.subject = args.subject;
414
+ if (args.category !== undefined) updates.category = args.category;
415
+ if (args.description !== undefined) updates.description = args.description;
416
+ if (args.html_content !== undefined) updates.html_content = args.html_content;
417
+ if (args.preview_text !== undefined) updates.preview_text = args.preview_text;
418
+ if (args.text_content !== undefined) updates.text_content = args.text_content;
419
+ if (Object.keys(updates).length === 0) return {
420
+ success: false,
421
+ error: "No fields to update"
422
+ };
423
+ const {
424
+ data,
425
+ error
426
+ } = await sb.from("email_templates").update(updates).eq("id", args.template_id).eq("store_id", sid).select("id, name, slug, subject, category, updated_at").single();
427
+ if (error) return {
428
+ success: false,
429
+ error: error.message
430
+ };
431
+ return {
432
+ success: true,
433
+ data
434
+ };
435
+ }
436
+ case "delete_template":
437
+ {
438
+ if (!args.template_id) return {
439
+ success: false,
440
+ error: "template_id is required"
441
+ };
442
+ const {
443
+ data,
444
+ error
445
+ } = await sb.from("email_templates").update({
446
+ is_active: false
447
+ }).eq("id", args.template_id).eq("store_id", sid).select("id, name").single();
448
+ if (error) return {
449
+ success: false,
450
+ error: error.message
451
+ };
452
+ return {
453
+ success: true,
454
+ data: {
455
+ ...data,
456
+ deleted: true
457
+ }
458
+ };
459
+ }
460
+ default:
461
+ return {
462
+ success: false,
463
+ error: `Unknown email action: ${args.action}. Valid: send, send_template, list, get, templates, inbox, inbox_get, inbox_reply, inbox_update, inbox_stats, create_template, update_template, delete_template`
464
+ };
465
+ }
466
+ }
467
+ export async function handleDocuments(sb, args, storeId) {
468
+ if (!storeId) return {
469
+ success: false,
470
+ error: "store_id required"
471
+ };
472
+ const sid = storeId;
473
+ const action = args.action;
474
+ switch (action) {
475
+ case "create":
476
+ {
477
+ const docType = args.document_type || "text";
478
+ const name = args.name;
479
+ if (!name) return {
480
+ success: false,
481
+ error: "name is required"
482
+ };
483
+ const extMap = {
484
+ csv: "csv",
485
+ json: "json",
486
+ text: "txt",
487
+ markdown: "md",
488
+ html: "html",
489
+ pdf: "pdf"
490
+ };
491
+ const mimeMap = {
492
+ csv: "text/csv",
493
+ json: "application/json",
494
+ text: "text/plain",
495
+ markdown: "text/markdown",
496
+ html: "text/html",
497
+ pdf: "application/pdf"
498
+ };
499
+ const ext = extMap[docType] || "txt";
500
+ const mime = mimeMap[docType] || "text/plain";
501
+ const safeName = name.replace(/[^a-zA-Z0-9_\-]/g, "_");
502
+ const fileName = `${safeName}_${Date.now()}.${ext}`;
503
+ const storagePath = `${sid}/${fileName}`;
504
+ let uploadBuffer;
505
+ if (docType === "pdf") {
506
+ // PDF generation from HTML content via React-PDF
507
+ const htmlContent = args.html || args.content;
508
+ if (!htmlContent) return {
509
+ success: false,
510
+ error: "html or content (HTML string) is required for PDF documents"
511
+ };
512
+ try {
513
+ const pdfBuffer = await renderHtmlToPdf(htmlContent, {
514
+ format: args.format || "A4",
515
+ landscape: args.landscape || false
516
+ });
517
+ uploadBuffer = new Uint8Array(pdfBuffer);
518
+ } catch (err) {
519
+ return {
520
+ success: false,
521
+ error: `PDF generation failed: ${err instanceof Error ? err.message : String(err)}`
522
+ };
523
+ }
524
+ } else {
525
+ let content;
526
+ if (docType === "csv") {
527
+ const headers = args.headers;
528
+ const rows = args.rows;
529
+ if (!headers || !rows) return {
530
+ success: false,
531
+ error: "headers and rows required for CSV"
532
+ };
533
+ const lines = [headers.map(escapeCSV).join(",")];
534
+ for (const row of rows) {
535
+ if (Array.isArray(row)) {
536
+ lines.push(row.map(escapeCSV).join(","));
537
+ } else {
538
+ lines.push(headers.map(h => escapeCSV(row[h])).join(","));
539
+ }
207
540
  }
541
+ content = lines.join("\n");
542
+ } else if (docType === "json") {
543
+ const jsonData = args.data || args.content;
544
+ content = typeof jsonData === "string" ? jsonData : JSON.stringify(jsonData, null, 2);
545
+ } else {
546
+ content = args.content || "";
547
+ }
548
+ uploadBuffer = new TextEncoder().encode(content);
208
549
  }
209
- case "inbox_update": {
210
- const updates = {};
211
- if (args.status)
212
- updates.status = args.status;
213
- if (args.priority)
214
- updates.priority = args.priority;
215
- if (args.intent)
216
- updates.ai_intent = args.intent;
217
- if (args.ai_summary)
218
- updates.ai_summary = args.ai_summary;
219
- const { data, error } = await sb.from("email_threads")
220
- .update(updates).eq("id", args.thread_id).eq("store_id", sid).select().single();
221
- return error ? { success: false, error: error.message } : { success: true, data };
550
+ const {
551
+ error: uploadErr
552
+ } = await sb.storage.from("documents").upload(storagePath, uploadBuffer, {
553
+ contentType: mime,
554
+ upsert: true
555
+ });
556
+ if (uploadErr) return {
557
+ success: false,
558
+ error: `Upload failed: ${uploadErr.message}`
559
+ };
560
+ const {
561
+ data: urlData
562
+ } = sb.storage.from("documents").getPublicUrl(storagePath);
563
+ const fileUrl = urlData.publicUrl;
564
+ const sizeBytes = uploadBuffer.length;
565
+ const {
566
+ data: record,
567
+ error: insertErr
568
+ } = await sb.from("store_documents").insert({
569
+ store_id: sid,
570
+ document_type: docType,
571
+ file_name: fileName,
572
+ file_url: fileUrl,
573
+ file_size: sizeBytes,
574
+ file_type: mime,
575
+ document_name: name,
576
+ source_name: "Documents Edge Function",
577
+ document_date: new Date().toISOString().split("T")[0],
578
+ data: {
579
+ document_type: docType
580
+ },
581
+ metadata: {
582
+ size_bytes: sizeBytes
583
+ }
584
+ }).select("id, document_name, file_url, created_at").single();
585
+ if (insertErr) return {
586
+ success: false,
587
+ error: insertErr.message
588
+ };
589
+ if (fileUrl.endsWith('.pdf')) await generateThumbnail(sb, record.id, fileUrl).catch(() => {});
590
+ return {
591
+ success: true,
592
+ data: {
593
+ id: record.id,
594
+ name: record.document_name,
595
+ type: docType,
596
+ url: record.file_url,
597
+ file_name: fileName,
598
+ size: sizeBytes
599
+ }
600
+ };
601
+ }
602
+ case "find":
603
+ {
604
+ let query = sb.from("store_documents").select("id, document_type, document_name, reference_number, file_url, file_type, file_size, created_at, metadata");
605
+ query = query.eq("store_id", sid);
606
+ if (args.document_type) query = query.eq("document_type", args.document_type);
607
+ if (args.name) {
608
+ const sn = sanitizeFilterValue(args.name);
609
+ query = query.ilike("document_name", `%${sn}%`);
222
610
  }
223
- case "inbox_stats": {
224
- const { data, error } = await sb.from("email_threads")
225
- .select("status, mailbox, priority").eq("store_id", sid).limit(1000);
226
- if (error)
227
- return { success: false, error: error.message };
228
- const stats = {
229
- total: data.length,
230
- by_status: groupBy(data, "status"),
231
- by_mailbox: groupBy(data, "mailbox"),
232
- by_priority: groupBy(data, "priority")
233
- };
234
- return { success: true, data: stats };
611
+ query = query.order("created_at", {
612
+ ascending: false
613
+ }).limit(args.limit || 50);
614
+ const {
615
+ data,
616
+ error
617
+ } = await query;
618
+ if (error) return {
619
+ success: false,
620
+ error: error.message
621
+ };
622
+ return {
623
+ success: true,
624
+ data: {
625
+ count: data?.length || 0,
626
+ documents: (data || []).map(d => ({
627
+ id: d.id,
628
+ type: d.document_type,
629
+ name: d.document_name,
630
+ reference: d.reference_number,
631
+ url: d.file_url,
632
+ size: d.file_size,
633
+ created: d.created_at
634
+ }))
635
+ }
636
+ };
637
+ }
638
+ case "delete":
639
+ {
640
+ if (!args.confirm) return {
641
+ success: false,
642
+ error: "Set confirm: true to delete"
643
+ };
644
+ let query = sb.from("store_documents").delete().eq("store_id", sid);
645
+ if (args.document_type) query = query.eq("document_type", args.document_type);
646
+ if (args.name) {
647
+ const sn = sanitizeFilterValue(args.name);
648
+ query = query.ilike("document_name", `%${sn}%`);
235
649
  }
236
- case "create_template": {
237
- if (!args.name)
238
- return { success: false, error: "name is required" };
239
- if (!args.subject)
240
- return { success: false, error: "subject is required" };
241
- const slug = args.slug || args.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
242
- const { data, error } = await sb.from("email_templates").insert({
243
- store_id: sid,
244
- name: args.name,
245
- slug,
246
- subject: args.subject,
247
- category: args.category || "general",
248
- description: args.description || null,
249
- html_content: args.html_content || null,
250
- preview_text: args.preview_text || null,
251
- text_content: args.text_content || null,
252
- is_active: true,
253
- }).select("id, name, slug, subject, category, created_at").single();
254
- if (error)
255
- return { success: false, error: error.message };
256
- return { success: true, data };
650
+ const {
651
+ data,
652
+ error
653
+ } = await query.select("id");
654
+ if (error) return {
655
+ success: false,
656
+ error: error.message
657
+ };
658
+ return {
659
+ success: true,
660
+ data: {
661
+ deleted: data?.length || 0
662
+ }
663
+ };
664
+ }
665
+ case "create_template":
666
+ {
667
+ if (!args.name) return {
668
+ success: false,
669
+ error: "name is required"
670
+ };
671
+ if (!args.document_type) return {
672
+ success: false,
673
+ error: "document_type is required"
674
+ };
675
+ const {
676
+ data,
677
+ error
678
+ } = await sb.from("document_templates").insert({
679
+ store_id: sid || null,
680
+ name: args.name,
681
+ description: args.description || null,
682
+ document_type: args.document_type,
683
+ content: args.content || null,
684
+ headers: args.headers || null,
685
+ schema: args.schema || [],
686
+ metadata: args.data || {}
687
+ }).select("id, name, document_type, created_at").single();
688
+ if (error) return {
689
+ success: false,
690
+ error: error.message
691
+ };
692
+ return {
693
+ success: true,
694
+ data: {
695
+ template_id: data.id,
696
+ name: data.name,
697
+ type: data.document_type
698
+ }
699
+ };
700
+ }
701
+ case "list_templates":
702
+ {
703
+ let query = sb.from("document_templates").select("id, name, description, document_type, headers, schema, created_at").eq("is_active", true);
704
+ if (sid) query = query.or(`store_id.eq.${sid},store_id.is.null`);
705
+ if (args.document_type) query = query.eq("document_type", args.document_type);
706
+ if (args.limit) query = query.limit(args.limit);
707
+ query = query.order("created_at", {
708
+ ascending: false
709
+ });
710
+ const {
711
+ data,
712
+ error
713
+ } = await query;
714
+ if (error) return {
715
+ success: false,
716
+ error: error.message
717
+ };
718
+ return {
719
+ success: true,
720
+ data: {
721
+ count: data?.length || 0,
722
+ templates: (data || []).map(t => ({
723
+ id: t.id,
724
+ name: t.name,
725
+ description: t.description,
726
+ type: t.document_type,
727
+ headers: t.headers,
728
+ fields: t.schema?.length || 0
729
+ }))
730
+ }
731
+ };
732
+ }
733
+ case "from_template":
734
+ {
735
+ const templateId = args.template_id;
736
+ if (!templateId) return {
737
+ success: false,
738
+ error: "template_id is required"
739
+ };
740
+ const {
741
+ data: template,
742
+ error: tErr
743
+ } = await sb.from("document_templates").select("*").eq("id", templateId).or(`store_id.eq.${sid},store_id.is.null`).single();
744
+ if (tErr || !template) return {
745
+ success: false,
746
+ error: "Template not found"
747
+ };
748
+ const tData = {
749
+ ...template.metadata,
750
+ ...(args.data || {}),
751
+ date: new Date().toISOString().split("T")[0]
752
+ };
753
+ const docType = template.document_type;
754
+ const docName = args.name || fillTemplate(template.name, tData);
755
+ let content;
756
+ if (docType === "csv") {
757
+ const headers = template.headers || [];
758
+ const rows = args.rows;
759
+ if (!rows) return {
760
+ success: false,
761
+ error: "rows required for CSV template"
762
+ };
763
+ const lines = [headers.map(escapeCSV).join(",")];
764
+ for (const row of rows) {
765
+ if (Array.isArray(row)) lines.push(row.map(escapeCSV).join(","));else lines.push(headers.map(h => escapeCSV(row[h])).join(","));
766
+ }
767
+ content = lines.join("\n");
768
+ } else if (docType === "json") {
769
+ content = template.content ? fillTemplate(template.content, tData) : JSON.stringify(tData, null, 2);
770
+ } else {
771
+ content = template.content ? fillTemplate(template.content, tData) : "";
257
772
  }
258
- case "update_template": {
259
- if (!args.template_id)
260
- return { success: false, error: "template_id is required" };
261
- const updates = {};
262
- if (args.name !== undefined)
263
- updates.name = args.name;
264
- if (args.slug !== undefined)
265
- updates.slug = args.slug;
266
- if (args.subject !== undefined)
267
- updates.subject = args.subject;
268
- if (args.category !== undefined)
269
- updates.category = args.category;
270
- if (args.description !== undefined)
271
- updates.description = args.description;
272
- if (args.html_content !== undefined)
273
- updates.html_content = args.html_content;
274
- if (args.preview_text !== undefined)
275
- updates.preview_text = args.preview_text;
276
- if (args.text_content !== undefined)
277
- updates.text_content = args.text_content;
278
- if (Object.keys(updates).length === 0)
279
- return { success: false, error: "No fields to update" };
280
- const { data, error } = await sb.from("email_templates")
281
- .update(updates).eq("id", args.template_id).eq("store_id", sid)
282
- .select("id, name, slug, subject, category, updated_at").single();
283
- if (error)
284
- return { success: false, error: error.message };
285
- return { success: true, data };
773
+ const extMap = {
774
+ csv: "csv",
775
+ json: "json",
776
+ text: "txt",
777
+ markdown: "md",
778
+ html: "html"
779
+ };
780
+ const mimeMap = {
781
+ csv: "text/csv",
782
+ json: "application/json",
783
+ text: "text/plain",
784
+ markdown: "text/markdown",
785
+ html: "text/html"
786
+ };
787
+ const ext = extMap[docType] || "txt";
788
+ const mime = mimeMap[docType] || "text/plain";
789
+ const safeName = docName.replace(/[^a-zA-Z0-9_\-]/g, "_");
790
+ const fileName = `${safeName}_${Date.now()}.${ext}`;
791
+ const storagePath = `${sid}/${fileName}`;
792
+ const {
793
+ error: uploadErr
794
+ } = await sb.storage.from("documents").upload(storagePath, new TextEncoder().encode(content), {
795
+ contentType: mime,
796
+ upsert: true
797
+ });
798
+ if (uploadErr) return {
799
+ success: false,
800
+ error: `Upload failed: ${uploadErr.message}`
801
+ };
802
+ const {
803
+ data: urlData
804
+ } = sb.storage.from("documents").getPublicUrl(storagePath);
805
+ const sizeBytes = new TextEncoder().encode(content).length;
806
+ const {
807
+ data: record,
808
+ error: insertErr
809
+ } = await sb.from("store_documents").insert({
810
+ store_id: sid,
811
+ document_type: docType,
812
+ file_name: fileName,
813
+ file_url: urlData.publicUrl,
814
+ file_size: sizeBytes,
815
+ file_type: mime,
816
+ document_name: docName,
817
+ source_name: "Documents Edge Function",
818
+ document_date: new Date().toISOString().split("T")[0],
819
+ data: {
820
+ template_id: template.id,
821
+ template_name: template.name
822
+ },
823
+ metadata: {
824
+ size_bytes: sizeBytes,
825
+ from_template: true
826
+ }
827
+ }).select("id, document_name, file_url, created_at").single();
828
+ if (insertErr) return {
829
+ success: false,
830
+ error: insertErr.message
831
+ };
832
+ if (urlData.publicUrl.endsWith('.pdf')) await generateThumbnail(sb, record.id, urlData.publicUrl).catch(() => {});
833
+ return {
834
+ success: true,
835
+ data: {
836
+ id: record.id,
837
+ name: record.document_name,
838
+ type: docType,
839
+ template: template.name,
840
+ url: record.file_url,
841
+ size: sizeBytes
842
+ }
843
+ };
844
+ }
845
+ case "list_stores":
846
+ {
847
+ const {
848
+ data,
849
+ error
850
+ } = await sb.from("stores").select("id, store_name, slug, status, created_at").eq("id", sid).order("store_name");
851
+ if (error) return {
852
+ success: false,
853
+ error: error.message
854
+ };
855
+ return {
856
+ success: true,
857
+ data: {
858
+ count: data?.length || 0,
859
+ stores: data
860
+ }
861
+ };
862
+ }
863
+ case "list_profiles":
864
+ {
865
+ const {
866
+ data,
867
+ error
868
+ } = await sb.from("document_profiles").select("id, name, category, sample_type, config, template_id, is_active, created_at").eq("is_active", true).eq("owner_store_id", sid).order("name");
869
+ if (error) return {
870
+ success: false,
871
+ error: error.message
872
+ };
873
+ return {
874
+ success: true,
875
+ data: {
876
+ count: data?.length || 0,
877
+ profiles: (data || []).map(p => ({
878
+ id: p.id,
879
+ name: p.name,
880
+ category: p.category,
881
+ sample_type: p.sample_type,
882
+ template_id: p.template_id,
883
+ has_config: Object.keys(p.config || {}).length > 0
884
+ }))
885
+ }
886
+ };
887
+ }
888
+ case "generate":
889
+ {
890
+ // Generate a document from a template profile with store data
891
+ const templateId = args.template_id;
892
+ if (!templateId) return {
893
+ success: false,
894
+ error: "template_id is required. Use list_profiles to find available templates."
895
+ };
896
+ // Route to from_template with the same args
897
+ return handleDocuments(sb, {
898
+ ...args,
899
+ action: "from_template"
900
+ }, storeId);
901
+ }
902
+ case "bulk_generate":
903
+ {
904
+ const templateId = args.template_id;
905
+ if (!templateId) return {
906
+ success: false,
907
+ error: "template_id required"
908
+ };
909
+ const items = args.items;
910
+ if (!items || !Array.isArray(items) || items.length === 0) {
911
+ return {
912
+ success: false,
913
+ error: "items required: array of variable sets for each document"
914
+ };
286
915
  }
287
- case "delete_template": {
288
- if (!args.template_id)
289
- return { success: false, error: "template_id is required" };
290
- const { data, error } = await sb.from("email_templates")
291
- .update({ is_active: false }).eq("id", args.template_id).eq("store_id", sid)
292
- .select("id, name").single();
293
- if (error)
294
- return { success: false, error: error.message };
295
- return { success: true, data: { ...data, deleted: true } };
916
+ if (items.length > 50) {
917
+ return {
918
+ success: false,
919
+ error: "Maximum 50 documents per bulk_generate call"
920
+ };
296
921
  }
297
- default:
298
- return { success: false, error: `Unknown email action: ${args.action}. Valid: send, send_template, list, get, templates, inbox, inbox_get, inbox_reply, inbox_update, inbox_stats, create_template, update_template, delete_template` };
299
- }
300
- }
301
- export async function handleDocuments(sb, args, storeId) {
302
- if (!storeId)
303
- return { success: false, error: "store_id required" };
304
- const sid = storeId;
305
- const action = args.action;
306
- switch (action) {
307
- case "create": {
308
- const docType = args.document_type || "text";
309
- const name = args.name;
310
- if (!name)
311
- return { success: false, error: "name is required" };
312
- const extMap = { csv: "csv", json: "json", text: "txt", markdown: "md", html: "html", pdf: "pdf" };
313
- const mimeMap = {
314
- csv: "text/csv", json: "application/json", text: "text/plain",
315
- markdown: "text/markdown", html: "text/html", pdf: "application/pdf",
316
- };
317
- const ext = extMap[docType] || "txt";
318
- const mime = mimeMap[docType] || "text/plain";
319
- const safeName = name.replace(/[^a-zA-Z0-9_\-]/g, "_");
320
- const fileName = `${safeName}_${Date.now()}.${ext}`;
321
- const storagePath = `${sid}/${fileName}`;
322
- let uploadBuffer;
323
- if (docType === "pdf") {
324
- // PDF generation from HTML content via React-PDF
325
- const htmlContent = args.html || args.content;
326
- if (!htmlContent)
327
- return { success: false, error: "html or content (HTML string) is required for PDF documents" };
328
- try {
329
- const pdfBuffer = await renderHtmlToPdf(htmlContent, {
330
- format: args.format || "A4",
331
- landscape: args.landscape || false,
332
- });
333
- uploadBuffer = new Uint8Array(pdfBuffer);
334
- }
335
- catch (err) {
336
- return { success: false, error: `PDF generation failed: ${err instanceof Error ? err.message : String(err)}` };
337
- }
922
+ const results = [];
923
+ for (const item of items) {
924
+ const itemArgs = {
925
+ ...args,
926
+ action: "from_template",
927
+ data: item,
928
+ name: item.name || undefined
929
+ };
930
+ const result = await handleDocuments(sb, itemArgs, storeId);
931
+ if (result.success && result.data) {
932
+ results.push({
933
+ name: result.data.name || "",
934
+ url: result.data.url || ""
935
+ });
936
+ } else {
937
+ results.push({
938
+ name: String(item.name || ""),
939
+ url: "",
940
+ error: result.error || "Generation failed"
941
+ });
942
+ }
943
+ }
944
+ const succeeded = results.filter(r => !r.error).length;
945
+ return {
946
+ success: true,
947
+ data: {
948
+ total: items.length,
949
+ succeeded,
950
+ failed: items.length - succeeded,
951
+ results
952
+ }
953
+ };
954
+ }
955
+
956
+ // ================================================================
957
+ // PDF TEMPLATE SYSTEM — full layout rendering, calculations, profiles
958
+ // ================================================================
959
+
960
+ case "generate_pdf":
961
+ {
962
+ const templateId = args.template_id;
963
+ const templateSlug = args.template_slug;
964
+ if (!templateId && !templateSlug) return {
965
+ success: false,
966
+ error: "template_id or template_slug required"
967
+ };
968
+
969
+ // 1. Load pdf_template
970
+ let tplQuery = sb.from("pdf_templates").select("*").eq("is_active", true);
971
+ if (templateId) tplQuery = tplQuery.eq("id", templateId);else tplQuery = tplQuery.eq("slug", templateSlug);
972
+ tplQuery = tplQuery.or(`store_id.eq.${sid},store_id.is.null`);
973
+ const {
974
+ data: tpl,
975
+ error: tplErr
976
+ } = await tplQuery.limit(1).single();
977
+ if (tplErr || !tpl) return {
978
+ success: false,
979
+ error: `Template not found: ${templateId || templateSlug}`
980
+ };
981
+
982
+ // 2. Optionally load document_profile + its client store
983
+ let profileConfig = {};
984
+ let profileConstants = {};
985
+ let profileClientData = {};
986
+ let resolvedClientStoreId = null;
987
+ if (args.profile_id) {
988
+ const {
989
+ data: profile
990
+ } = await sb.from("document_profiles").select("*").eq("id", args.profile_id).eq("is_active", true).single();
991
+ if (profile) {
992
+ const cfg = profile.config || {};
993
+ profileConfig = cfg;
994
+ profileConstants = cfg.constants || {};
995
+ resolvedClientStoreId = profile.client_store_id || null;
996
+ // Load client info from the profile's linked store
997
+ if (profile.client_store_id) {
998
+ const {
999
+ data: clientStore
1000
+ } = await sb.from("stores").select("store_name, legal_name, address, city, state, zip, distributor_license_number, phone, email").eq("id", profile.client_store_id).single();
1001
+ if (clientStore) {
1002
+ profileClientData = {
1003
+ clientName: clientStore.legal_name || clientStore.store_name,
1004
+ clientAddress: [clientStore.address, clientStore.city, clientStore.state, clientStore.zip].filter(Boolean).join(", ") || null,
1005
+ licenseNumber: clientStore.distributor_license_number || null,
1006
+ clientPhone: clientStore.phone || null,
1007
+ clientEmail: clientStore.email || null
1008
+ };
1009
+ }
338
1010
  }
339
- else {
340
- let content;
341
- if (docType === "csv") {
342
- const headers = args.headers;
343
- const rows = args.rows;
344
- if (!headers || !rows)
345
- return { success: false, error: "headers and rows required for CSV" };
346
- const lines = [headers.map(escapeCSV).join(",")];
347
- for (const row of rows) {
348
- if (Array.isArray(row)) {
349
- lines.push(row.map(escapeCSV).join(","));
350
- }
351
- else {
352
- lines.push(headers.map(h => escapeCSV(row[h])).join(","));
353
- }
354
- }
355
- content = lines.join("\n");
356
- }
357
- else if (docType === "json") {
358
- const jsonData = args.data || args.content;
359
- content = typeof jsonData === "string" ? jsonData : JSON.stringify(jsonData, null, 2);
360
- }
361
- else {
362
- content = args.content || "";
363
- }
364
- uploadBuffer = new TextEncoder().encode(content);
1011
+ // Use profile name as sampleName if not already set
1012
+ if (profile.name && !profileConstants.sampleName) {
1013
+ profileClientData.sampleName = profile.name;
365
1014
  }
366
- const { error: uploadErr } = await sb.storage
367
- .from("documents")
368
- .upload(storagePath, uploadBuffer, { contentType: mime, upsert: true });
369
- if (uploadErr)
370
- return { success: false, error: `Upload failed: ${uploadErr.message}` };
371
- const { data: urlData } = sb.storage.from("documents").getPublicUrl(storagePath);
372
- const fileUrl = urlData.publicUrl;
373
- const sizeBytes = uploadBuffer.length;
374
- const { data: record, error: insertErr } = await sb.from("store_documents").insert({
375
- store_id: sid,
376
- document_type: docType,
377
- file_name: fileName,
378
- file_url: fileUrl,
379
- file_size: sizeBytes,
380
- file_type: mime,
381
- document_name: name,
382
- source_name: "Documents Edge Function",
383
- document_date: new Date().toISOString().split("T")[0],
384
- data: { document_type: docType },
385
- metadata: { size_bytes: sizeBytes },
386
- }).select("id, document_name, file_url, created_at").single();
387
- if (insertErr)
388
- return { success: false, error: insertErr.message };
389
- if (fileUrl.endsWith('.pdf'))
390
- await generateThumbnail(sb, record.id, fileUrl).catch(() => { });
391
- return { success: true, data: { id: record.id, name: record.document_name, type: docType, url: record.file_url, file_name: fileName, size: sizeBytes } };
392
- }
393
- case "find": {
394
- let query = sb.from("store_documents")
395
- .select("id, document_type, document_name, reference_number, file_url, file_type, file_size, created_at, metadata");
396
- query = query.eq("store_id", sid);
397
- if (args.document_type)
398
- query = query.eq("document_type", args.document_type);
399
- if (args.name) {
400
- const sn = sanitizeFilterValue(args.name);
401
- query = query.ilike("document_name", `%${sn}%`);
1015
+ if (profile.sample_type) {
1016
+ profileClientData.sampleType = profile.sample_type;
402
1017
  }
403
- query = query.order("created_at", { ascending: false }).limit(args.limit || 50);
404
- const { data, error } = await query;
405
- if (error)
406
- return { success: false, error: error.message };
407
- return {
408
- success: true,
409
- data: {
410
- count: data?.length || 0,
411
- documents: (data || []).map(d => ({
412
- id: d.id, type: d.document_type, name: d.document_name,
413
- reference: d.reference_number, url: d.file_url,
414
- size: d.file_size, created: d.created_at,
415
- })),
416
- },
417
- };
418
- }
419
- case "delete": {
420
- if (!args.confirm)
421
- return { success: false, error: "Set confirm: true to delete" };
422
- let query = sb.from("store_documents").delete().eq("store_id", sid);
423
- if (args.document_type)
424
- query = query.eq("document_type", args.document_type);
425
- if (args.name) {
426
- const sn = sanitizeFilterValue(args.name);
427
- query = query.ilike("document_name", `%${sn}%`);
1018
+ if (profile.default_size) {
1019
+ profileClientData.sampleSize = profile.default_size;
428
1020
  }
429
- const { data, error } = await query.select("id");
430
- if (error)
431
- return { success: false, error: error.message };
432
- return { success: true, data: { deleted: data?.length || 0 } };
433
- }
434
- case "create_template": {
435
- if (!args.name)
436
- return { success: false, error: "name is required" };
437
- if (!args.document_type)
438
- return { success: false, error: "document_type is required" };
439
- const { data, error } = await sb.from("document_templates").insert({
440
- store_id: sid || null,
441
- name: args.name,
442
- description: args.description || null,
443
- document_type: args.document_type,
444
- content: args.content || null,
445
- headers: args.headers || null,
446
- schema: args.schema || [],
447
- metadata: args.data || {},
448
- }).select("id, name, document_type, created_at").single();
449
- if (error)
450
- return { success: false, error: error.message };
451
- return { success: true, data: { template_id: data.id, name: data.name, type: data.document_type } };
1021
+ }
452
1022
  }
453
- case "list_templates": {
454
- let query = sb.from("document_templates")
455
- .select("id, name, description, document_type, headers, schema, created_at")
456
- .eq("is_active", true);
457
- if (sid)
458
- query = query.or(`store_id.eq.${sid},store_id.is.null`);
459
- if (args.document_type)
460
- query = query.eq("document_type", args.document_type);
461
- if (args.limit)
462
- query = query.limit(args.limit);
463
- query = query.order("created_at", { ascending: false });
464
- const { data, error } = await query;
465
- if (error)
466
- return { success: false, error: error.message };
467
- return {
468
- success: true,
469
- data: {
470
- count: data?.length || 0,
471
- templates: (data || []).map(t => ({
472
- id: t.id, name: t.name, description: t.description,
473
- type: t.document_type, headers: t.headers,
474
- fields: t.schema?.length || 0,
475
- })),
476
- },
1023
+
1024
+ // 3. Optionally load customer (via store_customer_profiles + platform_users) — overrides profile client
1025
+ let clientData = {};
1026
+ if (args.customer_id) {
1027
+ const {
1028
+ data: rel
1029
+ } = await sb.from("user_creation_relationships").select("id, store_id, platform_users!inner(first_name, last_name, email, phone)").eq("id", args.customer_id).single();
1030
+ if (rel) {
1031
+ const pu = rel.platform_users;
1032
+ const {
1033
+ data: prof
1034
+ } = await sb.from("store_customer_profiles").select("street_address, city, state, postal_code, drivers_license_number, medical_card_number").eq("relationship_id", rel.id).maybeSingle();
1035
+ clientData = {
1036
+ clientName: [pu.first_name, pu.last_name].filter(Boolean).join(" "),
1037
+ clientEmail: pu.email,
1038
+ clientPhone: pu.phone,
1039
+ clientAddress: prof ? [prof.street_address, prof.city, prof.state, prof.postal_code].filter(Boolean).join(", ") : null,
1040
+ clientLicense: prof?.drivers_license_number || null,
1041
+ clientMedicalCard: prof?.medical_card_number || null
477
1042
  };
1043
+ }
478
1044
  }
479
- case "from_template": {
480
- const templateId = args.template_id;
481
- if (!templateId)
482
- return { success: false, error: "template_id is required" };
483
- const { data: template, error: tErr } = await sb.from("document_templates")
484
- .select("*").eq("id", templateId).or(`store_id.eq.${sid},store_id.is.null`).single();
485
- if (tErr || !template)
486
- return { success: false, error: "Template not found" };
487
- const tData = { ...template.metadata, ...(args.data || {}), date: new Date().toISOString().split("T")[0] };
488
- const docType = template.document_type;
489
- const docName = args.name || fillTemplate(template.name, tData);
490
- let content;
491
- if (docType === "csv") {
492
- const headers = template.headers || [];
493
- const rows = args.rows;
494
- if (!rows)
495
- return { success: false, error: "rows required for CSV template" };
496
- const lines = [headers.map(escapeCSV).join(",")];
497
- for (const row of rows) {
498
- if (Array.isArray(row))
499
- lines.push(row.map(escapeCSV).join(","));
500
- else
501
- lines.push(headers.map(h => escapeCSV(row[h])).join(","));
502
- }
503
- content = lines.join("\n");
1045
+
1046
+ // 4. Merge data: constants ← profile config ← profile client ← explicit client ← user data
1047
+ const tplConstants = tpl.constants || {};
1048
+ const mergedConstants = {
1049
+ ...tplConstants,
1050
+ ...profileConstants
1051
+ };
1052
+ const cannabinoidCfg = profileConfig.cannabinoids ?? undefined;
1053
+ const {
1054
+ cannabinoids: _cfgRanges,
1055
+ ...profileDataOnly
1056
+ } = profileConfig;
1057
+ let mergedData = {
1058
+ ...mergedConstants,
1059
+ ...profileDataOnly,
1060
+ ...profileClientData,
1061
+ ...clientData,
1062
+ ...(args.data || {}),
1063
+ date: new Date().toISOString().slice(0, 10),
1064
+ approvalDate: new Date().toISOString().slice(0, 10)
1065
+ };
1066
+
1067
+ // 5. Apply generation rules — auto-fill sampleId, dates, etc.
1068
+ mergedData = applyGenerationRules(tpl.generation_rules, mergedData);
1069
+
1070
+ // 6. Generate cannabinoid data if profile has cannabinoid config
1071
+ if (cannabinoidCfg && !mergedData.cannabinoids) {
1072
+ mergedData.cannabinoids = generateCannabinoidData(cannabinoidCfg, mergedConstants);
1073
+ }
1074
+
1075
+ // 6b. Flatten cannabinoid rows into top-level keys for calculation formulas
1076
+ // e.g. { name: "D9-THC", percentWeight: 0.22 } → D9_THC: 0.22
1077
+ if (Array.isArray(mergedData.cannabinoids)) {
1078
+ for (const row of mergedData.cannabinoids) {
1079
+ const key = row.name.replace(/-/g, "_").replace(/[^a-zA-Z0-9_]/g, "");
1080
+ if (key && row.percentWeight !== undefined) {
1081
+ mergedData[key] = row.percentWeight;
504
1082
  }
505
- else if (docType === "json") {
506
- content = template.content ? fillTemplate(template.content, tData) : JSON.stringify(tData, null, 2);
1083
+ }
1084
+ }
1085
+
1086
+ // 7. Apply calculations
1087
+ mergedData = applyCalculations(tpl.calculations, mergedConstants, mergedData);
1088
+
1089
+ // Also run per-row calculations if cannabinoids exist
1090
+ if (Array.isArray(mergedData.cannabinoids)) {
1091
+ const rows = mergedData.cannabinoids;
1092
+ for (const row of rows) {
1093
+ const rowCtx = {
1094
+ ...mergedConstants,
1095
+ ...row
1096
+ };
1097
+ const rowCalcs = tpl.row_calculations || tpl.calculations;
1098
+ if (rowCalcs) {
1099
+ const computed = applyCalculations(rowCalcs, mergedConstants, rowCtx);
1100
+ Object.assign(row, computed);
507
1101
  }
508
- else {
509
- content = template.content ? fillTemplate(template.content, tData) : "";
1102
+ }
1103
+ }
1104
+
1105
+ // 7b. Generate full panel data if requested (safety tests, pesticides, solvents)
1106
+ if (mergedData.fullPanel && !mergedData.microbialResults) {
1107
+ const tplConstants = tpl.constants || {};
1108
+ const fullPanel = generateFullPanelData(tplConstants);
1109
+ mergedData.microbialResults = fullPanel.microbialResults;
1110
+ mergedData.heavyMetalsResults = fullPanel.heavyMetalsResults;
1111
+ mergedData.mycotoxinResults = fullPanel.mycotoxinResults;
1112
+ mergedData.pesticidesCat1 = fullPanel.pesticidesCat1;
1113
+ mergedData.pesticidesCat2 = fullPanel.pesticidesCat2;
1114
+ mergedData.testsResidualSolvents = fullPanel.residualSolventsResults;
1115
+ mergedData.totalPages = 5;
1116
+ mergedData.complianceResults = mergedData.complianceResults || {
1117
+ heavyMetals: {
1118
+ status: "PASS"
1119
+ },
1120
+ microbial: {
1121
+ status: "PASS"
1122
+ },
1123
+ pesticides: {
1124
+ status: "PASS"
1125
+ },
1126
+ mycotoxins: {
1127
+ status: "PASS"
1128
+ },
1129
+ residualSolvents: {
1130
+ status: "PASS"
510
1131
  }
511
- const extMap = { csv: "csv", json: "json", text: "txt", markdown: "md", html: "html" };
512
- const mimeMap = {
513
- csv: "text/csv", json: "application/json", text: "text/plain",
514
- markdown: "text/markdown", html: "text/html",
515
- };
516
- const ext = extMap[docType] || "txt";
517
- const mime = mimeMap[docType] || "text/plain";
518
- const safeName = docName.replace(/[^a-zA-Z0-9_\-]/g, "_");
519
- const fileName = `${safeName}_${Date.now()}.${ext}`;
520
- const storagePath = `${sid}/${fileName}`;
521
- const { error: uploadErr } = await sb.storage
522
- .from("documents")
523
- .upload(storagePath, new TextEncoder().encode(content), { contentType: mime, upsert: true });
524
- if (uploadErr)
525
- return { success: false, error: `Upload failed: ${uploadErr.message}` };
526
- const { data: urlData } = sb.storage.from("documents").getPublicUrl(storagePath);
527
- const sizeBytes = new TextEncoder().encode(content).length;
528
- const { data: record, error: insertErr } = await sb.from("store_documents").insert({
529
- store_id: sid,
530
- document_type: docType,
531
- file_name: fileName,
532
- file_url: urlData.publicUrl,
533
- file_size: sizeBytes,
534
- file_type: mime,
535
- document_name: docName,
536
- source_name: "Documents Edge Function",
537
- document_date: new Date().toISOString().split("T")[0],
538
- data: { template_id: template.id, template_name: template.name },
539
- metadata: { size_bytes: sizeBytes, from_template: true },
540
- }).select("id, document_name, file_url, created_at").single();
541
- if (insertErr)
542
- return { success: false, error: insertErr.message };
543
- if (urlData.publicUrl.endsWith('.pdf'))
544
- await generateThumbnail(sb, record.id, urlData.publicUrl).catch(() => { });
545
- return { success: true, data: { id: record.id, name: record.document_name, type: docType, template: template.name, url: record.file_url, size: sizeBytes } };
1132
+ };
546
1133
  }
547
- case "list_stores": {
548
- const { data, error } = await sb.from("stores")
549
- .select("id, store_name, slug, status, created_at")
550
- .eq("id", sid)
551
- .order("store_name");
552
- if (error)
553
- return { success: false, error: error.message };
554
- return { success: true, data: { count: data?.length || 0, stores: data } };
1134
+
1135
+ // 7b2. Auto-set test status flags for summary section
1136
+ if (mergedData.cannabinoids && mergedData.cannabinoids.length > 0) {
1137
+ mergedData.testsCannabinoids = true;
555
1138
  }
556
- case "list_profiles": {
557
- const { data, error } = await sb.from("document_profiles")
558
- .select("id, name, category, sample_type, config, template_id, is_active, created_at")
559
- .eq("is_active", true)
560
- .eq("owner_store_id", sid)
561
- .order("name");
562
- if (error)
563
- return { success: false, error: error.message };
564
- return {
565
- success: true,
566
- data: {
567
- count: data?.length || 0,
568
- profiles: (data || []).map(p => ({
569
- id: p.id, name: p.name, category: p.category,
570
- sample_type: p.sample_type, template_id: p.template_id,
571
- has_config: Object.keys(p.config || {}).length > 0,
572
- })),
573
- },
574
- };
1139
+ if (mergedData.moisture !== undefined && mergedData.moisture !== null) {
1140
+ mergedData.testsMoisture = true;
575
1141
  }
576
- case "generate": {
577
- // Generate a document from a template profile with store data
578
- const templateId = args.template_id;
579
- if (!templateId)
580
- return { success: false, error: "template_id is required. Use list_profiles to find available templates." };
581
- // Route to from_template with the same args
582
- return handleDocuments(sb, { ...args, action: "from_template" }, storeId);
1142
+ if (mergedData.dateTested) {
1143
+ mergedData.testsBatch = true;
583
1144
  }
584
- case "bulk_generate": {
585
- const templateId = args.template_id;
586
- if (!templateId)
587
- return { success: false, error: "template_id required" };
588
- const items = args.items;
589
- if (!items || !Array.isArray(items) || items.length === 0) {
590
- return { success: false, error: "items required: array of variable sets for each document" };
591
- }
592
- if (items.length > 50) {
593
- return { success: false, error: "Maximum 50 documents per bulk_generate call" };
594
- }
595
- const results = [];
596
- for (const item of items) {
597
- const itemArgs = { ...args, action: "from_template", data: item, name: item.name || undefined };
598
- const result = await handleDocuments(sb, itemArgs, storeId);
599
- if (result.success && result.data) {
600
- results.push({ name: result.data.name || "", url: result.data.url || "" });
601
- }
602
- else {
603
- results.push({ name: String(item.name || ""), url: "", error: result.error || "Generation failed" });
604
- }
605
- }
606
- const succeeded = results.filter(r => !r.error).length;
607
- return { success: true, data: { total: items.length, succeeded, failed: items.length - succeeded, results } };
1145
+
1146
+ // 7c. Generate QR code for COA templates — links to Quantix COA landing page
1147
+ if (tpl.slug?.startsWith("cannabis-coa") && !mergedData.qrCodeDataUrl) {
1148
+ const productSlug = (mergedData.sampleName || "certificate").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1149
+ const coaUrl = `https://quantixanalytics.com/coa/${sid}/${productSlug}`;
1150
+ try {
1151
+ mergedData.qrCodeDataUrl = await QRCode.toDataURL(coaUrl, {
1152
+ width: 120,
1153
+ margin: 1
1154
+ });
1155
+ } catch {/* skip QR if generation fails */}
608
1156
  }
609
- // ================================================================
610
- // PDF TEMPLATE SYSTEM — full layout rendering, calculations, profiles
611
- // ================================================================
612
- case "generate_pdf": {
613
- const templateId = args.template_id;
614
- const templateSlug = args.template_slug;
615
- if (!templateId && !templateSlug)
616
- return { success: false, error: "template_id or template_slug required" };
617
- // 1. Load pdf_template
618
- let tplQuery = sb.from("pdf_templates").select("*").eq("is_active", true);
619
- if (templateId)
620
- tplQuery = tplQuery.eq("id", templateId);
621
- else
622
- tplQuery = tplQuery.eq("slug", templateSlug);
623
- tplQuery = tplQuery.or(`store_id.eq.${sid},store_id.is.null`);
624
- const { data: tpl, error: tplErr } = await tplQuery.limit(1).single();
625
- if (tplErr || !tpl)
626
- return { success: false, error: `Template not found: ${templateId || templateSlug}` };
627
- // 2. Optionally load document_profile + its client store
628
- let profileConfig = {};
629
- let profileConstants = {};
630
- let profileClientData = {};
631
- let resolvedClientStoreId = null;
632
- if (args.profile_id) {
633
- const { data: profile } = await sb.from("document_profiles")
634
- .select("*").eq("id", args.profile_id).eq("is_active", true).single();
635
- if (profile) {
636
- const cfg = (profile.config || {});
637
- profileConfig = cfg;
638
- profileConstants = (cfg.constants || {});
639
- resolvedClientStoreId = profile.client_store_id || null;
640
- // Load client info from the profile's linked store
641
- if (profile.client_store_id) {
642
- const { data: clientStore } = await sb.from("stores")
643
- .select("store_name, legal_name, address, city, state, zip, distributor_license_number, phone, email")
644
- .eq("id", profile.client_store_id).single();
645
- if (clientStore) {
646
- profileClientData = {
647
- clientName: clientStore.legal_name || clientStore.store_name,
648
- clientAddress: [clientStore.address, clientStore.city, clientStore.state, clientStore.zip].filter(Boolean).join(", ") || null,
649
- licenseNumber: clientStore.distributor_license_number || null,
650
- clientPhone: clientStore.phone || null,
651
- clientEmail: clientStore.email || null,
652
- };
653
- }
654
- }
655
- // Use profile name as sampleName if not already set
656
- if (profile.name && !profileConstants.sampleName) {
657
- profileClientData.sampleName = profile.name;
658
- }
659
- if (profile.sample_type) {
660
- profileClientData.sampleType = profile.sample_type;
661
- }
662
- if (profile.default_size) {
663
- profileClientData.sampleSize = profile.default_size;
664
- }
665
- }
666
- }
667
- // 3. Optionally load customer (via store_customer_profiles + platform_users) — overrides profile client
668
- let clientData = {};
669
- if (args.customer_id) {
670
- const { data: rel } = await sb.from("user_creation_relationships")
671
- .select("id, store_id, platform_users!inner(first_name, last_name, email, phone)")
672
- .eq("id", args.customer_id).single();
673
- if (rel) {
674
- const pu = rel.platform_users;
675
- const { data: prof } = await sb.from("store_customer_profiles")
676
- .select("street_address, city, state, postal_code, drivers_license_number, medical_card_number")
677
- .eq("relationship_id", rel.id).maybeSingle();
678
- clientData = {
679
- clientName: [pu.first_name, pu.last_name].filter(Boolean).join(" "),
680
- clientEmail: pu.email,
681
- clientPhone: pu.phone,
682
- clientAddress: prof ? [prof.street_address, prof.city, prof.state, prof.postal_code].filter(Boolean).join(", ") : null,
683
- clientLicense: prof?.drivers_license_number || null,
684
- clientMedicalCard: prof?.medical_card_number || null,
685
- };
686
- }
687
- }
688
- // 4. Merge data: constants ← profile config ← profile client ← explicit client ← user data
689
- const tplConstants = (tpl.constants || {});
690
- const mergedConstants = { ...tplConstants, ...profileConstants };
691
- const cannabinoidCfg = (profileConfig.cannabinoids ?? undefined);
692
- const { cannabinoids: _cfgRanges, ...profileDataOnly } = profileConfig;
693
- let mergedData = {
694
- ...mergedConstants,
695
- ...profileDataOnly,
696
- ...profileClientData,
697
- ...clientData,
698
- ...(args.data || {}),
699
- date: new Date().toISOString().slice(0, 10),
700
- approvalDate: new Date().toISOString().slice(0, 10),
701
- };
702
- // 5. Apply generation rules — auto-fill sampleId, dates, etc.
703
- mergedData = applyGenerationRules(tpl.generation_rules, mergedData);
704
- // 6. Generate cannabinoid data if profile has cannabinoid config
705
- if (cannabinoidCfg && !mergedData.cannabinoids) {
706
- mergedData.cannabinoids = generateCannabinoidData(cannabinoidCfg, mergedConstants);
707
- }
708
- // 6b. Flatten cannabinoid rows into top-level keys for calculation formulas
709
- // e.g. { name: "D9-THC", percentWeight: 0.22 } → D9_THC: 0.22
710
- if (Array.isArray(mergedData.cannabinoids)) {
711
- for (const row of mergedData.cannabinoids) {
712
- const key = row.name.replace(/-/g, "_").replace(/[^a-zA-Z0-9_]/g, "");
713
- if (key && row.percentWeight !== undefined) {
714
- mergedData[key] = row.percentWeight;
715
- }
716
- }
717
- }
718
- // 7. Apply calculations
719
- mergedData = applyCalculations(tpl.calculations, mergedConstants, mergedData);
720
- // Also run per-row calculations if cannabinoids exist
721
- if (Array.isArray(mergedData.cannabinoids)) {
722
- const rows = mergedData.cannabinoids;
723
- for (const row of rows) {
724
- const rowCtx = { ...mergedConstants, ...row };
725
- const rowCalcs = (tpl.row_calculations || tpl.calculations);
726
- if (rowCalcs) {
727
- const computed = applyCalculations(rowCalcs, mergedConstants, rowCtx);
728
- Object.assign(row, computed);
729
- }
730
- }
731
- }
732
- // 7b. Generate full panel data if requested (safety tests, pesticides, solvents)
733
- if (mergedData.fullPanel && !mergedData.microbialResults) {
734
- const tplConstants = (tpl.constants || {});
735
- const fullPanel = generateFullPanelData(tplConstants);
736
- mergedData.microbialResults = fullPanel.microbialResults;
737
- mergedData.heavyMetalsResults = fullPanel.heavyMetalsResults;
738
- mergedData.mycotoxinResults = fullPanel.mycotoxinResults;
739
- mergedData.pesticidesCat1 = fullPanel.pesticidesCat1;
740
- mergedData.pesticidesCat2 = fullPanel.pesticidesCat2;
741
- mergedData.testsResidualSolvents = fullPanel.residualSolventsResults;
742
- mergedData.totalPages = 5;
743
- mergedData.complianceResults = mergedData.complianceResults || {
744
- heavyMetals: { status: "PASS" }, microbial: { status: "PASS" },
745
- pesticides: { status: "PASS" }, mycotoxins: { status: "PASS" },
746
- residualSolvents: { status: "PASS" },
747
- };
748
- }
749
- // 7b2. Auto-set test status flags for summary section
750
- if (mergedData.cannabinoids && mergedData.cannabinoids.length > 0) {
751
- mergedData.testsCannabinoids = true;
752
- }
753
- if (mergedData.moisture !== undefined && mergedData.moisture !== null) {
754
- mergedData.testsMoisture = true;
755
- }
756
- if (mergedData.dateTested) {
757
- mergedData.testsBatch = true;
758
- }
759
- // 7c. Generate QR code for COA templates — links to Quantix COA landing page
760
- if (tpl.slug?.startsWith("cannabis-coa") && !mergedData.qrCodeDataUrl) {
761
- const productSlug = (mergedData.sampleName || "certificate")
762
- .toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
763
- const coaUrl = `https://quantixanalytics.com/coa/${sid}/${productSlug}`;
764
- try {
765
- mergedData.qrCodeDataUrl = await QRCode.toDataURL(coaUrl, { width: 120, margin: 1 });
766
- }
767
- catch { /* skip QR if generation fails */ }
768
- }
769
- // 8. Validate
770
- const validationResults = runValidation(tpl.validation_rules, mergedData);
771
- const errors = validationResults.filter(v => v.severity === "error" && !v.passed);
772
- const warnings = validationResults.filter(v => v.severity === "warning" && !v.passed);
773
- if (errors.length > 0 && !args.force) {
774
- return {
775
- success: false,
776
- error: `Validation failed: ${errors.map(e => e.message).join("; ")}`,
777
- data: { errors, warnings },
778
- };
779
- }
780
- // 9-10. Render PDF — all templates use React-PDF
781
- let pdfBuffer;
782
- try {
783
- if (tpl.slug?.startsWith("cannabis-coa")) {
784
- // COA templates use dedicated renderer
785
- pdfBuffer = await renderCOAToPdf(mergedData);
786
- }
787
- else {
788
- // Detect label layout vs generic layout
789
- const layout = tpl.layout;
790
- const isLabel = layout?.version === 1 && Array.isArray(layout?.elements) && layout.elements[0]?.field;
791
- if (isLabel) {
792
- const items = Array.isArray(mergedData.items)
793
- ? mergedData.items
794
- : [mergedData];
795
- pdfBuffer = await renderLabelToPdf(layout, tpl.page_config, items);
796
- }
797
- else {
798
- pdfBuffer = await renderLayoutToPdf(tpl.layout, tpl.page_config, tpl.styles, mergedData);
799
- }
800
- }
1157
+
1158
+ // 8. Validate
1159
+ const validationResults = runValidation(tpl.validation_rules, mergedData);
1160
+ const errors = validationResults.filter(v => v.severity === "error" && !v.passed);
1161
+ const warnings = validationResults.filter(v => v.severity === "warning" && !v.passed);
1162
+ if (errors.length > 0 && !args.force) {
1163
+ return {
1164
+ success: false,
1165
+ error: `Validation failed: ${errors.map(e => e.message).join("; ")}`,
1166
+ data: {
1167
+ errors,
1168
+ warnings
801
1169
  }
802
- catch (err) {
803
- return { success: false, error: `PDF generation failed: ${err instanceof Error ? err.message : String(err)}` };
804
- }
805
- // 11. Upload to Supabase storage
806
- const sampleName = mergedData.sampleName || mergedData.productName || "";
807
- const docName = args.name || (sampleName ? `${sampleName} COA` : fillTemplate(tpl.name || "Document", mergedData));
808
- const safeName = docName.replace(/[^a-zA-Z0-9_\-]/g, "_");
809
- const fileName = `${safeName}_${Date.now()}.pdf`;
810
- const storagePath = `${sid}/${fileName}`;
811
- const bucketName = "store-documents";
812
- const { error: uploadErr } = await sb.storage
813
- .from(bucketName)
814
- .upload(storagePath, new Uint8Array(pdfBuffer), { contentType: "application/pdf", upsert: true });
815
- if (uploadErr)
816
- return { success: false, error: `Upload failed: ${uploadErr.message}` };
817
- const { data: urlData } = sb.storage.from(bucketName).getPublicUrl(storagePath);
818
- // 12. Build COA metadata for the Quantix landing page
819
- const coaMetadata = {
820
- size_bytes: pdfBuffer.length,
821
- from_pdf_template: true,
822
- template_version: tpl.version,
823
- };
824
- if (tpl.slug?.startsWith("cannabis-coa")) {
825
- coaMetadata.sample_name = sampleName;
826
- coaMetadata.sample_id = mergedData.sampleId || null;
827
- coaMetadata.sample_type = mergedData.sampleType || mergedData.strain || null;
828
- coaMetadata.sample_size = mergedData.sampleSize || null;
829
- coaMetadata.batch_number = mergedData.batchId || null;
830
- coaMetadata.lab_name = mergedData.labName || null;
831
- coaMetadata.lab_contact = mergedData.labContact || null;
832
- coaMetadata.lab_website = mergedData.labWebsite || null;
833
- coaMetadata.lab_director = mergedData.labDirector || null;
834
- coaMetadata.director_title = mergedData.directorTitle || null;
835
- coaMetadata.logo_url = mergedData.logoUrl || null;
836
- coaMetadata.signature_url = mergedData.signatureUrl || null;
837
- coaMetadata.client_name = mergedData.clientName || null;
838
- coaMetadata.client_address = mergedData.clientAddress || null;
839
- coaMetadata.date_collected = mergedData.dateCollected || null;
840
- coaMetadata.date_received = mergedData.dateReceived || null;
841
- coaMetadata.date_tested = mergedData.dateTested || null;
842
- coaMetadata.date_reported = mergedData.dateReported || null;
843
- coaMetadata.status = "Pass";
844
- coaMetadata.thc_total = mergedData.totalTHC || null;
845
- coaMetadata.cbd_total = mergedData.totalCBD || null;
846
- coaMetadata.cannabinoids_total = mergedData.totalCannabinoids || null;
847
- coaMetadata.moisture = mergedData.moisture || null;
848
- // Detailed cannabinoid data for the interactive landing page
849
- const cannabinoids = mergedData.cannabinoids;
850
- if (Array.isArray(cannabinoids)) {
851
- coaMetadata.cannabinoids = Object.fromEntries(cannabinoids.filter((c) => typeof c.percentWeight === "number" && c.percentWeight > 0)
852
- .map((c) => [c.name, c.percentWeight]));
853
- coaMetadata.cannabinoids_detailed = cannabinoids.map((c) => ({
854
- name: c.name, percent: c.percentWeight, mg_per_g: c.mgPerG, lod: c.lod, loq: c.loq, result: String(c.result ?? c.percentWeight),
855
- }));
856
- }
857
- // Safety test panels
858
- if (mergedData.fullPanel) {
859
- coaMetadata.test_panels = {
860
- cannabinoids: true, microbial: true, heavy_metals: true,
861
- mycotoxins: true, pesticides: true, residual_solvents: true,
862
- };
863
- coaMetadata.safety_tests = {
864
- microbial: "Pass", heavy_metals: "Pass", mycotoxins: "Pass",
865
- pesticides: "Pass", residual_solvents: "Pass",
866
- };
867
- }
1170
+ };
1171
+ }
1172
+
1173
+ // 9-10. Render PDF all templates use React-PDF
1174
+ let pdfBuffer;
1175
+ try {
1176
+ if (tpl.slug?.startsWith("cannabis-coa")) {
1177
+ // COA templates use dedicated renderer
1178
+ pdfBuffer = await renderCOAToPdf(mergedData);
1179
+ } else {
1180
+ // Detect label layout vs generic layout
1181
+ const layout = tpl.layout;
1182
+ const isLabel = layout?.version === 1 && Array.isArray(layout?.elements) && layout.elements[0]?.field;
1183
+ if (isLabel) {
1184
+ const items = Array.isArray(mergedData.items) ? mergedData.items : [mergedData];
1185
+ pdfBuffer = await renderLabelToPdf(layout, tpl.page_config, items);
1186
+ } else {
1187
+ pdfBuffer = await renderLayoutToPdf(tpl.layout, tpl.page_config, tpl.styles, mergedData);
868
1188
  }
869
- // 13. Insert store_documents record
870
- const { data: record, error: insertErr } = await sb.from("store_documents").insert({
871
- store_id: sid,
872
- document_type: tpl.document_type || "pdf",
873
- file_name: fileName,
874
- file_url: urlData.publicUrl,
875
- file_size: pdfBuffer.length,
876
- file_type: "application/pdf",
877
- document_name: docName,
878
- source_name: "PDF Template Engine",
879
- document_date: new Date().toISOString().split("T")[0],
880
- customer_id: args.customer_id || null,
881
- client_store_id: resolvedClientStoreId,
882
- data: { template_id: tpl.id, template_slug: tpl.slug, profile_id: args.profile_id || null },
883
- metadata: coaMetadata,
884
- }).select("id, document_name, file_url, created_at").single();
885
- if (insertErr)
886
- return { success: false, error: insertErr.message };
887
- await generateThumbnail(sb, record.id, urlData.publicUrl).catch(() => { });
888
- return {
889
- success: true,
890
- data: {
891
- id: record.id,
892
- name: record.document_name,
893
- url: record.file_url,
894
- template: tpl.name,
895
- size: pdfBuffer.length,
896
- warnings: warnings.map(w => w.message),
897
- },
898
- };
1189
+ }
1190
+ } catch (err) {
1191
+ return {
1192
+ success: false,
1193
+ error: `PDF generation failed: ${err instanceof Error ? err.message : String(err)}`
1194
+ };
899
1195
  }
900
- case "list_pdf_templates": {
901
- let query = sb.from("pdf_templates")
902
- .select("id, slug, name, description, document_type, version, created_at")
903
- .eq("is_active", true)
904
- .or(`store_id.eq.${sid},store_id.is.null`)
905
- .order("name");
906
- if (args.document_type)
907
- query = query.eq("document_type", args.document_type);
908
- if (args.limit)
909
- query = query.limit(args.limit);
910
- const { data, error } = await query;
911
- if (error)
912
- return { success: false, error: error.message };
913
- return {
914
- success: true,
915
- data: {
916
- count: data?.length || 0,
917
- templates: (data || []).map(t => ({
918
- id: t.id, slug: t.slug, name: t.name,
919
- description: t.description, type: t.document_type, version: t.version,
920
- })),
921
- },
1196
+
1197
+ // 11. Upload to Supabase storage
1198
+ const sampleName = mergedData.sampleName || mergedData.productName || "";
1199
+ const docName = args.name || (sampleName ? `${sampleName} COA` : fillTemplate(tpl.name || "Document", mergedData));
1200
+ const safeName = docName.replace(/[^a-zA-Z0-9_\-]/g, "_");
1201
+ const fileName = `${safeName}_${Date.now()}.pdf`;
1202
+ const storagePath = `${sid}/${fileName}`;
1203
+ const bucketName = "store-documents";
1204
+ const {
1205
+ error: uploadErr
1206
+ } = await sb.storage.from(bucketName).upload(storagePath, new Uint8Array(pdfBuffer), {
1207
+ contentType: "application/pdf",
1208
+ upsert: true
1209
+ });
1210
+ if (uploadErr) return {
1211
+ success: false,
1212
+ error: `Upload failed: ${uploadErr.message}`
1213
+ };
1214
+ const {
1215
+ data: urlData
1216
+ } = sb.storage.from(bucketName).getPublicUrl(storagePath);
1217
+
1218
+ // 12. Build COA metadata for the Quantix landing page
1219
+ const coaMetadata = {
1220
+ size_bytes: pdfBuffer.length,
1221
+ from_pdf_template: true,
1222
+ template_version: tpl.version
1223
+ };
1224
+ if (tpl.slug?.startsWith("cannabis-coa")) {
1225
+ coaMetadata.sample_name = sampleName;
1226
+ coaMetadata.sample_id = mergedData.sampleId || null;
1227
+ coaMetadata.sample_type = mergedData.sampleType || mergedData.strain || null;
1228
+ coaMetadata.sample_size = mergedData.sampleSize || null;
1229
+ coaMetadata.batch_number = mergedData.batchId || null;
1230
+ coaMetadata.lab_name = mergedData.labName || null;
1231
+ coaMetadata.lab_contact = mergedData.labContact || null;
1232
+ coaMetadata.lab_website = mergedData.labWebsite || null;
1233
+ coaMetadata.lab_director = mergedData.labDirector || null;
1234
+ coaMetadata.director_title = mergedData.directorTitle || null;
1235
+ coaMetadata.logo_url = mergedData.logoUrl || null;
1236
+ coaMetadata.signature_url = mergedData.signatureUrl || null;
1237
+ coaMetadata.client_name = mergedData.clientName || null;
1238
+ coaMetadata.client_address = mergedData.clientAddress || null;
1239
+ coaMetadata.date_collected = mergedData.dateCollected || null;
1240
+ coaMetadata.date_received = mergedData.dateReceived || null;
1241
+ coaMetadata.date_tested = mergedData.dateTested || null;
1242
+ coaMetadata.date_reported = mergedData.dateReported || null;
1243
+ coaMetadata.status = "Pass";
1244
+ coaMetadata.thc_total = mergedData.totalTHC || null;
1245
+ coaMetadata.cbd_total = mergedData.totalCBD || null;
1246
+ coaMetadata.cannabinoids_total = mergedData.totalCannabinoids || null;
1247
+ coaMetadata.moisture = mergedData.moisture || null;
1248
+ // Detailed cannabinoid data for the interactive landing page
1249
+ const cannabinoids = mergedData.cannabinoids;
1250
+ if (Array.isArray(cannabinoids)) {
1251
+ coaMetadata.cannabinoids = Object.fromEntries(cannabinoids.filter(c => typeof c.percentWeight === "number" && c.percentWeight > 0).map(c => [c.name, c.percentWeight]));
1252
+ coaMetadata.cannabinoids_detailed = cannabinoids.map(c => ({
1253
+ name: c.name,
1254
+ percent: c.percentWeight,
1255
+ mg_per_g: c.mgPerG,
1256
+ lod: c.lod,
1257
+ loq: c.loq,
1258
+ result: String(c.result ?? c.percentWeight)
1259
+ }));
1260
+ }
1261
+ // Safety test panels
1262
+ if (mergedData.fullPanel) {
1263
+ coaMetadata.test_panels = {
1264
+ cannabinoids: true,
1265
+ microbial: true,
1266
+ heavy_metals: true,
1267
+ mycotoxins: true,
1268
+ pesticides: true,
1269
+ residual_solvents: true
922
1270
  };
923
- }
924
- case "list_document_profiles": {
925
- let query = sb.from("document_profiles")
926
- .select("id, name, category, sample_type, default_size, client_store_id, template_id, created_at")
927
- .eq("is_active", true)
928
- .eq("owner_store_id", sid)
929
- .order("name");
930
- if (args.template_id)
931
- query = query.eq("template_id", args.template_id);
932
- if (args.category)
933
- query = query.eq("category", args.category);
934
- if (args.limit)
935
- query = query.limit(args.limit);
936
- const { data, error } = await query;
937
- if (error)
938
- return { success: false, error: error.message };
939
- return {
940
- success: true,
941
- data: {
942
- count: data?.length || 0,
943
- profiles: (data || []).map(p => ({
944
- id: p.id, name: p.name, category: p.category,
945
- sample_type: p.sample_type, default_size: p.default_size,
946
- client_store_id: p.client_store_id,
947
- })),
948
- },
1271
+ coaMetadata.safety_tests = {
1272
+ microbial: "Pass",
1273
+ heavy_metals: "Pass",
1274
+ mycotoxins: "Pass",
1275
+ pesticides: "Pass",
1276
+ residual_solvents: "Pass"
949
1277
  };
1278
+ }
950
1279
  }
951
- case "list_clients":
952
- case "list_customers":
953
- return { success: false, error: "Use the 'customers' tool instead. Clients are customers." };
954
- case "deliver_documents": {
955
- // Batch-assign documents to a customer and optionally send notification email
956
- const docIds = args.document_ids || (args.document_id ? [args.document_id] : []);
957
- const customerId = args.customer_id;
958
- if (!docIds.length)
959
- return { success: false, error: "document_ids (array) or document_id (string) is required" };
960
- if (!customerId)
961
- return { success: false, error: "customer_id is required" };
962
- // Verify customer belongs to this store
963
- const { data: rel, error: relErr } = await sb.from("user_creation_relationships")
964
- .select("id, store_id, platform_users!inner(first_name, last_name, email)")
965
- .eq("id", customerId).eq("store_id", sid).single();
966
- if (relErr || !rel)
967
- return { success: false, error: "Customer not found for this store" };
968
- const pu = rel.platform_users;
969
- const customerEmail = pu.email;
970
- const customerName = [pu.first_name, pu.last_name].filter(Boolean).join(" ");
971
- // Assign all documents to the customer
972
- const { error: updateErr } = await sb.from("store_documents")
973
- .update({ customer_id: customerId })
974
- .in("id", docIds)
975
- .eq("store_id", sid);
976
- if (updateErr)
977
- return { success: false, error: `Failed to assign documents: ${updateErr.message}` };
978
- // Get document names for the email
979
- const { data: docs } = await sb.from("store_documents")
980
- .select("id, document_name, file_url")
981
- .in("id", docIds);
982
- let emailSent = false;
983
- if (!args.skip_email && customerEmail) {
984
- const portalUrl = args.portal_url || null;
985
- const docList = (docs || []).map(d => `<li><a href="${d.file_url}">${d.document_name || "Document"}</a></li>`).join("");
986
- // Get store name for template
987
- const { data: store } = await sb.from("stores").select("store_name").eq("id", sid).single();
988
- const emailResult = await handleEmail(sb, {
989
- action: "send_template",
990
- to: customerEmail,
991
- template: "documents-delivered",
992
- template_data: {
993
- customer_name: customerName || "there",
994
- store_name: store?.store_name || "",
995
- document_count: String(docIds.length),
996
- document_list: docList,
997
- portal_url: portalUrl || "",
998
- },
999
- }, storeId);
1000
- emailSent = emailResult.success;
1280
+
1281
+ // 13. Insert store_documents record
1282
+ const {
1283
+ data: record,
1284
+ error: insertErr
1285
+ } = await sb.from("store_documents").insert({
1286
+ store_id: sid,
1287
+ document_type: tpl.document_type || "pdf",
1288
+ file_name: fileName,
1289
+ file_url: urlData.publicUrl,
1290
+ file_size: pdfBuffer.length,
1291
+ file_type: "application/pdf",
1292
+ document_name: docName,
1293
+ source_name: "PDF Template Engine",
1294
+ document_date: new Date().toISOString().split("T")[0],
1295
+ customer_id: args.customer_id || null,
1296
+ client_store_id: resolvedClientStoreId,
1297
+ data: {
1298
+ template_id: tpl.id,
1299
+ template_slug: tpl.slug,
1300
+ profile_id: args.profile_id || null
1301
+ },
1302
+ metadata: coaMetadata
1303
+ }).select("id, document_name, file_url, created_at").single();
1304
+ if (insertErr) return {
1305
+ success: false,
1306
+ error: insertErr.message
1307
+ };
1308
+ await generateThumbnail(sb, record.id, urlData.publicUrl).catch(() => {});
1309
+ return {
1310
+ success: true,
1311
+ data: {
1312
+ id: record.id,
1313
+ name: record.document_name,
1314
+ url: record.file_url,
1315
+ template: tpl.name,
1316
+ size: pdfBuffer.length,
1317
+ warnings: warnings.map(w => w.message)
1318
+ }
1319
+ };
1320
+ }
1321
+ case "list_pdf_templates":
1322
+ {
1323
+ let query = sb.from("pdf_templates").select("id, slug, name, description, document_type, version, created_at").eq("is_active", true).or(`store_id.eq.${sid},store_id.is.null`).order("name");
1324
+ if (args.document_type) query = query.eq("document_type", args.document_type);
1325
+ if (args.limit) query = query.limit(args.limit);
1326
+ const {
1327
+ data,
1328
+ error
1329
+ } = await query;
1330
+ if (error) return {
1331
+ success: false,
1332
+ error: error.message
1333
+ };
1334
+ return {
1335
+ success: true,
1336
+ data: {
1337
+ count: data?.length || 0,
1338
+ templates: (data || []).map(t => ({
1339
+ id: t.id,
1340
+ slug: t.slug,
1341
+ name: t.name,
1342
+ description: t.description,
1343
+ type: t.document_type,
1344
+ version: t.version
1345
+ }))
1346
+ }
1347
+ };
1348
+ }
1349
+ case "list_document_profiles":
1350
+ {
1351
+ let query = sb.from("document_profiles").select("id, name, category, sample_type, default_size, client_store_id, template_id, created_at").eq("is_active", true).eq("owner_store_id", sid).order("name");
1352
+ if (args.template_id) query = query.eq("template_id", args.template_id);
1353
+ if (args.category) query = query.eq("category", args.category);
1354
+ if (args.limit) query = query.limit(args.limit);
1355
+ const {
1356
+ data,
1357
+ error
1358
+ } = await query;
1359
+ if (error) return {
1360
+ success: false,
1361
+ error: error.message
1362
+ };
1363
+ return {
1364
+ success: true,
1365
+ data: {
1366
+ count: data?.length || 0,
1367
+ profiles: (data || []).map(p => ({
1368
+ id: p.id,
1369
+ name: p.name,
1370
+ category: p.category,
1371
+ sample_type: p.sample_type,
1372
+ default_size: p.default_size,
1373
+ client_store_id: p.client_store_id
1374
+ }))
1375
+ }
1376
+ };
1377
+ }
1378
+ case "list_clients":
1379
+ case "list_customers":
1380
+ return {
1381
+ success: false,
1382
+ error: "Use the 'customers' tool instead. Clients are customers."
1383
+ };
1384
+ case "deliver_documents":
1385
+ {
1386
+ // Batch-assign documents to a customer and optionally send notification email
1387
+ const docIds = args.document_ids || (args.document_id ? [args.document_id] : []);
1388
+ const customerId = args.customer_id;
1389
+ if (!docIds.length) return {
1390
+ success: false,
1391
+ error: "document_ids (array) or document_id (string) is required"
1392
+ };
1393
+ if (!customerId) return {
1394
+ success: false,
1395
+ error: "customer_id is required"
1396
+ };
1397
+
1398
+ // Verify customer belongs to this store
1399
+ const {
1400
+ data: rel,
1401
+ error: relErr
1402
+ } = await sb.from("user_creation_relationships").select("id, store_id, platform_users!inner(first_name, last_name, email)").eq("id", customerId).eq("store_id", sid).single();
1403
+ if (relErr || !rel) return {
1404
+ success: false,
1405
+ error: "Customer not found for this store"
1406
+ };
1407
+ const pu = rel.platform_users;
1408
+ const customerEmail = pu.email;
1409
+ const customerName = [pu.first_name, pu.last_name].filter(Boolean).join(" ");
1410
+
1411
+ // Assign all documents to the customer
1412
+ const {
1413
+ error: updateErr
1414
+ } = await sb.from("store_documents").update({
1415
+ customer_id: customerId
1416
+ }).in("id", docIds).eq("store_id", sid);
1417
+ if (updateErr) return {
1418
+ success: false,
1419
+ error: `Failed to assign documents: ${updateErr.message}`
1420
+ };
1421
+
1422
+ // Get document names for the email
1423
+ const {
1424
+ data: docs
1425
+ } = await sb.from("store_documents").select("id, document_name, file_url").in("id", docIds);
1426
+ let emailSent = false;
1427
+ if (!args.skip_email && customerEmail) {
1428
+ const portalUrl = args.portal_url || null;
1429
+ const docList = (docs || []).map(d => `<li><a href="${d.file_url}">${d.document_name || "Document"}</a></li>`).join("");
1430
+
1431
+ // Get store name for template
1432
+ const {
1433
+ data: store
1434
+ } = await sb.from("stores").select("store_name").eq("id", sid).single();
1435
+ const emailResult = await handleEmail(sb, {
1436
+ action: "send_template",
1437
+ to: customerEmail,
1438
+ template: "documents-delivered",
1439
+ template_data: {
1440
+ customer_name: customerName || "there",
1441
+ store_name: store?.store_name || "",
1442
+ document_count: String(docIds.length),
1443
+ document_list: docList,
1444
+ portal_url: portalUrl || ""
1001
1445
  }
1002
- return {
1003
- success: true,
1004
- data: {
1005
- document_ids: docIds,
1006
- customer_id: customerId,
1007
- customer_email: customerEmail,
1008
- email_sent: emailSent,
1009
- count: docIds.length,
1010
- },
1011
- };
1012
- }
1013
- case "list_customer_documents": {
1014
- const customerId = args.customer_id;
1015
- if (!customerId)
1016
- return { success: false, error: "customer_id is required" };
1017
- let query = sb.from("store_documents")
1018
- .select("id, document_name, file_url, file_type, created_at, thumbnail_url, metadata, document_type")
1019
- .eq("store_id", sid)
1020
- .eq("customer_id", customerId)
1021
- .eq("is_active", true)
1022
- .order("created_at", { ascending: false });
1023
- if (args.limit)
1024
- query = query.limit(args.limit);
1025
- const { data, error } = await query;
1026
- if (error)
1027
- return { success: false, error: error.message };
1028
- return {
1029
- success: true,
1030
- data: {
1031
- count: data?.length || 0,
1032
- documents: data || [],
1033
- },
1034
- };
1446
+ }, storeId);
1447
+ emailSent = emailResult.success;
1035
1448
  }
1036
- default:
1037
- return { success: false, error: `Unknown documents action: ${action}. Valid: create, find, delete, create_template, list_templates, from_template, list_stores, list_profiles, generate, bulk_generate, generate_pdf, list_pdf_templates, list_document_profiles, deliver_documents, list_customer_documents` };
1038
- }
1449
+ return {
1450
+ success: true,
1451
+ data: {
1452
+ document_ids: docIds,
1453
+ customer_id: customerId,
1454
+ customer_email: customerEmail,
1455
+ email_sent: emailSent,
1456
+ count: docIds.length
1457
+ }
1458
+ };
1459
+ }
1460
+ case "list_customer_documents":
1461
+ {
1462
+ const customerId = args.customer_id;
1463
+ if (!customerId) return {
1464
+ success: false,
1465
+ error: "customer_id is required"
1466
+ };
1467
+ let query = sb.from("store_documents").select("id, document_name, file_url, file_type, created_at, thumbnail_url, metadata, document_type").eq("store_id", sid).eq("customer_id", customerId).eq("is_active", true).order("created_at", {
1468
+ ascending: false
1469
+ });
1470
+ if (args.limit) query = query.limit(args.limit);
1471
+ const {
1472
+ data,
1473
+ error
1474
+ } = await query;
1475
+ if (error) return {
1476
+ success: false,
1477
+ error: error.message
1478
+ };
1479
+ return {
1480
+ success: true,
1481
+ data: {
1482
+ count: data?.length || 0,
1483
+ documents: data || []
1484
+ }
1485
+ };
1486
+ }
1487
+ default:
1488
+ return {
1489
+ success: false,
1490
+ error: `Unknown documents action: ${action}. Valid: create, find, delete, create_template, list_templates, from_template, list_stores, list_profiles, generate, bulk_generate, generate_pdf, list_pdf_templates, list_document_profiles, deliver_documents, list_customer_documents`
1491
+ };
1492
+ }
1039
1493
  }
1494
+ //# sourceMappingURL=comms.js.map