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,517 @@
1
+ // server/handlers/browser.ts — Browser automation via Playwright
2
+ // Supports: navigate, screenshot, extract, click, fill, evaluate, pdf
3
+ import { chromium } from "playwright-core";
4
+ // ============================================================================
5
+ // BROWSER INSTANCE LIFECYCLE
6
+ // ============================================================================
7
+ let browserInstance = null;
8
+ let idleTimer = null;
9
+ const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
10
+ const PAGE_TIMEOUT_MS = 60_000; // 60 seconds per page operation (increased for CAPTCHA solving)
11
+ const MAX_RESULT_BYTES = 500 * 1024; // 500KB safety cap — context_management handles limits
12
+ const MAX_TEXT_CHARS = 200_000; // 200K chars for text content
13
+ function resetIdleTimer() {
14
+ if (idleTimer)
15
+ clearTimeout(idleTimer);
16
+ idleTimer = setTimeout(async () => {
17
+ if (browserInstance) {
18
+ try {
19
+ await browserInstance.close();
20
+ }
21
+ catch { /* browser may already be closed */ }
22
+ browserInstance = null;
23
+ console.log("[browser] Closed after idle timeout");
24
+ }
25
+ }, IDLE_TIMEOUT_MS);
26
+ }
27
+ async function getBrowser() {
28
+ if (browserInstance && browserInstance.isConnected()) {
29
+ resetIdleTimer();
30
+ return browserInstance;
31
+ }
32
+ browserInstance = await chromium.launch({
33
+ headless: true,
34
+ executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || undefined,
35
+ args: [
36
+ "--no-sandbox",
37
+ "--disable-setuid-sandbox",
38
+ "--disable-dev-shm-usage",
39
+ "--disable-gpu",
40
+ ],
41
+ });
42
+ browserInstance.on("disconnected", () => {
43
+ browserInstance = null;
44
+ if (idleTimer) {
45
+ clearTimeout(idleTimer);
46
+ idleTimer = null;
47
+ }
48
+ });
49
+ resetIdleTimer();
50
+ console.log("[browser] Launched new instance");
51
+ return browserInstance;
52
+ }
53
+ async function withPage(url, waitFor, fn) {
54
+ const browser = await getBrowser();
55
+ const context = await browser.newContext({
56
+ userAgent: "WhaleBot/1.0 (https://whale.app)",
57
+ viewport: { width: 1280, height: 720 },
58
+ ignoreHTTPSErrors: true,
59
+ });
60
+ context.setDefaultTimeout(PAGE_TIMEOUT_MS);
61
+ const page = await context.newPage();
62
+ try {
63
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: PAGE_TIMEOUT_MS });
64
+ // Auto-detect and solve CAPTCHAs after page load
65
+ const captchaResult = await detectAndSolveCaptcha(page);
66
+ if (captchaResult.solved) {
67
+ console.log(`[browser] Solved ${captchaResult.type} CAPTCHA on ${url}`);
68
+ // Wait for page to update after CAPTCHA solution
69
+ await page.waitForTimeout(2000);
70
+ }
71
+ if (waitFor) {
72
+ await page.waitForSelector(waitFor, { timeout: PAGE_TIMEOUT_MS });
73
+ }
74
+ return await fn(page);
75
+ }
76
+ finally {
77
+ await page.close().catch(() => { });
78
+ await context.close().catch(() => { });
79
+ }
80
+ }
81
+ // ============================================================================
82
+ // SSRF PROTECTION
83
+ // ============================================================================
84
+ function isBlockedUrl(url) {
85
+ let parsed;
86
+ try {
87
+ parsed = new URL(url);
88
+ }
89
+ catch {
90
+ return "Invalid URL";
91
+ }
92
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
93
+ return `Blocked protocol: ${parsed.protocol}`;
94
+ }
95
+ const host = parsed.hostname.toLowerCase();
96
+ // Block localhost variants
97
+ if (host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" ||
98
+ host === "::1" || host === "[::1]") {
99
+ return "Blocked: localhost";
100
+ }
101
+ // Block private/internal domains
102
+ if (host.endsWith(".internal") || host.endsWith(".local") ||
103
+ host === "metadata.google.internal") {
104
+ return "Blocked: internal/local domain";
105
+ }
106
+ // Block private IP ranges
107
+ if (/^10\./.test(host) ||
108
+ /^172\.(1[6-9]|2\d|3[01])\./.test(host) ||
109
+ /^192\.168\./.test(host) ||
110
+ /^169\.254\./.test(host)) {
111
+ return "Blocked: private IP range";
112
+ }
113
+ // Block IPv6 private
114
+ if (host.startsWith("fd") || host.startsWith("fc00:") ||
115
+ host.startsWith("fe80:") || host.includes("::ffff:")) {
116
+ return "Blocked: IPv6 private range";
117
+ }
118
+ return null;
119
+ }
120
+ // ============================================================================
121
+ // TEXT CLEANUP
122
+ // ============================================================================
123
+ function stripScriptsAndStyles(html) {
124
+ return html
125
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
126
+ .replace(/<style[\s\S]*?<\/style>/gi, "")
127
+ .replace(/<[^>]+>/g, " ")
128
+ .replace(/\s+/g, " ")
129
+ .trim();
130
+ }
131
+ function truncate(text, maxChars) {
132
+ if (text.length <= maxChars)
133
+ return text;
134
+ return text.substring(0, maxChars) + "\n...[truncated]";
135
+ }
136
+ function truncateResult(data) {
137
+ const json = JSON.stringify(data);
138
+ if (json.length <= MAX_RESULT_BYTES)
139
+ return data;
140
+ // If too large, stringify and truncate
141
+ return JSON.parse(truncate(json, MAX_RESULT_BYTES));
142
+ }
143
+ // ============================================================================
144
+ // ACTION HANDLERS
145
+ // ============================================================================
146
+ async function actionNavigate(url, waitFor) {
147
+ return withPage(url, waitFor, async (page) => {
148
+ const title = await page.title();
149
+ const html = await page.content();
150
+ const textContent = stripScriptsAndStyles(html);
151
+ return {
152
+ success: true,
153
+ data: {
154
+ url: page.url(),
155
+ title,
156
+ text: truncate(textContent, MAX_TEXT_CHARS),
157
+ text_length: textContent.length,
158
+ },
159
+ };
160
+ });
161
+ }
162
+ async function actionScreenshot(url, waitFor) {
163
+ return withPage(url, waitFor, async (page) => {
164
+ const buffer = await page.screenshot({ type: "png", fullPage: false });
165
+ const base64 = buffer.toString("base64");
166
+ const title = await page.title();
167
+ return {
168
+ success: true,
169
+ data: {
170
+ url: page.url(),
171
+ title,
172
+ screenshot_base64: base64,
173
+ format: "png",
174
+ size_bytes: buffer.length,
175
+ },
176
+ };
177
+ });
178
+ }
179
+ async function actionExtract(url, selector, waitFor) {
180
+ if (!selector) {
181
+ return { success: false, error: "selector is required for extract action" };
182
+ }
183
+ return withPage(url, waitFor, async (page) => {
184
+ const elements = await page.$$eval(selector, (els) => els.map((el) => ({
185
+ tag: el.tagName.toLowerCase(),
186
+ text: el.textContent?.trim() || "",
187
+ html: el.innerHTML.substring(0, 2000),
188
+ attributes: Object.fromEntries(Array.from(el.attributes).map((a) => [a.name, a.value])),
189
+ })));
190
+ return {
191
+ success: true,
192
+ data: {
193
+ url: page.url(),
194
+ selector,
195
+ count: elements.length,
196
+ elements: elements.slice(0, 50), // Cap at 50 elements
197
+ },
198
+ };
199
+ });
200
+ }
201
+ async function actionClick(url, selector, waitFor) {
202
+ if (!selector) {
203
+ return { success: false, error: "selector is required for click action" };
204
+ }
205
+ return withPage(url, waitFor, async (page) => {
206
+ await page.click(selector, { timeout: PAGE_TIMEOUT_MS });
207
+ // Wait a moment for any navigation or dynamic content
208
+ await page.waitForTimeout(1000);
209
+ const title = await page.title();
210
+ const html = await page.content();
211
+ const textContent = stripScriptsAndStyles(html);
212
+ return {
213
+ success: true,
214
+ data: {
215
+ url: page.url(),
216
+ title,
217
+ clicked: selector,
218
+ text: truncate(textContent, MAX_TEXT_CHARS),
219
+ },
220
+ };
221
+ });
222
+ }
223
+ async function actionFill(url, selector, value, waitFor) {
224
+ if (!selector) {
225
+ return { success: false, error: "selector is required for fill action" };
226
+ }
227
+ if (value === undefined || value === null) {
228
+ return { success: false, error: "value is required for fill action" };
229
+ }
230
+ return withPage(url, waitFor, async (page) => {
231
+ await page.fill(selector, value, { timeout: PAGE_TIMEOUT_MS });
232
+ return {
233
+ success: true,
234
+ data: {
235
+ url: page.url(),
236
+ filled: { selector, value },
237
+ },
238
+ };
239
+ });
240
+ }
241
+ async function actionEvaluate(url, script, waitFor) {
242
+ if (!script) {
243
+ return { success: false, error: "script is required for evaluate action" };
244
+ }
245
+ return withPage(url, waitFor, async (page) => {
246
+ const result = await page.evaluate(script);
247
+ return {
248
+ success: true,
249
+ data: {
250
+ url: page.url(),
251
+ result: truncateResult(result),
252
+ },
253
+ };
254
+ });
255
+ }
256
+ async function actionPdf(url, waitFor) {
257
+ return withPage(url, waitFor, async (page) => {
258
+ const buffer = await page.pdf({
259
+ format: "A4",
260
+ printBackground: true,
261
+ margin: { top: "1cm", bottom: "1cm", left: "1cm", right: "1cm" },
262
+ });
263
+ const base64 = buffer.toString("base64");
264
+ const title = await page.title();
265
+ return {
266
+ success: true,
267
+ data: {
268
+ url: page.url(),
269
+ title,
270
+ pdf_base64: base64,
271
+ format: "A4",
272
+ size_bytes: buffer.length,
273
+ },
274
+ };
275
+ });
276
+ }
277
+ // ============================================================================
278
+ // PDF FROM HTML (reusable by documents handler)
279
+ // ============================================================================
280
+ export async function generatePdfFromHtml(html, options) {
281
+ // P0 FIX: Strip dangerous tags from user HTML before rendering to prevent
282
+ // script execution, SSRF via iframes, and local file reads
283
+ const sanitizedHtml = html
284
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
285
+ .replace(/<script[^>]*\/?>/gi, "")
286
+ .replace(/<iframe[\s\S]*?<\/iframe>/gi, "")
287
+ .replace(/<iframe[^>]*\/?>/gi, "")
288
+ .replace(/<object[\s\S]*?<\/object>/gi, "")
289
+ .replace(/<object[^>]*\/?>/gi, "")
290
+ .replace(/<embed[^>]*\/?>/gi, "")
291
+ .replace(/<link[^>]*\/?>/gi, "");
292
+ const browser = await getBrowser();
293
+ const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
294
+ const page = await context.newPage();
295
+ try {
296
+ // P0 FIX: Block all network requests to prevent SSRF from headless Chromium.
297
+ // Only allow data: URIs (inline images/fonts) and about:blank.
298
+ await page.route('**/*', (route) => {
299
+ const url = route.request().url();
300
+ if (url.startsWith('data:') || url === 'about:blank') {
301
+ route.continue();
302
+ }
303
+ else {
304
+ route.abort();
305
+ }
306
+ });
307
+ // P0 FIX: Use domcontentloaded instead of networkidle to avoid waiting for
308
+ // (now-blocked) network requests and reduce attack surface
309
+ await page.setContent(sanitizedHtml, { waitUntil: "domcontentloaded", timeout: PAGE_TIMEOUT_MS });
310
+ const buffer = await page.pdf({
311
+ format: options?.format || "A4",
312
+ printBackground: true,
313
+ landscape: options?.landscape || false,
314
+ margin: { top: "1cm", bottom: "1cm", left: "1cm", right: "1cm" },
315
+ });
316
+ return Buffer.from(buffer);
317
+ }
318
+ finally {
319
+ await page.close().catch(() => { });
320
+ await context.close().catch(() => { });
321
+ }
322
+ }
323
+ async function callCapMonster(taskType, params) {
324
+ const apiKey = process.env.CAPMONSTER_API_KEY;
325
+ if (!apiKey)
326
+ throw new Error("CAPMONSTER_API_KEY not configured");
327
+ const createResp = await fetch("https://api.capmonster.cloud/createTask", {
328
+ method: "POST",
329
+ headers: { "Content-Type": "application/json" },
330
+ body: JSON.stringify({ clientKey: apiKey, task: { type: taskType, ...params } }),
331
+ });
332
+ const createData = await createResp.json();
333
+ if (createData.errorId)
334
+ throw new Error(`CapMonster create: ${createData.errorDescription}`);
335
+ const taskId = createData.taskId;
336
+ // Poll for result (max 120s, 2s intervals)
337
+ for (let i = 0; i < 60; i++) {
338
+ await new Promise(r => setTimeout(r, 2000));
339
+ const resultResp = await fetch("https://api.capmonster.cloud/getTaskResult", {
340
+ method: "POST",
341
+ headers: { "Content-Type": "application/json" },
342
+ body: JSON.stringify({ clientKey: apiKey, taskId }),
343
+ });
344
+ const resultData = await resultResp.json();
345
+ if (resultData.status === "ready") {
346
+ return resultData.solution?.gRecaptchaResponse || resultData.solution?.token || resultData.solution?.cf_clearance || "";
347
+ }
348
+ if (resultData.errorId)
349
+ throw new Error(`CapMonster result: ${resultData.errorDescription}`);
350
+ }
351
+ throw new Error("CapMonster: timeout waiting for solution (120s)");
352
+ }
353
+ async function detectAndSolveCaptcha(page) {
354
+ try {
355
+ // Skip if no API key configured
356
+ if (!process.env.CAPMONSTER_API_KEY)
357
+ return { solved: false };
358
+ const pageUrl = page.url();
359
+ // Detect reCAPTCHA v2
360
+ const recaptchaKey = await page.evaluate(() => {
361
+ const el = document.querySelector('.g-recaptcha[data-sitekey], [data-sitekey]');
362
+ if (el && el.classList.contains('g-recaptcha'))
363
+ return el.getAttribute('data-sitekey');
364
+ // Also check for reCAPTCHA iframe
365
+ const iframe = document.querySelector('iframe[src*="recaptcha"]');
366
+ if (iframe) {
367
+ const src = iframe.getAttribute('src') || '';
368
+ const match = src.match(/[?&]k=([^&]+)/);
369
+ return match ? match[1] : null;
370
+ }
371
+ return null;
372
+ }).catch(() => null);
373
+ if (recaptchaKey) {
374
+ console.log(`[browser] Detected reCAPTCHA v2 on ${pageUrl}`);
375
+ const token = await callCapMonster("RecaptchaV2TaskProxyless", {
376
+ websiteURL: pageUrl,
377
+ websiteKey: recaptchaKey,
378
+ });
379
+ await page.evaluate((t) => {
380
+ const textarea = document.getElementById('g-recaptcha-response');
381
+ if (textarea) {
382
+ textarea.style.display = 'block';
383
+ textarea.value = t;
384
+ }
385
+ // Try triggering the callback
386
+ const win = window;
387
+ try {
388
+ const cfg = win.___grecaptcha_cfg;
389
+ if (cfg?.clients) {
390
+ const clients = cfg.clients;
391
+ const firstKey = Object.keys(clients)[0];
392
+ const first = firstKey ? clients[firstKey] : null;
393
+ if (first) {
394
+ const findCallback = (obj, depth = 0) => {
395
+ if (depth > 4 || !obj || typeof obj !== 'object')
396
+ return null;
397
+ for (const val of Object.values(obj)) {
398
+ if (typeof val === 'function')
399
+ return val;
400
+ const found = findCallback(val, depth + 1);
401
+ if (found)
402
+ return found;
403
+ }
404
+ return null;
405
+ };
406
+ const cb = findCallback(first);
407
+ if (cb)
408
+ cb(t);
409
+ }
410
+ }
411
+ }
412
+ catch { /* callback search failed, form submit will work */ }
413
+ }, token);
414
+ return { solved: true, type: "reCAPTCHA v2" };
415
+ }
416
+ // Detect hCaptcha
417
+ const hcaptchaKey = await page.evaluate(() => {
418
+ const el = document.querySelector('.h-captcha[data-sitekey]');
419
+ return el?.getAttribute('data-sitekey') || null;
420
+ }).catch(() => null);
421
+ if (hcaptchaKey) {
422
+ console.log(`[browser] Detected hCaptcha on ${pageUrl}`);
423
+ const token = await callCapMonster("HCaptchaTaskProxyless", {
424
+ websiteURL: pageUrl,
425
+ websiteKey: hcaptchaKey,
426
+ });
427
+ await page.evaluate((t) => {
428
+ const textarea = document.querySelector('[name="h-captcha-response"], [name="g-recaptcha-response"]');
429
+ if (textarea) {
430
+ textarea.value = t;
431
+ }
432
+ const win = window;
433
+ if (typeof win.hcaptchaCallback === 'function') {
434
+ win.hcaptchaCallback(t);
435
+ }
436
+ }, token);
437
+ return { solved: true, type: "hCaptcha" };
438
+ }
439
+ // Detect Cloudflare Turnstile
440
+ const turnstileKey = await page.evaluate(() => {
441
+ const el = document.querySelector('.cf-turnstile[data-sitekey]');
442
+ return el?.getAttribute('data-sitekey') || null;
443
+ }).catch(() => null);
444
+ if (turnstileKey) {
445
+ console.log(`[browser] Detected Turnstile on ${pageUrl}`);
446
+ const token = await callCapMonster("TurnstileTaskProxyless", {
447
+ websiteURL: pageUrl,
448
+ websiteKey: turnstileKey,
449
+ });
450
+ await page.evaluate((t) => {
451
+ const input = document.querySelector('[name="cf-turnstile-response"]');
452
+ if (input)
453
+ input.value = t;
454
+ const win = window;
455
+ if (typeof win.turnstileCallback === 'function') {
456
+ win.turnstileCallback(t);
457
+ }
458
+ }, token);
459
+ return { solved: true, type: "Turnstile" };
460
+ }
461
+ return { solved: false };
462
+ }
463
+ catch (err) {
464
+ console.log(`[browser] CAPTCHA detection/solving failed: ${err instanceof Error ? err.message : String(err)}`);
465
+ return { solved: false };
466
+ }
467
+ }
468
+ // ============================================================================
469
+ // MAIN HANDLER
470
+ // ============================================================================
471
+ export async function handleBrowser(_sb, args, _storeId) {
472
+ const action = args.action;
473
+ const url = args.url;
474
+ const selector = args.selector;
475
+ const value = args.value;
476
+ const script = args.script;
477
+ const waitFor = args.wait_for;
478
+ if (!action) {
479
+ return { success: false, error: "action is required" };
480
+ }
481
+ if (!url) {
482
+ return { success: false, error: "url is required" };
483
+ }
484
+ // SSRF check
485
+ const blocked = isBlockedUrl(url);
486
+ if (blocked) {
487
+ return { success: false, error: blocked };
488
+ }
489
+ try {
490
+ switch (action) {
491
+ case "navigate":
492
+ return await actionNavigate(url, waitFor);
493
+ case "screenshot":
494
+ return await actionScreenshot(url, waitFor);
495
+ case "extract":
496
+ return await actionExtract(url, selector, waitFor);
497
+ case "click":
498
+ return await actionClick(url, selector, waitFor);
499
+ case "fill":
500
+ return await actionFill(url, selector, value, waitFor);
501
+ case "evaluate":
502
+ return await actionEvaluate(url, script, waitFor);
503
+ case "pdf":
504
+ return await actionPdf(url, waitFor);
505
+ default:
506
+ return { success: false, error: `Unknown browser action: ${action}. Valid: navigate, screenshot, extract, click, fill, evaluate, pdf` };
507
+ }
508
+ }
509
+ catch (err) {
510
+ const message = err instanceof Error ? err.message : String(err);
511
+ // Clean up common Playwright error noise
512
+ const cleanMessage = message
513
+ .replace(/=========================== logs ===========================[^]*?============================================================/gs, "")
514
+ .trim();
515
+ return { success: false, error: `Browser error: ${cleanMessage.substring(0, 500)}` };
516
+ }
517
+ }
@@ -0,0 +1,99 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function handleProducts(sb: SupabaseClient, args: Record<string, unknown>, storeId?: string): Promise<{
3
+ success: boolean;
4
+ error: string;
5
+ data?: undefined;
6
+ count?: undefined;
7
+ } | {
8
+ data: any[];
9
+ limit?: {} | undefined;
10
+ success: boolean;
11
+ total: number | null;
12
+ count: number;
13
+ offset: number;
14
+ error?: undefined;
15
+ } | {
16
+ success: boolean;
17
+ data: any;
18
+ error?: undefined;
19
+ count?: undefined;
20
+ } | {
21
+ success: boolean;
22
+ count: number;
23
+ data: {
24
+ id: any;
25
+ name: any;
26
+ slug: any;
27
+ description: any;
28
+ icon: any;
29
+ parent_id: any;
30
+ display_order: any;
31
+ is_active: any;
32
+ featured: any;
33
+ product_count: any;
34
+ catalog_id: any;
35
+ field_schema_id: any;
36
+ created_at: any;
37
+ }[];
38
+ error?: undefined;
39
+ } | {
40
+ success: boolean;
41
+ count: number;
42
+ data: {
43
+ id: any;
44
+ name: any;
45
+ slug: any;
46
+ description: any;
47
+ icon: any;
48
+ fields: any;
49
+ is_public: any;
50
+ is_active: any;
51
+ catalog_id: any;
52
+ store_id: any;
53
+ install_count: any;
54
+ created_at: any;
55
+ }[];
56
+ error?: undefined;
57
+ } | {
58
+ success: boolean;
59
+ count: number;
60
+ data: {
61
+ id: any;
62
+ name: any;
63
+ slug: any;
64
+ description: any;
65
+ tiers: any;
66
+ quality_tier: any;
67
+ is_public: any;
68
+ is_active: any;
69
+ catalog_id: any;
70
+ store_id: any;
71
+ install_count: any;
72
+ created_at: any;
73
+ }[];
74
+ error?: undefined;
75
+ } | {
76
+ success: boolean;
77
+ count: number;
78
+ data: {
79
+ id: any;
80
+ name: any;
81
+ slug: any;
82
+ description: any;
83
+ vertical: any;
84
+ is_active: any;
85
+ is_default: any;
86
+ display_order: any;
87
+ created_at: any;
88
+ }[];
89
+ error?: undefined;
90
+ }>;
91
+ export declare function handleCollections(sb: SupabaseClient, args: Record<string, unknown>, storeId?: string): Promise<{
92
+ success: boolean;
93
+ error: string;
94
+ data?: undefined;
95
+ } | {
96
+ success: boolean;
97
+ data: any;
98
+ error?: undefined;
99
+ }>;