whale-code 6.5.4 → 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 (853) 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 +29 -0
  73. package/dist/cli/services/agent-config.js +91 -0
  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 +978 -669
  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.d.ts +2 -3
  107. package/dist/cli/services/error-logger.js +189 -180
  108. package/dist/cli/services/error-logger.js.map +1 -0
  109. package/dist/cli/services/file-history.d.ts +1 -1
  110. package/dist/cli/services/file-history.js +50 -54
  111. package/dist/cli/services/file-history.js.map +1 -0
  112. package/dist/cli/services/format-server-response.js +332 -372
  113. package/dist/cli/services/format-server-response.js.map +1 -0
  114. package/dist/cli/services/git-context.js +61 -45
  115. package/dist/cli/services/git-context.js.map +1 -0
  116. package/dist/cli/services/hooks.d.ts +2 -2
  117. package/dist/cli/services/hooks.js +195 -180
  118. package/dist/cli/services/hooks.js.map +1 -0
  119. package/dist/cli/services/ink-incremental.d.ts +19 -0
  120. package/dist/cli/services/ink-incremental.js +59 -0
  121. package/dist/cli/services/ink-incremental.js.map +1 -0
  122. package/dist/cli/services/ink-resize-fix.js +54 -44
  123. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  124. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  125. package/dist/cli/services/ink-sync-output.js +16 -0
  126. package/dist/cli/services/ink-sync-output.js.map +1 -0
  127. package/dist/cli/services/interactive-tools.js +268 -212
  128. package/dist/cli/services/interactive-tools.js.map +1 -0
  129. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  130. package/dist/cli/services/keybinding-manager.js +126 -63
  131. package/dist/cli/services/keybinding-manager.js.map +1 -0
  132. package/dist/cli/services/local-tools.d.ts +1 -1
  133. package/dist/cli/services/local-tools.js +939 -656
  134. package/dist/cli/services/local-tools.js.map +1 -0
  135. package/dist/cli/services/lsp-manager.js +757 -594
  136. package/dist/cli/services/lsp-manager.js.map +1 -0
  137. package/dist/cli/services/mcp-client.d.ts +1 -1
  138. package/dist/cli/services/mcp-client.js +173 -134
  139. package/dist/cli/services/mcp-client.js.map +1 -0
  140. package/dist/cli/services/memory-manager.js +53 -40
  141. package/dist/cli/services/memory-manager.js.map +1 -0
  142. package/dist/cli/services/model-manager.js +55 -40
  143. package/dist/cli/services/model-manager.js.map +1 -0
  144. package/dist/cli/services/model-router.js +115 -85
  145. package/dist/cli/services/model-router.js.map +1 -0
  146. package/dist/cli/services/paths.d.ts +30 -0
  147. package/dist/cli/services/paths.js +81 -0
  148. package/dist/cli/services/paths.js.map +1 -0
  149. package/dist/cli/services/permission-modes.js +32 -25
  150. package/dist/cli/services/permission-modes.js.map +1 -0
  151. package/dist/cli/services/rewind.js +182 -168
  152. package/dist/cli/services/rewind.js.map +1 -0
  153. package/dist/cli/services/ripgrep.js +115 -115
  154. package/dist/cli/services/ripgrep.js.map +1 -0
  155. package/dist/cli/services/sandbox.d.ts +1 -1
  156. package/dist/cli/services/sandbox.js +58 -37
  157. package/dist/cli/services/sandbox.js.map +1 -0
  158. package/dist/cli/services/server-tools.js +738 -565
  159. package/dist/cli/services/server-tools.js.map +1 -0
  160. package/dist/cli/services/session-persistence.js +69 -74
  161. package/dist/cli/services/session-persistence.js.map +1 -0
  162. package/dist/cli/services/subagent-worker.js +42 -27
  163. package/dist/cli/services/subagent-worker.js.map +1 -0
  164. package/dist/cli/services/subagent.d.ts +2 -0
  165. package/dist/cli/services/subagent.js +606 -430
  166. package/dist/cli/services/subagent.js.map +1 -0
  167. package/dist/cli/services/system-prompt.js +86 -78
  168. package/dist/cli/services/system-prompt.js.map +1 -0
  169. package/dist/cli/services/task-decomposer.d.ts +1 -1
  170. package/dist/cli/services/task-decomposer.js +172 -139
  171. package/dist/cli/services/task-decomposer.js.map +1 -0
  172. package/dist/cli/services/team-lead.d.ts +2 -2
  173. package/dist/cli/services/team-lead.js +727 -529
  174. package/dist/cli/services/team-lead.js.map +1 -0
  175. package/dist/cli/services/team-state.js +319 -319
  176. package/dist/cli/services/team-state.js.map +1 -0
  177. package/dist/cli/services/teammate.d.ts +8 -2
  178. package/dist/cli/services/teammate.js +862 -560
  179. package/dist/cli/services/teammate.js.map +1 -0
  180. package/dist/cli/services/telemetry.d.ts +6 -1
  181. package/dist/cli/services/telemetry.js +180 -157
  182. package/dist/cli/services/telemetry.js.map +1 -0
  183. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  184. package/dist/cli/services/tools/agent-tools.js +480 -322
  185. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  186. package/dist/cli/services/tools/file-ops.js +563 -450
  187. package/dist/cli/services/tools/file-ops.js.map +1 -0
  188. package/dist/cli/services/tools/search-tools.js +231 -162
  189. package/dist/cli/services/tools/search-tools.js.map +1 -0
  190. package/dist/cli/services/tools/shell-exec.js +197 -151
  191. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  192. package/dist/cli/services/tools/task-manager.js +206 -173
  193. package/dist/cli/services/tools/task-manager.js.map +1 -0
  194. package/dist/cli/services/tools/web-tools.js +388 -341
  195. package/dist/cli/services/tools/web-tools.js.map +1 -0
  196. package/dist/cli/setup/SetupApp.d.ts +2 -2
  197. package/dist/cli/setup/SetupApp.js +608 -160
  198. package/dist/cli/setup/SetupApp.js.map +1 -0
  199. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  200. package/dist/cli/shared/ErrorBoundary.js +73 -0
  201. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  202. package/dist/cli/shared/MatrixIntro.js +66 -69
  203. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  204. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  205. package/dist/cli/shared/SpinnerSlot.js +63 -0
  206. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  207. package/dist/cli/shared/Theme.d.ts +1 -1
  208. package/dist/cli/shared/Theme.js +136 -92
  209. package/dist/cli/shared/Theme.js.map +1 -0
  210. package/dist/cli/shared/WhaleBanner.js +99 -11
  211. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  212. package/dist/cli/shared/markdown.d.ts +3 -1
  213. package/dist/cli/shared/markdown.js +736 -674
  214. package/dist/cli/shared/markdown.js.map +1 -0
  215. package/dist/cli/shared/marked-terminal.d.js +2 -0
  216. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  217. package/dist/cli/shared/theme-manager.js +99 -90
  218. package/dist/cli/shared/theme-manager.js.map +1 -0
  219. package/dist/cli/shared/theme-presets.js +256 -254
  220. package/dist/cli/shared/theme-presets.js.map +1 -0
  221. package/dist/cli/status/StatusApp.js +235 -86
  222. package/dist/cli/status/StatusApp.js.map +1 -0
  223. package/dist/cli/stores/StoreApp.js +275 -65
  224. package/dist/cli/stores/StoreApp.js.map +1 -0
  225. package/dist/index.d.ts +2 -2
  226. package/dist/index.js +509 -396
  227. package/dist/index.js.map +1 -0
  228. package/dist/local-agent/connection.d.ts +2 -2
  229. package/dist/local-agent/connection.js +352 -293
  230. package/dist/local-agent/connection.js.map +1 -0
  231. package/dist/local-agent/discovery.js +259 -122
  232. package/dist/local-agent/discovery.js.map +1 -0
  233. package/dist/local-agent/executor.js +216 -193
  234. package/dist/local-agent/executor.js.map +1 -0
  235. package/dist/local-agent/index.d.ts +2 -2
  236. package/dist/local-agent/index.js +156 -156
  237. package/dist/local-agent/index.js.map +1 -0
  238. package/dist/node/adapters/base.js +18 -8
  239. package/dist/node/adapters/base.js.map +1 -0
  240. package/dist/node/adapters/discord.js +286 -275
  241. package/dist/node/adapters/discord.js.map +1 -0
  242. package/dist/node/adapters/email.js +189 -202
  243. package/dist/node/adapters/email.js.map +1 -0
  244. package/dist/node/adapters/imessage.js +145 -142
  245. package/dist/node/adapters/imessage.js.map +1 -0
  246. package/dist/node/adapters/slack.js +237 -236
  247. package/dist/node/adapters/slack.js.map +1 -0
  248. package/dist/node/adapters/sms.js +149 -151
  249. package/dist/node/adapters/sms.js.map +1 -0
  250. package/dist/node/adapters/telegram.js +88 -92
  251. package/dist/node/adapters/telegram.js.map +1 -0
  252. package/dist/node/adapters/webchat.js +160 -136
  253. package/dist/node/adapters/webchat.js.map +1 -0
  254. package/dist/node/adapters/whatsapp.js +212 -215
  255. package/dist/node/adapters/whatsapp.js.map +1 -0
  256. package/dist/node/cli.js +884 -653
  257. package/dist/node/cli.js.map +1 -0
  258. package/dist/node/config.js +20 -18
  259. package/dist/node/config.js.map +1 -0
  260. package/dist/node/gateway-client.js +191 -181
  261. package/dist/node/gateway-client.js.map +1 -0
  262. package/dist/node/portal/clipboard.js +161 -130
  263. package/dist/node/portal/clipboard.js.map +1 -0
  264. package/dist/node/portal/discovery.js +51 -45
  265. package/dist/node/portal/discovery.js.map +1 -0
  266. package/dist/node/portal/forward.js +64 -58
  267. package/dist/node/portal/forward.js.map +1 -0
  268. package/dist/node/portal/index.js +246 -221
  269. package/dist/node/portal/index.js.map +1 -0
  270. package/dist/node/portal/multiplexer.js +192 -182
  271. package/dist/node/portal/multiplexer.js.map +1 -0
  272. package/dist/node/portal/permissions.js +102 -70
  273. package/dist/node/portal/permissions.js.map +1 -0
  274. package/dist/node/portal/protocol.js +153 -116
  275. package/dist/node/portal/protocol.js.map +1 -0
  276. package/dist/node/portal/screen.js +80 -69
  277. package/dist/node/portal/screen.js.map +1 -0
  278. package/dist/node/portal/session.js +124 -117
  279. package/dist/node/portal/session.js.map +1 -0
  280. package/dist/node/portal/shell.js +140 -113
  281. package/dist/node/portal/shell.js.map +1 -0
  282. package/dist/node/portal/stream.js +77 -75
  283. package/dist/node/portal/stream.js.map +1 -0
  284. package/dist/node/portal/transfer.js +190 -167
  285. package/dist/node/portal/transfer.js.map +1 -0
  286. package/dist/node/portal/ui.js +124 -99
  287. package/dist/node/portal/ui.js.map +1 -0
  288. package/dist/node/remote-desktop/compile-helper.js +50 -45
  289. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  290. package/dist/node/remote-desktop/index.js +215 -187
  291. package/dist/node/remote-desktop/index.js.map +1 -0
  292. package/dist/node/remote-desktop/protocol.js +45 -29
  293. package/dist/node/remote-desktop/protocol.js.map +1 -0
  294. package/dist/node/runtime.js +493 -410
  295. package/dist/node/runtime.js.map +1 -0
  296. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  297. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  298. package/dist/server/handlers/analytics.js +467 -261
  299. package/dist/server/handlers/analytics.js.map +1 -0
  300. package/dist/server/handlers/api-docs.d.ts +6 -0
  301. package/dist/server/handlers/api-docs.js +1613 -0
  302. package/dist/server/handlers/api-docs.js.map +1 -0
  303. package/dist/server/handlers/api-keys.js +295 -232
  304. package/dist/server/handlers/api-keys.js.map +1 -0
  305. package/dist/server/handlers/billing.js +330 -239
  306. package/dist/server/handlers/billing.js.map +1 -0
  307. package/dist/server/handlers/browser.js +468 -395
  308. package/dist/server/handlers/browser.js.map +1 -0
  309. package/dist/server/handlers/catalog.js +1377 -978
  310. package/dist/server/handlers/catalog.js.map +1 -0
  311. package/dist/server/handlers/clickhouse.js +157 -109
  312. package/dist/server/handlers/clickhouse.js.map +1 -0
  313. package/dist/server/handlers/comms.d.ts +0 -53
  314. package/dist/server/handlers/comms.js +1443 -970
  315. package/dist/server/handlers/comms.js.map +1 -0
  316. package/dist/server/handlers/creations.js +461 -394
  317. package/dist/server/handlers/creations.js.map +1 -0
  318. package/dist/server/handlers/crm.js +1082 -791
  319. package/dist/server/handlers/crm.js.map +1 -0
  320. package/dist/server/handlers/discovery.js +251 -232
  321. package/dist/server/handlers/discovery.js.map +1 -0
  322. package/dist/server/handlers/embeddings.js +241 -164
  323. package/dist/server/handlers/embeddings.js.map +1 -0
  324. package/dist/server/handlers/enrichment.js +887 -718
  325. package/dist/server/handlers/enrichment.js.map +1 -0
  326. package/dist/server/handlers/image-gen.js +467 -376
  327. package/dist/server/handlers/image-gen.js.map +1 -0
  328. package/dist/server/handlers/inventory.js +797 -424
  329. package/dist/server/handlers/inventory.js.map +1 -0
  330. package/dist/server/handlers/kali.js +272 -230
  331. package/dist/server/handlers/kali.js.map +1 -0
  332. package/dist/server/handlers/llm-providers.js +803 -580
  333. package/dist/server/handlers/llm-providers.js.map +1 -0
  334. package/dist/server/handlers/local-agent.js +133 -105
  335. package/dist/server/handlers/local-agent.js.map +1 -0
  336. package/dist/server/handlers/media.js +1179 -857
  337. package/dist/server/handlers/media.js.map +1 -0
  338. package/dist/server/handlers/meta-ads.js +2669 -2093
  339. package/dist/server/handlers/meta-ads.js.map +1 -0
  340. package/dist/server/handlers/nodes.js +1321 -913
  341. package/dist/server/handlers/nodes.js.map +1 -0
  342. package/dist/server/handlers/operations.js +183 -157
  343. package/dist/server/handlers/operations.js.map +1 -0
  344. package/dist/server/handlers/platform.js +346 -210
  345. package/dist/server/handlers/platform.js.map +1 -0
  346. package/dist/server/handlers/remove-bg.js +118 -86
  347. package/dist/server/handlers/remove-bg.js.map +1 -0
  348. package/dist/server/handlers/storefront.js +586 -446
  349. package/dist/server/handlers/storefront.js.map +1 -0
  350. package/dist/server/handlers/supply-chain.js +546 -326
  351. package/dist/server/handlers/supply-chain.js.map +1 -0
  352. package/dist/server/handlers/transcription.js +106 -97
  353. package/dist/server/handlers/transcription.js.map +1 -0
  354. package/dist/server/handlers/video-gen.js +593 -424
  355. package/dist/server/handlers/video-gen.js.map +1 -0
  356. package/dist/server/handlers/voice.js +1458 -1017
  357. package/dist/server/handlers/voice.js.map +1 -0
  358. package/dist/server/handlers/workflow-steps.js +2837 -2116
  359. package/dist/server/handlers/workflow-steps.js.map +1 -0
  360. package/dist/server/handlers/workflows.js +1630 -933
  361. package/dist/server/handlers/workflows.js.map +1 -0
  362. package/dist/server/index.js +3166 -2390
  363. package/dist/server/index.js.map +1 -0
  364. package/dist/server/lib/batch-client.js +471 -409
  365. package/dist/server/lib/batch-client.js.map +1 -0
  366. package/dist/server/lib/clickhouse-buffer.js +118 -104
  367. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  368. package/dist/server/lib/clickhouse-client.js +107 -107
  369. package/dist/server/lib/clickhouse-client.js.map +1 -0
  370. package/dist/server/lib/coa-renderer.js +1786 -356
  371. package/dist/server/lib/coa-renderer.js.map +1 -0
  372. package/dist/server/lib/code-worker-pool.js +227 -177
  373. package/dist/server/lib/code-worker-pool.js.map +1 -0
  374. package/dist/server/lib/code-worker.js +174 -164
  375. package/dist/server/lib/code-worker.js.map +1 -0
  376. package/dist/server/lib/compaction-service.d.ts +2 -12
  377. package/dist/server/lib/compaction-service.js +74 -184
  378. package/dist/server/lib/compaction-service.js.map +1 -0
  379. package/dist/server/lib/logger.js +36 -24
  380. package/dist/server/lib/logger.js.map +1 -0
  381. package/dist/server/lib/otel.js +101 -80
  382. package/dist/server/lib/otel.js.map +1 -0
  383. package/dist/server/lib/pdf-renderer.d.ts +1 -1
  384. package/dist/server/lib/pdf-renderer.js +954 -776
  385. package/dist/server/lib/pdf-renderer.js.map +1 -0
  386. package/dist/server/lib/prompt-sanitizer.js +188 -108
  387. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  388. package/dist/server/lib/provider-capabilities.js +136 -138
  389. package/dist/server/lib/provider-capabilities.js.map +1 -0
  390. package/dist/server/lib/provider-failover.js +190 -168
  391. package/dist/server/lib/provider-failover.js.map +1 -0
  392. package/dist/server/lib/rate-limiter.js +186 -117
  393. package/dist/server/lib/rate-limiter.js.map +1 -0
  394. package/dist/server/lib/react-pdf-layout.js +551 -382
  395. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  396. package/dist/server/lib/server-agent-loop.d.ts +9 -0
  397. package/dist/server/lib/server-agent-loop.js +906 -624
  398. package/dist/server/lib/server-agent-loop.js.map +1 -0
  399. package/dist/server/lib/server-subagent.d.ts +2 -0
  400. package/dist/server/lib/server-subagent.js +260 -162
  401. package/dist/server/lib/server-subagent.js.map +1 -0
  402. package/dist/server/lib/session-checkpoint.js +105 -96
  403. package/dist/server/lib/session-checkpoint.js.map +1 -0
  404. package/dist/server/lib/ssrf-guard.js +193 -184
  405. package/dist/server/lib/ssrf-guard.js.map +1 -0
  406. package/dist/server/lib/supabase-client.js +94 -82
  407. package/dist/server/lib/supabase-client.js.map +1 -0
  408. package/dist/server/lib/template-resolver.js +154 -176
  409. package/dist/server/lib/template-resolver.js.map +1 -0
  410. package/dist/server/lib/utils.js +242 -133
  411. package/dist/server/lib/utils.js.map +1 -0
  412. package/dist/server/local-agent-gateway.d.ts +2 -2
  413. package/dist/server/local-agent-gateway.js +785 -627
  414. package/dist/server/local-agent-gateway.js.map +1 -0
  415. package/dist/server/providers/anthropic.js +254 -176
  416. package/dist/server/providers/anthropic.js.map +1 -0
  417. package/dist/server/providers/bedrock.js +221 -162
  418. package/dist/server/providers/bedrock.js.map +1 -0
  419. package/dist/server/providers/gemini.js +548 -418
  420. package/dist/server/providers/gemini.js.map +1 -0
  421. package/dist/server/providers/openai.js +571 -437
  422. package/dist/server/providers/openai.js.map +1 -0
  423. package/dist/server/providers/registry.js +23 -18
  424. package/dist/server/providers/registry.js.map +1 -0
  425. package/dist/server/providers/shared.js +123 -95
  426. package/dist/server/providers/shared.js.map +1 -0
  427. package/dist/server/providers/types.js +1 -11
  428. package/dist/server/providers/types.js.map +1 -0
  429. package/dist/server/proxy-handlers.js +209 -165
  430. package/dist/server/proxy-handlers.js.map +1 -0
  431. package/dist/server/tool-router.d.ts +13 -0
  432. package/dist/server/tool-router.js +960 -598
  433. package/dist/server/tool-router.js.map +1 -0
  434. package/dist/server/validation.js +248 -188
  435. package/dist/server/validation.js.map +1 -0
  436. package/dist/server/worker.js +202 -133
  437. package/dist/server/worker.js.map +1 -0
  438. package/dist/setup.d.ts +2 -2
  439. package/dist/setup.js +151 -147
  440. package/dist/setup.js.map +1 -0
  441. package/dist/shared/agent-core.d.ts +191 -24
  442. package/dist/shared/agent-core.js +971 -462
  443. package/dist/shared/agent-core.js.map +1 -0
  444. package/dist/shared/anthropic-types.js +1 -6
  445. package/dist/shared/anthropic-types.js.map +1 -0
  446. package/dist/shared/api-client.d.ts +17 -9
  447. package/dist/shared/api-client.js +419 -327
  448. package/dist/shared/api-client.js.map +1 -0
  449. package/dist/shared/compaction.d.ts +36 -0
  450. package/dist/shared/compaction.js +138 -0
  451. package/dist/shared/compaction.js.map +1 -0
  452. package/dist/shared/constants.js +67 -64
  453. package/dist/shared/constants.js.map +1 -0
  454. package/dist/shared/sse-parser.js +221 -219
  455. package/dist/shared/sse-parser.js.map +1 -0
  456. package/dist/shared/tool-dispatch.d.ts +4 -2
  457. package/dist/shared/tool-dispatch.js +226 -165
  458. package/dist/shared/tool-dispatch.js.map +1 -0
  459. package/dist/shared/types.js +1 -6
  460. package/dist/shared/types.js.map +1 -0
  461. package/dist/types/cli-highlight.d.js +2 -0
  462. package/dist/types/cli-highlight.d.js.map +1 -0
  463. package/dist/types/diff.d.js +2 -0
  464. package/dist/types/diff.d.js.map +1 -0
  465. package/dist/types/pdf-parse.d.js +2 -0
  466. package/dist/types/pdf-parse.d.js.map +1 -0
  467. package/dist/updater.d.ts +1 -1
  468. package/dist/updater.js +118 -92
  469. package/dist/updater.js.map +1 -0
  470. package/dist/webchat/widget.js +227 -380
  471. package/dist/webchat/widget.js.map +1 -0
  472. package/package.json +22 -10
  473. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  474. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  475. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  476. package/vendor/ink/build/apply-styles.js +175 -0
  477. package/vendor/ink/build/build-layout.js +77 -0
  478. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  479. package/vendor/ink/build/colorize.d.ts +3 -0
  480. package/vendor/ink/build/colorize.js +48 -0
  481. package/vendor/ink/build/colorize.js.map +1 -0
  482. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  483. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  484. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  485. package/vendor/ink/build/components/App.d.ts +18 -0
  486. package/vendor/ink/build/components/App.js +351 -0
  487. package/vendor/ink/build/components/App.js.map +1 -0
  488. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  489. package/vendor/ink/build/components/AppContext.js +11 -0
  490. package/vendor/ink/build/components/AppContext.js.map +1 -0
  491. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  492. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  493. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  494. package/vendor/ink/build/components/Box.d.ts +117 -0
  495. package/vendor/ink/build/components/Box.js +34 -0
  496. package/vendor/ink/build/components/Box.js.map +1 -0
  497. package/vendor/ink/build/components/Color.js +62 -0
  498. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  499. package/vendor/ink/build/components/Cursor.js +53 -0
  500. package/vendor/ink/build/components/Cursor.js.map +1 -0
  501. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  502. package/vendor/ink/build/components/CursorContext.js +8 -0
  503. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  504. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  505. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  506. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  507. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  508. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  509. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  510. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  511. package/vendor/ink/build/components/FocusContext.js +17 -0
  512. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  513. package/vendor/ink/build/components/Newline.d.ts +13 -0
  514. package/vendor/ink/build/components/Newline.js +8 -0
  515. package/vendor/ink/build/components/Newline.js.map +1 -0
  516. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  517. package/vendor/ink/build/components/Spacer.js +11 -0
  518. package/vendor/ink/build/components/Spacer.js.map +1 -0
  519. package/vendor/ink/build/components/Static.d.ts +24 -0
  520. package/vendor/ink/build/components/Static.js +28 -0
  521. package/vendor/ink/build/components/Static.js.map +1 -0
  522. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StderrContext.js +13 -0
  524. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  525. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  526. package/vendor/ink/build/components/StdinContext.js +19 -0
  527. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  528. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  529. package/vendor/ink/build/components/StdoutContext.js +13 -0
  530. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  531. package/vendor/ink/build/components/Text.d.ts +55 -0
  532. package/vendor/ink/build/components/Text.js +50 -0
  533. package/vendor/ink/build/components/Text.js.map +1 -0
  534. package/vendor/ink/build/components/Transform.d.ts +16 -0
  535. package/vendor/ink/build/components/Transform.js +15 -0
  536. package/vendor/ink/build/components/Transform.js.map +1 -0
  537. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  538. package/vendor/ink/build/cursor-helpers.js +56 -0
  539. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  540. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  541. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  542. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  543. package/vendor/ink/build/devtools.d.ts +1 -0
  544. package/vendor/ink/build/devtools.js +11 -0
  545. package/vendor/ink/build/devtools.js.map +1 -0
  546. package/vendor/ink/build/dom.d.ts +56 -0
  547. package/vendor/ink/build/dom.js +124 -0
  548. package/vendor/ink/build/dom.js.map +1 -0
  549. package/vendor/ink/build/experimental/apply-style.js +140 -0
  550. package/vendor/ink/build/experimental/dom.js +123 -0
  551. package/vendor/ink/build/experimental/output.js +91 -0
  552. package/vendor/ink/build/experimental/reconciler.js +141 -0
  553. package/vendor/ink/build/experimental/renderer.js +81 -0
  554. package/vendor/ink/build/get-max-width.d.ts +3 -0
  555. package/vendor/ink/build/get-max-width.js +10 -0
  556. package/vendor/ink/build/get-max-width.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  558. package/vendor/ink/build/hooks/use-app.js +8 -0
  559. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  561. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  562. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  564. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  565. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  567. package/vendor/ink/build/hooks/use-focus.js +42 -0
  568. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  570. package/vendor/ink/build/hooks/use-input.js +124 -0
  571. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  574. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  577. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  578. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  579. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  580. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  581. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  582. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  583. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  584. package/vendor/ink/build/hooks/useInput.js +38 -0
  585. package/vendor/ink/build/index.d.ts +34 -0
  586. package/vendor/ink/build/index.js +20 -0
  587. package/vendor/ink/build/index.js.map +1 -0
  588. package/vendor/ink/build/ink.d.ts +90 -0
  589. package/vendor/ink/build/ink.js +654 -0
  590. package/vendor/ink/build/ink.js.map +1 -0
  591. package/vendor/ink/build/input-parser.d.ts +7 -0
  592. package/vendor/ink/build/input-parser.js +154 -0
  593. package/vendor/ink/build/input-parser.js.map +1 -0
  594. package/vendor/ink/build/instance.js +205 -0
  595. package/vendor/ink/build/instances.d.ts +3 -0
  596. package/vendor/ink/build/instances.js +8 -0
  597. package/vendor/ink/build/instances.js.map +1 -0
  598. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  599. package/vendor/ink/build/kitty-keyboard.js +32 -0
  600. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  601. package/vendor/ink/build/layout.d.ts +7 -0
  602. package/vendor/ink/build/layout.js +33 -0
  603. package/vendor/ink/build/layout.js.map +1 -0
  604. package/vendor/ink/build/log-update.d.ts +19 -0
  605. package/vendor/ink/build/log-update.js +243 -0
  606. package/vendor/ink/build/log-update.js.map +1 -0
  607. package/vendor/ink/build/measure-element.d.ts +16 -0
  608. package/vendor/ink/build/measure-element.js +9 -0
  609. package/vendor/ink/build/measure-element.js.map +1 -0
  610. package/vendor/ink/build/measure-text.d.ts +6 -0
  611. package/vendor/ink/build/measure-text.js +21 -0
  612. package/vendor/ink/build/measure-text.js.map +1 -0
  613. package/vendor/ink/build/options.d.ts +52 -0
  614. package/vendor/ink/build/options.js +2 -0
  615. package/vendor/ink/build/options.js.map +1 -0
  616. package/vendor/ink/build/output.d.ts +35 -0
  617. package/vendor/ink/build/output.js +183 -0
  618. package/vendor/ink/build/output.js.map +1 -0
  619. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  620. package/vendor/ink/build/parse-keypress.js +493 -0
  621. package/vendor/ink/build/parse-keypress.js.map +1 -0
  622. package/vendor/ink/build/reconciler.d.ts +4 -0
  623. package/vendor/ink/build/reconciler.js +274 -0
  624. package/vendor/ink/build/reconciler.js.map +1 -0
  625. package/vendor/ink/build/render-background.d.ts +4 -0
  626. package/vendor/ink/build/render-background.js +25 -0
  627. package/vendor/ink/build/render-background.js.map +1 -0
  628. package/vendor/ink/build/render-border.d.ts +4 -0
  629. package/vendor/ink/build/render-border.js +73 -0
  630. package/vendor/ink/build/render-border.js.map +1 -0
  631. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  632. package/vendor/ink/build/render-node-to-output.js +147 -0
  633. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  634. package/vendor/ink/build/render-to-string.d.ts +38 -0
  635. package/vendor/ink/build/render-to-string.js +115 -0
  636. package/vendor/ink/build/render-to-string.js.map +1 -0
  637. package/vendor/ink/build/render.d.ts +121 -0
  638. package/vendor/ink/build/render.js +55 -0
  639. package/vendor/ink/build/render.js.map +1 -0
  640. package/vendor/ink/build/renderer.d.ts +8 -0
  641. package/vendor/ink/build/renderer.js +55 -0
  642. package/vendor/ink/build/renderer.js.map +1 -0
  643. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  644. package/vendor/ink/build/sanitize-ansi.js +27 -0
  645. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  646. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  647. package/vendor/ink/build/screen-reader-update.js +38 -0
  648. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  649. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  650. package/vendor/ink/build/squash-text-nodes.js +36 -0
  651. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  652. package/vendor/ink/build/styles.d.ts +240 -0
  653. package/vendor/ink/build/styles.js +232 -0
  654. package/vendor/ink/build/styles.js.map +1 -0
  655. package/vendor/ink/build/utils.d.ts +2 -0
  656. package/vendor/ink/build/utils.js +4 -0
  657. package/vendor/ink/build/utils.js.map +1 -0
  658. package/vendor/ink/build/wrap-text.d.ts +3 -0
  659. package/vendor/ink/build/wrap-text.js +31 -0
  660. package/vendor/ink/build/wrap-text.js.map +1 -0
  661. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  662. package/vendor/ink/build/write-synchronized.js +7 -0
  663. package/vendor/ink/build/write-synchronized.js.map +1 -0
  664. package/vendor/ink/license +10 -0
  665. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  666. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  667. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  668. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  669. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  670. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  671. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  672. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  673. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  674. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  675. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  676. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  677. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  678. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  679. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  680. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  681. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  682. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  683. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  684. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  685. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  686. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  687. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  688. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  689. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  690. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  691. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  692. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  693. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  694. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  695. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  696. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  697. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  698. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  699. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  700. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  701. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  702. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  703. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  704. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  705. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  706. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  707. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  708. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  709. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  710. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  711. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  712. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  713. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  714. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  715. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  716. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  717. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  718. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  719. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  720. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  721. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  722. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  723. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  724. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  725. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  726. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  727. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  728. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  729. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  730. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  731. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  732. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  733. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  734. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  735. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  746. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  747. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  748. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  749. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  750. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  751. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  752. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  753. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  754. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  755. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  756. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  757. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  758. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  759. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  760. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  761. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  762. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  763. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  764. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  765. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  766. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  767. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  768. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  769. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  770. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  784. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  785. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  786. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  787. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  788. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  789. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  790. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  791. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  792. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  793. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  794. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  795. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  796. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  797. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  798. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  799. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  800. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  801. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  802. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  803. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  804. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  805. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  806. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  807. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  808. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  809. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  810. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  811. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  812. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  813. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  814. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  815. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  816. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  817. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  818. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  819. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  820. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  821. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  822. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  823. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  824. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  825. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  826. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  827. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  828. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  829. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  830. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  831. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  832. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  833. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  834. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  835. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  836. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  837. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  838. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  839. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  840. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  841. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  842. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  843. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  844. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  845. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  846. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  847. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  848. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  849. package/vendor/ink/package.json +201 -0
  850. package/vendor/ink/readme.md +2636 -0
  851. package/bin/swag-agent.js +0 -9
  852. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  853. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -1,999 +1,1398 @@
1
1
  import { sanitizeFilterValue } from "../lib/utils.js";
2
+
2
3
  // ============================================================================
3
4
  // PRODUCTS — Full product catalog management
4
5
  // Products, categories, field schemas, pricing schemas, catalogs, assignments
5
6
  // ============================================================================
7
+
6
8
  export async function handleProducts(sb, args, storeId) {
7
- const sid = storeId;
8
- switch (args.action) {
9
- // ======================== PRODUCTS ========================
10
- case "browse": {
11
- // Category summary with product counts, price ranges, stock status
12
- const { data: cats, error: catErr } = await sb.from("categories")
13
- .select("id, name, slug, parent_id, display_order, product_count, catalog_id")
14
- .eq("store_id", sid)
15
- .eq("is_active", true)
16
- .order("display_order", { ascending: true });
17
- if (catErr)
18
- return { success: false, error: catErr.message };
19
- // Status distribution via count queries
20
- const { count: activeCount } = await sb.from("products").select("id", { count: "exact", head: true }).eq("store_id", sid).neq("status", "archived");
21
- const { count: archivedCount } = await sb.from("products").select("id", { count: "exact", head: true }).eq("store_id", sid).eq("status", "archived");
22
- return {
23
- success: true,
24
- data: {
25
- categories: cats,
26
- product_status_summary: { active: activeCount || 0, archived: archivedCount || 0 },
27
- tip: "Use products.find with category or status filters to drill into a specific set. Use products.get for full product detail."
28
- }
29
- };
30
- }
31
- case "find": {
32
- const offset = args.offset || 0;
33
- let q = sb.from("products")
34
- .select("id, name, sku, status, type, stock_quantity, category:categories!primary_category_id(name)", { count: "exact" })
35
- .eq("store_id", sid)
36
- .order("created_at", { ascending: false });
37
- if (args.limit) {
38
- q = q.range(offset, offset + args.limit - 1);
39
- }
40
- else if (offset > 0) {
41
- q = q.range(offset, offset + 999);
42
- }
43
- if (args.query) {
44
- const sq = sanitizeFilterValue(String(args.query));
45
- q = q.or(`name.ilike.%${sq}%,sku.ilike.%${sq}%,description.ilike.%${sq}%`);
46
- }
47
- if (args.category || args.category_id) {
48
- // Accept both category (name or UUID) and category_id (UUID)
49
- const catInput = (args.category_id || args.category);
50
- const catVal = sanitizeFilterValue(catInput);
51
- let matchedCatId = null;
52
- if (/^[0-9a-f]{8}-/.test(catVal)) {
53
- matchedCatId = catVal;
54
- }
55
- else {
56
- const { data: cats } = await sb.from("categories").select("id").ilike("name", `%${catVal}%`).eq("store_id", sid).limit(1);
57
- if (cats?.length)
58
- matchedCatId = cats[0].id;
59
- }
60
- if (matchedCatId) {
61
- // Include sub-categories (children of the matched category)
62
- const { data: children } = await sb.from("categories").select("id").eq("parent_id", matchedCatId).eq("store_id", sid);
63
- const catIds = [matchedCatId, ...(children || []).map(c => c.id)];
64
- q = q.in("primary_category_id", catIds);
65
- }
66
- else {
67
- // Category not found — return empty result
68
- q = q.eq("primary_category_id", "00000000-0000-0000-0000-000000000000");
69
- }
70
- }
71
- if (args.catalog_id)
72
- q = q.eq("catalog_id", args.catalog_id);
73
- if (args.status)
74
- q = q.eq("status", args.status);
75
- // Exclude archived unless explicitly requested or filtering by status
76
- if (!args.status && !args.include_archived) {
77
- q = q.neq("status", "archived");
78
- }
79
- if (args.featured !== undefined)
80
- q = q.eq("featured", args.featured);
81
- const { data, error, count: totalCount } = await q;
82
- if (error)
83
- return { success: false, error: error.message };
84
- // Flatten nested category for display
85
- const flattened = (data || []).map((row) => {
86
- const { category, ...rest } = row;
87
- return { ...rest, category_name: category?.name || null };
9
+ const sid = storeId;
10
+ switch (args.action) {
11
+ // ======================== PRODUCTS ========================
12
+
13
+ case "browse":
14
+ {
15
+ // Category summary with product counts, price ranges, stock status
16
+ const {
17
+ data: cats,
18
+ error: catErr
19
+ } = await sb.from("categories").select("id, name, slug, parent_id, display_order, product_count, catalog_id").eq("store_id", sid).eq("is_active", true).order("display_order", {
20
+ ascending: true
21
+ });
22
+ if (catErr) return {
23
+ success: false,
24
+ error: catErr.message
25
+ };
26
+
27
+ // Status distribution via count queries
28
+ const {
29
+ count: activeCount
30
+ } = await sb.from("products").select("id", {
31
+ count: "exact",
32
+ head: true
33
+ }).eq("store_id", sid).neq("status", "archived");
34
+ const {
35
+ count: archivedCount
36
+ } = await sb.from("products").select("id", {
37
+ count: "exact",
38
+ head: true
39
+ }).eq("store_id", sid).eq("status", "archived");
40
+ return {
41
+ success: true,
42
+ data: {
43
+ categories: cats,
44
+ product_status_summary: {
45
+ active: activeCount || 0,
46
+ archived: archivedCount || 0
47
+ },
48
+ tip: "Use products.find with category or status filters to drill into a specific set. Use products.get for full product detail."
49
+ }
50
+ };
51
+ }
52
+ case "find":
53
+ {
54
+ const offset = args.offset || 0;
55
+ let q = sb.from("products").select("id, name, sku, status, type, stock_quantity, category:categories!primary_category_id(name)", {
56
+ count: "exact"
57
+ }).eq("store_id", sid).order("created_at", {
58
+ ascending: false
59
+ });
60
+ if (args.limit) {
61
+ q = q.range(offset, offset + args.limit - 1);
62
+ } else if (offset > 0) {
63
+ q = q.range(offset, offset + 999);
64
+ }
65
+ if (args.query) {
66
+ const sq = sanitizeFilterValue(String(args.query));
67
+ q = q.or(`name.ilike.%${sq}%,sku.ilike.%${sq}%,description.ilike.%${sq}%`);
68
+ }
69
+ if (args.category || args.category_id) {
70
+ // Accept both category (name or UUID) and category_id (UUID)
71
+ const catInput = args.category_id || args.category;
72
+ const catVal = sanitizeFilterValue(catInput);
73
+ let matchedCatId = null;
74
+ if (/^[0-9a-f]{8}-/.test(catVal)) {
75
+ matchedCatId = catVal;
76
+ } else {
77
+ const {
78
+ data: cats
79
+ } = await sb.from("categories").select("id").ilike("name", `%${catVal}%`).eq("store_id", sid).limit(1);
80
+ if (cats?.length) matchedCatId = cats[0].id;
81
+ }
82
+ if (matchedCatId) {
83
+ // Include sub-categories (children of the matched category)
84
+ const {
85
+ data: children
86
+ } = await sb.from("categories").select("id").eq("parent_id", matchedCatId).eq("store_id", sid);
87
+ const catIds = [matchedCatId, ...(children || []).map(c => c.id)];
88
+ q = q.in("primary_category_id", catIds);
89
+ } else {
90
+ // Category not found — return empty result
91
+ q = q.eq("primary_category_id", "00000000-0000-0000-0000-000000000000");
92
+ }
93
+ }
94
+ if (args.catalog_id) q = q.eq("catalog_id", args.catalog_id);
95
+ if (args.status) q = q.eq("status", args.status);
96
+ // Exclude archived unless explicitly requested or filtering by status
97
+ if (!args.status && !args.include_archived) {
98
+ q = q.neq("status", "archived");
99
+ }
100
+ if (args.featured !== undefined) q = q.eq("featured", args.featured);
101
+ const {
102
+ data,
103
+ error,
104
+ count: totalCount
105
+ } = await q;
106
+ if (error) return {
107
+ success: false,
108
+ error: error.message
109
+ };
110
+ // Flatten nested category for display
111
+ const flattened = (data || []).map(row => {
112
+ const {
113
+ category,
114
+ ...rest
115
+ } = row;
116
+ return {
117
+ ...rest,
118
+ category_name: category?.name || null
119
+ };
120
+ });
121
+ return {
122
+ success: true,
123
+ total: totalCount,
124
+ count: flattened.length,
125
+ offset,
126
+ ...(args.limit ? {
127
+ limit: args.limit
128
+ } : {}),
129
+ data: flattened
130
+ };
131
+ }
132
+ case "get":
133
+ {
134
+ const pid = args.product_id;
135
+ const {
136
+ data: product,
137
+ error: pErr
138
+ } = await sb.from("products").select("*, category:categories!primary_category_id(id, name, slug)").eq("id", pid).eq("store_id", sid).single();
139
+ if (pErr) return {
140
+ success: false,
141
+ error: pErr.message
142
+ };
143
+ const {
144
+ data: fieldSchemas
145
+ } = await sb.from("product_field_schemas").select("field_schema_id, field_schema:field_schemas!field_schema_id(id, name, fields, icon)").eq("product_id", pid);
146
+ const {
147
+ data: pricingSchemas
148
+ } = await sb.from("product_pricing_schemas").select("pricing_schema_id, pricing_schema:pricing_schemas!pricing_schema_id(id, name, tiers, quality_tier)").eq("product_id", pid);
149
+ const {
150
+ data: inventory
151
+ } = await sb.from("inventory").select("id, quantity, location:locations!location_id(id, name)").eq("product_id", pid).eq("store_id", sid);
152
+ return {
153
+ success: true,
154
+ data: {
155
+ ...product,
156
+ field_schemas: fieldSchemas?.map(fs => fs.field_schema) || [],
157
+ pricing_schemas: pricingSchemas?.map(ps => ps.pricing_schema) || [],
158
+ inventory: inventory || []
159
+ }
160
+ };
161
+ }
162
+ case "create":
163
+ {
164
+ const name = args.name;
165
+ if (!name) return {
166
+ success: false,
167
+ error: "name is required"
168
+ };
169
+ const insert = {
170
+ store_id: sid,
171
+ name
172
+ };
173
+ if (args.sku) insert.sku = args.sku;
174
+ if (args.description) insert.description = args.description;
175
+ if (args.short_description) insert.short_description = args.short_description;
176
+ if (args.type) insert.type = args.type;
177
+ if (args.status) insert.status = args.status;
178
+ if (args.cost_price !== undefined) insert.cost_price = args.cost_price;
179
+ if (args.wholesale_price !== undefined) insert.wholesale_price = args.wholesale_price;
180
+ if (args.featured !== undefined) insert.featured = args.featured;
181
+ if (args.stock_quantity !== undefined) insert.stock_quantity = args.stock_quantity;
182
+ if (args.manage_stock !== undefined) insert.manage_stock = args.manage_stock;
183
+ if (args.weight !== undefined) insert.weight = args.weight;
184
+ if (args.tax_status) insert.tax_status = args.tax_status;
185
+ if (args.tax_class) insert.tax_class = args.tax_class;
186
+ if (args.catalog_id) insert.catalog_id = args.catalog_id;
187
+ if (args.pricing_data) insert.pricing_data = args.pricing_data;
188
+ // custom_fields NOT inserted directly — schema is the source of truth
189
+ // Agent-provided custom_fields are filtered against schema keys post-insert
190
+ if (args.featured_image !== undefined) insert.featured_image = args.featured_image;
191
+ if (args.image_gallery !== undefined) insert.image_gallery = args.image_gallery;
192
+ if (args.is_wholesale !== undefined) insert.is_wholesale = args.is_wholesale;
193
+ if (args.wholesale_only !== undefined) insert.wholesale_only = args.wholesale_only;
194
+ if (args.minimum_wholesale_quantity !== undefined) insert.minimum_wholesale_quantity = args.minimum_wholesale_quantity;
195
+ const catArg = args.category || args.primary_category_id || args.category_id;
196
+ if (catArg) {
197
+ if (/^[0-9a-f]{8}-/.test(catArg)) {
198
+ insert.primary_category_id = catArg;
199
+ } else {
200
+ const {
201
+ data: cats
202
+ } = await sb.from("categories").select("id").ilike("name", `%${catArg}%`).eq("store_id", sid).limit(1);
203
+ if (cats?.length) insert.primary_category_id = cats[0].id;
204
+ }
205
+ }
206
+ if (args.pricing_schema_id) insert.pricing_schema_id = args.pricing_schema_id;
207
+ const {
208
+ data,
209
+ error
210
+ } = await sb.from("products").insert(insert).select("id, name, sku, slug, status, primary_category_id, pricing_schema_id, created_at").single();
211
+ if (error) return {
212
+ success: false,
213
+ error: error.message
214
+ };
215
+
216
+ // Explicit schema assignments from args
217
+ if (args.field_schema_ids && Array.isArray(args.field_schema_ids)) {
218
+ const rows = args.field_schema_ids.map(fsId => ({
219
+ product_id: data.id,
220
+ field_schema_id: fsId
221
+ }));
222
+ await sb.from("product_field_schemas").insert(rows);
223
+ }
224
+ if (args.pricing_schema_ids && Array.isArray(args.pricing_schema_ids)) {
225
+ const rows = args.pricing_schema_ids.map(psId => ({
226
+ product_id: data.id,
227
+ pricing_schema_id: psId
228
+ }));
229
+ await sb.from("product_pricing_schemas").insert(rows);
230
+ }
231
+ const productUpdates = {};
232
+ const inherited = [];
233
+
234
+ // Auto-inherit field schema from category — ALWAYS merge with schema template
235
+ const categoryId = insert.primary_category_id;
236
+ if (categoryId && !args.field_schema_ids) {
237
+ const {
238
+ data: cat
239
+ } = await sb.from("categories").select("field_schema_id").eq("id", categoryId).single();
240
+ if (cat?.field_schema_id) {
241
+ await sb.from("product_field_schemas").upsert({
242
+ product_id: data.id,
243
+ field_schema_id: cat.field_schema_id
244
+ }, {
245
+ onConflict: "product_id,field_schema_id"
88
246
  });
89
- return { success: true, total: totalCount, count: flattened.length, offset, ...(args.limit ? { limit: args.limit } : {}), data: flattened };
90
- }
91
- case "get": {
92
- const pid = args.product_id;
93
- const { data: product, error: pErr } = await sb.from("products")
94
- .select("*, category:categories!primary_category_id(id, name, slug)")
95
- .eq("id", pid).eq("store_id", sid).single();
96
- if (pErr)
97
- return { success: false, error: pErr.message };
98
- const { data: fieldSchemas } = await sb.from("product_field_schemas")
99
- .select("field_schema_id, field_schema:field_schemas!field_schema_id(id, name, fields, icon)")
100
- .eq("product_id", pid);
101
- const { data: pricingSchemas } = await sb.from("product_pricing_schemas")
102
- .select("pricing_schema_id, pricing_schema:pricing_schemas!pricing_schema_id(id, name, tiers, quality_tier)")
103
- .eq("product_id", pid);
104
- const { data: inventory } = await sb.from("inventory")
105
- .select("id, quantity, location:locations!location_id(id, name)")
106
- .eq("product_id", pid).eq("store_id", sid);
107
- return {
108
- success: true,
109
- data: {
110
- ...product,
111
- field_schemas: fieldSchemas?.map(fs => fs.field_schema) || [],
112
- pricing_schemas: pricingSchemas?.map(ps => ps.pricing_schema) || [],
113
- inventory: inventory || []
114
- }
115
- };
116
- }
117
- case "create": {
118
- const name = args.name;
119
- if (!name)
120
- return { success: false, error: "name is required" };
121
- const insert = { store_id: sid, name };
122
- if (args.sku)
123
- insert.sku = args.sku;
124
- if (args.description)
125
- insert.description = args.description;
126
- if (args.short_description)
127
- insert.short_description = args.short_description;
128
- if (args.type)
129
- insert.type = args.type;
130
- if (args.status)
131
- insert.status = args.status;
132
- if (args.cost_price !== undefined)
133
- insert.cost_price = args.cost_price;
134
- if (args.wholesale_price !== undefined)
135
- insert.wholesale_price = args.wholesale_price;
136
- if (args.featured !== undefined)
137
- insert.featured = args.featured;
138
- if (args.stock_quantity !== undefined)
139
- insert.stock_quantity = args.stock_quantity;
140
- if (args.manage_stock !== undefined)
141
- insert.manage_stock = args.manage_stock;
142
- if (args.weight !== undefined)
143
- insert.weight = args.weight;
144
- if (args.tax_status)
145
- insert.tax_status = args.tax_status;
146
- if (args.tax_class)
147
- insert.tax_class = args.tax_class;
148
- if (args.catalog_id)
149
- insert.catalog_id = args.catalog_id;
150
- if (args.pricing_data)
151
- insert.pricing_data = args.pricing_data;
152
- // custom_fields NOT inserted directly — schema is the source of truth
153
- // Agent-provided custom_fields are filtered against schema keys post-insert
154
- if (args.featured_image !== undefined)
155
- insert.featured_image = args.featured_image;
156
- if (args.image_gallery !== undefined)
157
- insert.image_gallery = args.image_gallery;
158
- if (args.is_wholesale !== undefined)
159
- insert.is_wholesale = args.is_wholesale;
160
- if (args.wholesale_only !== undefined)
161
- insert.wholesale_only = args.wholesale_only;
162
- if (args.minimum_wholesale_quantity !== undefined)
163
- insert.minimum_wholesale_quantity = args.minimum_wholesale_quantity;
164
- const catArg = (args.category || args.primary_category_id || args.category_id);
165
- if (catArg) {
166
- if (/^[0-9a-f]{8}-/.test(catArg)) {
167
- insert.primary_category_id = catArg;
168
- }
169
- else {
170
- const { data: cats } = await sb.from("categories").select("id").ilike("name", `%${catArg}%`).eq("store_id", sid).limit(1);
171
- if (cats?.length)
172
- insert.primary_category_id = cats[0].id;
173
- }
174
- }
175
- if (args.pricing_schema_id)
176
- insert.pricing_schema_id = args.pricing_schema_id;
177
- const { data, error } = await sb.from("products").insert(insert).select("id, name, sku, slug, status, primary_category_id, pricing_schema_id, created_at").single();
178
- if (error)
179
- return { success: false, error: error.message };
180
- // Explicit schema assignments from args
181
- if (args.field_schema_ids && Array.isArray(args.field_schema_ids)) {
182
- const rows = args.field_schema_ids.map(fsId => ({ product_id: data.id, field_schema_id: fsId }));
183
- await sb.from("product_field_schemas").insert(rows);
184
- }
185
- if (args.pricing_schema_ids && Array.isArray(args.pricing_schema_ids)) {
186
- const rows = args.pricing_schema_ids.map(psId => ({ product_id: data.id, pricing_schema_id: psId }));
187
- await sb.from("product_pricing_schemas").insert(rows);
188
- }
189
- const productUpdates = {};
190
- const inherited = [];
191
- // Auto-inherit field schema from category — ALWAYS merge with schema template
192
- const categoryId = insert.primary_category_id;
193
- if (categoryId && !args.field_schema_ids) {
194
- const { data: cat } = await sb.from("categories").select("field_schema_id").eq("id", categoryId).single();
195
- if (cat?.field_schema_id) {
196
- await sb.from("product_field_schemas").upsert({ product_id: data.id, field_schema_id: cat.field_schema_id }, { onConflict: "product_id,field_schema_id" });
197
- // Schema is source of truth — only schema keys allowed
198
- const { data: fs } = await sb.from("field_schemas").select("fields").eq("id", cat.field_schema_id).single();
199
- if (fs?.fields && Array.isArray(fs.fields)) {
200
- const schemaKeys = new Set();
201
- const fieldValues = {};
202
- for (const f of fs.fields) {
203
- const key = f.key;
204
- if (key) {
205
- schemaKeys.add(key);
206
- fieldValues[key] = f.default ?? null;
207
- }
208
- }
209
- // Only accept agent values for keys that exist in the schema
210
- const agentValues = args.custom_fields || {};
211
- for (const [k, v] of Object.entries(agentValues)) {
212
- if (schemaKeys.has(k))
213
- fieldValues[k] = v;
214
- }
215
- productUpdates.custom_fields = fieldValues;
216
- }
217
- inherited.push(`field_schema:${cat.field_schema_id}`);
218
- }
219
- // Also check junction table for additional schemas
220
- const { data: catFieldSchemas } = await sb.from("category_field_schemas").select("field_schema_id").eq("category_id", categoryId);
221
- if (catFieldSchemas?.length) {
222
- const cat2 = await sb.from("categories").select("field_schema_id").eq("id", categoryId).single();
223
- const rows = catFieldSchemas.filter(r => r.field_schema_id !== cat2?.data?.field_schema_id).map(r => ({ product_id: data.id, field_schema_id: r.field_schema_id }));
224
- if (rows.length)
225
- await sb.from("product_field_schemas").insert(rows);
226
- }
227
- }
228
- // If pricing_schema_id provided, hydrate pricing_data from schema
229
- if (insert.pricing_schema_id && !args.pricing_data) {
230
- const { data: ps } = await sb.from("pricing_schemas").select("tiers").eq("id", insert.pricing_schema_id).single();
231
- if (ps?.tiers)
232
- productUpdates.pricing_data = ps.tiers;
233
- }
234
- // Apply any post-insert updates
235
- if (Object.keys(productUpdates).length > 0) {
236
- await sb.from("products").update(productUpdates).eq("id", data.id);
237
- }
238
- // Re-read the full product for response
239
- const { data: full } = await sb.from("products")
240
- .select("id, name, sku, slug, status, primary_category_id, pricing_schema_id, custom_fields, pricing_data, created_at")
241
- .eq("id", data.id).single();
242
- if (inherited.length && full)
243
- full.inherited = inherited;
244
- return { success: true, data: full || data };
245
- }
246
- case "update": {
247
- const pid = args.product_id;
248
- if (!pid)
249
- return { success: false, error: "product_id is required" };
250
- const updates = {};
251
- if (args.name !== undefined)
252
- updates.name = args.name;
253
- if (args.sku !== undefined)
254
- updates.sku = args.sku;
255
- if (args.description !== undefined)
256
- updates.description = args.description;
257
- if (args.short_description !== undefined)
258
- updates.short_description = args.short_description;
259
- if (args.type !== undefined)
260
- updates.type = args.type;
261
- if (args.status !== undefined)
262
- updates.status = args.status;
263
- if (args.cost_price !== undefined)
264
- updates.cost_price = args.cost_price;
265
- if (args.wholesale_price !== undefined)
266
- updates.wholesale_price = args.wholesale_price;
267
- if (args.featured !== undefined)
268
- updates.featured = args.featured;
269
- if (args.stock_quantity !== undefined)
270
- updates.stock_quantity = args.stock_quantity;
271
- if (args.manage_stock !== undefined)
272
- updates.manage_stock = args.manage_stock;
273
- if (args.weight !== undefined)
274
- updates.weight = args.weight;
275
- if (args.tax_status !== undefined)
276
- updates.tax_status = args.tax_status;
277
- if (args.tax_class !== undefined)
278
- updates.tax_class = args.tax_class;
279
- if (args.catalog_id !== undefined)
280
- updates.catalog_id = args.catalog_id;
281
- if (args.pricing_schema_id !== undefined)
282
- updates.pricing_schema_id = args.pricing_schema_id;
283
- if (args.pricing_data !== undefined)
284
- updates.pricing_data = args.pricing_data;
285
- // custom_fields filtered to schema keys only (schema = source of truth)
286
- if (args.custom_fields !== undefined) {
287
- const agentFV = args.custom_fields;
288
- // Look up product's linked field schema to get allowed keys
289
- const { data: pfs } = await sb.from("product_field_schemas").select("field_schema_id").eq("product_id", pid).limit(1);
290
- if (pfs?.length) {
291
- const { data: fsDef } = await sb.from("field_schemas").select("fields").eq("id", pfs[0].field_schema_id).single();
292
- if (fsDef?.fields && Array.isArray(fsDef.fields)) {
293
- const { data: existing } = await sb.from("products").select("custom_fields").eq("id", pid).single();
294
- const base = existing?.custom_fields || {};
295
- const filtered = { ...base };
296
- const schemaKeys = new Set(fsDef.fields.map((f) => f.key).filter(Boolean));
297
- for (const [k, v] of Object.entries(agentFV)) {
298
- if (schemaKeys.has(k))
299
- filtered[k] = v;
300
- }
301
- updates.custom_fields = filtered;
302
- }
303
- else {
304
- updates.custom_fields = agentFV; // no schema definition found, pass through
305
- }
306
- }
307
- else {
308
- updates.custom_fields = agentFV; // no schema linked, pass through
309
- }
310
- }
311
- if (args.is_wholesale !== undefined)
312
- updates.is_wholesale = args.is_wholesale;
313
- if (args.wholesale_only !== undefined)
314
- updates.wholesale_only = args.wholesale_only;
315
- if (args.minimum_wholesale_quantity !== undefined)
316
- updates.minimum_wholesale_quantity = args.minimum_wholesale_quantity;
317
- if (args.featured_image !== undefined)
318
- updates.featured_image = args.featured_image;
319
- if (args.image_gallery !== undefined)
320
- updates.image_gallery = args.image_gallery;
321
- const updateCatArg = (args.category ?? args.primary_category_id ?? args.category_id);
322
- if (updateCatArg !== undefined) {
323
- if (!updateCatArg) {
324
- updates.primary_category_id = null;
325
- }
326
- else if (/^[0-9a-f]{8}-/.test(updateCatArg)) {
327
- updates.primary_category_id = updateCatArg;
328
- }
329
- else {
330
- const { data: cats } = await sb.from("categories").select("id").ilike("name", `%${updateCatArg}%`).eq("store_id", sid).limit(1);
331
- if (cats?.length)
332
- updates.primary_category_id = cats[0].id;
333
- }
334
- }
335
- const { data, error } = await sb.from("products")
336
- .update(updates).eq("id", pid).eq("store_id", sid)
337
- .select("id, name, sku, slug, status, cost_price, pricing_schema_id, updated_at").single();
338
- return error ? { success: false, error: error.message } : { success: true, data };
339
- }
340
- case "delete": {
341
- const pid = args.product_id;
342
- if (!pid)
343
- return { success: false, error: "product_id is required" };
344
- if (args.hard === true) {
345
- const { error } = await sb.from("products").delete().eq("id", pid).eq("store_id", sid);
346
- return error ? { success: false, error: error.message } : { success: true, data: { id: pid, deleted: true } };
347
- }
348
- const { data, error } = await sb.from("products")
349
- .update({ status: "archived" }).eq("id", pid).eq("store_id", sid)
350
- .select("id, name, status").single();
351
- return error ? { success: false, error: error.message } : { success: true, data };
352
- }
353
- // ======================== CATEGORIES ========================
354
- case "list_categories": {
355
- let q = sb.from("categories")
356
- .select("id, name, slug, description, icon, parent_id, display_order, is_active, featured, product_count, catalog_id, field_schema_id, created_at")
357
- .eq("store_id", sid)
358
- .order("display_order", { ascending: true });
359
- if (args.catalog_id)
360
- q = q.eq("catalog_id", args.catalog_id);
361
- if (args.parent_id)
362
- q = q.eq("parent_id", args.parent_id);
363
- if (args.active_only !== false)
364
- q = q.eq("is_active", true);
365
- const { data, error } = await q.limit(args.limit || 100);
366
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
367
- }
368
- case "get_category": {
369
- const catId = args.category_id;
370
- const { data: cat, error: catErr } = await sb.from("categories")
371
- .select("*").eq("id", catId).eq("store_id", sid).single();
372
- if (catErr)
373
- return { success: false, error: catErr.message };
374
- const { data: fieldAssigns } = await sb.from("category_field_schemas")
375
- .select("sort_order, is_active, field_schema:field_schemas!field_schema_id(id, name, fields, icon)")
376
- .eq("category_id", catId).eq("is_active", true).order("sort_order");
377
- const { data: pricingAssigns } = await sb.from("category_pricing_schemas")
378
- .select("sort_order, is_active, pricing_schema:pricing_schemas!pricing_schema_id(id, name, tiers, quality_tier)")
379
- .eq("category_id", catId).eq("is_active", true).order("sort_order");
380
- const { data: children } = await sb.from("categories")
381
- .select("id, name, slug, display_order, is_active, product_count")
382
- .eq("parent_id", catId).order("display_order");
383
- return {
384
- success: true,
385
- data: {
386
- ...cat,
387
- field_schemas: fieldAssigns?.map(a => ({ ...a.field_schema, sort_order: a.sort_order })) || [],
388
- pricing_schemas: pricingAssigns?.map(a => ({ ...a.pricing_schema, sort_order: a.sort_order })) || [],
389
- subcategories: children || []
390
- }
391
- };
392
- }
393
- case "create_category": {
394
- const name = args.name;
395
- if (!name)
396
- return { success: false, error: "name is required" };
397
- const insert = { store_id: sid, name, slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") };
398
- if (args.description)
399
- insert.description = args.description;
400
- if (args.icon)
401
- insert.icon = args.icon;
402
- if (args.parent_id)
403
- insert.parent_id = args.parent_id;
404
- if (args.catalog_id)
405
- insert.catalog_id = args.catalog_id;
406
- if (args.display_order !== undefined)
407
- insert.display_order = args.display_order;
408
- if (args.field_schema_id)
409
- insert.field_schema_id = args.field_schema_id;
410
- const { data, error } = await sb.from("categories").insert(insert)
411
- .select("id, name, slug, parent_id, catalog_id, display_order, created_at").single();
412
- if (error)
413
- return { success: false, error: error.message };
414
- if (args.field_schema_ids && Array.isArray(args.field_schema_ids)) {
415
- const rows = args.field_schema_ids.map((fsId, i) => ({ category_id: data.id, field_schema_id: fsId, sort_order: i + 1 }));
416
- await sb.from("category_field_schemas").insert(rows);
417
- }
418
- if (args.pricing_schema_ids && Array.isArray(args.pricing_schema_ids)) {
419
- const rows = args.pricing_schema_ids.map((psId, i) => ({ category_id: data.id, pricing_schema_id: psId, sort_order: i + 1 }));
420
- await sb.from("category_pricing_schemas").insert(rows);
421
- }
422
- return { success: true, data };
423
- }
424
- case "update_category": {
425
- const catId = args.category_id;
426
- if (!catId)
427
- return { success: false, error: "category_id is required" };
428
- const updates = {};
429
- if (args.name !== undefined)
430
- updates.name = args.name;
431
- if (args.description !== undefined)
432
- updates.description = args.description;
433
- if (args.icon !== undefined)
434
- updates.icon = args.icon;
435
- if (args.parent_id !== undefined)
436
- updates.parent_id = args.parent_id;
437
- if (args.catalog_id !== undefined)
438
- updates.catalog_id = args.catalog_id;
439
- if (args.display_order !== undefined)
440
- updates.display_order = args.display_order;
441
- if (args.is_active !== undefined)
442
- updates.is_active = args.is_active;
443
- if (args.featured !== undefined)
444
- updates.featured = args.featured;
445
- if (args.field_schema_id !== undefined)
446
- updates.field_schema_id = args.field_schema_id;
447
- const { data, error } = await sb.from("categories")
448
- .update(updates).eq("id", catId).eq("store_id", sid)
449
- .select("id, name, slug, is_active, display_order, updated_at").single();
450
- return error ? { success: false, error: error.message } : { success: true, data };
451
- }
452
- case "delete_category": {
453
- const catId = args.category_id;
454
- if (!catId)
455
- return { success: false, error: "category_id is required" };
456
- if (args.hard === true) {
457
- const { error } = await sb.from("categories").delete().eq("id", catId).eq("store_id", sid);
458
- return error ? { success: false, error: error.message } : { success: true, data: { id: catId, deleted: true } };
459
- }
460
- const { data, error } = await sb.from("categories")
461
- .update({ is_active: false }).eq("id", catId).eq("store_id", sid)
462
- .select("id, name, is_active").single();
463
- return error ? { success: false, error: error.message } : { success: true, data };
464
- }
465
- // ======================== FIELD SCHEMAS ========================
466
- case "list_field_schemas": {
467
- let q = sb.from("field_schemas")
468
- .select("id, name, slug, description, icon, fields, is_public, is_active, catalog_id, store_id, install_count, created_at")
469
- .eq("is_active", true);
470
- // Show store-owned schemas AND public schemas (prevent IDOR)
471
- if (args.public_only === true) {
472
- q = q.eq("is_public", true);
473
- }
474
- else {
475
- q = q.or(`store_id.eq.${sid},is_public.eq.true`);
476
- }
477
- if (args.catalog_id)
478
- q = q.eq("catalog_id", args.catalog_id);
479
- if (args.limit)
480
- q = q.limit(args.limit);
481
- const { data, error } = await q.order("name");
482
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
483
- }
484
- case "get_field_schema": {
485
- const fsId = (args.field_schema_id || args.schema_id);
486
- if (!fsId)
487
- return { success: false, error: "field_schema_id is required" };
488
- // Allow access to own schemas OR public schemas (prevent IDOR)
489
- const { data, error } = await sb.from("field_schemas").select("*").eq("id", fsId)
490
- .or(`store_id.eq.${sid},is_public.eq.true`).single();
491
- if (error)
492
- return { success: false, error: error.message };
493
- const { data: assignments } = await sb.from("category_field_schemas")
494
- .select("category:categories!category_id(id, name)").eq("field_schema_id", fsId).eq("is_active", true);
495
- return { success: true, data: { ...data, assigned_categories: assignments?.map(a => a.category) || [] } };
496
- }
497
- case "create_field_schema": {
498
- const name = args.name;
499
- if (!name)
500
- return { success: false, error: "name is required" };
501
- if (!args.fields || !Array.isArray(args.fields))
502
- return { success: false, error: "fields array is required" };
503
- // Auto-resolve catalog_id: use provided value, or fall back to store's default catalog
504
- let catalogId = args.catalog_id;
505
- if (!catalogId) {
506
- const { data: defaultCatalog } = await sb.from("catalogs")
507
- .select("id").eq("store_id", sid).eq("is_default", true).single();
508
- if (defaultCatalog)
509
- catalogId = defaultCatalog.id;
510
- }
511
- const insert = {
512
- store_id: sid,
513
- name,
514
- slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
515
- fields: args.fields
516
- };
517
- if (args.description)
518
- insert.description = args.description;
519
- if (args.icon)
520
- insert.icon = args.icon;
521
- if (catalogId)
522
- insert.catalog_id = catalogId;
523
- if (args.is_public !== undefined)
524
- insert.is_public = args.is_public;
525
- const { data, error } = await sb.from("field_schemas").insert(insert)
526
- .select("id, name, slug, fields, icon, is_active, catalog_id, created_at").single();
527
- return error ? { success: false, error: error.message } : { success: true, data };
528
- }
529
- case "update_field_schema": {
530
- const fsId = (args.field_schema_id || args.schema_id);
531
- if (!fsId)
532
- return { success: false, error: "field_schema_id is required" };
533
- const updates = {};
534
- if (args.name !== undefined)
535
- updates.name = args.name;
536
- if (args.description !== undefined)
537
- updates.description = args.description;
538
- if (args.icon !== undefined)
539
- updates.icon = args.icon;
540
- if (args.fields !== undefined)
541
- updates.fields = args.fields;
542
- if (args.is_public !== undefined)
543
- updates.is_public = args.is_public;
544
- if (args.is_active !== undefined)
545
- updates.is_active = args.is_active;
546
- if (args.catalog_id !== undefined)
547
- updates.catalog_id = args.catalog_id;
548
- // Only allow modification of own schemas (prevent IDOR)
549
- const { data, error } = await sb.from("field_schemas")
550
- .update(updates).eq("id", fsId).eq("store_id", sid)
551
- .select("id, name, slug, fields, icon, is_active, catalog_id, updated_at").single();
552
- return error ? { success: false, error: error.message } : { success: true, data };
553
- }
554
- case "delete_field_schema": {
555
- const fsId = (args.field_schema_id || args.schema_id);
556
- if (!fsId)
557
- return { success: false, error: "field_schema_id is required" };
558
- // Only allow deletion of own schemas (prevent IDOR)
559
- const { data, error } = await sb.from("field_schemas")
560
- .update({ is_active: false, deleted_at: new Date().toISOString() }).eq("id", fsId).eq("store_id", sid)
561
- .select("id, name, is_active").single();
562
- return error ? { success: false, error: error.message } : { success: true, data };
563
- }
564
- // ======================== PRICING SCHEMAS ========================
565
- case "list_pricing_schemas": {
566
- let q = sb.from("pricing_schemas")
567
- .select("id, name, slug, description, tiers, quality_tier, is_public, is_active, catalog_id, store_id, install_count, created_at")
568
- .eq("is_active", true);
569
- // Show store-owned schemas AND public schemas (prevent IDOR)
570
- if (args.public_only === true) {
571
- q = q.eq("is_public", true);
572
- }
573
- else {
574
- q = q.or(`store_id.eq.${sid},is_public.eq.true`);
575
- }
576
- if (args.catalog_id)
577
- q = q.eq("catalog_id", args.catalog_id);
578
- if (args.limit)
579
- q = q.limit(args.limit);
580
- const { data, error } = await q.order("name");
581
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
582
- }
583
- case "get_pricing_schema": {
584
- const psId = (args.pricing_schema_id || args.schema_id);
585
- if (!psId)
586
- return { success: false, error: "pricing_schema_id is required" };
587
- // Allow access to own schemas OR public schemas (prevent IDOR)
588
- const { data, error } = await sb.from("pricing_schemas").select("*").eq("id", psId)
589
- .or(`store_id.eq.${sid},is_public.eq.true`).single();
590
- if (error)
591
- return { success: false, error: error.message };
592
- const { data: assignments } = await sb.from("category_pricing_schemas")
593
- .select("category:categories!category_id(id, name)").eq("pricing_schema_id", psId).eq("is_active", true);
594
- return { success: true, data: { ...data, assigned_categories: assignments?.map(a => a.category) || [] } };
595
- }
596
- case "create_pricing_schema": {
597
- const name = args.name;
598
- if (!name)
599
- return { success: false, error: "name is required" };
600
- if (!args.tiers || !Array.isArray(args.tiers))
601
- return { success: false, error: "tiers array is required" };
602
- // Auto-resolve catalog_id: use provided value, or fall back to store's default catalog
603
- let catalogId = args.catalog_id;
604
- if (!catalogId) {
605
- const { data: defaultCatalog } = await sb.from("catalogs")
606
- .select("id").eq("store_id", sid).eq("is_default", true).single();
607
- if (defaultCatalog)
608
- catalogId = defaultCatalog.id;
609
- }
610
- const insert = {
611
- store_id: sid,
612
- name,
613
- slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
614
- tiers: args.tiers
615
- };
616
- if (args.description)
617
- insert.description = args.description;
618
- if (args.quality_tier)
619
- insert.quality_tier = args.quality_tier;
620
- if (catalogId)
621
- insert.catalog_id = catalogId;
622
- if (args.is_public !== undefined)
623
- insert.is_public = args.is_public;
624
- const { data, error } = await sb.from("pricing_schemas").insert(insert)
625
- .select("id, name, slug, tiers, quality_tier, is_active, catalog_id, created_at").single();
626
- return error ? { success: false, error: error.message } : { success: true, data };
627
- }
628
- case "update_pricing_schema": {
629
- const psId = (args.pricing_schema_id || args.schema_id);
630
- if (!psId)
631
- return { success: false, error: "pricing_schema_id is required" };
632
- const updates = {};
633
- if (args.name !== undefined)
634
- updates.name = args.name;
635
- if (args.description !== undefined)
636
- updates.description = args.description;
637
- if (args.tiers !== undefined)
638
- updates.tiers = args.tiers;
639
- if (args.quality_tier !== undefined)
640
- updates.quality_tier = args.quality_tier;
641
- if (args.is_public !== undefined)
642
- updates.is_public = args.is_public;
643
- if (args.is_active !== undefined)
644
- updates.is_active = args.is_active;
645
- if (args.catalog_id !== undefined)
646
- updates.catalog_id = args.catalog_id;
647
- // Only allow modification of own schemas (prevent IDOR)
648
- const { data, error } = await sb.from("pricing_schemas")
649
- .update(updates).eq("id", psId).eq("store_id", sid)
650
- .select("id, name, slug, tiers, quality_tier, is_active, catalog_id, updated_at").single();
651
- return error ? { success: false, error: error.message } : { success: true, data };
652
- }
653
- case "delete_pricing_schema": {
654
- const psId = (args.pricing_schema_id || args.schema_id);
655
- if (!psId)
656
- return { success: false, error: "pricing_schema_id is required" };
657
- // Only allow deletion of own schemas (prevent IDOR)
658
- const { data, error } = await sb.from("pricing_schemas")
659
- .update({ is_active: false, deleted_at: new Date().toISOString() }).eq("id", psId).eq("store_id", sid)
660
- .select("id, name, is_active").single();
661
- return error ? { success: false, error: error.message } : { success: true, data };
662
- }
663
- // ======================== CATALOGS ========================
664
- case "list_catalogs": {
665
- const { data, error } = await sb.from("catalogs")
666
- .select("id, name, slug, description, vertical, is_active, is_default, display_order, created_at")
667
- .eq("store_id", sid).order("display_order");
668
- return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };
669
- }
670
- case "create_catalog": {
671
- const name = args.name;
672
- if (!name)
673
- return { success: false, error: "name is required" };
674
- // Resolve owner_user_id from store
675
- const { data: store } = await sb.from("stores").select("owner_user_id").eq("id", sid).single();
676
- const insert = {
677
- store_id: sid, name,
678
- slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
679
- owner_user_id: store?.owner_user_id
680
- };
681
- if (args.description)
682
- insert.description = args.description;
683
- if (args.vertical)
684
- insert.vertical = args.vertical;
685
- if (args.is_default !== undefined)
686
- insert.is_default = args.is_default;
687
- if (args.settings)
688
- insert.settings = args.settings;
689
- const { data, error } = await sb.from("catalogs").insert(insert)
690
- .select("id, name, slug, vertical, is_default, created_at").single();
691
- return error ? { success: false, error: error.message } : { success: true, data };
692
- }
693
- case "update_catalog": {
694
- const catId = args.catalog_id;
695
- if (!catId)
696
- return { success: false, error: "catalog_id is required" };
697
- const updates = {};
698
- if (args.name !== undefined)
699
- updates.name = args.name;
700
- if (args.description !== undefined)
701
- updates.description = args.description;
702
- if (args.vertical !== undefined)
703
- updates.vertical = args.vertical;
704
- if (args.is_active !== undefined)
705
- updates.is_active = args.is_active;
706
- if (args.is_default !== undefined)
707
- updates.is_default = args.is_default;
708
- if (args.settings !== undefined)
709
- updates.settings = args.settings;
710
- if (args.display_order !== undefined)
711
- updates.display_order = args.display_order;
712
- const { data, error } = await sb.from("catalogs")
713
- .update(updates).eq("id", catId).eq("store_id", sid)
714
- .select("id, name, slug, vertical, is_active, is_default, updated_at").single();
715
- return error ? { success: false, error: error.message } : { success: true, data };
716
- }
717
- // ======================== SCHEMA ASSIGNMENTS ========================
718
- case "assign_schema": {
719
- const target = args.target; // "category" or "product"
720
- const schemaType = args.schema_type; // "field" or "pricing"
721
- const targetId = args.target_id;
722
- const schemaId = args.schema_id;
723
- if (!target || !schemaType || !targetId || !schemaId) {
724
- return { success: false, error: "target (category|product), schema_type (field|pricing), target_id, and schema_id are required" };
725
- }
726
- // Verify target product/category belongs to this store (prevent IDOR)
727
- const targetTable = target === "product" ? "products" : "categories";
728
- const { data: ownerCheck, error: ownerErr } = await sb.from(targetTable)
729
- .select("id").eq("id", targetId).eq("store_id", sid).single();
730
- if (ownerErr || !ownerCheck) {
731
- return { success: false, error: `${target} not found or does not belong to this store` };
732
- }
733
- // Verify schema is accessible (own schema or public)
734
- const schemaTable = schemaType === "field" ? "field_schemas" : "pricing_schemas";
735
- const { data: schemaCheck, error: schemaErr } = await sb.from(schemaTable)
736
- .select("id").eq("id", schemaId).or(`store_id.eq.${sid},is_public.eq.true`).single();
737
- if (schemaErr || !schemaCheck) {
738
- return { success: false, error: `${schemaType} schema not found or not accessible` };
739
- }
740
- const table = target === "product"
741
- ? (schemaType === "field" ? "product_field_schemas" : "product_pricing_schemas")
742
- : (schemaType === "field" ? "category_field_schemas" : "category_pricing_schemas");
743
- const fkCol = target === "product" ? "product_id" : "category_id";
744
- const schemaCol = schemaType === "field" ? "field_schema_id" : "pricing_schema_id";
745
- const row = { [fkCol]: targetId, [schemaCol]: schemaId };
746
- if (args.sort_order !== undefined)
747
- row.sort_order = args.sort_order;
748
- const { data, error } = await sb.from(table).upsert(row, { onConflict: `${fkCol},${schemaCol}` }).select().single();
749
- if (error)
750
- return { success: false, error: error.message };
751
- // Hydrate product with schema data
752
- if (target === "product") {
753
- const productUpdates = {};
754
- if (schemaType === "pricing") {
755
- const { data: schema } = await sb.from("pricing_schemas").select("tiers, quality_tier").eq("id", schemaId).single();
756
- productUpdates.pricing_schema_id = schemaId;
757
- if (schema?.tiers)
758
- productUpdates.pricing_data = schema.tiers;
759
- }
760
- if (schemaType === "field") {
761
- // Schema is source of truth — rebuild custom_fields from schema keys only
762
- const { data: schema } = await sb.from("field_schemas").select("fields").eq("id", schemaId).single();
763
- if (schema?.fields && Array.isArray(schema.fields)) {
764
- const { data: product } = await sb.from("products").select("custom_fields").eq("id", targetId).eq("store_id", sid).single();
765
- const existing = product?.custom_fields || {};
766
- const rebuilt = {};
767
- for (const f of schema.fields) {
768
- const key = f.key;
769
- if (key)
770
- rebuilt[key] = (key in existing) ? existing[key] : (f.default ?? null);
771
- }
772
- productUpdates.custom_fields = rebuilt;
773
- }
247
+ // Schema is source of truth only schema keys allowed
248
+ const {
249
+ data: fs
250
+ } = await sb.from("field_schemas").select("fields").eq("id", cat.field_schema_id).single();
251
+ if (fs?.fields && Array.isArray(fs.fields)) {
252
+ const schemaKeys = new Set();
253
+ const fieldValues = {};
254
+ for (const f of fs.fields) {
255
+ const key = f.key;
256
+ if (key) {
257
+ schemaKeys.add(key);
258
+ fieldValues[key] = f.default ?? null;
774
259
  }
775
- if (Object.keys(productUpdates).length > 0) {
776
- await sb.from("products").update(productUpdates).eq("id", targetId).eq("store_id", sid);
777
- }
778
- }
779
- return { success: true, data };
780
- }
781
- case "unassign_schema": {
782
- const target = args.target;
783
- const schemaType = args.schema_type;
784
- const targetId = args.target_id;
785
- const schemaId = args.schema_id;
786
- if (!target || !schemaType || !targetId || !schemaId) {
787
- return { success: false, error: "target (category|product), schema_type (field|pricing), target_id, and schema_id are required" };
788
- }
789
- // Verify target product/category belongs to this store (prevent IDOR)
790
- const targetTable = target === "product" ? "products" : "categories";
791
- const { data: ownerCheck, error: ownerErr } = await sb.from(targetTable)
792
- .select("id").eq("id", targetId).eq("store_id", sid).single();
793
- if (ownerErr || !ownerCheck) {
794
- return { success: false, error: `${target} not found or does not belong to this store` };
260
+ }
261
+ // Only accept agent values for keys that exist in the schema
262
+ const agentValues = args.custom_fields || {};
263
+ for (const [k, v] of Object.entries(agentValues)) {
264
+ if (schemaKeys.has(k)) fieldValues[k] = v;
265
+ }
266
+ productUpdates.custom_fields = fieldValues;
795
267
  }
796
- const table = target === "product"
797
- ? (schemaType === "field" ? "product_field_schemas" : "product_pricing_schemas")
798
- : (schemaType === "field" ? "category_field_schemas" : "category_pricing_schemas");
799
- const fkCol = target === "product" ? "product_id" : "category_id";
800
- const schemaCol = schemaType === "field" ? "field_schema_id" : "pricing_schema_id";
801
- const { error } = await sb.from(table).delete().eq(fkCol, targetId).eq(schemaCol, schemaId);
802
- return error ? { success: false, error: error.message } : { success: true, data: { removed: true, target, schema_type: schemaType, target_id: targetId, schema_id: schemaId } };
803
- }
804
- // ======================== PRODUCT DEDUP / MERGE ========================
805
- case "find_duplicates": {
806
- // Find products with similar names (potential duplicates)
807
- const { data: all, error: allErr } = await sb.from("products")
808
- .select("id, name, sku, status, stock_quantity, cost_price, created_at")
809
- .eq("store_id", sid).neq("status", "archived")
810
- .order("name");
811
- if (allErr)
812
- return { success: false, error: allErr.message };
813
- // Group by normalized name (lowercase, trim, strip common suffixes like "(TEMP)", "(OLD)", "(COPY)")
814
- const nameMap = new Map();
815
- for (const p of all || []) {
816
- const normalized = (p.name || "").toLowerCase().trim()
817
- .replace(/\s*\((temp|old|copy|duplicate|dup|new|v2)\)\s*/gi, "")
818
- .replace(/\s+/g, " ");
819
- if (!normalized)
820
- continue;
821
- if (!nameMap.has(normalized))
822
- nameMap.set(normalized, []);
823
- nameMap.get(normalized).push(p);
824
- }
825
- const duplicates = Array.from(nameMap.entries())
826
- .filter(([_, products]) => products.length > 1)
827
- .map(([normalizedName, products]) => ({
828
- normalized_name: normalizedName,
829
- count: products.length,
830
- products: products.map(p => ({
831
- id: p.id, name: p.name, sku: p.sku, status: p.status,
832
- stock_quantity: p.stock_quantity, cost_price: p.cost_price, created_at: p.created_at,
833
- })),
268
+ inherited.push(`field_schema:${cat.field_schema_id}`);
269
+ }
270
+ // Also check junction table for additional schemas
271
+ const {
272
+ data: catFieldSchemas
273
+ } = await sb.from("category_field_schemas").select("field_schema_id").eq("category_id", categoryId);
274
+ if (catFieldSchemas?.length) {
275
+ const cat2 = await sb.from("categories").select("field_schema_id").eq("id", categoryId).single();
276
+ const rows = catFieldSchemas.filter(r => r.field_schema_id !== cat2?.data?.field_schema_id).map(r => ({
277
+ product_id: data.id,
278
+ field_schema_id: r.field_schema_id
834
279
  }));
835
- // Also check by exact SKU match
836
- const skuMap = new Map();
837
- for (const p of all || []) {
838
- if (!p.sku)
839
- continue;
840
- const key = p.sku.toLowerCase().trim();
841
- if (!skuMap.has(key))
842
- skuMap.set(key, []);
843
- skuMap.get(key).push(p);
844
- }
845
- const skuDupes = Array.from(skuMap.entries())
846
- .filter(([_, products]) => products.length > 1)
847
- .map(([sku, products]) => ({ sku, count: products.length, products }));
848
- // Pre-format as markdown — formatter drops nested products arrays
849
- const totalNameDupes = duplicates.reduce((s, d) => s + d.count, 0);
850
- const totalSkuDupes = skuDupes.reduce((s, d) => s + d.count, 0);
851
- const lines = [
852
- `## Duplicate Products`,
853
- `**By Name**: ${duplicates.length} groups (${totalNameDupes} products) | **By SKU**: ${skuDupes.length} groups (${totalSkuDupes} products)\n`,
854
- ];
855
- if (duplicates.length > 0) {
856
- lines.push("### Name Duplicates");
857
- for (const group of duplicates.slice(0, 20)) {
858
- lines.push(`\n**"${group.normalized_name}"** (${group.count} products):`);
859
- lines.push("| Name | SKU | Status | Stock | Created |");
860
- lines.push("| --- | --- | --- | ---: | --- |");
861
- for (const p of group.products) {
862
- lines.push(`| ${p.name} | ${p.sku || ""} | ${p.status} | ${p.stock_quantity ?? "—"} | ${p.created_at?.slice(0, 10) || "—"} |`);
863
- }
864
- }
865
- }
866
- if (skuDupes.length > 0) {
867
- lines.push("\n### SKU Duplicates");
868
- for (const group of skuDupes.slice(0, 20)) {
869
- lines.push(`\n**SKU "${group.sku}"** (${group.count} products):`);
870
- lines.push("| Name | SKU | Status | Stock | Created |");
871
- lines.push("| --- | --- | --- | ---: | --- |");
872
- for (const p of group.products) {
873
- lines.push(`| ${p.name} | ${p.sku || "—"} | ${p.status} | ${p.stock_quantity ?? "—"} | ${p.created_at?.slice(0, 10) || "—"} |`);
874
- }
875
- }
280
+ if (rows.length) await sb.from("product_field_schemas").insert(rows);
281
+ }
282
+ }
283
+
284
+ // If pricing_schema_id provided, hydrate pricing_data from schema
285
+ if (insert.pricing_schema_id && !args.pricing_data) {
286
+ const {
287
+ data: ps
288
+ } = await sb.from("pricing_schemas").select("tiers").eq("id", insert.pricing_schema_id).single();
289
+ if (ps?.tiers) productUpdates.pricing_data = ps.tiers;
290
+ }
291
+
292
+ // Apply any post-insert updates
293
+ if (Object.keys(productUpdates).length > 0) {
294
+ await sb.from("products").update(productUpdates).eq("id", data.id);
295
+ }
296
+
297
+ // Re-read the full product for response
298
+ const {
299
+ data: full
300
+ } = await sb.from("products").select("id, name, sku, slug, status, primary_category_id, pricing_schema_id, custom_fields, pricing_data, created_at").eq("id", data.id).single();
301
+ if (inherited.length && full) full.inherited = inherited;
302
+ return {
303
+ success: true,
304
+ data: full || data
305
+ };
306
+ }
307
+ case "update":
308
+ {
309
+ const pid = args.product_id;
310
+ if (!pid) return {
311
+ success: false,
312
+ error: "product_id is required"
313
+ };
314
+ const updates = {};
315
+ if (args.name !== undefined) updates.name = args.name;
316
+ if (args.sku !== undefined) updates.sku = args.sku;
317
+ if (args.description !== undefined) updates.description = args.description;
318
+ if (args.short_description !== undefined) updates.short_description = args.short_description;
319
+ if (args.type !== undefined) updates.type = args.type;
320
+ if (args.status !== undefined) updates.status = args.status;
321
+ if (args.cost_price !== undefined) updates.cost_price = args.cost_price;
322
+ if (args.wholesale_price !== undefined) updates.wholesale_price = args.wholesale_price;
323
+ if (args.featured !== undefined) updates.featured = args.featured;
324
+ if (args.stock_quantity !== undefined) updates.stock_quantity = args.stock_quantity;
325
+ if (args.manage_stock !== undefined) updates.manage_stock = args.manage_stock;
326
+ if (args.weight !== undefined) updates.weight = args.weight;
327
+ if (args.tax_status !== undefined) updates.tax_status = args.tax_status;
328
+ if (args.tax_class !== undefined) updates.tax_class = args.tax_class;
329
+ if (args.catalog_id !== undefined) updates.catalog_id = args.catalog_id;
330
+ if (args.pricing_schema_id !== undefined) updates.pricing_schema_id = args.pricing_schema_id;
331
+ if (args.pricing_data !== undefined) updates.pricing_data = args.pricing_data;
332
+ // custom_fields filtered to schema keys only (schema = source of truth)
333
+ if (args.custom_fields !== undefined) {
334
+ const agentFV = args.custom_fields;
335
+ // Look up product's linked field schema to get allowed keys
336
+ const {
337
+ data: pfs
338
+ } = await sb.from("product_field_schemas").select("field_schema_id").eq("product_id", pid).limit(1);
339
+ if (pfs?.length) {
340
+ const {
341
+ data: fsDef
342
+ } = await sb.from("field_schemas").select("fields").eq("id", pfs[0].field_schema_id).single();
343
+ if (fsDef?.fields && Array.isArray(fsDef.fields)) {
344
+ const {
345
+ data: existing
346
+ } = await sb.from("products").select("custom_fields").eq("id", pid).single();
347
+ const base = existing?.custom_fields || {};
348
+ const filtered = {
349
+ ...base
350
+ };
351
+ const schemaKeys = new Set(fsDef.fields.map(f => f.key).filter(Boolean));
352
+ for (const [k, v] of Object.entries(agentFV)) {
353
+ if (schemaKeys.has(k)) filtered[k] = v;
354
+ }
355
+ updates.custom_fields = filtered;
356
+ } else {
357
+ updates.custom_fields = agentFV; // no schema definition found, pass through
876
358
  }
877
- if (duplicates.length === 0 && skuDupes.length === 0) {
878
- lines.push("\nNo duplicates found.");
359
+ } else {
360
+ updates.custom_fields = agentFV; // no schema linked, pass through
361
+ }
362
+ }
363
+ if (args.is_wholesale !== undefined) updates.is_wholesale = args.is_wholesale;
364
+ if (args.wholesale_only !== undefined) updates.wholesale_only = args.wholesale_only;
365
+ if (args.minimum_wholesale_quantity !== undefined) updates.minimum_wholesale_quantity = args.minimum_wholesale_quantity;
366
+ if (args.featured_image !== undefined) updates.featured_image = args.featured_image;
367
+ if (args.image_gallery !== undefined) updates.image_gallery = args.image_gallery;
368
+ const updateCatArg = args.category ?? args.primary_category_id ?? args.category_id;
369
+ if (updateCatArg !== undefined) {
370
+ if (!updateCatArg) {
371
+ updates.primary_category_id = null;
372
+ } else if (/^[0-9a-f]{8}-/.test(updateCatArg)) {
373
+ updates.primary_category_id = updateCatArg;
374
+ } else {
375
+ const {
376
+ data: cats
377
+ } = await sb.from("categories").select("id").ilike("name", `%${updateCatArg}%`).eq("store_id", sid).limit(1);
378
+ if (cats?.length) updates.primary_category_id = cats[0].id;
379
+ }
380
+ }
381
+ const {
382
+ data,
383
+ error
384
+ } = await sb.from("products").update(updates).eq("id", pid).eq("store_id", sid).select("id, name, sku, slug, status, cost_price, pricing_schema_id, updated_at").single();
385
+ return error ? {
386
+ success: false,
387
+ error: error.message
388
+ } : {
389
+ success: true,
390
+ data
391
+ };
392
+ }
393
+ case "delete":
394
+ {
395
+ const pid = args.product_id;
396
+ if (!pid) return {
397
+ success: false,
398
+ error: "product_id is required"
399
+ };
400
+ if (args.hard === true) {
401
+ const {
402
+ error
403
+ } = await sb.from("products").delete().eq("id", pid).eq("store_id", sid);
404
+ return error ? {
405
+ success: false,
406
+ error: error.message
407
+ } : {
408
+ success: true,
409
+ data: {
410
+ id: pid,
411
+ deleted: true
879
412
  }
880
- return { success: true, data: lines.join("\n") };
881
- }
882
- case "merge": {
883
- const primaryId = args.primary_product_id;
884
- const secondaryId = args.secondary_product_id;
885
- if (!primaryId || !secondaryId)
886
- return { success: false, error: "primary_product_id and secondary_product_id required" };
887
- if (primaryId === secondaryId)
888
- return { success: false, error: "Cannot merge a product with itself" };
889
- // Verify both exist and belong to this store
890
- const { data: primary } = await sb.from("products").select("*").eq("id", primaryId).eq("store_id", sid).single();
891
- const { data: secondary } = await sb.from("products").select("*").eq("id", secondaryId).eq("store_id", sid).single();
892
- if (!primary)
893
- return { success: false, error: `Primary product ${primaryId} not found` };
894
- if (!secondary)
895
- return { success: false, error: `Secondary product ${secondaryId} not found` };
896
- const reassignResults = {};
897
- // 1. Consolidate inventory — sum quantities per location
898
- const { data: secInv } = await sb.from("inventory")
899
- .select("product_id, location_id, quantity")
900
- .eq("product_id", secondaryId).eq("store_id", sid);
901
- if (secInv?.length) {
902
- for (const row of secInv) {
903
- if (!row.quantity || row.quantity <= 0)
904
- continue;
905
- // Try to add to existing primary inventory at same location
906
- const { data: priRow } = await sb.from("inventory")
907
- .select("id, quantity")
908
- .eq("product_id", primaryId).eq("location_id", row.location_id).eq("store_id", sid)
909
- .maybeSingle();
910
- if (priRow) {
911
- await sb.from("inventory").update({ quantity: (priRow.quantity || 0) + row.quantity, updated_at: new Date().toISOString() }).eq("id", priRow.id);
912
- }
913
- else {
914
- await sb.from("inventory").insert({ store_id: sid, product_id: primaryId, location_id: row.location_id, quantity: row.quantity });
915
- }
916
- }
917
- // Zero out secondary inventory
918
- await sb.from("inventory").update({ quantity: 0 }).eq("product_id", secondaryId).eq("store_id", sid);
919
- reassignResults.inventory = `consolidated ${secInv.length} location(s)`;
413
+ };
414
+ }
415
+ const {
416
+ data,
417
+ error
418
+ } = await sb.from("products").update({
419
+ status: "archived"
420
+ }).eq("id", pid).eq("store_id", sid).select("id, name, status").single();
421
+ return error ? {
422
+ success: false,
423
+ error: error.message
424
+ } : {
425
+ success: true,
426
+ data
427
+ };
428
+ }
429
+
430
+ // ======================== CATEGORIES ========================
431
+
432
+ case "list_categories":
433
+ {
434
+ let q = sb.from("categories").select("id, name, slug, description, icon, parent_id, display_order, is_active, featured, product_count, catalog_id, field_schema_id, created_at").eq("store_id", sid).order("display_order", {
435
+ ascending: true
436
+ });
437
+ if (args.catalog_id) q = q.eq("catalog_id", args.catalog_id);
438
+ if (args.parent_id) q = q.eq("parent_id", args.parent_id);
439
+ if (args.active_only !== false) q = q.eq("is_active", true);
440
+ const {
441
+ data,
442
+ error
443
+ } = await q.limit(args.limit || 100);
444
+ return error ? {
445
+ success: false,
446
+ error: error.message
447
+ } : {
448
+ success: true,
449
+ count: data?.length,
450
+ data
451
+ };
452
+ }
453
+ case "get_category":
454
+ {
455
+ const catId = args.category_id;
456
+ const {
457
+ data: cat,
458
+ error: catErr
459
+ } = await sb.from("categories").select("*").eq("id", catId).eq("store_id", sid).single();
460
+ if (catErr) return {
461
+ success: false,
462
+ error: catErr.message
463
+ };
464
+ const {
465
+ data: fieldAssigns
466
+ } = await sb.from("category_field_schemas").select("sort_order, is_active, field_schema:field_schemas!field_schema_id(id, name, fields, icon)").eq("category_id", catId).eq("is_active", true).order("sort_order");
467
+ const {
468
+ data: pricingAssigns
469
+ } = await sb.from("category_pricing_schemas").select("sort_order, is_active, pricing_schema:pricing_schemas!pricing_schema_id(id, name, tiers, quality_tier)").eq("category_id", catId).eq("is_active", true).order("sort_order");
470
+ const {
471
+ data: children
472
+ } = await sb.from("categories").select("id, name, slug, display_order, is_active, product_count").eq("parent_id", catId).order("display_order");
473
+ return {
474
+ success: true,
475
+ data: {
476
+ ...cat,
477
+ field_schemas: fieldAssigns?.map(a => ({
478
+ ...a.field_schema,
479
+ sort_order: a.sort_order
480
+ })) || [],
481
+ pricing_schemas: pricingAssigns?.map(a => ({
482
+ ...a.pricing_schema,
483
+ sort_order: a.sort_order
484
+ })) || [],
485
+ subcategories: children || []
486
+ }
487
+ };
488
+ }
489
+ case "create_category":
490
+ {
491
+ const name = args.name;
492
+ if (!name) return {
493
+ success: false,
494
+ error: "name is required"
495
+ };
496
+ const insert = {
497
+ store_id: sid,
498
+ name,
499
+ slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
500
+ };
501
+ if (args.description) insert.description = args.description;
502
+ if (args.icon) insert.icon = args.icon;
503
+ if (args.parent_id) insert.parent_id = args.parent_id;
504
+ if (args.catalog_id) insert.catalog_id = args.catalog_id;
505
+ if (args.display_order !== undefined) insert.display_order = args.display_order;
506
+ if (args.field_schema_id) insert.field_schema_id = args.field_schema_id;
507
+ const {
508
+ data,
509
+ error
510
+ } = await sb.from("categories").insert(insert).select("id, name, slug, parent_id, catalog_id, display_order, created_at").single();
511
+ if (error) return {
512
+ success: false,
513
+ error: error.message
514
+ };
515
+ if (args.field_schema_ids && Array.isArray(args.field_schema_ids)) {
516
+ const rows = args.field_schema_ids.map((fsId, i) => ({
517
+ category_id: data.id,
518
+ field_schema_id: fsId,
519
+ sort_order: i + 1
520
+ }));
521
+ await sb.from("category_field_schemas").insert(rows);
522
+ }
523
+ if (args.pricing_schema_ids && Array.isArray(args.pricing_schema_ids)) {
524
+ const rows = args.pricing_schema_ids.map((psId, i) => ({
525
+ category_id: data.id,
526
+ pricing_schema_id: psId,
527
+ sort_order: i + 1
528
+ }));
529
+ await sb.from("category_pricing_schemas").insert(rows);
530
+ }
531
+ return {
532
+ success: true,
533
+ data
534
+ };
535
+ }
536
+ case "update_category":
537
+ {
538
+ const catId = args.category_id;
539
+ if (!catId) return {
540
+ success: false,
541
+ error: "category_id is required"
542
+ };
543
+ const updates = {};
544
+ if (args.name !== undefined) updates.name = args.name;
545
+ if (args.description !== undefined) updates.description = args.description;
546
+ if (args.icon !== undefined) updates.icon = args.icon;
547
+ if (args.parent_id !== undefined) updates.parent_id = args.parent_id;
548
+ if (args.catalog_id !== undefined) updates.catalog_id = args.catalog_id;
549
+ if (args.display_order !== undefined) updates.display_order = args.display_order;
550
+ if (args.is_active !== undefined) updates.is_active = args.is_active;
551
+ if (args.featured !== undefined) updates.featured = args.featured;
552
+ if (args.field_schema_id !== undefined) updates.field_schema_id = args.field_schema_id;
553
+ const {
554
+ data,
555
+ error
556
+ } = await sb.from("categories").update(updates).eq("id", catId).eq("store_id", sid).select("id, name, slug, is_active, display_order, updated_at").single();
557
+ return error ? {
558
+ success: false,
559
+ error: error.message
560
+ } : {
561
+ success: true,
562
+ data
563
+ };
564
+ }
565
+ case "delete_category":
566
+ {
567
+ const catId = args.category_id;
568
+ if (!catId) return {
569
+ success: false,
570
+ error: "category_id is required"
571
+ };
572
+ if (args.hard === true) {
573
+ const {
574
+ error
575
+ } = await sb.from("categories").delete().eq("id", catId).eq("store_id", sid);
576
+ return error ? {
577
+ success: false,
578
+ error: error.message
579
+ } : {
580
+ success: true,
581
+ data: {
582
+ id: catId,
583
+ deleted: true
920
584
  }
921
- else {
922
- reassignResults.inventory = "no inventory to consolidate";
585
+ };
586
+ }
587
+ const {
588
+ data,
589
+ error
590
+ } = await sb.from("categories").update({
591
+ is_active: false
592
+ }).eq("id", catId).eq("store_id", sid).select("id, name, is_active").single();
593
+ return error ? {
594
+ success: false,
595
+ error: error.message
596
+ } : {
597
+ success: true,
598
+ data
599
+ };
600
+ }
601
+
602
+ // ======================== FIELD SCHEMAS ========================
603
+
604
+ case "list_field_schemas":
605
+ {
606
+ let q = sb.from("field_schemas").select("id, name, slug, description, icon, fields, is_public, is_active, catalog_id, store_id, install_count, created_at").eq("is_active", true);
607
+ // Show store-owned schemas AND public schemas (prevent IDOR)
608
+ if (args.public_only === true) {
609
+ q = q.eq("is_public", true);
610
+ } else {
611
+ q = q.or(`store_id.eq.${sid},is_public.eq.true`);
612
+ }
613
+ if (args.catalog_id) q = q.eq("catalog_id", args.catalog_id);
614
+ if (args.limit) q = q.limit(args.limit);
615
+ const {
616
+ data,
617
+ error
618
+ } = await q.order("name");
619
+ return error ? {
620
+ success: false,
621
+ error: error.message
622
+ } : {
623
+ success: true,
624
+ count: data?.length,
625
+ data
626
+ };
627
+ }
628
+ case "get_field_schema":
629
+ {
630
+ const fsId = args.field_schema_id || args.schema_id;
631
+ if (!fsId) return {
632
+ success: false,
633
+ error: "field_schema_id is required"
634
+ };
635
+ // Allow access to own schemas OR public schemas (prevent IDOR)
636
+ const {
637
+ data,
638
+ error
639
+ } = await sb.from("field_schemas").select("*").eq("id", fsId).or(`store_id.eq.${sid},is_public.eq.true`).single();
640
+ if (error) return {
641
+ success: false,
642
+ error: error.message
643
+ };
644
+ const {
645
+ data: assignments
646
+ } = await sb.from("category_field_schemas").select("category:categories!category_id(id, name)").eq("field_schema_id", fsId).eq("is_active", true);
647
+ return {
648
+ success: true,
649
+ data: {
650
+ ...data,
651
+ assigned_categories: assignments?.map(a => a.category) || []
652
+ }
653
+ };
654
+ }
655
+ case "create_field_schema":
656
+ {
657
+ const name = args.name;
658
+ if (!name) return {
659
+ success: false,
660
+ error: "name is required"
661
+ };
662
+ if (!args.fields || !Array.isArray(args.fields)) return {
663
+ success: false,
664
+ error: "fields array is required"
665
+ };
666
+
667
+ // Auto-resolve catalog_id: use provided value, or fall back to store's default catalog
668
+ let catalogId = args.catalog_id;
669
+ if (!catalogId) {
670
+ const {
671
+ data: defaultCatalog
672
+ } = await sb.from("catalogs").select("id").eq("store_id", sid).eq("is_default", true).single();
673
+ if (defaultCatalog) catalogId = defaultCatalog.id;
674
+ }
675
+ const insert = {
676
+ store_id: sid,
677
+ name,
678
+ slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
679
+ fields: args.fields
680
+ };
681
+ if (args.description) insert.description = args.description;
682
+ if (args.icon) insert.icon = args.icon;
683
+ if (catalogId) insert.catalog_id = catalogId;
684
+ if (args.is_public !== undefined) insert.is_public = args.is_public;
685
+ const {
686
+ data,
687
+ error
688
+ } = await sb.from("field_schemas").insert(insert).select("id, name, slug, fields, icon, is_active, catalog_id, created_at").single();
689
+ return error ? {
690
+ success: false,
691
+ error: error.message
692
+ } : {
693
+ success: true,
694
+ data
695
+ };
696
+ }
697
+ case "update_field_schema":
698
+ {
699
+ const fsId = args.field_schema_id || args.schema_id;
700
+ if (!fsId) return {
701
+ success: false,
702
+ error: "field_schema_id is required"
703
+ };
704
+ const updates = {};
705
+ if (args.name !== undefined) updates.name = args.name;
706
+ if (args.description !== undefined) updates.description = args.description;
707
+ if (args.icon !== undefined) updates.icon = args.icon;
708
+ if (args.fields !== undefined) updates.fields = args.fields;
709
+ if (args.is_public !== undefined) updates.is_public = args.is_public;
710
+ if (args.is_active !== undefined) updates.is_active = args.is_active;
711
+ if (args.catalog_id !== undefined) updates.catalog_id = args.catalog_id;
712
+
713
+ // Only allow modification of own schemas (prevent IDOR)
714
+ const {
715
+ data,
716
+ error
717
+ } = await sb.from("field_schemas").update(updates).eq("id", fsId).eq("store_id", sid).select("id, name, slug, fields, icon, is_active, catalog_id, updated_at").single();
718
+ return error ? {
719
+ success: false,
720
+ error: error.message
721
+ } : {
722
+ success: true,
723
+ data
724
+ };
725
+ }
726
+ case "delete_field_schema":
727
+ {
728
+ const fsId = args.field_schema_id || args.schema_id;
729
+ if (!fsId) return {
730
+ success: false,
731
+ error: "field_schema_id is required"
732
+ };
733
+ // Only allow deletion of own schemas (prevent IDOR)
734
+ const {
735
+ data,
736
+ error
737
+ } = await sb.from("field_schemas").update({
738
+ is_active: false,
739
+ deleted_at: new Date().toISOString()
740
+ }).eq("id", fsId).eq("store_id", sid).select("id, name, is_active").single();
741
+ return error ? {
742
+ success: false,
743
+ error: error.message
744
+ } : {
745
+ success: true,
746
+ data
747
+ };
748
+ }
749
+
750
+ // ======================== PRICING SCHEMAS ========================
751
+
752
+ case "list_pricing_schemas":
753
+ {
754
+ let q = sb.from("pricing_schemas").select("id, name, slug, description, tiers, quality_tier, is_public, is_active, catalog_id, store_id, install_count, created_at").eq("is_active", true);
755
+ // Show store-owned schemas AND public schemas (prevent IDOR)
756
+ if (args.public_only === true) {
757
+ q = q.eq("is_public", true);
758
+ } else {
759
+ q = q.or(`store_id.eq.${sid},is_public.eq.true`);
760
+ }
761
+ if (args.catalog_id) q = q.eq("catalog_id", args.catalog_id);
762
+ if (args.limit) q = q.limit(args.limit);
763
+ const {
764
+ data,
765
+ error
766
+ } = await q.order("name");
767
+ return error ? {
768
+ success: false,
769
+ error: error.message
770
+ } : {
771
+ success: true,
772
+ count: data?.length,
773
+ data
774
+ };
775
+ }
776
+ case "get_pricing_schema":
777
+ {
778
+ const psId = args.pricing_schema_id || args.schema_id;
779
+ if (!psId) return {
780
+ success: false,
781
+ error: "pricing_schema_id is required"
782
+ };
783
+ // Allow access to own schemas OR public schemas (prevent IDOR)
784
+ const {
785
+ data,
786
+ error
787
+ } = await sb.from("pricing_schemas").select("*").eq("id", psId).or(`store_id.eq.${sid},is_public.eq.true`).single();
788
+ if (error) return {
789
+ success: false,
790
+ error: error.message
791
+ };
792
+ const {
793
+ data: assignments
794
+ } = await sb.from("category_pricing_schemas").select("category:categories!category_id(id, name)").eq("pricing_schema_id", psId).eq("is_active", true);
795
+ return {
796
+ success: true,
797
+ data: {
798
+ ...data,
799
+ assigned_categories: assignments?.map(a => a.category) || []
800
+ }
801
+ };
802
+ }
803
+ case "create_pricing_schema":
804
+ {
805
+ const name = args.name;
806
+ if (!name) return {
807
+ success: false,
808
+ error: "name is required"
809
+ };
810
+ if (!args.tiers || !Array.isArray(args.tiers)) return {
811
+ success: false,
812
+ error: "tiers array is required"
813
+ };
814
+
815
+ // Auto-resolve catalog_id: use provided value, or fall back to store's default catalog
816
+ let catalogId = args.catalog_id;
817
+ if (!catalogId) {
818
+ const {
819
+ data: defaultCatalog
820
+ } = await sb.from("catalogs").select("id").eq("store_id", sid).eq("is_default", true).single();
821
+ if (defaultCatalog) catalogId = defaultCatalog.id;
822
+ }
823
+ const insert = {
824
+ store_id: sid,
825
+ name,
826
+ slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
827
+ tiers: args.tiers
828
+ };
829
+ if (args.description) insert.description = args.description;
830
+ if (args.quality_tier) insert.quality_tier = args.quality_tier;
831
+ if (catalogId) insert.catalog_id = catalogId;
832
+ if (args.is_public !== undefined) insert.is_public = args.is_public;
833
+ const {
834
+ data,
835
+ error
836
+ } = await sb.from("pricing_schemas").insert(insert).select("id, name, slug, tiers, quality_tier, is_active, catalog_id, created_at").single();
837
+ return error ? {
838
+ success: false,
839
+ error: error.message
840
+ } : {
841
+ success: true,
842
+ data
843
+ };
844
+ }
845
+ case "update_pricing_schema":
846
+ {
847
+ const psId = args.pricing_schema_id || args.schema_id;
848
+ if (!psId) return {
849
+ success: false,
850
+ error: "pricing_schema_id is required"
851
+ };
852
+ const updates = {};
853
+ if (args.name !== undefined) updates.name = args.name;
854
+ if (args.description !== undefined) updates.description = args.description;
855
+ if (args.tiers !== undefined) updates.tiers = args.tiers;
856
+ if (args.quality_tier !== undefined) updates.quality_tier = args.quality_tier;
857
+ if (args.is_public !== undefined) updates.is_public = args.is_public;
858
+ if (args.is_active !== undefined) updates.is_active = args.is_active;
859
+ if (args.catalog_id !== undefined) updates.catalog_id = args.catalog_id;
860
+
861
+ // Only allow modification of own schemas (prevent IDOR)
862
+ const {
863
+ data,
864
+ error
865
+ } = await sb.from("pricing_schemas").update(updates).eq("id", psId).eq("store_id", sid).select("id, name, slug, tiers, quality_tier, is_active, catalog_id, updated_at").single();
866
+ return error ? {
867
+ success: false,
868
+ error: error.message
869
+ } : {
870
+ success: true,
871
+ data
872
+ };
873
+ }
874
+ case "delete_pricing_schema":
875
+ {
876
+ const psId = args.pricing_schema_id || args.schema_id;
877
+ if (!psId) return {
878
+ success: false,
879
+ error: "pricing_schema_id is required"
880
+ };
881
+ // Only allow deletion of own schemas (prevent IDOR)
882
+ const {
883
+ data,
884
+ error
885
+ } = await sb.from("pricing_schemas").update({
886
+ is_active: false,
887
+ deleted_at: new Date().toISOString()
888
+ }).eq("id", psId).eq("store_id", sid).select("id, name, is_active").single();
889
+ return error ? {
890
+ success: false,
891
+ error: error.message
892
+ } : {
893
+ success: true,
894
+ data
895
+ };
896
+ }
897
+
898
+ // ======================== CATALOGS ========================
899
+
900
+ case "list_catalogs":
901
+ {
902
+ const {
903
+ data,
904
+ error
905
+ } = await sb.from("catalogs").select("id, name, slug, description, vertical, is_active, is_default, display_order, created_at").eq("store_id", sid).order("display_order");
906
+ return error ? {
907
+ success: false,
908
+ error: error.message
909
+ } : {
910
+ success: true,
911
+ count: data?.length,
912
+ data
913
+ };
914
+ }
915
+ case "create_catalog":
916
+ {
917
+ const name = args.name;
918
+ if (!name) return {
919
+ success: false,
920
+ error: "name is required"
921
+ };
922
+ // Resolve owner_user_id from store
923
+ const {
924
+ data: store
925
+ } = await sb.from("stores").select("owner_user_id").eq("id", sid).single();
926
+ const insert = {
927
+ store_id: sid,
928
+ name,
929
+ slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
930
+ owner_user_id: store?.owner_user_id
931
+ };
932
+ if (args.description) insert.description = args.description;
933
+ if (args.vertical) insert.vertical = args.vertical;
934
+ if (args.is_default !== undefined) insert.is_default = args.is_default;
935
+ if (args.settings) insert.settings = args.settings;
936
+ const {
937
+ data,
938
+ error
939
+ } = await sb.from("catalogs").insert(insert).select("id, name, slug, vertical, is_default, created_at").single();
940
+ return error ? {
941
+ success: false,
942
+ error: error.message
943
+ } : {
944
+ success: true,
945
+ data
946
+ };
947
+ }
948
+ case "update_catalog":
949
+ {
950
+ const catId = args.catalog_id;
951
+ if (!catId) return {
952
+ success: false,
953
+ error: "catalog_id is required"
954
+ };
955
+ const updates = {};
956
+ if (args.name !== undefined) updates.name = args.name;
957
+ if (args.description !== undefined) updates.description = args.description;
958
+ if (args.vertical !== undefined) updates.vertical = args.vertical;
959
+ if (args.is_active !== undefined) updates.is_active = args.is_active;
960
+ if (args.is_default !== undefined) updates.is_default = args.is_default;
961
+ if (args.settings !== undefined) updates.settings = args.settings;
962
+ if (args.display_order !== undefined) updates.display_order = args.display_order;
963
+ const {
964
+ data,
965
+ error
966
+ } = await sb.from("catalogs").update(updates).eq("id", catId).eq("store_id", sid).select("id, name, slug, vertical, is_active, is_default, updated_at").single();
967
+ return error ? {
968
+ success: false,
969
+ error: error.message
970
+ } : {
971
+ success: true,
972
+ data
973
+ };
974
+ }
975
+
976
+ // ======================== SCHEMA ASSIGNMENTS ========================
977
+
978
+ case "assign_schema":
979
+ {
980
+ const target = args.target; // "category" or "product"
981
+ const schemaType = args.schema_type; // "field" or "pricing"
982
+ const targetId = args.target_id;
983
+ const schemaId = args.schema_id;
984
+ if (!target || !schemaType || !targetId || !schemaId) {
985
+ return {
986
+ success: false,
987
+ error: "target (category|product), schema_type (field|pricing), target_id, and schema_id are required"
988
+ };
989
+ }
990
+
991
+ // Verify target product/category belongs to this store (prevent IDOR)
992
+ const targetTable = target === "product" ? "products" : "categories";
993
+ const {
994
+ data: ownerCheck,
995
+ error: ownerErr
996
+ } = await sb.from(targetTable).select("id").eq("id", targetId).eq("store_id", sid).single();
997
+ if (ownerErr || !ownerCheck) {
998
+ return {
999
+ success: false,
1000
+ error: `${target} not found or does not belong to this store`
1001
+ };
1002
+ }
1003
+
1004
+ // Verify schema is accessible (own schema or public)
1005
+ const schemaTable = schemaType === "field" ? "field_schemas" : "pricing_schemas";
1006
+ const {
1007
+ data: schemaCheck,
1008
+ error: schemaErr
1009
+ } = await sb.from(schemaTable).select("id").eq("id", schemaId).or(`store_id.eq.${sid},is_public.eq.true`).single();
1010
+ if (schemaErr || !schemaCheck) {
1011
+ return {
1012
+ success: false,
1013
+ error: `${schemaType} schema not found or not accessible`
1014
+ };
1015
+ }
1016
+ const table = target === "product" ? schemaType === "field" ? "product_field_schemas" : "product_pricing_schemas" : schemaType === "field" ? "category_field_schemas" : "category_pricing_schemas";
1017
+ const fkCol = target === "product" ? "product_id" : "category_id";
1018
+ const schemaCol = schemaType === "field" ? "field_schema_id" : "pricing_schema_id";
1019
+ const row = {
1020
+ [fkCol]: targetId,
1021
+ [schemaCol]: schemaId
1022
+ };
1023
+ if (args.sort_order !== undefined) row.sort_order = args.sort_order;
1024
+ const {
1025
+ data,
1026
+ error
1027
+ } = await sb.from(table).upsert(row, {
1028
+ onConflict: `${fkCol},${schemaCol}`
1029
+ }).select().single();
1030
+ if (error) return {
1031
+ success: false,
1032
+ error: error.message
1033
+ };
1034
+
1035
+ // Hydrate product with schema data
1036
+ if (target === "product") {
1037
+ const productUpdates = {};
1038
+ if (schemaType === "pricing") {
1039
+ const {
1040
+ data: schema
1041
+ } = await sb.from("pricing_schemas").select("tiers, quality_tier").eq("id", schemaId).single();
1042
+ productUpdates.pricing_schema_id = schemaId;
1043
+ if (schema?.tiers) productUpdates.pricing_data = schema.tiers;
1044
+ }
1045
+ if (schemaType === "field") {
1046
+ // Schema is source of truth — rebuild custom_fields from schema keys only
1047
+ const {
1048
+ data: schema
1049
+ } = await sb.from("field_schemas").select("fields").eq("id", schemaId).single();
1050
+ if (schema?.fields && Array.isArray(schema.fields)) {
1051
+ const {
1052
+ data: product
1053
+ } = await sb.from("products").select("custom_fields").eq("id", targetId).eq("store_id", sid).single();
1054
+ const existing = product?.custom_fields || {};
1055
+ const rebuilt = {};
1056
+ for (const f of schema.fields) {
1057
+ const key = f.key;
1058
+ if (key) rebuilt[key] = key in existing ? existing[key] : f.default ?? null;
1059
+ }
1060
+ productUpdates.custom_fields = rebuilt;
923
1061
  }
924
- // 2. Reassign order_items
925
- const { error: oiErr, count: oiCount } = await sb.from("order_items")
926
- .update({ product_id: primaryId }).eq("product_id", secondaryId);
927
- reassignResults.order_items = oiErr ? `error: ${oiErr.message}` : `moved ${oiCount ?? 0} rows`;
928
- // 3. Reassign cart_items
929
- const { error: ciErr, count: ciCount } = await sb.from("cart_items")
930
- .update({ product_id: primaryId }).eq("product_id", secondaryId);
931
- reassignResults.cart_items = ciErr ? `error: ${ciErr.message}` : `moved ${ciCount ?? 0} rows`;
932
- // 4. Reassign purchase_order_items
933
- const { error: poiErr, count: poiCount } = await sb.from("purchase_order_items")
934
- .update({ product_id: primaryId }).eq("product_id", secondaryId);
935
- reassignResults.purchase_order_items = poiErr ? `error: ${poiErr.message}` : `moved ${poiCount ?? 0} rows`;
936
- // 5. Reassign product_reviews
937
- const { error: prErr, count: prCount } = await sb.from("product_reviews")
938
- .update({ product_id: primaryId }).eq("product_id", secondaryId);
939
- reassignResults.product_reviews = prErr ? `error: ${prErr.message}` : `moved ${prCount ?? 0} rows`;
940
- // 6. Fill in missing fields on primary from secondary
941
- const fillFields = ["description", "short_description", "cost_price", "wholesale_price", "weight", "featured_image", "image_gallery"];
942
- const fills = {};
943
- for (const field of fillFields) {
944
- if (!primary[field] && secondary[field])
945
- fills[field] = secondary[field];
1062
+ }
1063
+ if (Object.keys(productUpdates).length > 0) {
1064
+ await sb.from("products").update(productUpdates).eq("id", targetId).eq("store_id", sid);
1065
+ }
1066
+ }
1067
+ return {
1068
+ success: true,
1069
+ data
1070
+ };
1071
+ }
1072
+ case "unassign_schema":
1073
+ {
1074
+ const target = args.target;
1075
+ const schemaType = args.schema_type;
1076
+ const targetId = args.target_id;
1077
+ const schemaId = args.schema_id;
1078
+ if (!target || !schemaType || !targetId || !schemaId) {
1079
+ return {
1080
+ success: false,
1081
+ error: "target (category|product), schema_type (field|pricing), target_id, and schema_id are required"
1082
+ };
1083
+ }
1084
+
1085
+ // Verify target product/category belongs to this store (prevent IDOR)
1086
+ const targetTable = target === "product" ? "products" : "categories";
1087
+ const {
1088
+ data: ownerCheck,
1089
+ error: ownerErr
1090
+ } = await sb.from(targetTable).select("id").eq("id", targetId).eq("store_id", sid).single();
1091
+ if (ownerErr || !ownerCheck) {
1092
+ return {
1093
+ success: false,
1094
+ error: `${target} not found or does not belong to this store`
1095
+ };
1096
+ }
1097
+ const table = target === "product" ? schemaType === "field" ? "product_field_schemas" : "product_pricing_schemas" : schemaType === "field" ? "category_field_schemas" : "category_pricing_schemas";
1098
+ const fkCol = target === "product" ? "product_id" : "category_id";
1099
+ const schemaCol = schemaType === "field" ? "field_schema_id" : "pricing_schema_id";
1100
+ const {
1101
+ error
1102
+ } = await sb.from(table).delete().eq(fkCol, targetId).eq(schemaCol, schemaId);
1103
+ return error ? {
1104
+ success: false,
1105
+ error: error.message
1106
+ } : {
1107
+ success: true,
1108
+ data: {
1109
+ removed: true,
1110
+ target,
1111
+ schema_type: schemaType,
1112
+ target_id: targetId,
1113
+ schema_id: schemaId
1114
+ }
1115
+ };
1116
+ }
1117
+
1118
+ // ======================== PRODUCT DEDUP / MERGE ========================
1119
+
1120
+ case "find_duplicates":
1121
+ {
1122
+ // Find products with similar names (potential duplicates)
1123
+ const {
1124
+ data: all,
1125
+ error: allErr
1126
+ } = await sb.from("products").select("id, name, sku, status, stock_quantity, cost_price, created_at").eq("store_id", sid).neq("status", "archived").order("name");
1127
+ if (allErr) return {
1128
+ success: false,
1129
+ error: allErr.message
1130
+ };
1131
+
1132
+ // Group by normalized name (lowercase, trim, strip common suffixes like "(TEMP)", "(OLD)", "(COPY)")
1133
+ const nameMap = new Map();
1134
+ for (const p of all || []) {
1135
+ const normalized = (p.name || "").toLowerCase().trim().replace(/\s*\((temp|old|copy|duplicate|dup|new|v2)\)\s*/gi, "").replace(/\s+/g, " ");
1136
+ if (!normalized) continue;
1137
+ if (!nameMap.has(normalized)) nameMap.set(normalized, []);
1138
+ nameMap.get(normalized).push(p);
1139
+ }
1140
+ const duplicates = Array.from(nameMap.entries()).filter(([_, products]) => products.length > 1).map(([normalizedName, products]) => ({
1141
+ normalized_name: normalizedName,
1142
+ count: products.length,
1143
+ products: products.map(p => ({
1144
+ id: p.id,
1145
+ name: p.name,
1146
+ sku: p.sku,
1147
+ status: p.status,
1148
+ stock_quantity: p.stock_quantity,
1149
+ cost_price: p.cost_price,
1150
+ created_at: p.created_at
1151
+ }))
1152
+ }));
1153
+
1154
+ // Also check by exact SKU match
1155
+ const skuMap = new Map();
1156
+ for (const p of all || []) {
1157
+ if (!p.sku) continue;
1158
+ const key = p.sku.toLowerCase().trim();
1159
+ if (!skuMap.has(key)) skuMap.set(key, []);
1160
+ skuMap.get(key).push(p);
1161
+ }
1162
+ const skuDupes = Array.from(skuMap.entries()).filter(([_, products]) => products.length > 1).map(([sku, products]) => ({
1163
+ sku,
1164
+ count: products.length,
1165
+ products
1166
+ }));
1167
+
1168
+ // Pre-format as markdown — formatter drops nested products arrays
1169
+ const totalNameDupes = duplicates.reduce((s, d) => s + d.count, 0);
1170
+ const totalSkuDupes = skuDupes.reduce((s, d) => s + d.count, 0);
1171
+ const lines = [`## Duplicate Products`, `**By Name**: ${duplicates.length} groups (${totalNameDupes} products) | **By SKU**: ${skuDupes.length} groups (${totalSkuDupes} products)\n`];
1172
+ if (duplicates.length > 0) {
1173
+ lines.push("### Name Duplicates");
1174
+ for (const group of duplicates.slice(0, 20)) {
1175
+ lines.push(`\n**"${group.normalized_name}"** (${group.count} products):`);
1176
+ lines.push("| Name | SKU | Status | Stock | Created |");
1177
+ lines.push("| --- | --- | --- | ---: | --- |");
1178
+ for (const p of group.products) {
1179
+ lines.push(`| ${p.name} | ${p.sku || "—"} | ${p.status} | ${p.stock_quantity ?? "—"} | ${p.created_at?.slice(0, 10) || "—"} |`);
946
1180
  }
947
- // Merge custom_fields (secondary fills gaps)
948
- if (secondary.custom_fields && typeof secondary.custom_fields === "object") {
949
- const merged = { ...secondary.custom_fields, ...(primary.custom_fields || {}) };
950
- fills.custom_fields = merged;
1181
+ }
1182
+ }
1183
+ if (skuDupes.length > 0) {
1184
+ lines.push("\n### SKU Duplicates");
1185
+ for (const group of skuDupes.slice(0, 20)) {
1186
+ lines.push(`\n**SKU "${group.sku}"** (${group.count} products):`);
1187
+ lines.push("| Name | SKU | Status | Stock | Created |");
1188
+ lines.push("| --- | --- | --- | ---: | --- |");
1189
+ for (const p of group.products) {
1190
+ lines.push(`| ${p.name} | ${p.sku || "—"} | ${p.status} | ${p.stock_quantity ?? "—"} | ${p.created_at?.slice(0, 10) || "—"} |`);
951
1191
  }
952
- if (Object.keys(fills).length > 0) {
953
- await sb.from("products").update(fills).eq("id", primaryId).eq("store_id", sid);
954
- reassignResults.field_fills = Object.keys(fills).join(", ");
1192
+ }
1193
+ }
1194
+ if (duplicates.length === 0 && skuDupes.length === 0) {
1195
+ lines.push("\nNo duplicates found.");
1196
+ }
1197
+ return {
1198
+ success: true,
1199
+ data: lines.join("\n")
1200
+ };
1201
+ }
1202
+ case "merge":
1203
+ {
1204
+ const primaryId = args.primary_product_id;
1205
+ const secondaryId = args.secondary_product_id;
1206
+ if (!primaryId || !secondaryId) return {
1207
+ success: false,
1208
+ error: "primary_product_id and secondary_product_id required"
1209
+ };
1210
+ if (primaryId === secondaryId) return {
1211
+ success: false,
1212
+ error: "Cannot merge a product with itself"
1213
+ };
1214
+
1215
+ // Verify both exist and belong to this store
1216
+ const {
1217
+ data: primary
1218
+ } = await sb.from("products").select("*").eq("id", primaryId).eq("store_id", sid).single();
1219
+ const {
1220
+ data: secondary
1221
+ } = await sb.from("products").select("*").eq("id", secondaryId).eq("store_id", sid).single();
1222
+ if (!primary) return {
1223
+ success: false,
1224
+ error: `Primary product ${primaryId} not found`
1225
+ };
1226
+ if (!secondary) return {
1227
+ success: false,
1228
+ error: `Secondary product ${secondaryId} not found`
1229
+ };
1230
+ const reassignResults = {};
1231
+
1232
+ // 1. Consolidate inventory — sum quantities per location
1233
+ const {
1234
+ data: secInv
1235
+ } = await sb.from("inventory").select("product_id, location_id, quantity").eq("product_id", secondaryId).eq("store_id", sid);
1236
+ if (secInv?.length) {
1237
+ for (const row of secInv) {
1238
+ if (!row.quantity || row.quantity <= 0) continue;
1239
+ // Try to add to existing primary inventory at same location
1240
+ const {
1241
+ data: priRow
1242
+ } = await sb.from("inventory").select("id, quantity").eq("product_id", primaryId).eq("location_id", row.location_id).eq("store_id", sid).maybeSingle();
1243
+ if (priRow) {
1244
+ await sb.from("inventory").update({
1245
+ quantity: (priRow.quantity || 0) + row.quantity,
1246
+ updated_at: new Date().toISOString()
1247
+ }).eq("id", priRow.id);
1248
+ } else {
1249
+ await sb.from("inventory").insert({
1250
+ store_id: sid,
1251
+ product_id: primaryId,
1252
+ location_id: row.location_id,
1253
+ quantity: row.quantity
1254
+ });
955
1255
  }
956
- // 7. Archive the secondary product
957
- await sb.from("products").update({
958
- status: "archived",
959
- name: `[MERGED] ${secondary.name}`,
960
- }).eq("id", secondaryId).eq("store_id", sid);
961
- // Re-read primary
962
- const { data: merged } = await sb.from("products")
963
- .select("id, name, sku, status, stock_quantity, cost_price, created_at, updated_at")
964
- .eq("id", primaryId).single();
965
- return {
966
- success: true,
967
- data: {
968
- merged_product: merged,
969
- archived_product: { id: secondaryId, name: secondary.name },
970
- reassign_results: reassignResults,
971
- }
972
- };
1256
+ }
1257
+ // Zero out secondary inventory
1258
+ await sb.from("inventory").update({
1259
+ quantity: 0
1260
+ }).eq("product_id", secondaryId).eq("store_id", sid);
1261
+ reassignResults.inventory = `consolidated ${secInv.length} location(s)`;
1262
+ } else {
1263
+ reassignResults.inventory = "no inventory to consolidate";
973
1264
  }
974
- default:
975
- return { success: false, error: `Unknown products action: ${args.action}. Valid: browse, find, get, create, update, delete, find_duplicates, merge, list_categories, get_category, create_category, update_category, delete_category, list_field_schemas, get_field_schema, create_field_schema, update_field_schema, delete_field_schema, list_pricing_schemas, get_pricing_schema, create_pricing_schema, update_pricing_schema, delete_pricing_schema, list_catalogs, create_catalog, update_catalog, assign_schema, unassign_schema` };
976
- }
1265
+
1266
+ // 2. Reassign order_items
1267
+ const {
1268
+ error: oiErr,
1269
+ count: oiCount
1270
+ } = await sb.from("order_items").update({
1271
+ product_id: primaryId
1272
+ }).eq("product_id", secondaryId);
1273
+ reassignResults.order_items = oiErr ? `error: ${oiErr.message}` : `moved ${oiCount ?? 0} rows`;
1274
+
1275
+ // 3. Reassign cart_items
1276
+ const {
1277
+ error: ciErr,
1278
+ count: ciCount
1279
+ } = await sb.from("cart_items").update({
1280
+ product_id: primaryId
1281
+ }).eq("product_id", secondaryId);
1282
+ reassignResults.cart_items = ciErr ? `error: ${ciErr.message}` : `moved ${ciCount ?? 0} rows`;
1283
+
1284
+ // 4. Reassign purchase_order_items
1285
+ const {
1286
+ error: poiErr,
1287
+ count: poiCount
1288
+ } = await sb.from("purchase_order_items").update({
1289
+ product_id: primaryId
1290
+ }).eq("product_id", secondaryId);
1291
+ reassignResults.purchase_order_items = poiErr ? `error: ${poiErr.message}` : `moved ${poiCount ?? 0} rows`;
1292
+
1293
+ // 5. Reassign product_reviews
1294
+ const {
1295
+ error: prErr,
1296
+ count: prCount
1297
+ } = await sb.from("product_reviews").update({
1298
+ product_id: primaryId
1299
+ }).eq("product_id", secondaryId);
1300
+ reassignResults.product_reviews = prErr ? `error: ${prErr.message}` : `moved ${prCount ?? 0} rows`;
1301
+
1302
+ // 6. Fill in missing fields on primary from secondary
1303
+ const fillFields = ["description", "short_description", "cost_price", "wholesale_price", "weight", "featured_image", "image_gallery"];
1304
+ const fills = {};
1305
+ for (const field of fillFields) {
1306
+ if (!primary[field] && secondary[field]) fills[field] = secondary[field];
1307
+ }
1308
+ // Merge custom_fields (secondary fills gaps)
1309
+ if (secondary.custom_fields && typeof secondary.custom_fields === "object") {
1310
+ const merged = {
1311
+ ...secondary.custom_fields,
1312
+ ...(primary.custom_fields || {})
1313
+ };
1314
+ fills.custom_fields = merged;
1315
+ }
1316
+ if (Object.keys(fills).length > 0) {
1317
+ await sb.from("products").update(fills).eq("id", primaryId).eq("store_id", sid);
1318
+ reassignResults.field_fills = Object.keys(fills).join(", ");
1319
+ }
1320
+
1321
+ // 7. Archive the secondary product
1322
+ await sb.from("products").update({
1323
+ status: "archived",
1324
+ name: `[MERGED] ${secondary.name}`
1325
+ }).eq("id", secondaryId).eq("store_id", sid);
1326
+
1327
+ // Re-read primary
1328
+ const {
1329
+ data: merged
1330
+ } = await sb.from("products").select("id, name, sku, status, stock_quantity, cost_price, created_at, updated_at").eq("id", primaryId).single();
1331
+ return {
1332
+ success: true,
1333
+ data: {
1334
+ merged_product: merged,
1335
+ archived_product: {
1336
+ id: secondaryId,
1337
+ name: secondary.name
1338
+ },
1339
+ reassign_results: reassignResults
1340
+ }
1341
+ };
1342
+ }
1343
+ default:
1344
+ return {
1345
+ success: false,
1346
+ error: `Unknown products action: ${args.action}. Valid: browse, find, get, create, update, delete, find_duplicates, merge, list_categories, get_category, create_category, update_category, delete_category, list_field_schemas, get_field_schema, create_field_schema, update_field_schema, delete_field_schema, list_pricing_schemas, get_pricing_schema, create_pricing_schema, update_pricing_schema, delete_pricing_schema, list_catalogs, create_catalog, update_catalog, assign_schema, unassign_schema`
1347
+ };
1348
+ }
977
1349
  }
978
1350
  export async function handleCollections(sb, args, storeId) {
979
- const sid = storeId;
980
- switch (args.action) {
981
- case "find": {
982
- let q = sb.from("creation_collections").select("*").eq("store_id", sid);
983
- if (args.name) {
984
- const sn = sanitizeFilterValue(args.name);
985
- q = q.ilike("name", `%${sn}%`);
986
- }
987
- const { data, error } = await q.limit(100);
988
- return error ? { success: false, error: error.message } : { success: true, data };
989
- }
990
- case "create": {
991
- const slug = args.slug || (args.name || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
992
- const { data, error } = await sb.from("creation_collections")
993
- .insert({ store_id: sid, name: args.name, slug }).select().single();
994
- return error ? { success: false, error: error.message } : { success: true, data };
995
- }
996
- default:
997
- return { success: false, error: `Unknown collections action: ${args.action}` };
998
- }
1351
+ const sid = storeId;
1352
+ switch (args.action) {
1353
+ case "find":
1354
+ {
1355
+ let q = sb.from("creation_collections").select("*").eq("store_id", sid);
1356
+ if (args.name) {
1357
+ const sn = sanitizeFilterValue(args.name);
1358
+ q = q.ilike("name", `%${sn}%`);
1359
+ }
1360
+ const {
1361
+ data,
1362
+ error
1363
+ } = await q.limit(100);
1364
+ return error ? {
1365
+ success: false,
1366
+ error: error.message
1367
+ } : {
1368
+ success: true,
1369
+ data
1370
+ };
1371
+ }
1372
+ case "create":
1373
+ {
1374
+ const slug = args.slug || (args.name || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1375
+ const {
1376
+ data,
1377
+ error
1378
+ } = await sb.from("creation_collections").insert({
1379
+ store_id: sid,
1380
+ name: args.name,
1381
+ slug
1382
+ }).select().single();
1383
+ return error ? {
1384
+ success: false,
1385
+ error: error.message
1386
+ } : {
1387
+ success: true,
1388
+ data
1389
+ };
1390
+ }
1391
+ default:
1392
+ return {
1393
+ success: false,
1394
+ error: `Unknown collections action: ${args.action}`
1395
+ };
1396
+ }
999
1397
  }
1398
+ //# sourceMappingURL=catalog.js.map