whale-code 6.5.5 → 6.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (847) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +17 -2
  3. package/dist/cli/app.js +148 -72
  4. package/dist/cli/app.js.map +1 -0
  5. package/dist/cli/chat/AgentSelector.js +105 -10
  6. package/dist/cli/chat/AgentSelector.js.map +1 -0
  7. package/dist/cli/chat/ChatApp.d.ts +31 -0
  8. package/dist/cli/chat/ChatApp.js +539 -286
  9. package/dist/cli/chat/ChatApp.js.map +1 -0
  10. package/dist/cli/chat/ChatInput.js +1088 -770
  11. package/dist/cli/chat/ChatInput.js.map +1 -0
  12. package/dist/cli/chat/MarkdownText.js +39 -14
  13. package/dist/cli/chat/MarkdownText.js.map +1 -0
  14. package/dist/cli/chat/MemoryManager.js +181 -46
  15. package/dist/cli/chat/MemoryManager.js.map +1 -0
  16. package/dist/cli/chat/MessageList.d.ts +2 -3
  17. package/dist/cli/chat/MessageList.js +186 -45
  18. package/dist/cli/chat/MessageList.js.map +1 -0
  19. package/dist/cli/chat/ModelSelector.js +282 -63
  20. package/dist/cli/chat/ModelSelector.js.map +1 -0
  21. package/dist/cli/chat/NodeManager.js +165 -75
  22. package/dist/cli/chat/NodeManager.js.map +1 -0
  23. package/dist/cli/chat/NodeSelector.js +171 -30
  24. package/dist/cli/chat/NodeSelector.js.map +1 -0
  25. package/dist/cli/chat/PlanApproval.js +281 -57
  26. package/dist/cli/chat/PlanApproval.js.map +1 -0
  27. package/dist/cli/chat/RewindViewer.js +559 -144
  28. package/dist/cli/chat/RewindViewer.js.map +1 -0
  29. package/dist/cli/chat/SessionManager.js +137 -30
  30. package/dist/cli/chat/SessionManager.js.map +1 -0
  31. package/dist/cli/chat/SlashMenu.js +293 -164
  32. package/dist/cli/chat/SlashMenu.js.map +1 -0
  33. package/dist/cli/chat/StatusBar.js +172 -9
  34. package/dist/cli/chat/StatusBar.js.map +1 -0
  35. package/dist/cli/chat/StoreSelector.js +147 -18
  36. package/dist/cli/chat/StoreSelector.js.map +1 -0
  37. package/dist/cli/chat/StreamingText.d.ts +1 -5
  38. package/dist/cli/chat/StreamingText.js +22 -7
  39. package/dist/cli/chat/StreamingText.js.map +1 -0
  40. package/dist/cli/chat/SubagentPanel.d.ts +1 -2
  41. package/dist/cli/chat/SubagentPanel.js +612 -72
  42. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  43. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  44. package/dist/cli/chat/TeamPanel.js +230 -30
  45. package/dist/cli/chat/TeamPanel.js.map +1 -0
  46. package/dist/cli/chat/ThemeSelector.js +84 -24
  47. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  48. package/dist/cli/chat/ToolIndicator.js +1476 -371
  49. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  50. package/dist/cli/chat/hooks/useAgentLoop.d.ts +1 -0
  51. package/dist/cli/chat/hooks/useAgentLoop.js +481 -367
  52. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  53. package/dist/cli/chat/hooks/useSlashCommands.d.ts +3 -14
  54. package/dist/cli/chat/hooks/useSlashCommands.js +744 -572
  55. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  56. package/dist/cli/commands/config-cmd.js +56 -57
  57. package/dist/cli/commands/config-cmd.js.map +1 -0
  58. package/dist/cli/commands/db.js +184 -169
  59. package/dist/cli/commands/db.js.map +1 -0
  60. package/dist/cli/commands/doctor.js +212 -122
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/init.js +211 -244
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/mcp.js +127 -122
  65. package/dist/cli/commands/mcp.js.map +1 -0
  66. package/dist/cli/login/LoginApp.js +355 -141
  67. package/dist/cli/login/LoginApp.js.map +1 -0
  68. package/dist/cli/print-mode.js +196 -177
  69. package/dist/cli/print-mode.js.map +1 -0
  70. package/dist/cli/serve-mode.js +615 -530
  71. package/dist/cli/serve-mode.js.map +1 -0
  72. package/dist/cli/services/agent-config.d.ts +5 -1
  73. package/dist/cli/services/agent-config.js +66 -36
  74. package/dist/cli/services/agent-config.js.map +1 -0
  75. package/dist/cli/services/agent-definitions.d.ts +4 -1
  76. package/dist/cli/services/agent-definitions.js +97 -56
  77. package/dist/cli/services/agent-definitions.js.map +1 -0
  78. package/dist/cli/services/agent-events.js +225 -162
  79. package/dist/cli/services/agent-events.js.map +1 -0
  80. package/dist/cli/services/agent-loop.js +976 -688
  81. package/dist/cli/services/agent-loop.js.map +1 -0
  82. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  83. package/dist/cli/services/agent-worker-base.js +337 -153
  84. package/dist/cli/services/agent-worker-base.js.map +1 -0
  85. package/dist/cli/services/api-retry.js +69 -64
  86. package/dist/cli/services/api-retry.js.map +1 -0
  87. package/dist/cli/services/auth-service.d.ts +3 -3
  88. package/dist/cli/services/auth-service.js +209 -132
  89. package/dist/cli/services/auth-service.js.map +1 -0
  90. package/dist/cli/services/background-processes.js +343 -267
  91. package/dist/cli/services/background-processes.js.map +1 -0
  92. package/dist/cli/services/browser-auth.d.ts +2 -2
  93. package/dist/cli/services/browser-auth.js +159 -118
  94. package/dist/cli/services/browser-auth.js.map +1 -0
  95. package/dist/cli/services/claude-md-loader.js +40 -36
  96. package/dist/cli/services/claude-md-loader.js.map +1 -0
  97. package/dist/cli/services/config-store.d.ts +9 -4
  98. package/dist/cli/services/config-store.js +164 -117
  99. package/dist/cli/services/config-store.js.map +1 -0
  100. package/dist/cli/services/debug-log.d.ts +1 -1
  101. package/dist/cli/services/debug-log.js +34 -35
  102. package/dist/cli/services/debug-log.js.map +1 -0
  103. package/dist/cli/services/env-detect.d.ts +7 -0
  104. package/dist/cli/services/env-detect.js +9 -0
  105. package/dist/cli/services/env-detect.js.map +1 -0
  106. package/dist/cli/services/error-logger.js +187 -169
  107. package/dist/cli/services/error-logger.js.map +1 -0
  108. package/dist/cli/services/file-history.d.ts +1 -1
  109. package/dist/cli/services/file-history.js +50 -54
  110. package/dist/cli/services/file-history.js.map +1 -0
  111. package/dist/cli/services/format-server-response.js +332 -372
  112. package/dist/cli/services/format-server-response.js.map +1 -0
  113. package/dist/cli/services/git-context.js +61 -45
  114. package/dist/cli/services/git-context.js.map +1 -0
  115. package/dist/cli/services/hooks.d.ts +2 -2
  116. package/dist/cli/services/hooks.js +195 -180
  117. package/dist/cli/services/hooks.js.map +1 -0
  118. package/dist/cli/services/ink-incremental.d.ts +19 -0
  119. package/dist/cli/services/ink-incremental.js +59 -0
  120. package/dist/cli/services/ink-incremental.js.map +1 -0
  121. package/dist/cli/services/ink-resize-fix.js +54 -44
  122. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  123. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  124. package/dist/cli/services/ink-sync-output.js +16 -0
  125. package/dist/cli/services/ink-sync-output.js.map +1 -0
  126. package/dist/cli/services/interactive-tools.js +268 -212
  127. package/dist/cli/services/interactive-tools.js.map +1 -0
  128. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  129. package/dist/cli/services/keybinding-manager.js +126 -63
  130. package/dist/cli/services/keybinding-manager.js.map +1 -0
  131. package/dist/cli/services/local-tools.d.ts +1 -1
  132. package/dist/cli/services/local-tools.js +939 -656
  133. package/dist/cli/services/local-tools.js.map +1 -0
  134. package/dist/cli/services/lsp-manager.js +757 -594
  135. package/dist/cli/services/lsp-manager.js.map +1 -0
  136. package/dist/cli/services/mcp-client.d.ts +1 -1
  137. package/dist/cli/services/mcp-client.js +173 -134
  138. package/dist/cli/services/mcp-client.js.map +1 -0
  139. package/dist/cli/services/memory-manager.js +53 -40
  140. package/dist/cli/services/memory-manager.js.map +1 -0
  141. package/dist/cli/services/model-manager.js +55 -40
  142. package/dist/cli/services/model-manager.js.map +1 -0
  143. package/dist/cli/services/model-router.js +115 -85
  144. package/dist/cli/services/model-router.js.map +1 -0
  145. package/dist/cli/services/paths.d.ts +30 -0
  146. package/dist/cli/services/paths.js +81 -0
  147. package/dist/cli/services/paths.js.map +1 -0
  148. package/dist/cli/services/permission-modes.js +32 -25
  149. package/dist/cli/services/permission-modes.js.map +1 -0
  150. package/dist/cli/services/rewind.js +182 -168
  151. package/dist/cli/services/rewind.js.map +1 -0
  152. package/dist/cli/services/ripgrep.js +115 -115
  153. package/dist/cli/services/ripgrep.js.map +1 -0
  154. package/dist/cli/services/sandbox.d.ts +1 -1
  155. package/dist/cli/services/sandbox.js +58 -37
  156. package/dist/cli/services/sandbox.js.map +1 -0
  157. package/dist/cli/services/server-tools.js +738 -565
  158. package/dist/cli/services/server-tools.js.map +1 -0
  159. package/dist/cli/services/session-persistence.js +69 -74
  160. package/dist/cli/services/session-persistence.js.map +1 -0
  161. package/dist/cli/services/subagent-worker.js +42 -27
  162. package/dist/cli/services/subagent-worker.js.map +1 -0
  163. package/dist/cli/services/subagent.d.ts +2 -0
  164. package/dist/cli/services/subagent.js +605 -433
  165. package/dist/cli/services/subagent.js.map +1 -0
  166. package/dist/cli/services/system-prompt.js +86 -78
  167. package/dist/cli/services/system-prompt.js.map +1 -0
  168. package/dist/cli/services/task-decomposer.d.ts +1 -1
  169. package/dist/cli/services/task-decomposer.js +172 -139
  170. package/dist/cli/services/task-decomposer.js.map +1 -0
  171. package/dist/cli/services/team-lead.d.ts +2 -2
  172. package/dist/cli/services/team-lead.js +727 -529
  173. package/dist/cli/services/team-lead.js.map +1 -0
  174. package/dist/cli/services/team-state.js +319 -319
  175. package/dist/cli/services/team-state.js.map +1 -0
  176. package/dist/cli/services/teammate.d.ts +8 -2
  177. package/dist/cli/services/teammate.js +857 -569
  178. package/dist/cli/services/teammate.js.map +1 -0
  179. package/dist/cli/services/telemetry.d.ts +6 -1
  180. package/dist/cli/services/telemetry.js +180 -157
  181. package/dist/cli/services/telemetry.js.map +1 -0
  182. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  183. package/dist/cli/services/tools/agent-tools.js +480 -322
  184. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  185. package/dist/cli/services/tools/file-ops.js +563 -450
  186. package/dist/cli/services/tools/file-ops.js.map +1 -0
  187. package/dist/cli/services/tools/search-tools.js +231 -162
  188. package/dist/cli/services/tools/search-tools.js.map +1 -0
  189. package/dist/cli/services/tools/shell-exec.js +197 -151
  190. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  191. package/dist/cli/services/tools/task-manager.js +206 -173
  192. package/dist/cli/services/tools/task-manager.js.map +1 -0
  193. package/dist/cli/services/tools/web-tools.js +388 -341
  194. package/dist/cli/services/tools/web-tools.js.map +1 -0
  195. package/dist/cli/setup/SetupApp.d.ts +2 -2
  196. package/dist/cli/setup/SetupApp.js +608 -160
  197. package/dist/cli/setup/SetupApp.js.map +1 -0
  198. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  199. package/dist/cli/shared/ErrorBoundary.js +73 -0
  200. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  201. package/dist/cli/shared/MatrixIntro.js +66 -69
  202. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  203. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  204. package/dist/cli/shared/SpinnerSlot.js +63 -0
  205. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  206. package/dist/cli/shared/Theme.d.ts +1 -1
  207. package/dist/cli/shared/Theme.js +136 -92
  208. package/dist/cli/shared/Theme.js.map +1 -0
  209. package/dist/cli/shared/WhaleBanner.js +99 -11
  210. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  211. package/dist/cli/shared/markdown.d.ts +3 -1
  212. package/dist/cli/shared/markdown.js +736 -674
  213. package/dist/cli/shared/markdown.js.map +1 -0
  214. package/dist/cli/shared/marked-terminal.d.js +2 -0
  215. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  216. package/dist/cli/shared/theme-manager.js +99 -90
  217. package/dist/cli/shared/theme-manager.js.map +1 -0
  218. package/dist/cli/shared/theme-presets.js +256 -254
  219. package/dist/cli/shared/theme-presets.js.map +1 -0
  220. package/dist/cli/status/StatusApp.js +235 -86
  221. package/dist/cli/status/StatusApp.js.map +1 -0
  222. package/dist/cli/stores/StoreApp.js +275 -65
  223. package/dist/cli/stores/StoreApp.js.map +1 -0
  224. package/dist/index.d.ts +2 -2
  225. package/dist/index.js +509 -396
  226. package/dist/index.js.map +1 -0
  227. package/dist/local-agent/connection.d.ts +2 -2
  228. package/dist/local-agent/connection.js +352 -293
  229. package/dist/local-agent/connection.js.map +1 -0
  230. package/dist/local-agent/discovery.js +259 -122
  231. package/dist/local-agent/discovery.js.map +1 -0
  232. package/dist/local-agent/executor.js +216 -193
  233. package/dist/local-agent/executor.js.map +1 -0
  234. package/dist/local-agent/index.d.ts +2 -2
  235. package/dist/local-agent/index.js +156 -156
  236. package/dist/local-agent/index.js.map +1 -0
  237. package/dist/node/adapters/base.js +18 -8
  238. package/dist/node/adapters/base.js.map +1 -0
  239. package/dist/node/adapters/discord.js +286 -275
  240. package/dist/node/adapters/discord.js.map +1 -0
  241. package/dist/node/adapters/email.js +189 -202
  242. package/dist/node/adapters/email.js.map +1 -0
  243. package/dist/node/adapters/imessage.js +145 -142
  244. package/dist/node/adapters/imessage.js.map +1 -0
  245. package/dist/node/adapters/slack.js +237 -236
  246. package/dist/node/adapters/slack.js.map +1 -0
  247. package/dist/node/adapters/sms.js +149 -151
  248. package/dist/node/adapters/sms.js.map +1 -0
  249. package/dist/node/adapters/telegram.js +88 -92
  250. package/dist/node/adapters/telegram.js.map +1 -0
  251. package/dist/node/adapters/webchat.js +160 -136
  252. package/dist/node/adapters/webchat.js.map +1 -0
  253. package/dist/node/adapters/whatsapp.js +212 -215
  254. package/dist/node/adapters/whatsapp.js.map +1 -0
  255. package/dist/node/cli.js +884 -653
  256. package/dist/node/cli.js.map +1 -0
  257. package/dist/node/config.js +20 -18
  258. package/dist/node/config.js.map +1 -0
  259. package/dist/node/gateway-client.js +191 -181
  260. package/dist/node/gateway-client.js.map +1 -0
  261. package/dist/node/portal/clipboard.js +161 -130
  262. package/dist/node/portal/clipboard.js.map +1 -0
  263. package/dist/node/portal/discovery.js +51 -45
  264. package/dist/node/portal/discovery.js.map +1 -0
  265. package/dist/node/portal/forward.js +64 -58
  266. package/dist/node/portal/forward.js.map +1 -0
  267. package/dist/node/portal/index.js +246 -221
  268. package/dist/node/portal/index.js.map +1 -0
  269. package/dist/node/portal/multiplexer.js +192 -182
  270. package/dist/node/portal/multiplexer.js.map +1 -0
  271. package/dist/node/portal/permissions.js +102 -70
  272. package/dist/node/portal/permissions.js.map +1 -0
  273. package/dist/node/portal/protocol.js +153 -116
  274. package/dist/node/portal/protocol.js.map +1 -0
  275. package/dist/node/portal/screen.js +80 -69
  276. package/dist/node/portal/screen.js.map +1 -0
  277. package/dist/node/portal/session.js +124 -117
  278. package/dist/node/portal/session.js.map +1 -0
  279. package/dist/node/portal/shell.js +140 -113
  280. package/dist/node/portal/shell.js.map +1 -0
  281. package/dist/node/portal/stream.js +77 -75
  282. package/dist/node/portal/stream.js.map +1 -0
  283. package/dist/node/portal/transfer.js +190 -167
  284. package/dist/node/portal/transfer.js.map +1 -0
  285. package/dist/node/portal/ui.js +124 -99
  286. package/dist/node/portal/ui.js.map +1 -0
  287. package/dist/node/remote-desktop/compile-helper.js +50 -45
  288. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  289. package/dist/node/remote-desktop/index.js +215 -187
  290. package/dist/node/remote-desktop/index.js.map +1 -0
  291. package/dist/node/remote-desktop/protocol.js +45 -29
  292. package/dist/node/remote-desktop/protocol.js.map +1 -0
  293. package/dist/node/runtime.js +493 -410
  294. package/dist/node/runtime.js.map +1 -0
  295. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  296. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  297. package/dist/server/handlers/analytics.js +467 -261
  298. package/dist/server/handlers/analytics.js.map +1 -0
  299. package/dist/server/handlers/api-docs.js +1030 -895
  300. package/dist/server/handlers/api-docs.js.map +1 -0
  301. package/dist/server/handlers/api-keys.js +291 -242
  302. package/dist/server/handlers/api-keys.js.map +1 -0
  303. package/dist/server/handlers/billing.js +330 -239
  304. package/dist/server/handlers/billing.js.map +1 -0
  305. package/dist/server/handlers/browser.js +468 -395
  306. package/dist/server/handlers/browser.js.map +1 -0
  307. package/dist/server/handlers/catalog.js +1377 -978
  308. package/dist/server/handlers/catalog.js.map +1 -0
  309. package/dist/server/handlers/clickhouse.js +157 -109
  310. package/dist/server/handlers/clickhouse.js.map +1 -0
  311. package/dist/server/handlers/comms.js +1439 -984
  312. package/dist/server/handlers/comms.js.map +1 -0
  313. package/dist/server/handlers/creations.js +461 -394
  314. package/dist/server/handlers/creations.js.map +1 -0
  315. package/dist/server/handlers/crm.js +1082 -791
  316. package/dist/server/handlers/crm.js.map +1 -0
  317. package/dist/server/handlers/discovery.js +251 -232
  318. package/dist/server/handlers/discovery.js.map +1 -0
  319. package/dist/server/handlers/embeddings.js +241 -164
  320. package/dist/server/handlers/embeddings.js.map +1 -0
  321. package/dist/server/handlers/enrichment.js +887 -718
  322. package/dist/server/handlers/enrichment.js.map +1 -0
  323. package/dist/server/handlers/image-gen.js +467 -376
  324. package/dist/server/handlers/image-gen.js.map +1 -0
  325. package/dist/server/handlers/inventory.js +797 -424
  326. package/dist/server/handlers/inventory.js.map +1 -0
  327. package/dist/server/handlers/kali.js +272 -230
  328. package/dist/server/handlers/kali.js.map +1 -0
  329. package/dist/server/handlers/llm-providers.js +803 -580
  330. package/dist/server/handlers/llm-providers.js.map +1 -0
  331. package/dist/server/handlers/local-agent.js +133 -105
  332. package/dist/server/handlers/local-agent.js.map +1 -0
  333. package/dist/server/handlers/media.js +1179 -857
  334. package/dist/server/handlers/media.js.map +1 -0
  335. package/dist/server/handlers/meta-ads.js +2669 -2093
  336. package/dist/server/handlers/meta-ads.js.map +1 -0
  337. package/dist/server/handlers/nodes.js +1321 -913
  338. package/dist/server/handlers/nodes.js.map +1 -0
  339. package/dist/server/handlers/operations.js +183 -157
  340. package/dist/server/handlers/operations.js.map +1 -0
  341. package/dist/server/handlers/platform.js +346 -210
  342. package/dist/server/handlers/platform.js.map +1 -0
  343. package/dist/server/handlers/remove-bg.js +118 -86
  344. package/dist/server/handlers/remove-bg.js.map +1 -0
  345. package/dist/server/handlers/storefront.js +586 -446
  346. package/dist/server/handlers/storefront.js.map +1 -0
  347. package/dist/server/handlers/supply-chain.js +546 -326
  348. package/dist/server/handlers/supply-chain.js.map +1 -0
  349. package/dist/server/handlers/transcription.js +106 -97
  350. package/dist/server/handlers/transcription.js.map +1 -0
  351. package/dist/server/handlers/video-gen.js +593 -424
  352. package/dist/server/handlers/video-gen.js.map +1 -0
  353. package/dist/server/handlers/voice.js +1458 -1039
  354. package/dist/server/handlers/voice.js.map +1 -0
  355. package/dist/server/handlers/workflow-steps.js +2837 -2116
  356. package/dist/server/handlers/workflow-steps.js.map +1 -0
  357. package/dist/server/handlers/workflows.js +1630 -933
  358. package/dist/server/handlers/workflows.js.map +1 -0
  359. package/dist/server/index.js +3167 -2422
  360. package/dist/server/index.js.map +1 -0
  361. package/dist/server/lib/batch-client.js +471 -409
  362. package/dist/server/lib/batch-client.js.map +1 -0
  363. package/dist/server/lib/clickhouse-buffer.js +118 -104
  364. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  365. package/dist/server/lib/clickhouse-client.js +107 -107
  366. package/dist/server/lib/clickhouse-client.js.map +1 -0
  367. package/dist/server/lib/coa-renderer.js +1786 -356
  368. package/dist/server/lib/coa-renderer.js.map +1 -0
  369. package/dist/server/lib/code-worker-pool.js +227 -177
  370. package/dist/server/lib/code-worker-pool.js.map +1 -0
  371. package/dist/server/lib/code-worker.js +174 -164
  372. package/dist/server/lib/code-worker.js.map +1 -0
  373. package/dist/server/lib/compaction-service.d.ts +2 -12
  374. package/dist/server/lib/compaction-service.js +74 -184
  375. package/dist/server/lib/compaction-service.js.map +1 -0
  376. package/dist/server/lib/logger.js +36 -24
  377. package/dist/server/lib/logger.js.map +1 -0
  378. package/dist/server/lib/otel.js +101 -80
  379. package/dist/server/lib/otel.js.map +1 -0
  380. package/dist/server/lib/pdf-renderer.js +952 -788
  381. package/dist/server/lib/pdf-renderer.js.map +1 -0
  382. package/dist/server/lib/prompt-sanitizer.js +188 -108
  383. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  384. package/dist/server/lib/provider-capabilities.js +136 -138
  385. package/dist/server/lib/provider-capabilities.js.map +1 -0
  386. package/dist/server/lib/provider-failover.js +190 -168
  387. package/dist/server/lib/provider-failover.js.map +1 -0
  388. package/dist/server/lib/rate-limiter.js +186 -117
  389. package/dist/server/lib/rate-limiter.js.map +1 -0
  390. package/dist/server/lib/react-pdf-layout.js +551 -382
  391. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  392. package/dist/server/lib/server-agent-loop.d.ts +4 -1
  393. package/dist/server/lib/server-agent-loop.js +906 -634
  394. package/dist/server/lib/server-agent-loop.js.map +1 -0
  395. package/dist/server/lib/server-subagent.js +260 -164
  396. package/dist/server/lib/server-subagent.js.map +1 -0
  397. package/dist/server/lib/session-checkpoint.js +105 -96
  398. package/dist/server/lib/session-checkpoint.js.map +1 -0
  399. package/dist/server/lib/ssrf-guard.js +193 -184
  400. package/dist/server/lib/ssrf-guard.js.map +1 -0
  401. package/dist/server/lib/supabase-client.js +94 -82
  402. package/dist/server/lib/supabase-client.js.map +1 -0
  403. package/dist/server/lib/template-resolver.js +154 -176
  404. package/dist/server/lib/template-resolver.js.map +1 -0
  405. package/dist/server/lib/utils.js +242 -133
  406. package/dist/server/lib/utils.js.map +1 -0
  407. package/dist/server/local-agent-gateway.d.ts +2 -2
  408. package/dist/server/local-agent-gateway.js +785 -627
  409. package/dist/server/local-agent-gateway.js.map +1 -0
  410. package/dist/server/providers/anthropic.js +250 -172
  411. package/dist/server/providers/anthropic.js.map +1 -0
  412. package/dist/server/providers/bedrock.js +217 -158
  413. package/dist/server/providers/bedrock.js.map +1 -0
  414. package/dist/server/providers/gemini.js +548 -418
  415. package/dist/server/providers/gemini.js.map +1 -0
  416. package/dist/server/providers/openai.js +571 -437
  417. package/dist/server/providers/openai.js.map +1 -0
  418. package/dist/server/providers/registry.js +23 -18
  419. package/dist/server/providers/registry.js.map +1 -0
  420. package/dist/server/providers/shared.js +123 -95
  421. package/dist/server/providers/shared.js.map +1 -0
  422. package/dist/server/providers/types.js +1 -11
  423. package/dist/server/providers/types.js.map +1 -0
  424. package/dist/server/proxy-handlers.js +209 -165
  425. package/dist/server/proxy-handlers.js.map +1 -0
  426. package/dist/server/tool-router.js +959 -599
  427. package/dist/server/tool-router.js.map +1 -0
  428. package/dist/server/validation.js +248 -188
  429. package/dist/server/validation.js.map +1 -0
  430. package/dist/server/worker.js +202 -133
  431. package/dist/server/worker.js.map +1 -0
  432. package/dist/setup.d.ts +2 -2
  433. package/dist/setup.js +151 -147
  434. package/dist/setup.js.map +1 -0
  435. package/dist/shared/agent-core.d.ts +115 -26
  436. package/dist/shared/agent-core.js +956 -522
  437. package/dist/shared/agent-core.js.map +1 -0
  438. package/dist/shared/anthropic-types.js +1 -6
  439. package/dist/shared/anthropic-types.js.map +1 -0
  440. package/dist/shared/api-client.d.ts +16 -9
  441. package/dist/shared/api-client.js +419 -327
  442. package/dist/shared/api-client.js.map +1 -0
  443. package/dist/shared/compaction.d.ts +36 -0
  444. package/dist/shared/compaction.js +138 -0
  445. package/dist/shared/compaction.js.map +1 -0
  446. package/dist/shared/constants.js +67 -64
  447. package/dist/shared/constants.js.map +1 -0
  448. package/dist/shared/sse-parser.js +221 -219
  449. package/dist/shared/sse-parser.js.map +1 -0
  450. package/dist/shared/tool-dispatch.d.ts +4 -0
  451. package/dist/shared/tool-dispatch.js +226 -165
  452. package/dist/shared/tool-dispatch.js.map +1 -0
  453. package/dist/shared/types.js +1 -6
  454. package/dist/shared/types.js.map +1 -0
  455. package/dist/types/cli-highlight.d.js +2 -0
  456. package/dist/types/cli-highlight.d.js.map +1 -0
  457. package/dist/types/diff.d.js +2 -0
  458. package/dist/types/diff.d.js.map +1 -0
  459. package/dist/types/pdf-parse.d.js +2 -0
  460. package/dist/types/pdf-parse.d.js.map +1 -0
  461. package/dist/updater.d.ts +1 -1
  462. package/dist/updater.js +118 -92
  463. package/dist/updater.js.map +1 -0
  464. package/dist/webchat/widget.js +227 -380
  465. package/dist/webchat/widget.js.map +1 -0
  466. package/package.json +22 -10
  467. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  468. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  469. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  470. package/vendor/ink/build/apply-styles.js +175 -0
  471. package/vendor/ink/build/build-layout.js +77 -0
  472. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  473. package/vendor/ink/build/colorize.d.ts +3 -0
  474. package/vendor/ink/build/colorize.js +48 -0
  475. package/vendor/ink/build/colorize.js.map +1 -0
  476. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  477. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  478. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  479. package/vendor/ink/build/components/App.d.ts +18 -0
  480. package/vendor/ink/build/components/App.js +351 -0
  481. package/vendor/ink/build/components/App.js.map +1 -0
  482. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  483. package/vendor/ink/build/components/AppContext.js +11 -0
  484. package/vendor/ink/build/components/AppContext.js.map +1 -0
  485. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  486. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  487. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  488. package/vendor/ink/build/components/Box.d.ts +117 -0
  489. package/vendor/ink/build/components/Box.js +34 -0
  490. package/vendor/ink/build/components/Box.js.map +1 -0
  491. package/vendor/ink/build/components/Color.js +62 -0
  492. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  493. package/vendor/ink/build/components/Cursor.js +53 -0
  494. package/vendor/ink/build/components/Cursor.js.map +1 -0
  495. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  496. package/vendor/ink/build/components/CursorContext.js +8 -0
  497. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  498. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  499. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  500. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  501. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  502. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  503. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  504. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  505. package/vendor/ink/build/components/FocusContext.js +17 -0
  506. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  507. package/vendor/ink/build/components/Newline.d.ts +13 -0
  508. package/vendor/ink/build/components/Newline.js +8 -0
  509. package/vendor/ink/build/components/Newline.js.map +1 -0
  510. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  511. package/vendor/ink/build/components/Spacer.js +11 -0
  512. package/vendor/ink/build/components/Spacer.js.map +1 -0
  513. package/vendor/ink/build/components/Static.d.ts +24 -0
  514. package/vendor/ink/build/components/Static.js +28 -0
  515. package/vendor/ink/build/components/Static.js.map +1 -0
  516. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  517. package/vendor/ink/build/components/StderrContext.js +13 -0
  518. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  519. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  520. package/vendor/ink/build/components/StdinContext.js +19 -0
  521. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  522. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StdoutContext.js +13 -0
  524. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  525. package/vendor/ink/build/components/Text.d.ts +55 -0
  526. package/vendor/ink/build/components/Text.js +50 -0
  527. package/vendor/ink/build/components/Text.js.map +1 -0
  528. package/vendor/ink/build/components/Transform.d.ts +16 -0
  529. package/vendor/ink/build/components/Transform.js +15 -0
  530. package/vendor/ink/build/components/Transform.js.map +1 -0
  531. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  532. package/vendor/ink/build/cursor-helpers.js +56 -0
  533. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  534. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  535. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  536. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  537. package/vendor/ink/build/devtools.d.ts +1 -0
  538. package/vendor/ink/build/devtools.js +11 -0
  539. package/vendor/ink/build/devtools.js.map +1 -0
  540. package/vendor/ink/build/dom.d.ts +56 -0
  541. package/vendor/ink/build/dom.js +124 -0
  542. package/vendor/ink/build/dom.js.map +1 -0
  543. package/vendor/ink/build/experimental/apply-style.js +140 -0
  544. package/vendor/ink/build/experimental/dom.js +123 -0
  545. package/vendor/ink/build/experimental/output.js +91 -0
  546. package/vendor/ink/build/experimental/reconciler.js +141 -0
  547. package/vendor/ink/build/experimental/renderer.js +81 -0
  548. package/vendor/ink/build/get-max-width.d.ts +3 -0
  549. package/vendor/ink/build/get-max-width.js +10 -0
  550. package/vendor/ink/build/get-max-width.js.map +1 -0
  551. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  552. package/vendor/ink/build/hooks/use-app.js +8 -0
  553. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  554. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  555. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  556. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  558. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  559. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  561. package/vendor/ink/build/hooks/use-focus.js +42 -0
  562. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  564. package/vendor/ink/build/hooks/use-input.js +124 -0
  565. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  567. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  568. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  570. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  571. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  574. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  577. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  578. package/vendor/ink/build/hooks/useInput.js +38 -0
  579. package/vendor/ink/build/index.d.ts +34 -0
  580. package/vendor/ink/build/index.js +20 -0
  581. package/vendor/ink/build/index.js.map +1 -0
  582. package/vendor/ink/build/ink.d.ts +90 -0
  583. package/vendor/ink/build/ink.js +654 -0
  584. package/vendor/ink/build/ink.js.map +1 -0
  585. package/vendor/ink/build/input-parser.d.ts +7 -0
  586. package/vendor/ink/build/input-parser.js +154 -0
  587. package/vendor/ink/build/input-parser.js.map +1 -0
  588. package/vendor/ink/build/instance.js +205 -0
  589. package/vendor/ink/build/instances.d.ts +3 -0
  590. package/vendor/ink/build/instances.js +8 -0
  591. package/vendor/ink/build/instances.js.map +1 -0
  592. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  593. package/vendor/ink/build/kitty-keyboard.js +32 -0
  594. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  595. package/vendor/ink/build/layout.d.ts +7 -0
  596. package/vendor/ink/build/layout.js +33 -0
  597. package/vendor/ink/build/layout.js.map +1 -0
  598. package/vendor/ink/build/log-update.d.ts +19 -0
  599. package/vendor/ink/build/log-update.js +243 -0
  600. package/vendor/ink/build/log-update.js.map +1 -0
  601. package/vendor/ink/build/measure-element.d.ts +16 -0
  602. package/vendor/ink/build/measure-element.js +9 -0
  603. package/vendor/ink/build/measure-element.js.map +1 -0
  604. package/vendor/ink/build/measure-text.d.ts +6 -0
  605. package/vendor/ink/build/measure-text.js +21 -0
  606. package/vendor/ink/build/measure-text.js.map +1 -0
  607. package/vendor/ink/build/options.d.ts +52 -0
  608. package/vendor/ink/build/options.js +2 -0
  609. package/vendor/ink/build/options.js.map +1 -0
  610. package/vendor/ink/build/output.d.ts +35 -0
  611. package/vendor/ink/build/output.js +183 -0
  612. package/vendor/ink/build/output.js.map +1 -0
  613. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  614. package/vendor/ink/build/parse-keypress.js +493 -0
  615. package/vendor/ink/build/parse-keypress.js.map +1 -0
  616. package/vendor/ink/build/reconciler.d.ts +4 -0
  617. package/vendor/ink/build/reconciler.js +274 -0
  618. package/vendor/ink/build/reconciler.js.map +1 -0
  619. package/vendor/ink/build/render-background.d.ts +4 -0
  620. package/vendor/ink/build/render-background.js +25 -0
  621. package/vendor/ink/build/render-background.js.map +1 -0
  622. package/vendor/ink/build/render-border.d.ts +4 -0
  623. package/vendor/ink/build/render-border.js +73 -0
  624. package/vendor/ink/build/render-border.js.map +1 -0
  625. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  626. package/vendor/ink/build/render-node-to-output.js +147 -0
  627. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  628. package/vendor/ink/build/render-to-string.d.ts +38 -0
  629. package/vendor/ink/build/render-to-string.js +115 -0
  630. package/vendor/ink/build/render-to-string.js.map +1 -0
  631. package/vendor/ink/build/render.d.ts +121 -0
  632. package/vendor/ink/build/render.js +55 -0
  633. package/vendor/ink/build/render.js.map +1 -0
  634. package/vendor/ink/build/renderer.d.ts +8 -0
  635. package/vendor/ink/build/renderer.js +55 -0
  636. package/vendor/ink/build/renderer.js.map +1 -0
  637. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  638. package/vendor/ink/build/sanitize-ansi.js +27 -0
  639. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  640. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  641. package/vendor/ink/build/screen-reader-update.js +38 -0
  642. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  643. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  644. package/vendor/ink/build/squash-text-nodes.js +36 -0
  645. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  646. package/vendor/ink/build/styles.d.ts +240 -0
  647. package/vendor/ink/build/styles.js +232 -0
  648. package/vendor/ink/build/styles.js.map +1 -0
  649. package/vendor/ink/build/utils.d.ts +2 -0
  650. package/vendor/ink/build/utils.js +4 -0
  651. package/vendor/ink/build/utils.js.map +1 -0
  652. package/vendor/ink/build/wrap-text.d.ts +3 -0
  653. package/vendor/ink/build/wrap-text.js +31 -0
  654. package/vendor/ink/build/wrap-text.js.map +1 -0
  655. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  656. package/vendor/ink/build/write-synchronized.js +7 -0
  657. package/vendor/ink/build/write-synchronized.js.map +1 -0
  658. package/vendor/ink/license +10 -0
  659. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  660. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  661. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  662. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  663. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  664. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  665. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  666. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  667. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  668. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  669. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  670. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  671. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  672. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  673. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  674. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  675. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  676. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  677. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  678. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  679. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  680. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  681. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  682. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  683. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  684. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  685. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  686. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  687. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  688. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  689. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  690. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  691. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  692. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  693. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  694. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  695. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  696. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  697. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  698. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  699. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  700. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  701. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  702. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  703. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  704. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  705. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  706. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  707. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  708. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  709. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  710. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  711. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  712. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  713. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  714. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  715. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  716. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  717. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  718. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  719. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  720. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  721. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  722. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  723. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  724. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  725. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  726. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  727. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  728. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  729. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  730. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  731. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  732. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  733. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  734. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  735. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  746. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  747. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  748. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  749. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  750. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  751. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  752. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  753. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  754. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  755. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  756. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  757. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  758. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  759. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  760. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  761. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  762. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  763. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  764. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  765. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  766. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  767. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  768. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  769. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  770. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  784. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  785. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  786. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  787. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  788. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  789. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  790. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  791. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  792. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  793. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  794. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  795. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  796. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  797. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  798. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  799. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  800. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  801. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  802. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  803. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  804. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  805. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  806. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  807. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  808. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  809. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  810. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  811. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  812. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  813. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  814. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  815. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  816. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  817. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  818. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  819. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  820. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  821. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  822. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  823. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  824. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  825. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  826. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  827. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  828. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  829. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  830. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  831. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  832. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  833. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  834. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  835. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  836. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  837. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  838. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  839. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  840. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  841. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  842. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  843. package/vendor/ink/package.json +201 -0
  844. package/vendor/ink/readme.md +2636 -0
  845. package/bin/swag-agent.js +0 -9
  846. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  847. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -1,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