whale-code 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,221 @@
1
+ // server/handlers/api-keys.ts — API Key management: create, list, get, revoke, update
2
+ // Supports linking keys to creations (TV menus, displays, landing pages)
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ const KEY_COLS = "id, name, key_prefix, key_type, scope, is_active, rate_limit_per_minute, rate_limit_per_day, last_used_at, request_count, expires_at, revoked_at, revoked_reason, creation_id, created_at, updated_at";
5
+ export async function handleAPIKeys(sb, args, storeId) {
6
+ const sid = storeId;
7
+ const action = args.action;
8
+ switch (action) {
9
+ // ---- generate: Create a new API key ----
10
+ case "generate": {
11
+ const name = args.name;
12
+ if (!name?.trim())
13
+ return { success: false, error: "name is required" };
14
+ const keyType = args.key_type || "live";
15
+ if (keyType !== "live" && keyType !== "test") {
16
+ return { success: false, error: "key_type must be 'live' or 'test'" };
17
+ }
18
+ const scopes = args.scopes || ["*"];
19
+ const rateLimitPerMinute = args.rate_limit_per_minute || 60;
20
+ const rateLimitPerDay = args.rate_limit_per_day || 10000;
21
+ const expiresAt = args.expires_at;
22
+ const creationId = args.creation_id;
23
+ // Resolve owner — use provided owner_user_id or find store owner
24
+ let ownerUserId = args.owner_user_id;
25
+ if (!ownerUserId) {
26
+ const { data: store } = await sb
27
+ .from("stores")
28
+ .select("owner_user_id")
29
+ .eq("id", sid)
30
+ .single();
31
+ if (store?.owner_user_id) {
32
+ ownerUserId = store.owner_user_id;
33
+ }
34
+ }
35
+ if (!ownerUserId) {
36
+ return { success: false, error: "Could not determine key owner. Provide owner_user_id or ensure store has an owner." };
37
+ }
38
+ // If creation_id provided, verify it belongs to this store
39
+ if (creationId) {
40
+ const { data: creation } = await sb
41
+ .from("creations")
42
+ .select("id, name")
43
+ .eq("id", creationId)
44
+ .eq("store_id", sid)
45
+ .single();
46
+ if (!creation) {
47
+ return { success: false, error: "Creation not found in this store." };
48
+ }
49
+ }
50
+ // Generate key: wk_<type>_<32-char-random>
51
+ const rawUuid = randomUUID().replace(/-/g, "");
52
+ const keyValue = `wk_${keyType}_${rawUuid}`;
53
+ const keyPrefix = keyValue.substring(0, 12);
54
+ const keyHash = createHash("sha256").update(keyValue).digest("hex");
55
+ const { data, error } = await sb.from("api_keys").insert({
56
+ owner_user_id: ownerUserId,
57
+ store_id: sid,
58
+ name: name.trim(),
59
+ key_prefix: keyPrefix,
60
+ key_hash: keyHash,
61
+ key_type: keyType,
62
+ scope: scopes,
63
+ is_active: true,
64
+ rate_limit_per_minute: rateLimitPerMinute,
65
+ rate_limit_per_day: rateLimitPerDay,
66
+ expires_at: expiresAt || null,
67
+ creation_id: creationId || null,
68
+ }).select(KEY_COLS).single();
69
+ if (error)
70
+ return { success: false, error: error.message };
71
+ return {
72
+ success: true,
73
+ data: {
74
+ ...data,
75
+ key_value: keyValue,
76
+ warning: "Copy this key now. The full key value will NOT be returned again — only the hash is stored.",
77
+ },
78
+ };
79
+ }
80
+ // ---- list: List all API keys for store ----
81
+ case "list": {
82
+ const limit = Math.min(args.limit || 25, 100);
83
+ let q = sb
84
+ .from("api_keys")
85
+ .select(KEY_COLS)
86
+ .eq("store_id", sid)
87
+ .order("created_at", { ascending: false })
88
+ .limit(limit);
89
+ if (args.is_active !== undefined)
90
+ q = q.eq("is_active", args.is_active);
91
+ if (args.key_type)
92
+ q = q.eq("key_type", args.key_type);
93
+ if (args.creation_id)
94
+ q = q.eq("creation_id", args.creation_id);
95
+ const { data, error } = await q;
96
+ if (error)
97
+ return { success: false, error: error.message };
98
+ return { success: true, data: { count: data?.length || 0, keys: data } };
99
+ }
100
+ // ---- get: Get a single API key by ID ----
101
+ case "get": {
102
+ const keyId = args.key_id;
103
+ if (!keyId)
104
+ return { success: false, error: "key_id is required" };
105
+ const { data, error } = await sb
106
+ .from("api_keys")
107
+ .select(KEY_COLS)
108
+ .eq("id", keyId)
109
+ .eq("store_id", sid)
110
+ .single();
111
+ if (error)
112
+ return { success: false, error: error.message };
113
+ return { success: true, data };
114
+ }
115
+ // ---- revoke: Deactivate an API key ----
116
+ case "revoke": {
117
+ const keyId = args.key_id;
118
+ if (!keyId)
119
+ return { success: false, error: "key_id is required" };
120
+ const reason = args.reason || "Revoked via MCP tool";
121
+ const { data, error } = await sb
122
+ .from("api_keys")
123
+ .update({
124
+ is_active: false,
125
+ revoked_at: new Date().toISOString(),
126
+ revoked_reason: reason,
127
+ })
128
+ .eq("id", keyId)
129
+ .eq("store_id", sid)
130
+ .eq("is_active", true)
131
+ .select("id, name, key_prefix, is_active, revoked_at, revoked_reason, creation_id")
132
+ .single();
133
+ if (error)
134
+ return { success: false, error: error.message };
135
+ return { success: true, data };
136
+ }
137
+ // ---- update: Update key name, scopes, rate limits, or creation link ----
138
+ case "update": {
139
+ const keyId = args.key_id;
140
+ if (!keyId)
141
+ return { success: false, error: "key_id is required" };
142
+ const updates = {};
143
+ if (args.name !== undefined)
144
+ updates.name = args.name;
145
+ if (args.scopes !== undefined)
146
+ updates.scope = args.scopes;
147
+ if (args.rate_limit_per_minute !== undefined)
148
+ updates.rate_limit_per_minute = args.rate_limit_per_minute;
149
+ if (args.rate_limit_per_day !== undefined)
150
+ updates.rate_limit_per_day = args.rate_limit_per_day;
151
+ if (args.expires_at !== undefined)
152
+ updates.expires_at = args.expires_at || null;
153
+ if (args.creation_id !== undefined)
154
+ updates.creation_id = args.creation_id || null;
155
+ if (Object.keys(updates).length === 0) {
156
+ return { success: false, error: "No fields to update." };
157
+ }
158
+ const { data, error } = await sb
159
+ .from("api_keys")
160
+ .update(updates)
161
+ .eq("id", keyId)
162
+ .eq("store_id", sid)
163
+ .select(KEY_COLS)
164
+ .single();
165
+ if (error)
166
+ return { success: false, error: error.message };
167
+ return { success: true, data };
168
+ }
169
+ // ---- delete: Permanently delete an API key ----
170
+ case "delete": {
171
+ const keyId = args.key_id;
172
+ if (!keyId)
173
+ return { success: false, error: "key_id is required" };
174
+ const { error } = await sb
175
+ .from("api_keys")
176
+ .delete()
177
+ .eq("id", keyId)
178
+ .eq("store_id", sid);
179
+ if (error)
180
+ return { success: false, error: error.message };
181
+ return { success: true, data: { deleted: true, key_id: keyId } };
182
+ }
183
+ // ---- list_creations: List creations available for key linking ----
184
+ case "list_creations": {
185
+ const creationType = args.creation_type;
186
+ const limit = Math.min(args.limit || 50, 100);
187
+ let q = sb
188
+ .from("creations")
189
+ .select("id, name, creation_type, status, slug, display_mode, location_id, created_at")
190
+ .eq("store_id", sid)
191
+ .order("creation_type")
192
+ .order("name")
193
+ .limit(limit);
194
+ if (creationType)
195
+ q = q.eq("creation_type", creationType);
196
+ const { data, error } = await q;
197
+ if (error)
198
+ return { success: false, error: error.message };
199
+ // Also fetch which creations already have API keys
200
+ const creationIds = (data || []).map((c) => c.id);
201
+ const { data: linkedKeys } = creationIds.length > 0
202
+ ? await sb.from("api_keys").select("creation_id, id, name, key_prefix, is_active").in("creation_id", creationIds).eq("store_id", sid)
203
+ : { data: [] };
204
+ const keyMap = new Map();
205
+ for (const k of linkedKeys || []) {
206
+ if (k.creation_id)
207
+ keyMap.set(k.creation_id, k);
208
+ }
209
+ const enriched = (data || []).map((c) => ({
210
+ ...c,
211
+ api_key: keyMap.get(c.id) || null,
212
+ }));
213
+ return { success: true, data: { count: enriched.length, creations: enriched } };
214
+ }
215
+ default:
216
+ return {
217
+ success: false,
218
+ error: `Unknown api_keys action: ${action}. Available: generate, list, get, revoke, update, delete, list_creations`,
219
+ };
220
+ }
221
+ }
@@ -0,0 +1,33 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export interface UsageCounters {
3
+ messages_in: number;
4
+ messages_out: number;
5
+ agent_invocations: number;
6
+ tokens_used: number;
7
+ api_calls: number;
8
+ }
9
+ export interface StorePlan {
10
+ plan: "free" | "starter" | "pro" | "enterprise";
11
+ limits: {
12
+ messages_per_month: number;
13
+ agent_invocations_per_month: number;
14
+ tokens_per_month: number;
15
+ nodes_max: number;
16
+ channels_per_node: number;
17
+ };
18
+ }
19
+ /** Increment usage counters for a store */
20
+ export declare function incrementUsage(supabase: SupabaseClient, storeId: string, counters: Partial<UsageCounters>): Promise<void>;
21
+ /** Check if a store is within its plan limits */
22
+ export declare function checkPlanLimits(supabase: SupabaseClient, storeId: string, action: "message" | "agent_invocation" | "api_call"): Promise<{
23
+ allowed: boolean;
24
+ reason?: string;
25
+ }>;
26
+ /** Handle billing-related API routes */
27
+ export declare function handleBillingRoutes(pathname: string, method: string, body: Record<string, unknown> | null, supabase: SupabaseClient, auth: {
28
+ userId?: string;
29
+ isServiceRole: boolean;
30
+ }): Promise<{
31
+ status: number;
32
+ body: Record<string, unknown>;
33
+ } | null>;
@@ -0,0 +1,272 @@
1
+ const DEFAULT_PLANS = {
2
+ free: {
3
+ messages_per_month: 500,
4
+ agent_invocations_per_month: 100,
5
+ tokens_per_month: 500_000,
6
+ nodes_max: 1,
7
+ channels_per_node: 2,
8
+ },
9
+ starter: {
10
+ messages_per_month: 5_000,
11
+ agent_invocations_per_month: 1_000,
12
+ tokens_per_month: 5_000_000,
13
+ nodes_max: 3,
14
+ channels_per_node: 5,
15
+ },
16
+ pro: {
17
+ messages_per_month: 50_000,
18
+ agent_invocations_per_month: 10_000,
19
+ tokens_per_month: 50_000_000,
20
+ nodes_max: 5,
21
+ channels_per_node: 10,
22
+ },
23
+ enterprise: {
24
+ messages_per_month: -1, // unlimited
25
+ agent_invocations_per_month: -1,
26
+ tokens_per_month: -1,
27
+ nodes_max: 50,
28
+ channels_per_node: 50,
29
+ },
30
+ };
31
+ /** Get current billing period string (YYYY-MM) */
32
+ function getCurrentPeriod() {
33
+ const now = new Date();
34
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
35
+ }
36
+ /** Increment usage counters for a store */
37
+ export async function incrementUsage(supabase, storeId, counters) {
38
+ const period = getCurrentPeriod();
39
+ // Upsert the usage row
40
+ const { data: existing } = await supabase
41
+ .from("store_usage")
42
+ .select("id, messages_in, messages_out, agent_invocations, tokens_used, api_calls")
43
+ .eq("store_id", storeId)
44
+ .eq("period", period)
45
+ .single();
46
+ if (existing) {
47
+ await supabase
48
+ .from("store_usage")
49
+ .update({
50
+ messages_in: (existing.messages_in || 0) + (counters.messages_in || 0),
51
+ messages_out: (existing.messages_out || 0) + (counters.messages_out || 0),
52
+ agent_invocations: (existing.agent_invocations || 0) + (counters.agent_invocations || 0),
53
+ tokens_used: (existing.tokens_used || 0) + (counters.tokens_used || 0),
54
+ api_calls: (existing.api_calls || 0) + (counters.api_calls || 0),
55
+ updated_at: new Date().toISOString(),
56
+ })
57
+ .eq("id", existing.id);
58
+ }
59
+ else {
60
+ await supabase.from("store_usage").insert({
61
+ store_id: storeId,
62
+ period,
63
+ messages_in: counters.messages_in || 0,
64
+ messages_out: counters.messages_out || 0,
65
+ agent_invocations: counters.agent_invocations || 0,
66
+ tokens_used: counters.tokens_used || 0,
67
+ api_calls: counters.api_calls || 0,
68
+ });
69
+ }
70
+ }
71
+ /** Check if a store is within its plan limits */
72
+ export async function checkPlanLimits(supabase, storeId, action) {
73
+ const period = getCurrentPeriod();
74
+ // Get store plan
75
+ const { data: planRow } = await supabase
76
+ .from("store_plans")
77
+ .select("plan, limits")
78
+ .eq("store_id", storeId)
79
+ .single();
80
+ const planName = planRow?.plan || "free";
81
+ const limits = planRow?.limits || DEFAULT_PLANS[planName] || DEFAULT_PLANS.free;
82
+ // Get current usage
83
+ const { data: usage } = await supabase
84
+ .from("store_usage")
85
+ .select("messages_in, messages_out, agent_invocations, tokens_used, api_calls")
86
+ .eq("store_id", storeId)
87
+ .eq("period", period)
88
+ .single();
89
+ const currentUsage = usage || { messages_in: 0, messages_out: 0, agent_invocations: 0, tokens_used: 0, api_calls: 0 };
90
+ switch (action) {
91
+ case "message": {
92
+ const total = (currentUsage.messages_in || 0) + (currentUsage.messages_out || 0);
93
+ if (limits.messages_per_month > 0 && total >= limits.messages_per_month) {
94
+ return { allowed: false, reason: `Message limit reached (${total}/${limits.messages_per_month} this month)` };
95
+ }
96
+ break;
97
+ }
98
+ case "agent_invocation": {
99
+ if (limits.agent_invocations_per_month > 0 && (currentUsage.agent_invocations || 0) >= limits.agent_invocations_per_month) {
100
+ return { allowed: false, reason: `Agent invocation limit reached (${currentUsage.agent_invocations}/${limits.agent_invocations_per_month} this month)` };
101
+ }
102
+ break;
103
+ }
104
+ case "api_call": {
105
+ // API calls are generous — don't limit for now
106
+ break;
107
+ }
108
+ }
109
+ return { allowed: true };
110
+ }
111
+ /** Handle billing-related API routes */
112
+ export async function handleBillingRoutes(pathname, method, body, supabase, auth) {
113
+ // GET /usage — current period usage for a store
114
+ if (pathname === "/usage" && (method === "GET" || method === "POST")) {
115
+ const storeId = body?.store_id;
116
+ if (!storeId) {
117
+ return { status: 400, body: { error: "store_id required" } };
118
+ }
119
+ const period = getCurrentPeriod();
120
+ const [usageResult, planResult] = await Promise.all([
121
+ supabase.from("store_usage")
122
+ .select("*")
123
+ .eq("store_id", storeId)
124
+ .eq("period", period)
125
+ .single(),
126
+ supabase.from("store_plans")
127
+ .select("plan, limits")
128
+ .eq("store_id", storeId)
129
+ .single(),
130
+ ]);
131
+ const usage = usageResult.data || {
132
+ messages_in: 0, messages_out: 0, agent_invocations: 0, tokens_used: 0, api_calls: 0,
133
+ };
134
+ const planName = planResult.data?.plan || "free";
135
+ const limits = planResult.data?.limits || DEFAULT_PLANS[planName] || DEFAULT_PLANS.free;
136
+ return {
137
+ status: 200,
138
+ body: {
139
+ success: true,
140
+ period,
141
+ plan: planName,
142
+ usage,
143
+ limits,
144
+ },
145
+ };
146
+ }
147
+ // GET /usage/history — usage history for a store
148
+ if (pathname === "/usage/history" && (method === "GET" || method === "POST")) {
149
+ const storeId = body?.store_id;
150
+ if (!storeId) {
151
+ return { status: 400, body: { error: "store_id required" } };
152
+ }
153
+ const { data: history, error } = await supabase
154
+ .from("store_usage")
155
+ .select("*")
156
+ .eq("store_id", storeId)
157
+ .order("period", { ascending: false })
158
+ .limit(12);
159
+ if (error) {
160
+ return { status: 500, body: { error: error.message } };
161
+ }
162
+ return { status: 200, body: { success: true, history: history || [] } };
163
+ }
164
+ // POST /billing/checkout — create a Stripe Checkout session for plan upgrade
165
+ if (pathname === "/billing/checkout" && method === "POST") {
166
+ const storeId = body?.store_id;
167
+ const planId = body?.plan_id;
168
+ if (!storeId || !planId) {
169
+ return { status: 400, body: { error: "store_id and plan_id required" } };
170
+ }
171
+ const stripeKey = process.env.STRIPE_SECRET_KEY;
172
+ if (!stripeKey) {
173
+ return { status: 500, body: { error: "Stripe not configured" } };
174
+ }
175
+ // Map plan IDs to Stripe price IDs (set in env)
176
+ const priceMap = {
177
+ starter: process.env.STRIPE_PRICE_STARTER || "",
178
+ pro: process.env.STRIPE_PRICE_PRO || "",
179
+ enterprise: process.env.STRIPE_PRICE_ENTERPRISE || "",
180
+ };
181
+ const priceId = priceMap[planId];
182
+ if (!priceId) {
183
+ return { status: 400, body: { error: `No Stripe price configured for plan: ${planId}` } };
184
+ }
185
+ // Check if store already has a Stripe customer
186
+ const { data: planRow } = await supabase
187
+ .from("store_plans")
188
+ .select("stripe_customer_id")
189
+ .eq("store_id", storeId)
190
+ .single();
191
+ const successUrl = body?.success_url || "https://whaletools.app/settings/billing?success=true";
192
+ const cancelUrl = body?.cancel_url || "https://whaletools.app/settings/billing?canceled=true";
193
+ const params = new URLSearchParams();
194
+ params.set("mode", "subscription");
195
+ params.set("line_items[0][price]", priceId);
196
+ params.set("line_items[0][quantity]", "1");
197
+ params.set("success_url", successUrl);
198
+ params.set("cancel_url", cancelUrl);
199
+ params.set("metadata[store_id]", storeId);
200
+ if (auth.userId)
201
+ params.set("metadata[user_id]", auth.userId);
202
+ if (planRow?.stripe_customer_id) {
203
+ params.set("customer", planRow.stripe_customer_id);
204
+ }
205
+ else if (auth.userId) {
206
+ // Look up user email for pre-fill
207
+ const { data: profile } = await supabase.from("profiles").select("email").eq("id", auth.userId).single();
208
+ if (profile?.email)
209
+ params.set("customer_email", profile.email);
210
+ }
211
+ try {
212
+ const res = await fetch("https://api.stripe.com/v1/checkout/sessions", {
213
+ method: "POST",
214
+ headers: {
215
+ "Authorization": `Basic ${Buffer.from(stripeKey + ":").toString("base64")}`,
216
+ "Content-Type": "application/x-www-form-urlencoded",
217
+ },
218
+ body: params.toString(),
219
+ });
220
+ const session = await res.json();
221
+ if (!res.ok) {
222
+ return { status: 500, body: { error: session.error?.message || "Stripe error" } };
223
+ }
224
+ return { status: 200, body: { success: true, url: session.url } };
225
+ }
226
+ catch (err) {
227
+ return { status: 500, body: { error: `Stripe checkout failed: ${err.message}` } };
228
+ }
229
+ }
230
+ // POST /billing/portal — create a Stripe Customer Portal session
231
+ if (pathname === "/billing/portal" && method === "POST") {
232
+ const storeId = body?.store_id;
233
+ if (!storeId) {
234
+ return { status: 400, body: { error: "store_id required" } };
235
+ }
236
+ const stripeKey = process.env.STRIPE_SECRET_KEY;
237
+ if (!stripeKey) {
238
+ return { status: 500, body: { error: "Stripe not configured" } };
239
+ }
240
+ const { data: planRow } = await supabase
241
+ .from("store_plans")
242
+ .select("stripe_customer_id")
243
+ .eq("store_id", storeId)
244
+ .single();
245
+ if (!planRow?.stripe_customer_id) {
246
+ return { status: 400, body: { error: "No Stripe customer found. Subscribe to a plan first." } };
247
+ }
248
+ const returnUrl = body?.return_url || "https://whaletools.app/settings/billing";
249
+ const params = new URLSearchParams();
250
+ params.set("customer", planRow.stripe_customer_id);
251
+ params.set("return_url", returnUrl);
252
+ try {
253
+ const res = await fetch("https://api.stripe.com/v1/billing_portal/sessions", {
254
+ method: "POST",
255
+ headers: {
256
+ "Authorization": `Basic ${Buffer.from(stripeKey + ":").toString("base64")}`,
257
+ "Content-Type": "application/x-www-form-urlencoded",
258
+ },
259
+ body: params.toString(),
260
+ });
261
+ const session = await res.json();
262
+ if (!res.ok) {
263
+ return { status: 500, body: { error: session.error?.message || "Stripe error" } };
264
+ }
265
+ return { status: 200, body: { success: true, url: session.url } };
266
+ }
267
+ catch (err) {
268
+ return { status: 500, body: { error: `Portal session failed: ${err.message}` } };
269
+ }
270
+ }
271
+ return null;
272
+ }
@@ -0,0 +1,10 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function generatePdfFromHtml(html: string, options?: {
3
+ format?: string;
4
+ landscape?: boolean;
5
+ }): Promise<Buffer>;
6
+ export declare function handleBrowser(_sb: SupabaseClient, args: Record<string, unknown>, _storeId?: string): Promise<{
7
+ success: boolean;
8
+ data?: unknown;
9
+ error?: string;
10
+ }>;