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,823 +1,1114 @@
1
1
  import { sanitizeFilterValue } from "../lib/utils.js";
2
2
  import { handleEmail } from "./comms.js";
3
+
3
4
  /** Strip internal infrastructure fields that stores should never see */
4
5
  function stripInternal(row) {
5
- if (!row)
6
- return null;
7
- const { platform_user_id, store_id, password_hash, ...clean } = row;
8
- return clean;
6
+ if (!row) return null;
7
+ const {
8
+ platform_user_id,
9
+ store_id,
10
+ password_hash,
11
+ ...clean
12
+ } = row;
13
+ return clean;
9
14
  }
10
15
  function stripInternalArray(rows) {
11
- return (rows || []).map(r => stripInternal(r));
16
+ return (rows || []).map(r => stripInternal(r));
12
17
  }
13
18
  export async function handleCustomers(sb, args, storeId) {
14
- const sid = storeId;
15
- switch (args.action) {
16
- // ---- FIND: search customers via v_segment_customers view ----
17
- case "find": {
18
- const sortField = args.sort_by || "created_at";
19
- const sortAsc = args.sort_order === "asc";
20
- let q = sb.from("v_segment_customers")
21
- .select("id, first_name, last_name, email, phone, loyalty_points, loyalty_tier, total_spent, total_orders, lifetime_value, is_active, rfm_segment, is_vip_customer, is_at_risk, is_churned, reorder_due, ai_churn_risk, days_since_last_order, engagement_score, age_bracket, created_at")
22
- .eq("store_id", sid)
23
- .order(sortField, { ascending: sortAsc });
24
- if (args.limit)
25
- q = q.limit(args.limit);
26
- if (args.query) {
27
- const raw = sanitizeFilterValue(String(args.query).trim());
28
- const words = raw.split(/\s+/).filter(Boolean);
29
- if (words.length > 1) {
30
- const clauses = words.map(w => { const sw = sanitizeFilterValue(w); return `first_name.ilike.%${sw}%,last_name.ilike.%${sw}%`; }).join(",");
31
- q = q.or(`${clauses},email.ilike.%${raw}%,phone.ilike.%${raw}%`);
32
- }
33
- else {
34
- const term = `%${raw}%`;
35
- q = q.or(`first_name.ilike.${term},last_name.ilike.${term},email.ilike.${term},phone.ilike.${term}`);
36
- }
37
- }
38
- if (args.status === "active")
39
- q = q.eq("is_active", true);
40
- if (args.status === "inactive")
41
- q = q.eq("is_active", false);
42
- if (args.loyalty_tier)
43
- q = q.eq("loyalty_tier", args.loyalty_tier);
44
- if (args.rfm_segment)
45
- q = q.eq("rfm_segment", args.rfm_segment);
46
- if (args.is_vip !== undefined)
47
- q = q.eq("is_vip_customer", args.is_vip);
48
- if (args.is_at_risk !== undefined)
49
- q = q.eq("is_at_risk", args.is_at_risk);
50
- if (args.is_churned !== undefined)
51
- q = q.eq("is_churned", args.is_churned);
52
- if (args.reorder_due !== undefined)
53
- q = q.eq("reorder_due", args.reorder_due);
54
- if (args.age_bracket)
55
- q = q.eq("age_bracket", args.age_bracket);
56
- if (args.min_orders !== undefined)
57
- q = q.gte("total_orders", args.min_orders);
58
- if (args.max_orders !== undefined)
59
- q = q.lte("total_orders", args.max_orders);
60
- if (args.min_spent !== undefined)
61
- q = q.gte("total_spent", args.min_spent);
62
- if (args.max_spent !== undefined)
63
- q = q.lte("total_spent", args.max_spent);
64
- const { data, error } = await q;
65
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
19
+ const sid = storeId;
20
+ switch (args.action) {
21
+ // ---- FIND: search customers via v_segment_customers view ----
22
+ case "find":
23
+ {
24
+ const sortField = args.sort_by || "created_at";
25
+ const sortAsc = args.sort_order === "asc";
26
+ let q = sb.from("v_segment_customers").select("id, first_name, last_name, email, phone, loyalty_points, loyalty_tier, total_spent, total_orders, lifetime_value, is_active, rfm_segment, is_vip_customer, is_at_risk, is_churned, reorder_due, ai_churn_risk, days_since_last_order, engagement_score, age_bracket, created_at").eq("store_id", sid).order(sortField, {
27
+ ascending: sortAsc
28
+ });
29
+ if (args.limit) q = q.limit(args.limit);
30
+ if (args.query) {
31
+ const raw = sanitizeFilterValue(String(args.query).trim());
32
+ const words = raw.split(/\s+/).filter(Boolean);
33
+ if (words.length > 1) {
34
+ const clauses = words.map(w => {
35
+ const sw = sanitizeFilterValue(w);
36
+ return `first_name.ilike.%${sw}%,last_name.ilike.%${sw}%`;
37
+ }).join(",");
38
+ q = q.or(`${clauses},email.ilike.%${raw}%,phone.ilike.%${raw}%`);
39
+ } else {
40
+ const term = `%${raw}%`;
41
+ q = q.or(`first_name.ilike.${term},last_name.ilike.${term},email.ilike.${term},phone.ilike.${term}`);
42
+ }
66
43
  }
67
- // ---- GET: full 360° customer detail with orders, activity, notes, loyalty, segments ----
68
- case "get": {
69
- const custId = args.customer_id;
70
- const { data: customer, error: custErr } = await sb.from("v_segment_customers")
71
- .select("*").eq("id", custId).eq("store_id", sid).single();
72
- if (custErr)
73
- return { success: false, error: custErr.message };
74
- const [{ data: orders }, { data: notes }, { data: activity }, { data: profile }, { data: loyaltyHistory }, { data: segmentRows }] = await Promise.all([
75
- sb.from("orders")
76
- .select("id, order_number, status, total_amount, payment_status, fulfillment_status, created_at")
77
- .eq("customer_id", custId).eq("store_id", sid)
78
- .order("created_at", { ascending: false })
79
- .limit(args.orders_limit || 10),
80
- sb.from("customer_notes")
81
- .select("id, note, created_by, created_at")
82
- .eq("customer_id", custId)
83
- .order("created_at", { ascending: false }).limit(10),
84
- sb.from("customer_activity")
85
- .select("id, activity_type, description, created_at")
86
- .eq("customer_id", custId)
87
- .order("created_at", { ascending: false }).limit(10),
88
- sb.from("store_customer_profiles")
89
- .select("*").eq("relationship_id", custId).maybeSingle(),
90
- sb.from("loyalty_transactions")
91
- .select("id, points, transaction_type, reference_type, reference_id, description, balance_before, balance_after, expires_at, created_at")
92
- .eq("customer_id", custId)
93
- .order("created_at", { ascending: false }).limit(20),
94
- sb.from("customer_segment_memberships")
95
- .select("added_at, segment:customer_segments(id, name, type)")
96
- .eq("customer_id", custId),
97
- ]);
98
- // Flatten profile fields into customer object (skip sensitive + computed-only fields)
99
- const prof = profile;
100
- const profileFields = {};
101
- if (prof) {
102
- const skip = new Set([
103
- "id", "relationship_id", "created_at", "updated_at",
104
- "password_hash", // never expose
105
- // These are recomputed by refresh_metrics — read from the view instead
106
- "total_spent", "total_orders", "lifetime_value", "average_order_value",
107
- "first_order_at", "last_order_at",
108
- ]);
109
- for (const [k, v] of Object.entries(prof)) {
110
- if (!skip.has(k))
111
- profileFields[k] = v ?? null;
112
- }
113
- }
114
- // Flatten segment memberships
115
- const segments = (segmentRows || []).map((r) => ({
116
- segment_id: r.segment?.id, name: r.segment?.name, type: r.segment?.type, added_at: r.added_at,
117
- }));
118
- return { success: true, data: stripInternal({ ...customer, ...profileFields, orders, notes, activity, loyalty_history: loyaltyHistory || [], segments }) };
44
+ if (args.status === "active") q = q.eq("is_active", true);
45
+ if (args.status === "inactive") q = q.eq("is_active", false);
46
+ if (args.loyalty_tier) q = q.eq("loyalty_tier", args.loyalty_tier);
47
+ if (args.rfm_segment) q = q.eq("rfm_segment", args.rfm_segment);
48
+ if (args.is_vip !== undefined) q = q.eq("is_vip_customer", args.is_vip);
49
+ if (args.is_at_risk !== undefined) q = q.eq("is_at_risk", args.is_at_risk);
50
+ if (args.is_churned !== undefined) q = q.eq("is_churned", args.is_churned);
51
+ if (args.reorder_due !== undefined) q = q.eq("reorder_due", args.reorder_due);
52
+ if (args.age_bracket) q = q.eq("age_bracket", args.age_bracket);
53
+ if (args.min_orders !== undefined) q = q.gte("total_orders", args.min_orders);
54
+ if (args.max_orders !== undefined) q = q.lte("total_orders", args.max_orders);
55
+ if (args.min_spent !== undefined) q = q.gte("total_spent", args.min_spent);
56
+ if (args.max_spent !== undefined) q = q.lte("total_spent", args.max_spent);
57
+ const {
58
+ data,
59
+ error
60
+ } = await q;
61
+ return error ? {
62
+ success: false,
63
+ error: error.message
64
+ } : {
65
+ success: true,
66
+ count: data?.length,
67
+ data
68
+ };
69
+ }
70
+
71
+ // ---- GET: full 360° customer detail with orders, activity, notes, loyalty, segments ----
72
+ case "get":
73
+ {
74
+ const custId = args.customer_id;
75
+ const {
76
+ data: customer,
77
+ error: custErr
78
+ } = await sb.from("v_segment_customers").select("*").eq("id", custId).eq("store_id", sid).single();
79
+ if (custErr) return {
80
+ success: false,
81
+ error: custErr.message
82
+ };
83
+ const [{
84
+ data: orders
85
+ }, {
86
+ data: notes
87
+ }, {
88
+ data: activity
89
+ }, {
90
+ data: profile
91
+ }, {
92
+ data: loyaltyHistory
93
+ }, {
94
+ data: segmentRows
95
+ }] = await Promise.all([sb.from("orders").select("id, order_number, status, total_amount, payment_status, fulfillment_status, created_at").eq("customer_id", custId).eq("store_id", sid).order("created_at", {
96
+ ascending: false
97
+ }).limit(args.orders_limit || 10), sb.from("customer_notes").select("id, note, created_by, created_at").eq("customer_id", custId).order("created_at", {
98
+ ascending: false
99
+ }).limit(10), sb.from("customer_activity").select("id, activity_type, description, created_at").eq("customer_id", custId).order("created_at", {
100
+ ascending: false
101
+ }).limit(10), sb.from("store_customer_profiles").select("*").eq("relationship_id", custId).maybeSingle(), sb.from("loyalty_transactions").select("id, points, transaction_type, reference_type, reference_id, description, balance_before, balance_after, expires_at, created_at").eq("customer_id", custId).order("created_at", {
102
+ ascending: false
103
+ }).limit(20), sb.from("customer_segment_memberships").select("added_at, segment:customer_segments(id, name, type)").eq("customer_id", custId)]);
104
+
105
+ // Flatten profile fields into customer object (skip sensitive + computed-only fields)
106
+ const prof = profile;
107
+ const profileFields = {};
108
+ if (prof) {
109
+ const skip = new Set(["id", "relationship_id", "created_at", "updated_at", "password_hash",
110
+ // never expose
111
+ // These are recomputed by refresh_metrics — read from the view instead
112
+ "total_spent", "total_orders", "lifetime_value", "average_order_value", "first_order_at", "last_order_at"]);
113
+ for (const [k, v] of Object.entries(prof)) {
114
+ if (!skip.has(k)) profileFields[k] = v ?? null;
115
+ }
119
116
  }
120
- // ---- CREATE: new customer (platform_user + relationship + profile) ----
121
- // Matching priority: email → phone → drivers_license → name+DOB → create new
122
- case "create": {
123
- const email = args.email;
124
- const phone = args.phone;
125
- const firstName = args.first_name;
126
- const lastName = args.last_name;
127
- const dob = args.date_of_birth;
128
- const dl = args.drivers_license_number;
129
- if (!firstName && !lastName)
130
- return { success: false, error: "first_name or last_name is required" };
131
- // Multi-tier matching to find existing platform_user (mirrors POS verify flow)
132
- let platformUserId = null;
133
- let matchedVia = null;
134
- // Tier 1: email
135
- if (!platformUserId && email) {
136
- const { data: existing } = await sb.from("platform_users")
137
- .select("id").eq("email", email).maybeSingle();
138
- if (existing) {
139
- platformUserId = existing.id;
140
- matchedVia = "email";
141
- }
142
- }
143
- // Tier 2: phone
144
- if (!platformUserId && phone) {
145
- const { data: existing } = await sb.from("platform_users")
146
- .select("id").eq("phone", phone).maybeSingle();
147
- if (existing) {
148
- platformUserId = existing.id;
149
- matchedVia = "phone";
150
- }
151
- }
152
- // Tier 3: drivers license (look up via store_customer_profiles → relationship → platform_user)
153
- if (!platformUserId && dl) {
154
- const { data: profMatch } = await sb.from("store_customer_profiles")
155
- .select("relationship_id, relationship:user_creation_relationships!relationship_id(user_id, store_id)")
156
- .eq("drivers_license_number", dl).limit(1).maybeSingle();
157
- if (profMatch) {
158
- const rel = profMatch.relationship;
159
- if (rel?.user_id) {
160
- platformUserId = rel.user_id;
161
- matchedVia = "drivers_license";
162
- }
163
- }
164
- }
165
- // Tier 4: exact name + date of birth (only when all 3 are provided)
166
- if (!platformUserId && firstName && lastName && dob) {
167
- const { data: nameMatch } = await sb.from("platform_users")
168
- .select("id").ilike("first_name", firstName).ilike("last_name", lastName)
169
- .eq("date_of_birth", dob).limit(1).maybeSingle();
170
- if (nameMatch) {
171
- platformUserId = nameMatch.id;
172
- matchedVia = "name+dob";
173
- }
174
- }
175
- // Create platform_user if no match found
176
- const isNameOnly = !email && !phone;
177
- if (!platformUserId) {
178
- const puInsert = { first_name: firstName, last_name: lastName };
179
- if (email)
180
- puInsert.email = email;
181
- if (phone)
182
- puInsert.phone = phone;
183
- if (dob)
184
- puInsert.date_of_birth = dob;
185
- const { data: newPu, error: puErr } = await sb.from("platform_users")
186
- .insert(puInsert).select("id").single();
187
- if (puErr)
188
- return { success: false, error: `Failed to create platform user: ${puErr.message}` };
189
- platformUserId = newPu.id;
190
- }
191
- // Check if relationship already exists for this store
192
- const { data: existingRel } = await sb.from("user_creation_relationships")
193
- .select("id").eq("user_id", platformUserId).eq("store_id", sid).maybeSingle();
194
- if (existingRel) {
195
- const { data: existing } = await sb.from("v_store_customers")
196
- .select("*").eq("id", existingRel.id).single();
197
- return { success: true, data: stripInternal(existing), note: `Customer already exists (matched via ${matchedVia || "existing record"})` };
198
- }
199
- // Create relationship
200
- const { data: rel, error: relErr } = await sb.from("user_creation_relationships")
201
- .insert({
202
- user_id: platformUserId, creation_id: sid, creation_type: "store",
203
- store_id: sid, role: "user", status: "active",
204
- email_consent: args.email_consent ?? false,
205
- sms_consent: args.sms_consent ?? false,
206
- }).select("id").single();
207
- if (relErr)
208
- return { success: false, error: `Failed to create customer relationship: ${relErr.message}` };
209
- // Create store profile
210
- const profileInsert = { relationship_id: rel.id };
211
- if (args.street_address)
212
- profileInsert.street_address = args.street_address;
213
- if (args.city)
214
- profileInsert.city = args.city;
215
- if (args.state)
216
- profileInsert.state = args.state;
217
- if (args.postal_code)
218
- profileInsert.postal_code = args.postal_code;
219
- if (dl)
220
- profileInsert.drivers_license_number = dl;
221
- if (args.medical_card_number)
222
- profileInsert.medical_card_number = args.medical_card_number;
223
- if (args.medical_card_expiry)
224
- profileInsert.medical_card_expiry = args.medical_card_expiry;
225
- if (args.gender)
226
- profileInsert.gender = args.gender;
227
- if (args.eye_color)
228
- profileInsert.eye_color = args.eye_color;
229
- if (args.hair_color)
230
- profileInsert.hair_color = args.hair_color;
231
- if (args.height_raw)
232
- profileInsert.height_raw = args.height_raw;
233
- if (args.weight)
234
- profileInsert.weight = args.weight;
235
- if (args.suffix)
236
- profileInsert.suffix = args.suffix;
237
- if (args.is_staff !== undefined)
238
- profileInsert.is_staff = args.is_staff;
239
- await sb.from("store_customer_profiles").insert(profileInsert);
240
- const { data: created } = await sb.from("v_store_customers")
241
- .select("*").eq("id", rel.id).single();
242
- return {
243
- success: true,
244
- data: stripInternal(created),
245
- ...(matchedVia ? { note: `Linked to existing customer record (matched via ${matchedVia})` } : {}),
246
- ...(isNameOnly ? { warning: "Created with no email or phone — add contact info to prevent duplicates." } : {}),
247
- };
117
+
118
+ // Flatten segment memberships
119
+ const segments = (segmentRows || []).map(r => ({
120
+ segment_id: r.segment?.id,
121
+ name: r.segment?.name,
122
+ type: r.segment?.type,
123
+ added_at: r.added_at
124
+ }));
125
+ return {
126
+ success: true,
127
+ data: stripInternal({
128
+ ...customer,
129
+ ...profileFields,
130
+ orders,
131
+ notes,
132
+ activity,
133
+ loyalty_history: loyaltyHistory || [],
134
+ segments
135
+ })
136
+ };
137
+ }
138
+
139
+ // ---- CREATE: new customer (platform_user + relationship + profile) ----
140
+ // Matching priority: email → phone → drivers_license → name+DOB → create new
141
+ case "create":
142
+ {
143
+ const email = args.email;
144
+ const phone = args.phone;
145
+ const firstName = args.first_name;
146
+ const lastName = args.last_name;
147
+ const dob = args.date_of_birth;
148
+ const dl = args.drivers_license_number;
149
+ if (!firstName && !lastName) return {
150
+ success: false,
151
+ error: "first_name or last_name is required"
152
+ };
153
+
154
+ // Multi-tier matching to find existing platform_user (mirrors POS verify flow)
155
+ let platformUserId = null;
156
+ let matchedVia = null;
157
+
158
+ // Tier 1: email
159
+ if (!platformUserId && email) {
160
+ const {
161
+ data: existing
162
+ } = await sb.from("platform_users").select("id").eq("email", email).maybeSingle();
163
+ if (existing) {
164
+ platformUserId = existing.id;
165
+ matchedVia = "email";
166
+ }
248
167
  }
249
- // ---- UPDATE: modify any customer fields ----
250
- case "update": {
251
- const custId = args.customer_id;
252
- const { data: rel, error: relErr } = await sb.from("user_creation_relationships")
253
- .select("id, user_id").eq("id", custId).eq("store_id", sid).single();
254
- if (relErr)
255
- return { success: false, error: `Customer not found: ${relErr.message}` };
256
- // Customer identity (name, email, phone, DOB)
257
- const identityUpdates = {};
258
- if (args.first_name !== undefined)
259
- identityUpdates.first_name = args.first_name;
260
- if (args.last_name !== undefined)
261
- identityUpdates.last_name = args.last_name;
262
- if (args.email !== undefined)
263
- identityUpdates.email = args.email;
264
- if (args.phone !== undefined)
265
- identityUpdates.phone = args.phone;
266
- if (args.date_of_birth !== undefined)
267
- identityUpdates.date_of_birth = args.date_of_birth;
268
- if (Object.keys(identityUpdates).length > 0) {
269
- identityUpdates.updated_at = new Date().toISOString();
270
- const { error: puErr } = await sb.from("platform_users")
271
- .update(identityUpdates).eq("id", rel.user_id);
272
- if (puErr)
273
- return { success: false, error: `Failed to update customer: ${puErr.message}` };
274
- }
275
- // Consent & status
276
- const relUpdates = {};
277
- if (args.status !== undefined)
278
- relUpdates.status = args.status;
279
- if (args.email_consent !== undefined)
280
- relUpdates.email_consent = args.email_consent;
281
- if (args.sms_consent !== undefined)
282
- relUpdates.sms_consent = args.sms_consent;
283
- if (args.push_consent !== undefined)
284
- relUpdates.push_consent = args.push_consent;
285
- if (Object.keys(relUpdates).length > 0) {
286
- relUpdates.updated_at = new Date().toISOString();
287
- await sb.from("user_creation_relationships").update(relUpdates).eq("id", custId);
288
- }
289
- // Profile fields
290
- const profUpdates = {};
291
- if (args.loyalty_points !== undefined)
292
- profUpdates.loyalty_points = args.loyalty_points;
293
- if (args.loyalty_tier !== undefined)
294
- profUpdates.loyalty_tier = args.loyalty_tier;
295
- if (args.street_address !== undefined)
296
- profUpdates.street_address = args.street_address;
297
- if (args.city !== undefined)
298
- profUpdates.city = args.city;
299
- if (args.state !== undefined)
300
- profUpdates.state = args.state;
301
- if (args.postal_code !== undefined)
302
- profUpdates.postal_code = args.postal_code;
303
- if (args.billing_address !== undefined)
304
- profUpdates.billing_address = args.billing_address;
305
- if (args.shipping_addresses !== undefined)
306
- profUpdates.shipping_addresses = args.shipping_addresses;
307
- if (args.default_shipping_address_index !== undefined)
308
- profUpdates.default_shipping_address_index = args.default_shipping_address_index;
309
- if (args.drivers_license_number !== undefined)
310
- profUpdates.drivers_license_number = args.drivers_license_number;
311
- if (args.id_verified !== undefined) {
312
- profUpdates.id_verified = args.id_verified;
313
- if (args.id_verified === true)
314
- profUpdates.id_verified_at = new Date().toISOString();
315
- }
316
- if (args.license_expiration_date !== undefined)
317
- profUpdates.license_expiration_date = args.license_expiration_date;
318
- if (args.license_issue_date !== undefined)
319
- profUpdates.license_issue_date = args.license_issue_date;
320
- if (args.medical_card_number !== undefined)
321
- profUpdates.medical_card_number = args.medical_card_number;
322
- if (args.medical_card_expiry !== undefined)
323
- profUpdates.medical_card_expiry = args.medical_card_expiry;
324
- if (args.gender !== undefined)
325
- profUpdates.gender = args.gender;
326
- if (args.eye_color !== undefined)
327
- profUpdates.eye_color = args.eye_color;
328
- if (args.height_raw !== undefined)
329
- profUpdates.height_raw = args.height_raw;
330
- if (args.weight !== undefined)
331
- profUpdates.weight = args.weight;
332
- if (args.hair_color !== undefined)
333
- profUpdates.hair_color = args.hair_color;
334
- if (args.suffix !== undefined)
335
- profUpdates.suffix = args.suffix;
336
- if (args.is_staff !== undefined)
337
- profUpdates.is_staff = args.is_staff;
338
- if (args.has_wallet_pass !== undefined)
339
- profUpdates.has_wallet_pass = args.has_wallet_pass;
340
- if (args.is_wholesale_approved !== undefined)
341
- profUpdates.is_wholesale_approved = args.is_wholesale_approved;
342
- if (args.wholesale_tier !== undefined)
343
- profUpdates.wholesale_tier = args.wholesale_tier;
344
- if (args.wholesale_business_name !== undefined)
345
- profUpdates.wholesale_business_name = args.wholesale_business_name;
346
- if (args.wholesale_business_type !== undefined)
347
- profUpdates.wholesale_business_type = args.wholesale_business_type;
348
- if (args.wholesale_license_number !== undefined)
349
- profUpdates.wholesale_license_number = args.wholesale_license_number;
350
- if (args.wholesale_tax_id !== undefined)
351
- profUpdates.wholesale_tax_id = args.wholesale_tax_id;
352
- if (args.wholesale_discount_percent !== undefined)
353
- profUpdates.wholesale_discount_percent = args.wholesale_discount_percent;
354
- if (args.wholesale_payment_terms !== undefined)
355
- profUpdates.wholesale_payment_terms = args.wholesale_payment_terms;
356
- if (args.wholesale_credit_limit !== undefined)
357
- profUpdates.wholesale_credit_limit = args.wholesale_credit_limit;
358
- if (args.wholesale_resale_certificate !== undefined)
359
- profUpdates.wholesale_resale_certificate = args.wholesale_resale_certificate;
360
- if (args.wholesale_notes !== undefined)
361
- profUpdates.wholesale_notes = args.wholesale_notes;
362
- if (Object.keys(profUpdates).length > 0) {
363
- profUpdates.updated_at = new Date().toISOString();
364
- const { error: profErr } = await sb.from("store_customer_profiles")
365
- .update(profUpdates).eq("relationship_id", custId);
366
- if (profErr)
367
- return { success: false, error: `Failed to update profile: ${profErr.message}` };
368
- }
369
- const { data: updated } = await sb.from("v_segment_customers")
370
- .select("*").eq("id", custId).eq("store_id", sid).single();
371
- return { success: true, data: stripInternal(updated) };
168
+ // Tier 2: phone
169
+ if (!platformUserId && phone) {
170
+ const {
171
+ data: existing
172
+ } = await sb.from("platform_users").select("id").eq("phone", phone).maybeSingle();
173
+ if (existing) {
174
+ platformUserId = existing.id;
175
+ matchedVia = "phone";
176
+ }
372
177
  }
373
- // ---- FIND_DUPLICATES: identify potential duplicate customer accounts ----
374
- case "find_duplicates": {
375
- // Try RPC first (may not exist yet)
376
- const { data: dupes, error: dupeErr } = await sb.rpc("find_duplicate_customers", { p_store_id: sid });
377
- if (!dupeErr && dupes)
378
- return { success: true, data: dupes };
379
- // Fallback: manual duplicate detection by phone
380
- const { data: byPhone, error: phErr } = await sb.from("v_store_customers")
381
- .select("id, first_name, last_name, email, phone, total_spent, total_orders, created_at")
382
- .eq("store_id", sid).not("phone", "is", null).order("phone");
383
- if (phErr)
384
- return { success: false, error: phErr.message };
385
- const phoneMap = new Map();
386
- for (const c of byPhone || []) {
387
- if (!c.phone)
388
- continue;
389
- const normalized = c.phone.replace(/\D/g, "").slice(-10);
390
- if (!phoneMap.has(normalized))
391
- phoneMap.set(normalized, []);
392
- phoneMap.get(normalized).push(c);
393
- }
394
- const phoneDupes = Array.from(phoneMap.entries())
395
- .filter(([_, custs]) => custs.length > 1)
396
- .map(([phone, custs]) => ({ phone, count: custs.length, customers: custs }));
397
- // Also check by exact name match
398
- const { data: byName } = await sb.from("v_store_customers")
399
- .select("id, first_name, last_name, email, phone, total_spent, total_orders, created_at")
400
- .eq("store_id", sid).order("last_name").order("first_name");
401
- const nameMap = new Map();
402
- for (const c of byName || []) {
403
- const key = `${(c.first_name || "").toLowerCase().trim()} ${(c.last_name || "").toLowerCase().trim()}`;
404
- if (!key.trim())
405
- continue;
406
- if (!nameMap.has(key))
407
- nameMap.set(key, []);
408
- nameMap.get(key).push(c);
409
- }
410
- const nameDupes = Array.from(nameMap.entries())
411
- .filter(([_, custs]) => custs.length > 1)
412
- .map(([name, custs]) => ({ name, count: custs.length, customers: custs }));
413
- // Pre-format as markdown — formatter drops nested customer arrays
414
- const totalPhoneDupes = phoneDupes.reduce((s, d) => s + d.count, 0);
415
- const totalNameDupes = nameDupes.reduce((s, d) => s + d.count, 0);
416
- const lines = [
417
- `## Duplicate Customers`,
418
- `**By Phone**: ${phoneDupes.length} groups (${totalPhoneDupes} customers) | **By Name**: ${nameDupes.length} groups (${totalNameDupes} customers)\n`,
419
- ];
420
- if (phoneDupes.length > 0) {
421
- lines.push("### Phone Duplicates");
422
- for (const group of phoneDupes.slice(0, 20)) {
423
- lines.push(`\n**Phone ${group.phone}** (${group.count} customers):`);
424
- lines.push("| Name | Email | Phone | Orders | Spent | Created |");
425
- lines.push("| --- | --- | --- | ---: | ---: | --- |");
426
- for (const c of group.customers) {
427
- lines.push(`| ${c.first_name || ""} ${c.last_name || ""} | ${c.email || "—"} | ${c.phone || "—"} | ${c.total_orders ?? 0} | $${c.total_spent ?? 0} | ${c.created_at?.slice(0, 10) || "—"} |`);
428
- }
429
- }
430
- }
431
- if (nameDupes.length > 0) {
432
- lines.push("\n### Name Duplicates");
433
- for (const group of nameDupes.slice(0, 20)) {
434
- lines.push(`\n**"${group.name}"** (${group.count} customers):`);
435
- lines.push("| Name | Email | Phone | Orders | Spent | Created |");
436
- lines.push("| --- | --- | --- | ---: | ---: | --- |");
437
- for (const c of group.customers) {
438
- lines.push(`| ${c.first_name || ""} ${c.last_name || ""} | ${c.email || "—"} | ${c.phone || "—"} | ${c.total_orders ?? 0} | $${c.total_spent ?? 0} | ${c.created_at?.slice(0, 10) || "—"} |`);
439
- }
440
- }
178
+ // Tier 3: drivers license (look up via store_customer_profiles → relationship → platform_user)
179
+ if (!platformUserId && dl) {
180
+ const {
181
+ data: profMatch
182
+ } = await sb.from("store_customer_profiles").select("relationship_id, relationship:user_creation_relationships!relationship_id(user_id, store_id)").eq("drivers_license_number", dl).limit(1).maybeSingle();
183
+ if (profMatch) {
184
+ const rel = profMatch.relationship;
185
+ if (rel?.user_id) {
186
+ platformUserId = rel.user_id;
187
+ matchedVia = "drivers_license";
441
188
  }
442
- if (phoneDupes.length === 0 && nameDupes.length === 0) {
443
- lines.push("\nNo duplicates found.");
444
- }
445
- return { success: true, data: lines.join("\n") };
189
+ }
446
190
  }
447
- // ---- MERGE: merge two customer records into one ----
448
- case "merge": {
449
- const primaryId = args.primary_customer_id;
450
- const secondaryId = args.secondary_customer_id;
451
- if (!primaryId || !secondaryId)
452
- return { success: false, error: "primary_customer_id and secondary_customer_id required" };
453
- if (primaryId === secondaryId)
454
- return { success: false, error: "Cannot merge a customer with itself" };
455
- // Verify both exist and belong to this store
456
- const { data: primary } = await sb.from("v_store_customers").select("*").eq("id", primaryId).eq("store_id", sid).single();
457
- const { data: secondary } = await sb.from("v_store_customers").select("*").eq("id", secondaryId).eq("store_id", sid).single();
458
- if (!primary)
459
- return { success: false, error: `Primary customer ${primaryId} not found in this store` };
460
- if (!secondary)
461
- return { success: false, error: `Secondary customer ${secondaryId} not found in this store` };
462
- // Reassign all child records from secondary to primary
463
- // Tables with store_id column get scoped; tables without rely on customer ownership verification above
464
- const storeScoped = ["orders"];
465
- const customerOnly = ["customer_activity", "customer_notes", "customer_loyalty", "customer_sessions", "customer_segment_memberships"];
466
- const reassignResults = {};
467
- for (const table of storeScoped) {
468
- const { error, count } = await sb.from(table)
469
- .update({ customer_id: primaryId }).eq("customer_id", secondaryId).eq("store_id", sid);
470
- reassignResults[table] = error ? `error: ${error.message}` : `moved ${count ?? "?"} rows`;
471
- }
472
- for (const table of customerOnly) {
473
- const { error, count } = await sb.from(table)
474
- .update({ customer_id: primaryId }).eq("customer_id", secondaryId);
475
- reassignResults[table] = error ? `error: ${error.message}` : `moved ${count ?? "?"} rows`;
476
- }
477
- // Merge profile stats
478
- const { data: primaryProf } = await sb.from("store_customer_profiles")
479
- .select("*").eq("relationship_id", primaryId).maybeSingle();
480
- const { data: secondaryProf } = await sb.from("store_customer_profiles")
481
- .select("*").eq("relationship_id", secondaryId).maybeSingle();
482
- if (primaryProf && secondaryProf) {
483
- await sb.from("store_customer_profiles").update({
484
- total_spent: parseFloat(primaryProf.total_spent || 0) + parseFloat(secondaryProf.total_spent || 0),
485
- total_orders: (primaryProf.total_orders || 0) + (secondaryProf.total_orders || 0),
486
- loyalty_points: (primaryProf.loyalty_points || 0) + (secondaryProf.loyalty_points || 0),
487
- lifetime_points_earned: (primaryProf.lifetime_points_earned || 0) + (secondaryProf.lifetime_points_earned || 0),
488
- first_order_at: primaryProf.first_order_at && secondaryProf.first_order_at
489
- ? (primaryProf.first_order_at < secondaryProf.first_order_at ? primaryProf.first_order_at : secondaryProf.first_order_at)
490
- : primaryProf.first_order_at || secondaryProf.first_order_at,
491
- last_order_at: primaryProf.last_order_at && secondaryProf.last_order_at
492
- ? (primaryProf.last_order_at > secondaryProf.last_order_at ? primaryProf.last_order_at : secondaryProf.last_order_at)
493
- : primaryProf.last_order_at || secondaryProf.last_order_at,
494
- updated_at: new Date().toISOString(),
495
- }).eq("relationship_id", primaryId);
496
- await sb.from("store_customer_profiles").delete().eq("relationship_id", secondaryId);
497
- }
498
- // Mark secondary as merged
499
- await sb.from("user_creation_relationships").update({
500
- status: "merged",
501
- relationship_data: { merged_into: primaryId, merged_at: new Date().toISOString() },
502
- updated_at: new Date().toISOString(),
503
- }).eq("id", secondaryId).eq("store_id", sid);
504
- // Fill in missing identity fields from secondary
505
- const { data: primaryRel } = await sb.from("user_creation_relationships").select("user_id").eq("id", primaryId).eq("store_id", sid).single();
506
- const { data: secondaryRel } = await sb.from("user_creation_relationships").select("user_id").eq("id", secondaryId).eq("store_id", sid).single();
507
- if (primaryRel && secondaryRel) {
508
- const { data: pPu } = await sb.from("platform_users").select("*").eq("id", primaryRel.user_id).single();
509
- const { data: sPu } = await sb.from("platform_users").select("*").eq("id", secondaryRel.user_id).single();
510
- if (pPu && sPu) {
511
- const fills = {};
512
- if (!pPu.email && sPu.email && !sPu.email.startsWith("merged."))
513
- fills.email = sPu.email;
514
- if (!pPu.phone && sPu.phone && !sPu.phone.startsWith("merged_"))
515
- fills.phone = sPu.phone;
516
- if (!pPu.date_of_birth && sPu.date_of_birth)
517
- fills.date_of_birth = sPu.date_of_birth;
518
- if (Object.keys(fills).length > 0)
519
- await sb.from("platform_users").update(fills).eq("id", primaryRel.user_id);
520
- // Mark secondary identity as merged
521
- const marks = {};
522
- if (sPu.email && !sPu.email.startsWith("merged."))
523
- marks.email = `merged.${sPu.email}`;
524
- if (sPu.phone && !sPu.phone.startsWith("merged_"))
525
- marks.phone = `merged_${sPu.phone}`;
526
- if (Object.keys(marks).length > 0)
527
- await sb.from("platform_users").update(marks).eq("id", secondaryRel.user_id);
528
- }
529
- }
530
- const { data: merged } = await sb.from("v_store_customers").select("*").eq("id", primaryId).single();
531
- return { success: true, data: { merged_customer: stripInternal(merged), reassign_results: reassignResults } };
191
+ // Tier 4: exact name + date of birth (only when all 3 are provided)
192
+ if (!platformUserId && firstName && lastName && dob) {
193
+ const {
194
+ data: nameMatch
195
+ } = await sb.from("platform_users").select("id").ilike("first_name", firstName).ilike("last_name", lastName).eq("date_of_birth", dob).limit(1).maybeSingle();
196
+ if (nameMatch) {
197
+ platformUserId = nameMatch.id;
198
+ matchedVia = "name+dob";
199
+ }
532
200
  }
533
- // ---- ADD_NOTE ----
534
- case "add_note": {
535
- // Verify customer belongs to this store before adding note
536
- const { data: custCheck } = await sb.from("v_store_customers").select("id").eq("id", args.customer_id).eq("store_id", sid).single();
537
- if (!custCheck)
538
- return { success: false, error: "Customer not found in this store" };
539
- const { data, error } = await sb.from("customer_notes")
540
- .insert({ customer_id: args.customer_id, note: args.note, created_by: args.created_by || "agent" })
541
- .select().single();
542
- return error ? { success: false, error: error.message } : { success: true, data };
201
+
202
+ // Create platform_user if no match found
203
+ const isNameOnly = !email && !phone;
204
+ if (!platformUserId) {
205
+ const puInsert = {
206
+ first_name: firstName,
207
+ last_name: lastName
208
+ };
209
+ if (email) puInsert.email = email;
210
+ if (phone) puInsert.phone = phone;
211
+ if (dob) puInsert.date_of_birth = dob;
212
+ const {
213
+ data: newPu,
214
+ error: puErr
215
+ } = await sb.from("platform_users").insert(puInsert).select("id").single();
216
+ if (puErr) return {
217
+ success: false,
218
+ error: `Failed to create platform user: ${puErr.message}`
219
+ };
220
+ platformUserId = newPu.id;
543
221
  }
544
- // ---- NOTES ----
545
- case "notes": {
546
- // Verify customer belongs to this store
547
- const { data: custCheck } = await sb.from("v_store_customers").select("id").eq("id", args.customer_id).eq("store_id", sid).single();
548
- if (!custCheck)
549
- return { success: false, error: "Customer not found in this store" };
550
- const { data, error } = await sb.from("customer_notes")
551
- .select("id, note, created_by, created_at")
552
- .eq("customer_id", args.customer_id)
553
- .order("created_at", { ascending: false }).limit(args.limit || 25);
554
- return error ? { success: false, error: error.message } : { success: true, data };
222
+
223
+ // Check if relationship already exists for this store
224
+ const {
225
+ data: existingRel
226
+ } = await sb.from("user_creation_relationships").select("id").eq("user_id", platformUserId).eq("store_id", sid).maybeSingle();
227
+ if (existingRel) {
228
+ const {
229
+ data: existing
230
+ } = await sb.from("v_store_customers").select("*").eq("id", existingRel.id).single();
231
+ return {
232
+ success: true,
233
+ data: stripInternal(existing),
234
+ note: `Customer already exists (matched via ${matchedVia || "existing record"})`
235
+ };
555
236
  }
556
- // ---- ACTIVITY ----
557
- case "activity": {
558
- // Verify customer belongs to this store
559
- const { data: custCheck } = await sb.from("v_store_customers").select("id").eq("id", args.customer_id).eq("store_id", sid).single();
560
- if (!custCheck)
561
- return { success: false, error: "Customer not found in this store" };
562
- const { data, error } = await sb.from("customer_activity")
563
- .select("id, activity_type, description, metadata, created_at")
564
- .eq("customer_id", args.customer_id)
565
- .order("created_at", { ascending: false }).limit(args.limit || 25);
566
- return error ? { success: false, error: error.message } : { success: true, data };
237
+
238
+ // Create relationship
239
+ const {
240
+ data: rel,
241
+ error: relErr
242
+ } = await sb.from("user_creation_relationships").insert({
243
+ user_id: platformUserId,
244
+ creation_id: sid,
245
+ creation_type: "store",
246
+ store_id: sid,
247
+ role: "user",
248
+ status: "active",
249
+ email_consent: args.email_consent ?? false,
250
+ sms_consent: args.sms_consent ?? false
251
+ }).select("id").single();
252
+ if (relErr) return {
253
+ success: false,
254
+ error: `Failed to create customer relationship: ${relErr.message}`
255
+ };
256
+
257
+ // Create store profile
258
+ const profileInsert = {
259
+ relationship_id: rel.id
260
+ };
261
+ if (args.street_address) profileInsert.street_address = args.street_address;
262
+ if (args.city) profileInsert.city = args.city;
263
+ if (args.state) profileInsert.state = args.state;
264
+ if (args.postal_code) profileInsert.postal_code = args.postal_code;
265
+ if (dl) profileInsert.drivers_license_number = dl;
266
+ if (args.medical_card_number) profileInsert.medical_card_number = args.medical_card_number;
267
+ if (args.medical_card_expiry) profileInsert.medical_card_expiry = args.medical_card_expiry;
268
+ if (args.gender) profileInsert.gender = args.gender;
269
+ if (args.eye_color) profileInsert.eye_color = args.eye_color;
270
+ if (args.hair_color) profileInsert.hair_color = args.hair_color;
271
+ if (args.height_raw) profileInsert.height_raw = args.height_raw;
272
+ if (args.weight) profileInsert.weight = args.weight;
273
+ if (args.suffix) profileInsert.suffix = args.suffix;
274
+ if (args.is_staff !== undefined) profileInsert.is_staff = args.is_staff;
275
+ await sb.from("store_customer_profiles").insert(profileInsert);
276
+ const {
277
+ data: created
278
+ } = await sb.from("v_store_customers").select("*").eq("id", rel.id).single();
279
+ return {
280
+ success: true,
281
+ data: stripInternal(created),
282
+ ...(matchedVia ? {
283
+ note: `Linked to existing customer record (matched via ${matchedVia})`
284
+ } : {}),
285
+ ...(isNameOnly ? {
286
+ warning: "Created with no email or phone — add contact info to prevent duplicates."
287
+ } : {})
288
+ };
289
+ }
290
+
291
+ // ---- UPDATE: modify any customer fields ----
292
+ case "update":
293
+ {
294
+ const custId = args.customer_id;
295
+ const {
296
+ data: rel,
297
+ error: relErr
298
+ } = await sb.from("user_creation_relationships").select("id, user_id").eq("id", custId).eq("store_id", sid).single();
299
+ if (relErr) return {
300
+ success: false,
301
+ error: `Customer not found: ${relErr.message}`
302
+ };
303
+
304
+ // Customer identity (name, email, phone, DOB)
305
+ const identityUpdates = {};
306
+ if (args.first_name !== undefined) identityUpdates.first_name = args.first_name;
307
+ if (args.last_name !== undefined) identityUpdates.last_name = args.last_name;
308
+ if (args.email !== undefined) identityUpdates.email = args.email;
309
+ if (args.phone !== undefined) identityUpdates.phone = args.phone;
310
+ if (args.date_of_birth !== undefined) identityUpdates.date_of_birth = args.date_of_birth;
311
+ if (Object.keys(identityUpdates).length > 0) {
312
+ identityUpdates.updated_at = new Date().toISOString();
313
+ const {
314
+ error: puErr
315
+ } = await sb.from("platform_users").update(identityUpdates).eq("id", rel.user_id);
316
+ if (puErr) return {
317
+ success: false,
318
+ error: `Failed to update customer: ${puErr.message}`
319
+ };
567
320
  }
568
- // ---- ORDERS: customer order history ----
569
- case "orders": {
570
- const { data, error } = await sb.from("orders")
571
- .select("id, order_number, status, total_amount, subtotal, tax_amount, payment_status, fulfillment_status, payment_method, created_at")
572
- .eq("customer_id", args.customer_id).eq("store_id", sid)
573
- .order("created_at", { ascending: false }).limit(args.limit || 25);
574
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
321
+
322
+ // Consent & status
323
+ const relUpdates = {};
324
+ if (args.status !== undefined) relUpdates.status = args.status;
325
+ if (args.email_consent !== undefined) relUpdates.email_consent = args.email_consent;
326
+ if (args.sms_consent !== undefined) relUpdates.sms_consent = args.sms_consent;
327
+ if (args.push_consent !== undefined) relUpdates.push_consent = args.push_consent;
328
+ if (Object.keys(relUpdates).length > 0) {
329
+ relUpdates.updated_at = new Date().toISOString();
330
+ await sb.from("user_creation_relationships").update(relUpdates).eq("id", custId);
575
331
  }
576
- // ---- LINK_CHANNEL_IDENTITY: connect a sender_id on any channel to a customer ----
577
- case "link_channel_identity": {
578
- const { customer_id, channel_type, sender_id, sender_name } = args;
579
- if (!customer_id || !channel_type || !sender_id) {
580
- return { success: false, error: "customer_id, channel_type, and sender_id are required" };
581
- }
582
- // Verify customer belongs to this store
583
- const { data: custCheck } = await sb.from("v_store_customers")
584
- .select("id").eq("id", customer_id).eq("store_id", sid).single();
585
- if (!custCheck)
586
- return { success: false, error: "Customer not found in this store" };
587
- const { error } = await sb.from("customer_channel_identities").upsert({
588
- store_id: sid,
589
- customer_id,
590
- channel_type,
591
- sender_id,
592
- sender_name: sender_name || null,
593
- verified: true,
594
- last_seen_at: new Date().toISOString(),
595
- }, { onConflict: "store_id,channel_type,sender_id" });
596
- return error
597
- ? { success: false, error: error.message }
598
- : { success: true, linked: { customer_id, channel_type, sender_id } };
332
+
333
+ // Profile fields
334
+ const profUpdates = {};
335
+ if (args.loyalty_points !== undefined) profUpdates.loyalty_points = args.loyalty_points;
336
+ if (args.loyalty_tier !== undefined) profUpdates.loyalty_tier = args.loyalty_tier;
337
+ if (args.street_address !== undefined) profUpdates.street_address = args.street_address;
338
+ if (args.city !== undefined) profUpdates.city = args.city;
339
+ if (args.state !== undefined) profUpdates.state = args.state;
340
+ if (args.postal_code !== undefined) profUpdates.postal_code = args.postal_code;
341
+ if (args.billing_address !== undefined) profUpdates.billing_address = args.billing_address;
342
+ if (args.shipping_addresses !== undefined) profUpdates.shipping_addresses = args.shipping_addresses;
343
+ if (args.default_shipping_address_index !== undefined) profUpdates.default_shipping_address_index = args.default_shipping_address_index;
344
+ if (args.drivers_license_number !== undefined) profUpdates.drivers_license_number = args.drivers_license_number;
345
+ if (args.id_verified !== undefined) {
346
+ profUpdates.id_verified = args.id_verified;
347
+ if (args.id_verified === true) profUpdates.id_verified_at = new Date().toISOString();
348
+ }
349
+ if (args.license_expiration_date !== undefined) profUpdates.license_expiration_date = args.license_expiration_date;
350
+ if (args.license_issue_date !== undefined) profUpdates.license_issue_date = args.license_issue_date;
351
+ if (args.medical_card_number !== undefined) profUpdates.medical_card_number = args.medical_card_number;
352
+ if (args.medical_card_expiry !== undefined) profUpdates.medical_card_expiry = args.medical_card_expiry;
353
+ if (args.gender !== undefined) profUpdates.gender = args.gender;
354
+ if (args.eye_color !== undefined) profUpdates.eye_color = args.eye_color;
355
+ if (args.height_raw !== undefined) profUpdates.height_raw = args.height_raw;
356
+ if (args.weight !== undefined) profUpdates.weight = args.weight;
357
+ if (args.hair_color !== undefined) profUpdates.hair_color = args.hair_color;
358
+ if (args.suffix !== undefined) profUpdates.suffix = args.suffix;
359
+ if (args.is_staff !== undefined) profUpdates.is_staff = args.is_staff;
360
+ if (args.has_wallet_pass !== undefined) profUpdates.has_wallet_pass = args.has_wallet_pass;
361
+ if (args.is_wholesale_approved !== undefined) profUpdates.is_wholesale_approved = args.is_wholesale_approved;
362
+ if (args.wholesale_tier !== undefined) profUpdates.wholesale_tier = args.wholesale_tier;
363
+ if (args.wholesale_business_name !== undefined) profUpdates.wholesale_business_name = args.wholesale_business_name;
364
+ if (args.wholesale_business_type !== undefined) profUpdates.wholesale_business_type = args.wholesale_business_type;
365
+ if (args.wholesale_license_number !== undefined) profUpdates.wholesale_license_number = args.wholesale_license_number;
366
+ if (args.wholesale_tax_id !== undefined) profUpdates.wholesale_tax_id = args.wholesale_tax_id;
367
+ if (args.wholesale_discount_percent !== undefined) profUpdates.wholesale_discount_percent = args.wholesale_discount_percent;
368
+ if (args.wholesale_payment_terms !== undefined) profUpdates.wholesale_payment_terms = args.wholesale_payment_terms;
369
+ if (args.wholesale_credit_limit !== undefined) profUpdates.wholesale_credit_limit = args.wholesale_credit_limit;
370
+ if (args.wholesale_resale_certificate !== undefined) profUpdates.wholesale_resale_certificate = args.wholesale_resale_certificate;
371
+ if (args.wholesale_notes !== undefined) profUpdates.wholesale_notes = args.wholesale_notes;
372
+ if (Object.keys(profUpdates).length > 0) {
373
+ profUpdates.updated_at = new Date().toISOString();
374
+ const {
375
+ error: profErr
376
+ } = await sb.from("store_customer_profiles").update(profUpdates).eq("relationship_id", custId);
377
+ if (profErr) return {
378
+ success: false,
379
+ error: `Failed to update profile: ${profErr.message}`
380
+ };
381
+ }
382
+ const {
383
+ data: updated
384
+ } = await sb.from("v_segment_customers").select("*").eq("id", custId).eq("store_id", sid).single();
385
+ return {
386
+ success: true,
387
+ data: stripInternal(updated)
388
+ };
389
+ }
390
+
391
+ // ---- FIND_DUPLICATES: identify potential duplicate customer accounts ----
392
+ case "find_duplicates":
393
+ {
394
+ // Try RPC first (may not exist yet)
395
+ const {
396
+ data: dupes,
397
+ error: dupeErr
398
+ } = await sb.rpc("find_duplicate_customers", {
399
+ p_store_id: sid
400
+ });
401
+ if (!dupeErr && dupes) return {
402
+ success: true,
403
+ data: dupes
404
+ };
405
+
406
+ // Fallback: manual duplicate detection by phone
407
+ const {
408
+ data: byPhone,
409
+ error: phErr
410
+ } = await sb.from("v_store_customers").select("id, first_name, last_name, email, phone, total_spent, total_orders, created_at").eq("store_id", sid).not("phone", "is", null).order("phone");
411
+ if (phErr) return {
412
+ success: false,
413
+ error: phErr.message
414
+ };
415
+ const phoneMap = new Map();
416
+ for (const c of byPhone || []) {
417
+ if (!c.phone) continue;
418
+ const normalized = c.phone.replace(/\D/g, "").slice(-10);
419
+ if (!phoneMap.has(normalized)) phoneMap.set(normalized, []);
420
+ phoneMap.get(normalized).push(c);
599
421
  }
600
- // ---- GET_CHANNEL_IDENTITIES: list all channel identities for a customer ----
601
- case "get_channel_identities": {
602
- const custId = args.customer_id;
603
- if (!custId)
604
- return { success: false, error: "customer_id is required" };
605
- const { data, error } = await sb.from("customer_channel_identities")
606
- .select("id, channel_type, sender_id, sender_name, verified, last_seen_at, created_at")
607
- .eq("store_id", sid).eq("customer_id", custId)
608
- .order("last_seen_at", { ascending: false });
609
- return error ? { success: false, error: error.message } : { success: true, data };
422
+ const phoneDupes = Array.from(phoneMap.entries()).filter(([_, custs]) => custs.length > 1).map(([phone, custs]) => ({
423
+ phone,
424
+ count: custs.length,
425
+ customers: custs
426
+ }));
427
+
428
+ // Also check by exact name match
429
+ const {
430
+ data: byName
431
+ } = await sb.from("v_store_customers").select("id, first_name, last_name, email, phone, total_spent, total_orders, created_at").eq("store_id", sid).order("last_name").order("first_name");
432
+ const nameMap = new Map();
433
+ for (const c of byName || []) {
434
+ const key = `${(c.first_name || "").toLowerCase().trim()} ${(c.last_name || "").toLowerCase().trim()}`;
435
+ if (!key.trim()) continue;
436
+ if (!nameMap.has(key)) nameMap.set(key, []);
437
+ nameMap.get(key).push(c);
610
438
  }
611
- // ---- INVITE: create auth account for customer and send branded invite email ----
612
- case "invite": {
613
- const custId = args.customer_id;
614
- if (!custId)
615
- return { success: false, error: "customer_id is required" };
616
- // Verify customer belongs to this store
617
- const { data: rel, error: relErr } = await sb.from("user_creation_relationships")
618
- .select("id, user_id, store_id")
619
- .eq("id", custId).eq("store_id", sid).single();
620
- if (relErr || !rel)
621
- return { success: false, error: "Customer not found for this store" };
622
- // Get platform user + store name in parallel
623
- const [{ data: pu }, { data: store }] = await Promise.all([
624
- sb.from("platform_users").select("id, auth_id, email, first_name, last_name").eq("id", rel.user_id).single(),
625
- sb.from("stores").select("store_name").eq("id", sid).single(),
626
- ]);
627
- if (!pu)
628
- return { success: false, error: "Platform user not found" };
629
- if (!pu.email)
630
- return { success: false, error: "Customer has no email address — add one first" };
631
- const storeName = store?.store_name || "our platform";
632
- const customerName = [pu.first_name, pu.last_name].filter(Boolean).join(" ") || "there";
633
- const portalUrl = args.portal_url || args.redirect_to || null;
634
- // If already has an auth account, just resend the email with a new magic link
635
- let authId = pu.auth_id;
636
- if (!authId) {
637
- // Create auth account silently (no Supabase built-in email)
638
- const { data: newUser, error: createErr } = await sb.auth.admin.createUser({
639
- email: pu.email,
640
- email_confirm: true, // skip verification — store is vouching for this email
641
- user_metadata: {
642
- first_name: pu.first_name,
643
- last_name: pu.last_name,
644
- invited_by_store: sid,
645
- },
646
- });
647
- if (createErr)
648
- return { success: false, error: `Account creation failed: ${createErr.message}` };
649
- authId = newUser.user.id;
650
- // Link auth account to platform user
651
- await sb.from("platform_users").update({ auth_id: authId }).eq("id", pu.id);
439
+ const nameDupes = Array.from(nameMap.entries()).filter(([_, custs]) => custs.length > 1).map(([name, custs]) => ({
440
+ name,
441
+ count: custs.length,
442
+ customers: custs
443
+ }));
444
+
445
+ // Pre-format as markdown formatter drops nested customer arrays
446
+ const totalPhoneDupes = phoneDupes.reduce((s, d) => s + d.count, 0);
447
+ const totalNameDupes = nameDupes.reduce((s, d) => s + d.count, 0);
448
+ const lines = [`## Duplicate Customers`, `**By Phone**: ${phoneDupes.length} groups (${totalPhoneDupes} customers) | **By Name**: ${nameDupes.length} groups (${totalNameDupes} customers)\n`];
449
+ if (phoneDupes.length > 0) {
450
+ lines.push("### Phone Duplicates");
451
+ for (const group of phoneDupes.slice(0, 20)) {
452
+ lines.push(`\n**Phone ${group.phone}** (${group.count} customers):`);
453
+ lines.push("| Name | Email | Phone | Orders | Spent | Created |");
454
+ lines.push("| --- | --- | --- | ---: | ---: | --- |");
455
+ for (const c of group.customers) {
456
+ lines.push(`| ${c.first_name || ""} ${c.last_name || ""} | ${c.email || "—"} | ${c.phone || "—"} | ${c.total_orders ?? 0} | $${c.total_spent ?? 0} | ${c.created_at?.slice(0, 10) || "—"} |`);
652
457
  }
653
- // Generate a magic link for the customer to set up / log in
654
- const { data: linkData, error: linkErr } = await sb.auth.admin.generateLink({
655
- type: "magiclink",
656
- email: pu.email,
657
- options: {
658
- ...(portalUrl ? { redirectTo: portalUrl } : {}),
659
- },
660
- });
661
- if (linkErr)
662
- return { success: false, error: `Link generation failed: ${linkErr.message}` };
663
- const magicLink = linkData?.properties?.action_link || "";
664
- // Send branded invite email through our email system
665
- const emailResult = await handleEmail(sb, {
666
- action: "send_template",
667
- to: pu.email,
668
- template: "customer-portal-invite",
669
- template_data: {
670
- customer_name: customerName,
671
- store_name: storeName,
672
- magic_link: magicLink,
673
- portal_url: portalUrl || magicLink,
674
- },
675
- }, sid);
676
- return {
677
- success: true,
678
- data: {
679
- email: pu.email,
680
- name: customerName,
681
- invited: true,
682
- email_sent: emailResult.success,
683
- ...(emailResult.success ? {} : { email_error: emailResult.error }),
684
- },
685
- };
458
+ }
686
459
  }
687
- // ---- LOYALTY_HISTORY: transaction history for a customer ----
688
- case "loyalty_history": {
689
- const custId = args.customer_id;
690
- if (!custId)
691
- return { success: false, error: "customer_id is required" };
692
- let q = sb.from("loyalty_transactions")
693
- .select("id, points, transaction_type, reference_type, reference_id, description, balance_before, balance_after, expires_at, created_at")
694
- .eq("customer_id", custId)
695
- .order("created_at", { ascending: false });
696
- if (args.transaction_type)
697
- q = q.eq("transaction_type", args.transaction_type);
698
- q = q.limit(args.limit || 50);
699
- const { data, error } = await q;
700
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
460
+ if (nameDupes.length > 0) {
461
+ lines.push("\n### Name Duplicates");
462
+ for (const group of nameDupes.slice(0, 20)) {
463
+ lines.push(`\n**"${group.name}"** (${group.count} customers):`);
464
+ lines.push("| Name | Email | Phone | Orders | Spent | Created |");
465
+ lines.push("| --- | --- | --- | ---: | ---: | --- |");
466
+ for (const c of group.customers) {
467
+ lines.push(`| ${c.first_name || ""} ${c.last_name || ""} | ${c.email || "—"} | ${c.phone || "—"} | ${c.total_orders ?? 0} | $${c.total_spent ?? 0} | ${c.created_at?.slice(0, 10) || "—"} |`);
468
+ }
469
+ }
701
470
  }
702
- // ---- ADJUST_LOYALTY: award or deduct loyalty points via RPC ----
703
- case "adjust_loyalty": {
704
- const custId = args.customer_id;
705
- const points = args.points;
706
- const reason = args.reason;
707
- if (!custId || points === undefined || !reason)
708
- return { success: false, error: "customer_id, points, and reason are required" };
709
- const { data, error } = await sb.rpc("adjust_customer_loyalty_points", {
710
- p_customer_id: custId,
711
- p_points_change: points,
712
- p_reason: reason,
713
- });
714
- if (error)
715
- return { success: false, error: error.message };
716
- const { data: updated } = await sb.from("store_customer_profiles")
717
- .select("loyalty_points, loyalty_tier, lifetime_points_earned")
718
- .eq("relationship_id", custId).maybeSingle();
719
- return { success: true, data: { rpc_result: data, ...updated } };
471
+ if (phoneDupes.length === 0 && nameDupes.length === 0) {
472
+ lines.push("\nNo duplicates found.");
720
473
  }
721
- // ---- SEGMENTS: list all customer segments for the store ----
722
- case "segments": {
723
- let q = sb.from("customer_segments")
724
- .select("id, name, ai_description, type, segment_rules, filter_criteria, customer_count, is_active, color, icon, targeting_tips, created_at")
725
- .eq("store_id", sid)
726
- .order("customer_count", { ascending: false });
727
- if (args.limit)
728
- q = q.limit(args.limit);
729
- const { data, error } = await q;
730
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
474
+ return {
475
+ success: true,
476
+ data: lines.join("\n")
477
+ };
478
+ }
479
+
480
+ // ---- MERGE: merge two customer records into one ----
481
+ case "merge":
482
+ {
483
+ const primaryId = args.primary_customer_id;
484
+ const secondaryId = args.secondary_customer_id;
485
+ if (!primaryId || !secondaryId) return {
486
+ success: false,
487
+ error: "primary_customer_id and secondary_customer_id required"
488
+ };
489
+ if (primaryId === secondaryId) return {
490
+ success: false,
491
+ error: "Cannot merge a customer with itself"
492
+ };
493
+
494
+ // Verify both exist and belong to this store
495
+ const {
496
+ data: primary
497
+ } = await sb.from("v_store_customers").select("*").eq("id", primaryId).eq("store_id", sid).single();
498
+ const {
499
+ data: secondary
500
+ } = await sb.from("v_store_customers").select("*").eq("id", secondaryId).eq("store_id", sid).single();
501
+ if (!primary) return {
502
+ success: false,
503
+ error: `Primary customer ${primaryId} not found in this store`
504
+ };
505
+ if (!secondary) return {
506
+ success: false,
507
+ error: `Secondary customer ${secondaryId} not found in this store`
508
+ };
509
+
510
+ // Reassign all child records from secondary to primary
511
+ // Tables with store_id column get scoped; tables without rely on customer ownership verification above
512
+ const storeScoped = ["orders"];
513
+ const customerOnly = ["customer_activity", "customer_notes", "customer_loyalty", "customer_sessions", "customer_segment_memberships"];
514
+ const reassignResults = {};
515
+ for (const table of storeScoped) {
516
+ const {
517
+ error,
518
+ count
519
+ } = await sb.from(table).update({
520
+ customer_id: primaryId
521
+ }).eq("customer_id", secondaryId).eq("store_id", sid);
522
+ reassignResults[table] = error ? `error: ${error.message}` : `moved ${count ?? "?"} rows`;
731
523
  }
732
- // ---- SEGMENT_MEMBERS: list customers in a specific segment ----
733
- case "segment_members": {
734
- const segId = args.segment_id;
735
- if (!segId)
736
- return { success: false, error: "segment_id is required" };
737
- const { data, error } = await sb.from("customer_segment_memberships")
738
- .select("added_at, customer:v_segment_customers!customer_id(*)")
739
- .eq("segment_id", segId)
740
- .order("added_at", { ascending: false })
741
- .limit(args.limit || 50);
742
- if (error)
743
- return { success: false, error: error.message };
744
- const members = (data || []).map((r) => ({ added_at: r.added_at, ...r.customer }))
745
- .filter((m) => m.store_id === sid);
746
- return { success: true, count: members.length, data: stripInternalArray(members) };
524
+ for (const table of customerOnly) {
525
+ const {
526
+ error,
527
+ count
528
+ } = await sb.from(table).update({
529
+ customer_id: primaryId
530
+ }).eq("customer_id", secondaryId);
531
+ reassignResults[table] = error ? `error: ${error.message}` : `moved ${count ?? "?"} rows`;
747
532
  }
748
- // ---- REFRESH_METRICS: recalculate all customer metrics for the store ----
749
- case "refresh_metrics": {
750
- const start = Date.now();
751
- const { data, error } = await sb.rpc("refresh_customer_metrics", { p_store_id: sid });
752
- const elapsed = Date.now() - start;
753
- if (error)
754
- return { success: false, error: error.message };
755
- return { success: true, data: { result: data, elapsed_ms: elapsed, message: `Customer metrics refreshed in ${elapsed}ms` } };
533
+
534
+ // Merge profile stats
535
+ const {
536
+ data: primaryProf
537
+ } = await sb.from("store_customer_profiles").select("*").eq("relationship_id", primaryId).maybeSingle();
538
+ const {
539
+ data: secondaryProf
540
+ } = await sb.from("store_customer_profiles").select("*").eq("relationship_id", secondaryId).maybeSingle();
541
+ if (primaryProf && secondaryProf) {
542
+ await sb.from("store_customer_profiles").update({
543
+ total_spent: parseFloat(primaryProf.total_spent || 0) + parseFloat(secondaryProf.total_spent || 0),
544
+ total_orders: (primaryProf.total_orders || 0) + (secondaryProf.total_orders || 0),
545
+ loyalty_points: (primaryProf.loyalty_points || 0) + (secondaryProf.loyalty_points || 0),
546
+ lifetime_points_earned: (primaryProf.lifetime_points_earned || 0) + (secondaryProf.lifetime_points_earned || 0),
547
+ first_order_at: primaryProf.first_order_at && secondaryProf.first_order_at ? primaryProf.first_order_at < secondaryProf.first_order_at ? primaryProf.first_order_at : secondaryProf.first_order_at : primaryProf.first_order_at || secondaryProf.first_order_at,
548
+ last_order_at: primaryProf.last_order_at && secondaryProf.last_order_at ? primaryProf.last_order_at > secondaryProf.last_order_at ? primaryProf.last_order_at : secondaryProf.last_order_at : primaryProf.last_order_at || secondaryProf.last_order_at,
549
+ updated_at: new Date().toISOString()
550
+ }).eq("relationship_id", primaryId);
551
+ await sb.from("store_customer_profiles").delete().eq("relationship_id", secondaryId);
756
552
  }
757
- default:
758
- return { success: false, error: `Unknown customers action: ${args.action}. Valid: find, get, create, update, invite, find_duplicates, merge, add_note, notes, activity, orders, loyalty_history, adjust_loyalty, segments, segment_members, refresh_metrics, link_channel_identity, get_channel_identities` };
759
- }
760
- }
761
- export async function handleOrders(sb, args, storeId) {
762
- const sid = storeId;
763
- switch (args.action) {
764
- case "find": {
765
- let q = sb.from("orders")
766
- .select("id, order_number, status, total_amount, subtotal, tax_amount, created_at, customer_id, customer:v_store_customers!customer_id(id, first_name, last_name, email, phone)")
767
- .eq("store_id", sid).order("created_at", { ascending: false });
768
- if (args.limit)
769
- q = q.limit(args.limit);
770
- if (args.status)
771
- q = q.eq("status", args.status);
772
- if (args.customer_id)
773
- q = q.eq("customer_id", args.customer_id);
774
- if (args.order_number)
775
- q = q.eq("order_number", args.order_number);
776
- if (args.query) {
777
- const sq = sanitizeFilterValue(String(args.query));
778
- q = q.or(`order_number.ilike.%${sq}%`);
553
+
554
+ // Mark secondary as merged
555
+ await sb.from("user_creation_relationships").update({
556
+ status: "merged",
557
+ relationship_data: {
558
+ merged_into: primaryId,
559
+ merged_at: new Date().toISOString()
560
+ },
561
+ updated_at: new Date().toISOString()
562
+ }).eq("id", secondaryId).eq("store_id", sid);
563
+
564
+ // Fill in missing identity fields from secondary
565
+ const {
566
+ data: primaryRel
567
+ } = await sb.from("user_creation_relationships").select("user_id").eq("id", primaryId).eq("store_id", sid).single();
568
+ const {
569
+ data: secondaryRel
570
+ } = await sb.from("user_creation_relationships").select("user_id").eq("id", secondaryId).eq("store_id", sid).single();
571
+ if (primaryRel && secondaryRel) {
572
+ const {
573
+ data: pPu
574
+ } = await sb.from("platform_users").select("*").eq("id", primaryRel.user_id).single();
575
+ const {
576
+ data: sPu
577
+ } = await sb.from("platform_users").select("*").eq("id", secondaryRel.user_id).single();
578
+ if (pPu && sPu) {
579
+ const fills = {};
580
+ if (!pPu.email && sPu.email && !sPu.email.startsWith("merged.")) fills.email = sPu.email;
581
+ if (!pPu.phone && sPu.phone && !sPu.phone.startsWith("merged_")) fills.phone = sPu.phone;
582
+ if (!pPu.date_of_birth && sPu.date_of_birth) fills.date_of_birth = sPu.date_of_birth;
583
+ if (Object.keys(fills).length > 0) await sb.from("platform_users").update(fills).eq("id", primaryRel.user_id);
584
+ // Mark secondary identity as merged
585
+ const marks = {};
586
+ if (sPu.email && !sPu.email.startsWith("merged.")) marks.email = `merged.${sPu.email}`;
587
+ if (sPu.phone && !sPu.phone.startsWith("merged_")) marks.phone = `merged_${sPu.phone}`;
588
+ if (Object.keys(marks).length > 0) await sb.from("platform_users").update(marks).eq("id", secondaryRel.user_id);
589
+ }
590
+ }
591
+ const {
592
+ data: merged
593
+ } = await sb.from("v_store_customers").select("*").eq("id", primaryId).single();
594
+ return {
595
+ success: true,
596
+ data: {
597
+ merged_customer: stripInternal(merged),
598
+ reassign_results: reassignResults
599
+ }
600
+ };
601
+ }
602
+
603
+ // ---- ADD_NOTE ----
604
+ case "add_note":
605
+ {
606
+ // Verify customer belongs to this store before adding note
607
+ const {
608
+ data: custCheck
609
+ } = await sb.from("v_store_customers").select("id").eq("id", args.customer_id).eq("store_id", sid).single();
610
+ if (!custCheck) return {
611
+ success: false,
612
+ error: "Customer not found in this store"
613
+ };
614
+ const {
615
+ data,
616
+ error
617
+ } = await sb.from("customer_notes").insert({
618
+ customer_id: args.customer_id,
619
+ note: args.note,
620
+ created_by: args.created_by || "agent"
621
+ }).select().single();
622
+ return error ? {
623
+ success: false,
624
+ error: error.message
625
+ } : {
626
+ success: true,
627
+ data
628
+ };
629
+ }
630
+
631
+ // ---- NOTES ----
632
+ case "notes":
633
+ {
634
+ // Verify customer belongs to this store
635
+ const {
636
+ data: custCheck
637
+ } = await sb.from("v_store_customers").select("id").eq("id", args.customer_id).eq("store_id", sid).single();
638
+ if (!custCheck) return {
639
+ success: false,
640
+ error: "Customer not found in this store"
641
+ };
642
+ const {
643
+ data,
644
+ error
645
+ } = await sb.from("customer_notes").select("id, note, created_by, created_at").eq("customer_id", args.customer_id).order("created_at", {
646
+ ascending: false
647
+ }).limit(args.limit || 25);
648
+ return error ? {
649
+ success: false,
650
+ error: error.message
651
+ } : {
652
+ success: true,
653
+ data
654
+ };
655
+ }
656
+
657
+ // ---- ACTIVITY ----
658
+ case "activity":
659
+ {
660
+ // Verify customer belongs to this store
661
+ const {
662
+ data: custCheck
663
+ } = await sb.from("v_store_customers").select("id").eq("id", args.customer_id).eq("store_id", sid).single();
664
+ if (!custCheck) return {
665
+ success: false,
666
+ error: "Customer not found in this store"
667
+ };
668
+ const {
669
+ data,
670
+ error
671
+ } = await sb.from("customer_activity").select("id, activity_type, description, metadata, created_at").eq("customer_id", args.customer_id).order("created_at", {
672
+ ascending: false
673
+ }).limit(args.limit || 25);
674
+ return error ? {
675
+ success: false,
676
+ error: error.message
677
+ } : {
678
+ success: true,
679
+ data
680
+ };
681
+ }
682
+
683
+ // ---- ORDERS: customer order history ----
684
+ case "orders":
685
+ {
686
+ const {
687
+ data,
688
+ error
689
+ } = await sb.from("orders").select("id, order_number, status, total_amount, subtotal, tax_amount, payment_status, fulfillment_status, payment_method, created_at").eq("customer_id", args.customer_id).eq("store_id", sid).order("created_at", {
690
+ ascending: false
691
+ }).limit(args.limit || 25);
692
+ return error ? {
693
+ success: false,
694
+ error: error.message
695
+ } : {
696
+ success: true,
697
+ count: data?.length,
698
+ data
699
+ };
700
+ }
701
+
702
+ // ---- LINK_CHANNEL_IDENTITY: connect a sender_id on any channel to a customer ----
703
+ case "link_channel_identity":
704
+ {
705
+ const {
706
+ customer_id,
707
+ channel_type,
708
+ sender_id,
709
+ sender_name
710
+ } = args;
711
+ if (!customer_id || !channel_type || !sender_id) {
712
+ return {
713
+ success: false,
714
+ error: "customer_id, channel_type, and sender_id are required"
715
+ };
716
+ }
717
+ // Verify customer belongs to this store
718
+ const {
719
+ data: custCheck
720
+ } = await sb.from("v_store_customers").select("id").eq("id", customer_id).eq("store_id", sid).single();
721
+ if (!custCheck) return {
722
+ success: false,
723
+ error: "Customer not found in this store"
724
+ };
725
+ const {
726
+ error
727
+ } = await sb.from("customer_channel_identities").upsert({
728
+ store_id: sid,
729
+ customer_id,
730
+ channel_type,
731
+ sender_id,
732
+ sender_name: sender_name || null,
733
+ verified: true,
734
+ last_seen_at: new Date().toISOString()
735
+ }, {
736
+ onConflict: "store_id,channel_type,sender_id"
737
+ });
738
+ return error ? {
739
+ success: false,
740
+ error: error.message
741
+ } : {
742
+ success: true,
743
+ linked: {
744
+ customer_id,
745
+ channel_type,
746
+ sender_id
747
+ }
748
+ };
749
+ }
750
+
751
+ // ---- GET_CHANNEL_IDENTITIES: list all channel identities for a customer ----
752
+ case "get_channel_identities":
753
+ {
754
+ const custId = args.customer_id;
755
+ if (!custId) return {
756
+ success: false,
757
+ error: "customer_id is required"
758
+ };
759
+ const {
760
+ data,
761
+ error
762
+ } = await sb.from("customer_channel_identities").select("id, channel_type, sender_id, sender_name, verified, last_seen_at, created_at").eq("store_id", sid).eq("customer_id", custId).order("last_seen_at", {
763
+ ascending: false
764
+ });
765
+ return error ? {
766
+ success: false,
767
+ error: error.message
768
+ } : {
769
+ success: true,
770
+ data
771
+ };
772
+ }
773
+
774
+ // ---- INVITE: create auth account for customer and send branded invite email ----
775
+ case "invite":
776
+ {
777
+ const custId = args.customer_id;
778
+ if (!custId) return {
779
+ success: false,
780
+ error: "customer_id is required"
781
+ };
782
+
783
+ // Verify customer belongs to this store
784
+ const {
785
+ data: rel,
786
+ error: relErr
787
+ } = await sb.from("user_creation_relationships").select("id, user_id, store_id").eq("id", custId).eq("store_id", sid).single();
788
+ if (relErr || !rel) return {
789
+ success: false,
790
+ error: "Customer not found for this store"
791
+ };
792
+
793
+ // Get platform user + store name in parallel
794
+ const [{
795
+ data: pu
796
+ }, {
797
+ data: store
798
+ }] = await Promise.all([sb.from("platform_users").select("id, auth_id, email, first_name, last_name").eq("id", rel.user_id).single(), sb.from("stores").select("store_name").eq("id", sid).single()]);
799
+ if (!pu) return {
800
+ success: false,
801
+ error: "Platform user not found"
802
+ };
803
+ if (!pu.email) return {
804
+ success: false,
805
+ error: "Customer has no email address — add one first"
806
+ };
807
+ const storeName = store?.store_name || "our platform";
808
+ const customerName = [pu.first_name, pu.last_name].filter(Boolean).join(" ") || "there";
809
+ const portalUrl = args.portal_url || args.redirect_to || null;
810
+
811
+ // If already has an auth account, just resend the email with a new magic link
812
+ let authId = pu.auth_id;
813
+ if (!authId) {
814
+ // Create auth account silently (no Supabase built-in email)
815
+ const {
816
+ data: newUser,
817
+ error: createErr
818
+ } = await sb.auth.admin.createUser({
819
+ email: pu.email,
820
+ email_confirm: true,
821
+ // skip verification — store is vouching for this email
822
+ user_metadata: {
823
+ first_name: pu.first_name,
824
+ last_name: pu.last_name,
825
+ invited_by_store: sid
779
826
  }
780
- const { data, error } = await q;
781
- if (error)
782
- return { success: false, error: error.message };
783
- // Flatten customer join so name/email appear as table columns
784
- const flattened = (data || []).map((row) => {
785
- const { customer, ...rest } = row;
786
- return {
787
- ...rest,
788
- customer_name: customer ? `${customer.first_name || ""} ${customer.last_name || ""}`.trim() : "—",
789
- customer_email: customer?.email || null,
790
- };
791
- });
792
- return { success: true, count: flattened.length, data: flattened };
827
+ });
828
+ if (createErr) return {
829
+ success: false,
830
+ error: `Account creation failed: ${createErr.message}`
831
+ };
832
+ authId = newUser.user.id;
833
+
834
+ // Link auth account to platform user
835
+ await sb.from("platform_users").update({
836
+ auth_id: authId
837
+ }).eq("id", pu.id);
793
838
  }
794
- case "get": {
795
- const { data, error } = await sb.from("orders")
796
- .select("*, customer:v_store_customers!customer_id(id, first_name, last_name, email, phone, loyalty_points, total_spent, total_orders), items:order_items(id, product_id, quantity, line_total, cost_per_unit, product_sku, product:products(id, name, sku))")
797
- .eq("id", args.order_id).eq("store_id", sid).single();
798
- if (error)
799
- return { success: false, error: error.message };
800
- // Flatten customer and item product joins for readable display
801
- const order = data;
802
- const { customer, items: rawItems, ...orderFields } = order;
803
- const flatItems = (rawItems || []).map((item) => ({
804
- id: item.id, product_id: item.product_id,
805
- product_name: item.product?.name || "—", product_sku: item.product_sku || item.product?.sku || "—",
806
- quantity: item.quantity, line_total: item.line_total, cost_per_unit: item.cost_per_unit,
807
- }));
808
- return {
809
- success: true,
810
- data: {
811
- ...orderFields,
812
- customer_name: customer ? `${customer.first_name || ""} ${customer.last_name || ""}`.trim() : "—",
813
- customer_email: customer?.email || null,
814
- customer_phone: customer?.phone || null,
815
- customer_id: customer?.id || orderFields.customer_id,
816
- items: flatItems,
817
- }
818
- };
839
+
840
+ // Generate a magic link for the customer to set up / log in
841
+ const {
842
+ data: linkData,
843
+ error: linkErr
844
+ } = await sb.auth.admin.generateLink({
845
+ type: "magiclink",
846
+ email: pu.email,
847
+ options: {
848
+ ...(portalUrl ? {
849
+ redirectTo: portalUrl
850
+ } : {})
851
+ }
852
+ });
853
+ if (linkErr) return {
854
+ success: false,
855
+ error: `Link generation failed: ${linkErr.message}`
856
+ };
857
+ const magicLink = linkData?.properties?.action_link || "";
858
+
859
+ // Send branded invite email through our email system
860
+ const emailResult = await handleEmail(sb, {
861
+ action: "send_template",
862
+ to: pu.email,
863
+ template: "customer-portal-invite",
864
+ template_data: {
865
+ customer_name: customerName,
866
+ store_name: storeName,
867
+ magic_link: magicLink,
868
+ portal_url: portalUrl || magicLink
869
+ }
870
+ }, sid);
871
+ return {
872
+ success: true,
873
+ data: {
874
+ email: pu.email,
875
+ name: customerName,
876
+ invited: true,
877
+ email_sent: emailResult.success,
878
+ ...(emailResult.success ? {} : {
879
+ email_error: emailResult.error
880
+ })
881
+ }
882
+ };
883
+ }
884
+
885
+ // ---- LOYALTY_HISTORY: transaction history for a customer ----
886
+ case "loyalty_history":
887
+ {
888
+ const custId = args.customer_id;
889
+ if (!custId) return {
890
+ success: false,
891
+ error: "customer_id is required"
892
+ };
893
+ let q = sb.from("loyalty_transactions").select("id, points, transaction_type, reference_type, reference_id, description, balance_before, balance_after, expires_at, created_at").eq("customer_id", custId).order("created_at", {
894
+ ascending: false
895
+ });
896
+ if (args.transaction_type) q = q.eq("transaction_type", args.transaction_type);
897
+ q = q.limit(args.limit || 50);
898
+ const {
899
+ data,
900
+ error
901
+ } = await q;
902
+ return error ? {
903
+ success: false,
904
+ error: error.message
905
+ } : {
906
+ success: true,
907
+ count: data?.length,
908
+ data
909
+ };
910
+ }
911
+
912
+ // ---- ADJUST_LOYALTY: award or deduct loyalty points via RPC ----
913
+ case "adjust_loyalty":
914
+ {
915
+ const custId = args.customer_id;
916
+ const points = args.points;
917
+ const reason = args.reason;
918
+ if (!custId || points === undefined || !reason) return {
919
+ success: false,
920
+ error: "customer_id, points, and reason are required"
921
+ };
922
+ const {
923
+ data,
924
+ error
925
+ } = await sb.rpc("adjust_customer_loyalty_points", {
926
+ p_customer_id: custId,
927
+ p_points_change: points,
928
+ p_reason: reason
929
+ });
930
+ if (error) return {
931
+ success: false,
932
+ error: error.message
933
+ };
934
+ const {
935
+ data: updated
936
+ } = await sb.from("store_customer_profiles").select("loyalty_points, loyalty_tier, lifetime_points_earned").eq("relationship_id", custId).maybeSingle();
937
+ return {
938
+ success: true,
939
+ data: {
940
+ rpc_result: data,
941
+ ...updated
942
+ }
943
+ };
944
+ }
945
+
946
+ // ---- SEGMENTS: list all customer segments for the store ----
947
+ case "segments":
948
+ {
949
+ let q = sb.from("customer_segments").select("id, name, ai_description, type, segment_rules, filter_criteria, customer_count, is_active, color, icon, targeting_tips, created_at").eq("store_id", sid).order("customer_count", {
950
+ ascending: false
951
+ });
952
+ if (args.limit) q = q.limit(args.limit);
953
+ const {
954
+ data,
955
+ error
956
+ } = await q;
957
+ return error ? {
958
+ success: false,
959
+ error: error.message
960
+ } : {
961
+ success: true,
962
+ count: data?.length,
963
+ data
964
+ };
965
+ }
966
+
967
+ // ---- SEGMENT_MEMBERS: list customers in a specific segment ----
968
+ case "segment_members":
969
+ {
970
+ const segId = args.segment_id;
971
+ if (!segId) return {
972
+ success: false,
973
+ error: "segment_id is required"
974
+ };
975
+ const {
976
+ data,
977
+ error
978
+ } = await sb.from("customer_segment_memberships").select("added_at, customer:v_segment_customers!customer_id(*)").eq("segment_id", segId).order("added_at", {
979
+ ascending: false
980
+ }).limit(args.limit || 50);
981
+ if (error) return {
982
+ success: false,
983
+ error: error.message
984
+ };
985
+ const members = (data || []).map(r => ({
986
+ added_at: r.added_at,
987
+ ...r.customer
988
+ })).filter(m => m.store_id === sid);
989
+ return {
990
+ success: true,
991
+ count: members.length,
992
+ data: stripInternalArray(members)
993
+ };
994
+ }
995
+
996
+ // ---- REFRESH_METRICS: recalculate all customer metrics for the store ----
997
+ case "refresh_metrics":
998
+ {
999
+ const start = Date.now();
1000
+ const {
1001
+ data,
1002
+ error
1003
+ } = await sb.rpc("refresh_customer_metrics", {
1004
+ p_store_id: sid
1005
+ });
1006
+ const elapsed = Date.now() - start;
1007
+ if (error) return {
1008
+ success: false,
1009
+ error: error.message
1010
+ };
1011
+ return {
1012
+ success: true,
1013
+ data: {
1014
+ result: data,
1015
+ elapsed_ms: elapsed,
1016
+ message: `Customer metrics refreshed in ${elapsed}ms`
1017
+ }
1018
+ };
1019
+ }
1020
+ default:
1021
+ return {
1022
+ success: false,
1023
+ error: `Unknown customers action: ${args.action}. Valid: find, get, create, update, invite, find_duplicates, merge, add_note, notes, activity, orders, loyalty_history, adjust_loyalty, segments, segment_members, refresh_metrics, link_channel_identity, get_channel_identities`
1024
+ };
1025
+ }
1026
+ }
1027
+ export async function handleOrders(sb, args, storeId) {
1028
+ const sid = storeId;
1029
+ switch (args.action) {
1030
+ case "find":
1031
+ {
1032
+ let q = sb.from("orders").select("id, order_number, status, total_amount, subtotal, tax_amount, created_at, customer_id, customer:v_store_customers!customer_id(id, first_name, last_name, email, phone)").eq("store_id", sid).order("created_at", {
1033
+ ascending: false
1034
+ });
1035
+ if (args.limit) q = q.limit(args.limit);
1036
+ if (args.status) q = q.eq("status", args.status);
1037
+ if (args.customer_id) q = q.eq("customer_id", args.customer_id);
1038
+ if (args.order_number) q = q.eq("order_number", args.order_number);
1039
+ if (args.query) {
1040
+ const sq = sanitizeFilterValue(String(args.query));
1041
+ q = q.or(`order_number.ilike.%${sq}%`);
819
1042
  }
820
- default:
821
- return { success: false, error: `Unknown orders action: ${args.action}` };
822
- }
1043
+ const {
1044
+ data,
1045
+ error
1046
+ } = await q;
1047
+ if (error) return {
1048
+ success: false,
1049
+ error: error.message
1050
+ };
1051
+ // Flatten customer join so name/email appear as table columns
1052
+ const flattened = (data || []).map(row => {
1053
+ const {
1054
+ customer,
1055
+ ...rest
1056
+ } = row;
1057
+ return {
1058
+ ...rest,
1059
+ customer_name: customer ? `${customer.first_name || ""} ${customer.last_name || ""}`.trim() : "—",
1060
+ customer_email: customer?.email || null
1061
+ };
1062
+ });
1063
+ return {
1064
+ success: true,
1065
+ count: flattened.length,
1066
+ data: flattened
1067
+ };
1068
+ }
1069
+ case "get":
1070
+ {
1071
+ const {
1072
+ data,
1073
+ error
1074
+ } = await sb.from("orders").select("*, customer:v_store_customers!customer_id(id, first_name, last_name, email, phone, loyalty_points, total_spent, total_orders), items:order_items(id, product_id, quantity, line_total, cost_per_unit, product_sku, product:products(id, name, sku))").eq("id", args.order_id).eq("store_id", sid).single();
1075
+ if (error) return {
1076
+ success: false,
1077
+ error: error.message
1078
+ };
1079
+ // Flatten customer and item product joins for readable display
1080
+ const order = data;
1081
+ const {
1082
+ customer,
1083
+ items: rawItems,
1084
+ ...orderFields
1085
+ } = order;
1086
+ const flatItems = (rawItems || []).map(item => ({
1087
+ id: item.id,
1088
+ product_id: item.product_id,
1089
+ product_name: item.product?.name || "—",
1090
+ product_sku: item.product_sku || item.product?.sku || "—",
1091
+ quantity: item.quantity,
1092
+ line_total: item.line_total,
1093
+ cost_per_unit: item.cost_per_unit
1094
+ }));
1095
+ return {
1096
+ success: true,
1097
+ data: {
1098
+ ...orderFields,
1099
+ customer_name: customer ? `${customer.first_name || ""} ${customer.last_name || ""}`.trim() : "—",
1100
+ customer_email: customer?.email || null,
1101
+ customer_phone: customer?.phone || null,
1102
+ customer_id: customer?.id || orderFields.customer_id,
1103
+ items: flatItems
1104
+ }
1105
+ };
1106
+ }
1107
+ default:
1108
+ return {
1109
+ success: false,
1110
+ error: `Unknown orders action: ${args.action}`
1111
+ };
1112
+ }
823
1113
  }
1114
+ //# sourceMappingURL=crm.js.map