@bubblelab/bubble-core 0.1.11 → 0.1.13

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 (143) hide show
  1. package/dist/bubble-bundle.d.ts +183 -117
  2. package/dist/bubble-factory.d.ts +2 -3
  3. package/dist/bubble-factory.d.ts.map +1 -1
  4. package/dist/bubble-factory.js +28 -96
  5. package/dist/bubble-factory.js.map +1 -1
  6. package/dist/bubbles/service-bubble/ai-agent.d.ts +14 -14
  7. package/dist/bubbles/service-bubble/ai-agent.d.ts.map +1 -1
  8. package/dist/bubbles/service-bubble/ai-agent.js +3 -6
  9. package/dist/bubbles/service-bubble/ai-agent.js.map +1 -1
  10. package/dist/bubbles/service-bubble/apify/apify.d.ts +5 -5
  11. package/dist/bubbles/service-bubble/apify/apify.d.ts.map +1 -1
  12. package/dist/bubbles/service-bubble/apify/apify.js +20 -8
  13. package/dist/bubbles/service-bubble/apify/apify.js.map +1 -1
  14. package/dist/bubbles/service-bubble/browserbase/browserbase.d.ts +542 -0
  15. package/dist/bubbles/service-bubble/browserbase/browserbase.d.ts.map +1 -0
  16. package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.d.ts +37 -0
  17. package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.d.ts.map +1 -0
  18. package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.js +203 -0
  19. package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.js.map +1 -0
  20. package/dist/bubbles/service-bubble/browserbase/browserbase.js +593 -0
  21. package/dist/bubbles/service-bubble/browserbase/browserbase.js.map +1 -0
  22. package/dist/bubbles/service-bubble/browserbase/browserbase.schema.d.ts +518 -0
  23. package/dist/bubbles/service-bubble/browserbase/browserbase.schema.d.ts.map +1 -0
  24. package/dist/bubbles/service-bubble/browserbase/browserbase.schema.js +311 -0
  25. package/dist/bubbles/service-bubble/browserbase/browserbase.schema.js.map +1 -0
  26. package/dist/bubbles/service-bubble/browserbase/index.d.ts +3 -0
  27. package/dist/bubbles/service-bubble/browserbase/index.d.ts.map +1 -0
  28. package/dist/bubbles/service-bubble/browserbase/index.js +3 -0
  29. package/dist/bubbles/service-bubble/browserbase/index.js.map +1 -0
  30. package/dist/bubbles/service-bubble/crustdata/crustdata.d.ts +1358 -0
  31. package/dist/bubbles/service-bubble/crustdata/crustdata.d.ts.map +1 -0
  32. package/dist/bubbles/service-bubble/crustdata/crustdata.js +219 -0
  33. package/dist/bubbles/service-bubble/crustdata/crustdata.js.map +1 -0
  34. package/dist/bubbles/service-bubble/crustdata/crustdata.schema.d.ts +1604 -0
  35. package/dist/bubbles/service-bubble/crustdata/crustdata.schema.d.ts.map +1 -0
  36. package/dist/bubbles/service-bubble/crustdata/crustdata.schema.js +194 -0
  37. package/dist/bubbles/service-bubble/crustdata/crustdata.schema.js.map +1 -0
  38. package/dist/bubbles/service-bubble/crustdata/index.d.ts +3 -0
  39. package/dist/bubbles/service-bubble/crustdata/index.d.ts.map +1 -0
  40. package/dist/bubbles/service-bubble/crustdata/index.js +3 -0
  41. package/dist/bubbles/service-bubble/crustdata/index.js.map +1 -0
  42. package/dist/bubbles/service-bubble/firecrawl.d.ts +4 -4
  43. package/dist/bubbles/service-bubble/firecrawl.js +1 -1
  44. package/dist/bubbles/service-bubble/firecrawl.js.map +1 -1
  45. package/dist/bubbles/service-bubble/github.d.ts +0 -6
  46. package/dist/bubbles/service-bubble/github.d.ts.map +1 -1
  47. package/dist/bubbles/service-bubble/github.js +1 -7
  48. package/dist/bubbles/service-bubble/github.js.map +1 -1
  49. package/dist/bubbles/service-bubble/gmail.d.ts +84 -84
  50. package/dist/bubbles/service-bubble/google-drive.d.ts +32 -32
  51. package/dist/bubbles/service-bubble/http.d.ts +13 -1
  52. package/dist/bubbles/service-bubble/http.d.ts.map +1 -1
  53. package/dist/bubbles/service-bubble/http.integration.flow.d.ts +49 -0
  54. package/dist/bubbles/service-bubble/http.integration.flow.d.ts.map +1 -0
  55. package/dist/bubbles/service-bubble/http.integration.flow.js +425 -0
  56. package/dist/bubbles/service-bubble/http.integration.flow.js.map +1 -0
  57. package/dist/bubbles/service-bubble/http.js +40 -4
  58. package/dist/bubbles/service-bubble/http.js.map +1 -1
  59. package/dist/bubbles/service-bubble/resend.d.ts +4 -4
  60. package/dist/bubbles/service-bubble/slack.d.ts +978 -35
  61. package/dist/bubbles/service-bubble/slack.d.ts.map +1 -1
  62. package/dist/bubbles/service-bubble/slack.js +320 -26
  63. package/dist/bubbles/service-bubble/slack.js.map +1 -1
  64. package/dist/bubbles/service-bubble/storage.d.ts +1 -1
  65. package/dist/bubbles/service-bubble/storage.d.ts.map +1 -1
  66. package/dist/bubbles/service-bubble/storage.js +2 -2
  67. package/dist/bubbles/service-bubble/storage.js.map +1 -1
  68. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.d.ts +494 -0
  69. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.d.ts.map +1 -0
  70. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.d.ts +31 -0
  71. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.d.ts.map +1 -0
  72. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.js +100 -0
  73. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.js.map +1 -0
  74. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.js +1301 -0
  75. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.js.map +1 -0
  76. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.d.ts +473 -0
  77. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.d.ts.map +1 -0
  78. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.js +230 -0
  79. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.js.map +1 -0
  80. package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.d.ts +3 -0
  81. package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.d.ts.map +1 -0
  82. package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.js +3 -0
  83. package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.js.map +1 -0
  84. package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.d.ts +8 -8
  85. package/dist/bubbles/tool-bubble/company-enrichment-tool.d.ts +740 -0
  86. package/dist/bubbles/tool-bubble/company-enrichment-tool.d.ts.map +1 -0
  87. package/dist/bubbles/tool-bubble/company-enrichment-tool.js +350 -0
  88. package/dist/bubbles/tool-bubble/company-enrichment-tool.js.map +1 -0
  89. package/dist/bubbles/tool-bubble/get-trigger-detail-tool.d.ts +146 -0
  90. package/dist/bubbles/tool-bubble/get-trigger-detail-tool.d.ts.map +1 -0
  91. package/dist/bubbles/tool-bubble/get-trigger-detail-tool.js +128 -0
  92. package/dist/bubbles/tool-bubble/get-trigger-detail-tool.js.map +1 -0
  93. package/dist/bubbles/tool-bubble/google-maps-tool.d.ts.map +1 -1
  94. package/dist/bubbles/tool-bubble/google-maps-tool.js +1 -0
  95. package/dist/bubbles/tool-bubble/google-maps-tool.js.map +1 -1
  96. package/dist/bubbles/tool-bubble/instagram-tool.d.ts.map +1 -1
  97. package/dist/bubbles/tool-bubble/instagram-tool.js +2 -0
  98. package/dist/bubbles/tool-bubble/instagram-tool.js.map +1 -1
  99. package/dist/bubbles/tool-bubble/linkedin-tool.d.ts +6 -6
  100. package/dist/bubbles/tool-bubble/linkedin-tool.d.ts.map +1 -1
  101. package/dist/bubbles/tool-bubble/linkedin-tool.js +6 -4
  102. package/dist/bubbles/tool-bubble/linkedin-tool.js.map +1 -1
  103. package/dist/bubbles/tool-bubble/research-agent-tool.d.ts.map +1 -1
  104. package/dist/bubbles/tool-bubble/research-agent-tool.js +6 -1
  105. package/dist/bubbles/tool-bubble/research-agent-tool.js.map +1 -1
  106. package/dist/bubbles/tool-bubble/tiktok-tool.d.ts +8 -8
  107. package/dist/bubbles/tool-bubble/twitter-tool.d.ts +10 -10
  108. package/dist/bubbles/tool-bubble/twitter-tool.d.ts.map +1 -1
  109. package/dist/bubbles/tool-bubble/twitter-tool.js +3 -0
  110. package/dist/bubbles/tool-bubble/twitter-tool.js.map +1 -1
  111. package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts +6 -6
  112. package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts.map +1 -1
  113. package/dist/bubbles/tool-bubble/web-scrape-tool.js +4 -1
  114. package/dist/bubbles/tool-bubble/web-scrape-tool.js.map +1 -1
  115. package/dist/bubbles/tool-bubble/youtube-tool.d.ts.map +1 -1
  116. package/dist/bubbles/tool-bubble/youtube-tool.js +3 -0
  117. package/dist/bubbles/tool-bubble/youtube-tool.js.map +1 -1
  118. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts +24 -24
  119. package/dist/bubbles/workflow-bubble/slack-formatter-agent.d.ts +8 -8
  120. package/dist/bubbles.json +64 -15
  121. package/dist/index.d.ts +3 -0
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +3 -0
  124. package/dist/index.js.map +1 -1
  125. package/dist/logging/BubbleLogger.d.ts +10 -0
  126. package/dist/logging/BubbleLogger.d.ts.map +1 -1
  127. package/dist/logging/BubbleLogger.js +14 -0
  128. package/dist/logging/BubbleLogger.js.map +1 -1
  129. package/dist/logging/StreamingBubbleLogger.d.ts +8 -0
  130. package/dist/logging/StreamingBubbleLogger.d.ts.map +1 -1
  131. package/dist/logging/StreamingBubbleLogger.js +29 -0
  132. package/dist/logging/StreamingBubbleLogger.js.map +1 -1
  133. package/dist/types/available-tools.d.ts +1 -1
  134. package/dist/types/available-tools.d.ts.map +1 -1
  135. package/dist/types/available-tools.js +7 -0
  136. package/dist/types/available-tools.js.map +1 -1
  137. package/dist/types/base-bubble-class.d.ts.map +1 -1
  138. package/dist/types/base-bubble-class.js +0 -8
  139. package/dist/types/base-bubble-class.js.map +1 -1
  140. package/dist/types/tool-bubble-class.d.ts.map +1 -1
  141. package/dist/types/tool-bubble-class.js +58 -8
  142. package/dist/types/tool-bubble-class.js.map +1 -1
  143. package/package.json +3 -2
@@ -0,0 +1,1301 @@
1
+ import { ToolBubble } from '../../../types/tool-bubble-class.js';
2
+ import { CredentialType } from '@bubblelab/shared-schemas';
3
+ import { BrowserBaseBubble, BrowserSessionDataSchema, } from '../../service-bubble/browserbase/index.js';
4
+ import { StorageBubble } from '../../service-bubble/storage.js';
5
+ import { AmazonShoppingToolParamsSchema, AmazonShoppingToolResultSchema, } from './amazon-shopping-tool.schema.js';
6
+ // Debug logging helper - only logs when ENABLE_DEBUG_LOGS env var is set
7
+ const DEBUG = process.env.ENABLE_DEBUG_LOGS;
8
+ function debugLog(...args) {
9
+ if (DEBUG) {
10
+ console.log(...args);
11
+ }
12
+ }
13
+ /**
14
+ * Amazon Shopping Tool
15
+ *
16
+ * A tool bubble for automating Amazon shopping operations including
17
+ * adding items to cart, viewing cart, and completing checkout.
18
+ *
19
+ * This tool uses the BrowserBase service bubble internally to
20
+ * manage browser sessions with authenticated Amazon credentials.
21
+ *
22
+ * Features:
23
+ * - Add products to cart by URL or ASIN
24
+ * - View current cart contents and totals
25
+ * - Complete checkout with saved payment methods
26
+ * - Search for products
27
+ * - Get detailed product information
28
+ *
29
+ * Required Credentials:
30
+ * - AMAZON_CRED: Browser session credential with Amazon cookies
31
+ *
32
+ * Security:
33
+ * - Uses BrowserBase cloud browsers (isolated)
34
+ * - Credentials are encrypted at rest
35
+ * - Session data is not persisted beyond operation
36
+ */
37
+ export class AmazonShoppingTool extends ToolBubble {
38
+ static bubbleName = 'amazon-shopping-tool';
39
+ static schema = AmazonShoppingToolParamsSchema;
40
+ static resultSchema = AmazonShoppingToolResultSchema;
41
+ static shortDescription = 'Amazon shopping automation - add to cart, view cart, checkout, search products';
42
+ static longDescription = `
43
+ Amazon Shopping Tool for automating shopping operations.
44
+
45
+ Features:
46
+ - Add products to cart by URL or ASIN
47
+ - View current cart contents and totals
48
+ - Complete checkout with saved payment methods
49
+ - Search for products on Amazon
50
+ - Get detailed product information
51
+
52
+ Required Credentials:
53
+ - AMAZON_CRED: Browser session credential (authenticate via browser session)
54
+
55
+ Note: Checkout requires saved payment and shipping information in Amazon account.
56
+ The tool operates using authenticated browser sessions to ensure security.
57
+ `;
58
+ static alias = 'amazon';
59
+ static type = 'tool';
60
+ sessionId = null;
61
+ contextId = null;
62
+ cookies = null;
63
+ constructor(params = { operation: 'get_cart' }, context) {
64
+ super(params, context);
65
+ }
66
+ /**
67
+ * Choose the credential to use for Amazon operations
68
+ * Returns AMAZON_CRED which contains contextId and cookies
69
+ */
70
+ chooseCredential() {
71
+ const { credentials } = this.params;
72
+ if (!credentials || typeof credentials !== 'object') {
73
+ return undefined;
74
+ }
75
+ return credentials[CredentialType.AMAZON_CRED];
76
+ }
77
+ /**
78
+ * Parse the AMAZON_CRED to extract contextId and cookies
79
+ * Credential is base64-encoded JSON to avoid escaping issues
80
+ */
81
+ parseBrowserSessionData() {
82
+ const credential = this.chooseCredential();
83
+ if (!credential) {
84
+ return null;
85
+ }
86
+ try {
87
+ // Credential is base64-encoded JSON
88
+ const jsonString = Buffer.from(credential, 'base64').toString('utf-8');
89
+ const parsed = JSON.parse(jsonString);
90
+ const validated = BrowserSessionDataSchema.safeParse(parsed);
91
+ if (validated.success) {
92
+ return validated.data;
93
+ }
94
+ console.error('[AmazonShoppingTool] Invalid credential format:', validated.error);
95
+ return null;
96
+ }
97
+ catch (error) {
98
+ console.error('[AmazonShoppingTool] Failed to parse credential:', error);
99
+ return null;
100
+ }
101
+ }
102
+ /**
103
+ * Extract ASIN from Amazon URL or return as-is if already an ASIN
104
+ */
105
+ extractAsin(productUrlOrAsin) {
106
+ // If it's already an ASIN (10 characters, alphanumeric)
107
+ if (/^[A-Z0-9]{10}$/i.test(productUrlOrAsin)) {
108
+ return productUrlOrAsin.toUpperCase();
109
+ }
110
+ // Try to extract ASIN from URL patterns
111
+ // Pattern 1: /dp/ASIN
112
+ const dpMatch = productUrlOrAsin.match(/\/dp\/([A-Z0-9]{10})/i);
113
+ if (dpMatch)
114
+ return dpMatch[1].toUpperCase();
115
+ // Pattern 2: /gp/product/ASIN
116
+ const gpMatch = productUrlOrAsin.match(/\/gp\/product\/([A-Z0-9]{10})/i);
117
+ if (gpMatch)
118
+ return gpMatch[1].toUpperCase();
119
+ // Pattern 3: ASIN in query string
120
+ const asinMatch = productUrlOrAsin.match(/[?&]ASIN=([A-Z0-9]{10})/i);
121
+ if (asinMatch)
122
+ return asinMatch[1].toUpperCase();
123
+ // If we can't extract, return the original (might fail later)
124
+ return productUrlOrAsin;
125
+ }
126
+ /**
127
+ * Build Amazon product URL from ASIN
128
+ */
129
+ buildProductUrl(asin) {
130
+ return `https://www.amazon.com/dp/${asin}`;
131
+ }
132
+ /**
133
+ * Start a browser session using BrowserBase
134
+ * Extracts contextId and cookies from AMAZON_CRED and passes them explicitly
135
+ */
136
+ async startBrowserSession() {
137
+ debugLog('[AmazonShoppingTool] Starting browser session');
138
+ debugLog('[AmazonShoppingTool] Session ID:', this.sessionId);
139
+ if (this.sessionId) {
140
+ return this.sessionId;
141
+ }
142
+ // Parse credential to get contextId and cookies
143
+ const sessionData = this.parseBrowserSessionData();
144
+ if (sessionData) {
145
+ this.contextId = sessionData.contextId;
146
+ this.cookies = sessionData.cookies;
147
+ debugLog(`[AmazonShoppingTool] Loaded session data: contextId=${this.contextId}, cookies=${this.cookies.length}`);
148
+ }
149
+ else {
150
+ debugLog('[AmazonShoppingTool] No AMAZON_CRED found, creating new context');
151
+ }
152
+ // Create BrowserBaseBubble with explicit context_id and cookies
153
+ const startsession_browserbase = new BrowserBaseBubble({
154
+ operation: 'start_session',
155
+ context_id: this.contextId || undefined,
156
+ cookies: this.cookies || undefined,
157
+ credentials: this.params.credentials,
158
+ }, this.context, 'startsession_browserbase');
159
+ const result = await startsession_browserbase.action();
160
+ if (!result.data.success || !result.data.session_id) {
161
+ throw new Error(result.data.error || 'Failed to start browser session');
162
+ }
163
+ this.sessionId = result.data.session_id;
164
+ // Store the contextId from the result in case a new one was created
165
+ if (result.data.context_id) {
166
+ this.contextId = result.data.context_id;
167
+ }
168
+ debugLog(`[AmazonShoppingTool] Browser session started: ${this.sessionId}, context: ${this.contextId}`);
169
+ // Emit browser session start event with Amazon Shopping Tool's variableId
170
+ // so the live session shows on this tool's step in the UI
171
+ if (this.context?.logger && result.data.debug_url) {
172
+ this.context.logger.logBrowserSessionStart(this.sessionId, result.data.debug_url, this.context.variableId);
173
+ }
174
+ return this.sessionId;
175
+ }
176
+ /**
177
+ * End the browser session
178
+ */
179
+ async endBrowserSession() {
180
+ if (!this.sessionId)
181
+ return;
182
+ const sessionIdToEnd = this.sessionId;
183
+ try {
184
+ const endsession_browserbase = new BrowserBaseBubble({
185
+ operation: 'end_session',
186
+ session_id: sessionIdToEnd,
187
+ }, this.context, 'endsession_browserbase');
188
+ await endsession_browserbase.action();
189
+ debugLog(`[AmazonShoppingTool] Browser session ended: ${sessionIdToEnd}`);
190
+ }
191
+ catch (error) {
192
+ console.error('[AmazonShoppingTool] Error ending session:', error);
193
+ }
194
+ finally {
195
+ // Emit browser session end event to stop showing live view in UI
196
+ if (this.context?.logger) {
197
+ this.context.logger.logBrowserSessionEnd(sessionIdToEnd, this.context.variableId);
198
+ }
199
+ this.sessionId = null;
200
+ }
201
+ }
202
+ /**
203
+ * Navigate to a URL
204
+ */
205
+ async navigateTo(url) {
206
+ if (!this.sessionId) {
207
+ throw new Error('No active browser session');
208
+ }
209
+ const navigate_browserbase = new BrowserBaseBubble({
210
+ operation: 'navigate',
211
+ session_id: this.sessionId,
212
+ url,
213
+ wait_until: 'domcontentloaded',
214
+ timeout: 30000,
215
+ }, this.context, 'navigate_browserbase');
216
+ const result = await navigate_browserbase.action();
217
+ if (!result.data.success) {
218
+ throw new Error(result.data.error || 'Navigation failed');
219
+ }
220
+ }
221
+ /**
222
+ * Click an element
223
+ */
224
+ async clickElement(selector, waitForNav = false) {
225
+ if (!this.sessionId) {
226
+ throw new Error('No active browser session');
227
+ }
228
+ const click_browserbase = new BrowserBaseBubble({
229
+ operation: 'click',
230
+ session_id: this.sessionId,
231
+ selector,
232
+ wait_for_navigation: waitForNav,
233
+ timeout: 5000,
234
+ }, this.context, 'click_browserbase');
235
+ const result = await click_browserbase.action();
236
+ return result.data.success;
237
+ }
238
+ /**
239
+ * Evaluate JavaScript in page
240
+ */
241
+ async evaluate(script) {
242
+ if (!this.sessionId) {
243
+ throw new Error('No active browser session');
244
+ }
245
+ const evaluate_browserbase = new BrowserBaseBubble({
246
+ operation: 'evaluate',
247
+ session_id: this.sessionId,
248
+ script,
249
+ }, this.context, 'evaluate_browserbase');
250
+ const result = await evaluate_browserbase.action();
251
+ if (!result.data.success) {
252
+ throw new Error(result.data.error || 'Script evaluation failed');
253
+ }
254
+ return result.data.result;
255
+ }
256
+ /**
257
+ * Wait for selector
258
+ */
259
+ async waitForSelector(selector, timeout = 5000) {
260
+ if (!this.sessionId) {
261
+ throw new Error('No active browser session');
262
+ }
263
+ const waitselector_browserbase = new BrowserBaseBubble({
264
+ operation: 'wait',
265
+ session_id: this.sessionId,
266
+ wait_type: 'selector',
267
+ selector,
268
+ timeout,
269
+ }, this.context, 'waitselector_browserbase');
270
+ const result = await waitselector_browserbase.action();
271
+ return result.data.success;
272
+ }
273
+ /**
274
+ * Wait for navigation to complete
275
+ */
276
+ async waitForNavigation(timeout = 30000) {
277
+ if (!this.sessionId) {
278
+ throw new Error('No active browser session');
279
+ }
280
+ const waitnavigation_browserbase = new BrowserBaseBubble({
281
+ operation: 'wait',
282
+ session_id: this.sessionId,
283
+ wait_type: 'navigation',
284
+ timeout,
285
+ }, this.context, 'waitnavigation_browserbase');
286
+ const result = await waitnavigation_browserbase.action();
287
+ return result.data.success;
288
+ }
289
+ /**
290
+ * Take a screenshot and upload to Cloudflare R2
291
+ * Returns the URL of the uploaded screenshot
292
+ */
293
+ async takeScreenshotAndUpload(label, fullPage = false) {
294
+ if (!this.sessionId) {
295
+ console.error('[AmazonShoppingTool] No session for screenshot');
296
+ return null;
297
+ }
298
+ try {
299
+ debugLog(`[AmazonShoppingTool] Taking screenshot: ${label}`);
300
+ // Take screenshot using BrowserBase
301
+ const screenshot_browserbase = new BrowserBaseBubble({
302
+ operation: 'screenshot',
303
+ session_id: this.sessionId,
304
+ full_page: fullPage,
305
+ format: 'png',
306
+ credentials: this.params.credentials,
307
+ }, this.context, 'screenshot_browserbase');
308
+ const screenshotResult = await screenshot_browserbase.action();
309
+ if (!screenshotResult.data.success || !screenshotResult.data.data) {
310
+ console.error('[AmazonShoppingTool] Screenshot failed:', screenshotResult.data.error);
311
+ return null;
312
+ }
313
+ const base64Data = screenshotResult.data.data;
314
+ debugLog(`[AmazonShoppingTool] Screenshot captured, size: ${base64Data.length} chars`);
315
+ // Upload to Cloudflare R2 using StorageBubble
316
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
317
+ const fileName = `amazon-${label}-${timestamp}.png`;
318
+ const updatefile_storagebubble = new StorageBubble({
319
+ operation: 'updateFile',
320
+ bucketName: 'bubble-lab-bucket',
321
+ fileName,
322
+ fileContent: `data:image/png;base64,${base64Data}`,
323
+ contentType: 'image/png',
324
+ credentials: this.params.credentials,
325
+ }, this.context, 'updatefile_storagebubble');
326
+ const uploadResult = await updatefile_storagebubble.action();
327
+ if (!uploadResult.data.success || !uploadResult.data.fileName) {
328
+ console.error('[AmazonShoppingTool] Upload failed:', uploadResult.data.error);
329
+ return null;
330
+ }
331
+ debugLog(`[AmazonShoppingTool] Screenshot uploaded: ${uploadResult.data.fileName}`);
332
+ // Get the download URL for the uploaded file
333
+ const getfile_storagebubble = new StorageBubble({
334
+ operation: 'getFile',
335
+ bucketName: 'bubble-lab-bucket',
336
+ fileName: uploadResult.data.fileName,
337
+ expirationMinutes: 60 * 24 * 7, // 7 days expiry
338
+ credentials: this.params.credentials,
339
+ }, this.context, 'getfile_storagebubble');
340
+ const fileResult = await getfile_storagebubble.action();
341
+ if (!fileResult.data.success || !fileResult.data.downloadUrl) {
342
+ console.error('[AmazonShoppingTool] Failed to get download URL:', fileResult.data.error);
343
+ return null;
344
+ }
345
+ debugLog(`[AmazonShoppingTool] Screenshot URL generated: ${fileResult.data.downloadUrl.substring(0, 80)}...`);
346
+ return fileResult.data.downloadUrl;
347
+ }
348
+ catch (error) {
349
+ console.error('[AmazonShoppingTool] Screenshot error:', error);
350
+ return null;
351
+ }
352
+ }
353
+ async performAction() {
354
+ const { operation } = this.params;
355
+ // Cast to output type since base class already parsed input through Zod
356
+ const parsedParams = this.params;
357
+ try {
358
+ // Start browser session
359
+ await this.startBrowserSession();
360
+ const result = await (async () => {
361
+ switch (operation) {
362
+ case 'add_to_cart':
363
+ return await this.addToCart(parsedParams);
364
+ case 'get_cart':
365
+ return (await this.getCart());
366
+ case 'checkout':
367
+ return (await this.checkout());
368
+ case 'search':
369
+ return await this.searchProducts(parsedParams);
370
+ case 'get_product':
371
+ return await this.getProduct(parsedParams);
372
+ case 'screenshot':
373
+ return await this.takeScreenshot(parsedParams);
374
+ default:
375
+ throw new Error(`Unknown operation: ${operation}`);
376
+ }
377
+ })();
378
+ return result;
379
+ }
380
+ catch (error) {
381
+ console.error('[AmazonShoppingTool] Error:', error);
382
+ return {
383
+ operation,
384
+ success: false,
385
+ error: error instanceof Error ? error.message : 'Unknown error occurred',
386
+ };
387
+ }
388
+ finally {
389
+ // Always clean up the session
390
+ await this.endBrowserSession();
391
+ }
392
+ }
393
+ /**
394
+ * Add a product to cart
395
+ */
396
+ async addToCart(params) {
397
+ const asin = this.extractAsin(params.product_url);
398
+ const productUrl = this.buildProductUrl(asin);
399
+ debugLog(`[AmazonShoppingTool] Adding to cart: ${asin}`);
400
+ // Navigate to product page
401
+ await this.navigateTo(productUrl);
402
+ // Wait for and click Add to Cart button
403
+ const addToCartSelectors = [
404
+ '#submit\\.add-to-cart',
405
+ '#add-to-cart-button',
406
+ 'input[name="submit.add-to-cart"]',
407
+ ];
408
+ let clicked = false;
409
+ for (const selector of addToCartSelectors) {
410
+ const exists = await this.waitForSelector(selector, 2000);
411
+ if (exists) {
412
+ clicked = await this.clickElement(selector, true);
413
+ break;
414
+ }
415
+ }
416
+ // Wait a moment for any modal/popup to appear
417
+ await new Promise((resolve) => setTimeout(resolve, 2000));
418
+ // Check if we've already navigated away from product page (to cart confirmation)
419
+ // If so, skip the "No thanks" check entirely - we're done
420
+ let currentUrlAfterAdd;
421
+ try {
422
+ currentUrlAfterAdd = await this.getCurrentUrl();
423
+ debugLog(`[AmazonShoppingTool] URL after Add to Cart click: ${currentUrlAfterAdd}`);
424
+ // If we've navigated to cart or confirmation page, we're done - skip No Thanks check
425
+ if (currentUrlAfterAdd.includes('/cart') ||
426
+ currentUrlAfterAdd.includes('/gp/aw/d/') ||
427
+ currentUrlAfterAdd.includes('sw_pt=') ||
428
+ currentUrlAfterAdd.includes('/gp/product/handle-buy-box')) {
429
+ debugLog('[AmazonShoppingTool] Already navigated to cart/confirmation - skipping No Thanks check');
430
+ return {
431
+ operation: 'add_to_cart',
432
+ success: true,
433
+ message: `Added ${asin} to cart`,
434
+ cart_count: undefined,
435
+ error: '',
436
+ };
437
+ }
438
+ }
439
+ catch {
440
+ debugLog('[AmazonShoppingTool] Could not get URL (navigation may have occurred)');
441
+ }
442
+ // Handle protection plan modal - click "No thanks" if it appears
443
+ // This modal asks "Add to your order" with protection plan options
444
+ // Try multiple times as modal may take time to fully render
445
+ // NOTE: If "No thanks" is not found, we just skip and proceed - it's optional
446
+ debugLog('[AmazonShoppingTool] Checking for protection plan modal...');
447
+ // First, try to click using BrowserBase click operation with selector
448
+ // This handles Amazon's dynamically rendered modals better
449
+ // The attachSiNoCoverage button is in the side sheet (NOT in a-popover-preload)
450
+ const noThanksSelectors = [
451
+ '#attachSiNoCoverage',
452
+ '#attachSiNoCoverage input',
453
+ '#attachSiNoCoverage-ld',
454
+ '#attachSiNoCoverage-ld input',
455
+ '#attachSiNoCoverage-eu-enhanced',
456
+ '#attachSiNoCoverage-eu-enhanced input',
457
+ '.a-popover-wrapper .mbb__no button',
458
+ '.a-sheet-content .mbb__no button',
459
+ ];
460
+ for (const selector of noThanksSelectors) {
461
+ try {
462
+ const selectorClicked = await this.clickElement(selector, false);
463
+ if (selectorClicked) {
464
+ debugLog(`[AmazonShoppingTool] Clicked No Thanks via selector: ${selector}`);
465
+ clicked = true;
466
+ // Wait for navigation after dismissing modal
467
+ debugLog('[AmazonShoppingTool] Waiting for navigation after No Thanks click...');
468
+ try {
469
+ await this.waitForNavigation(5000);
470
+ debugLog('[AmazonShoppingTool] Navigation complete after No Thanks');
471
+ }
472
+ catch {
473
+ debugLog('[AmazonShoppingTool] Navigation wait completed or timed out');
474
+ }
475
+ break;
476
+ }
477
+ }
478
+ catch (err) {
479
+ // Selector not found or error during click, continue
480
+ const errorMsg = err instanceof Error ? err.message : String(err);
481
+ if (errorMsg.includes('navigation') || errorMsg.includes('context')) {
482
+ // Navigation happened, which means click succeeded
483
+ debugLog('[AmazonShoppingTool] Click caused navigation (success)');
484
+ clicked = true;
485
+ break;
486
+ }
487
+ }
488
+ }
489
+ // If selector-based click didn't work, try evaluate-based approach
490
+ if (!clicked) {
491
+ const noThanksResult = (await this.evaluate(`
492
+ (() => {
493
+ // Helper function to check if element is truly visible (not in preload/hidden containers)
494
+ function isElementVisible(el) {
495
+ // Check if inside a-popover-preload (hidden preload content)
496
+ if (el.closest('.a-popover-preload')) {
497
+ return false;
498
+ }
499
+ // Check computed style
500
+ const style = window.getComputedStyle(el);
501
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
502
+ return false;
503
+ }
504
+ // Check bounding rect
505
+ const rect = el.getBoundingClientRect();
506
+ return rect.width > 0 && rect.height > 0;
507
+ }
508
+
509
+ // First try: Look for attachSiNoCoverage buttons (the visible side sheet buttons)
510
+ const attachSiButtons = ['#attachSiNoCoverage', '#attachSiNoCoverage-ld', '#attachSiNoCoverage-eu-enhanced'];
511
+ for (const id of attachSiButtons) {
512
+ const btn = document.querySelector(id);
513
+ if (btn && isElementVisible(btn)) {
514
+ console.log('[Debug] Clicking attachSi button:', id);
515
+ btn.click();
516
+ return { clicked: true, text: 'No thanks', method: 'attachSi' };
517
+ }
518
+ }
519
+
520
+ // Second try: Look for visible mbb__no buttons (not in preload)
521
+ const mbbNoButtons = document.querySelectorAll('.mbb__no button, span.mbb__no');
522
+ console.log('[Debug] Found mbb__no elements:', mbbNoButtons.length);
523
+ for (const btn of mbbNoButtons) {
524
+ if (isElementVisible(btn)) {
525
+ console.log('[Debug] Clicking visible mbb__no button:', btn.textContent?.trim());
526
+ btn.click();
527
+ return { clicked: true, text: btn.textContent?.trim() || 'mbb__no', method: 'mbb__no' };
528
+ }
529
+ }
530
+
531
+ // Third try: Look for visible "No thanks" text buttons (not in preload)
532
+ const allButtons = document.querySelectorAll('button, input[type="submit"], a, span[role="button"]');
533
+ for (const btn of allButtons) {
534
+ const text = (btn.value || btn.innerText || btn.textContent || '').trim().toLowerCase();
535
+ if (text === 'no thanks' || text === 'no, thanks') {
536
+ if (isElementVisible(btn)) {
537
+ console.log('[Debug] Clicking visible no thanks button:', text);
538
+ btn.click();
539
+ return { clicked: true, text: text, method: 'text-match' };
540
+ }
541
+ }
542
+ }
543
+
544
+ // Log debug info about what we found
545
+ const preloadCount = document.querySelectorAll('.a-popover-preload .mbb__no').length;
546
+ const attachSiExists = document.querySelector('#attachSiNoCoverage') ? 'yes' : 'no';
547
+ console.log('[Debug] attachSiNoCoverage exists:', attachSiExists);
548
+ console.log('[Debug] mbb__no in preload (hidden):', preloadCount);
549
+ console.log('[Debug] No visible No Thanks button found');
550
+ return { clicked: false };
551
+ })()
552
+ `));
553
+ if (noThanksResult.clicked) {
554
+ clicked = true;
555
+ debugLog(`[AmazonShoppingTool] Dismissed protection plan modal by clicking "${noThanksResult.text}" (method: ${noThanksResult.method})`);
556
+ // Wait for navigation after dismissing modal
557
+ debugLog('[AmazonShoppingTool] Waiting for navigation...');
558
+ try {
559
+ await this.waitForNavigation(4000);
560
+ debugLog('[AmazonShoppingTool] Navigation complete after No thanks');
561
+ }
562
+ catch {
563
+ debugLog('[AmazonShoppingTool] Navigation wait timed out, continuing...');
564
+ }
565
+ }
566
+ else {
567
+ debugLog('[AmazonShoppingTool] No protection plan modal detected (via evaluate)');
568
+ }
569
+ } // Close if (!clicked) block
570
+ // Debug: Save current page state for inspection (may fail after navigation)
571
+ try {
572
+ const currentUrl = await this.getCurrentUrl();
573
+ debugLog(`[AmazonShoppingTool] Current URL after add to cart: ${currentUrl}`);
574
+ await this.saveDebugState('add-to-cart-after');
575
+ }
576
+ catch {
577
+ debugLog('[AmazonShoppingTool] Could not save debug state (page may have navigated)');
578
+ }
579
+ // Try to get cart count (may fail after navigation)
580
+ let cartCount;
581
+ try {
582
+ const countResult = (await this.evaluate(`
583
+ (() => {
584
+ const cartEl = document.querySelector('#nav-cart-count');
585
+ if (cartEl) {
586
+ const count = parseInt(cartEl.textContent || '0', 10);
587
+ return isNaN(count) ? 0 : count;
588
+ }
589
+ return 0;
590
+ })()
591
+ `));
592
+ cartCount = countResult;
593
+ }
594
+ catch {
595
+ // Cart count is optional, may fail if page navigated
596
+ debugLog('[AmazonShoppingTool] Could not get cart count (page may have navigated)');
597
+ }
598
+ if (!clicked) {
599
+ return {
600
+ operation: 'add_to_cart',
601
+ success: false,
602
+ error: 'Could not find Add to Cart button. Product may be unavailable.',
603
+ };
604
+ }
605
+ return {
606
+ operation: 'add_to_cart',
607
+ success: true,
608
+ message: `Added ${asin} to cart`,
609
+ cart_count: cartCount,
610
+ error: '',
611
+ };
612
+ }
613
+ /**
614
+ * Get cart contents
615
+ */
616
+ async getCart() {
617
+ debugLog('[AmazonShoppingTool] Getting cart contents');
618
+ // Navigate to cart page
619
+ await this.navigateTo('https://www.amazon.com/gp/cart/view.html');
620
+ // Wait for cart to load
621
+ await this.waitForSelector('#sc-active-cart', 5000);
622
+ // Extract cart items using JavaScript
623
+ const cartData = (await this.evaluate(`
624
+ (() => {
625
+ const items = [];
626
+ const cartItems = document.querySelectorAll('[data-asin]');
627
+
628
+ cartItems.forEach(item => {
629
+ const asin = item.getAttribute('data-asin');
630
+ if (!asin) return;
631
+
632
+ const titleEl = item.querySelector('.sc-product-title, .a-truncate-cut');
633
+ const priceEl = item.querySelector('.sc-product-price, .sc-price');
634
+ const quantityEl = item.querySelector('select[name*="quantity"], .sc-quantity-textfield');
635
+ const imageEl = item.querySelector('img.sc-product-image, img[data-a-hires]');
636
+
637
+ items.push({
638
+ asin,
639
+ title: titleEl?.textContent?.trim() || 'Unknown Product',
640
+ price: priceEl?.textContent?.trim() || '',
641
+ quantity: quantityEl ? parseInt(quantityEl.value || '1', 10) : 1,
642
+ image: imageEl?.src || '',
643
+ url: 'https://www.amazon.com/dp/' + asin,
644
+ });
645
+ });
646
+
647
+ // Get subtotal
648
+ const subtotalEl = document.querySelector('#sc-subtotal-amount-activecart, .sc-subtotal-amount-activecart');
649
+ const subtotal = subtotalEl?.textContent?.trim() || '';
650
+
651
+ return { items, subtotal, totalItems: items.reduce((sum, i) => sum + i.quantity, 0) };
652
+ })()
653
+ `));
654
+ // Take confirmation screenshot of the cart
655
+ const screenshotUrl = await this.takeScreenshotAndUpload('cart', false);
656
+ return {
657
+ operation: 'get_cart',
658
+ success: true,
659
+ items: cartData.items,
660
+ subtotal: cartData.subtotal,
661
+ total_items: cartData.totalItems,
662
+ screenshot_url: screenshotUrl || undefined,
663
+ error: '',
664
+ };
665
+ }
666
+ /**
667
+ * Get current page URL
668
+ */
669
+ async getCurrentUrl() {
670
+ const result = (await this.evaluate(`window.location.href`));
671
+ return result;
672
+ }
673
+ /**
674
+ * Complete checkout - uses same heuristics as amazon-cart-browserbase.ts
675
+ */
676
+ async checkout() {
677
+ debugLog('[AmazonShoppingTool] Starting checkout');
678
+ // Step 1: Go to cart
679
+ debugLog('[AmazonShoppingTool] Step 1: Loading cart...');
680
+ await this.navigateTo('https://www.amazon.com/gp/cart/view.html');
681
+ await new Promise((resolve) => setTimeout(resolve, 2000));
682
+ // Check cart has items
683
+ const cartItemCount = (await this.evaluate(`
684
+ document.querySelectorAll('[data-asin]:not([data-asin=""])').length
685
+ `));
686
+ debugLog(`[AmazonShoppingTool] Found ${cartItemCount} items in cart`);
687
+ if (cartItemCount === 0) {
688
+ return {
689
+ operation: 'checkout',
690
+ success: false,
691
+ error: 'Cart is empty',
692
+ };
693
+ }
694
+ // Step 2: Click proceed to checkout
695
+ debugLog('[AmazonShoppingTool] Step 2: Looking for checkout button...');
696
+ const checkoutClickResult = (await this.evaluate(`
697
+ (() => {
698
+ // Prioritize input/button over span/a - input.value is the actual clickable form element
699
+ const priorityOrder = ['input', 'button', 'a', 'span'];
700
+ for (const tagName of priorityOrder) {
701
+ const elements = document.querySelectorAll(tagName);
702
+ for (const el of elements) {
703
+ const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
704
+ if (text === 'proceed to checkout' || text === 'proceed to retail checkout') {
705
+ el.click();
706
+ return { clicked: true, tag: el.tagName, className: el.className, text: text };
707
+ }
708
+ }
709
+ }
710
+ return { clicked: false };
711
+ })()
712
+ `));
713
+ if (!checkoutClickResult.clicked) {
714
+ return {
715
+ operation: 'checkout',
716
+ success: false,
717
+ error: 'Could not find checkout button',
718
+ };
719
+ }
720
+ debugLog(`[AmazonShoppingTool] Clicked Proceed to checkout: <${checkoutClickResult.tag} class="${checkoutClickResult.className}">${checkoutClickResult.text}`);
721
+ await new Promise((resolve) => setTimeout(resolve, 5000));
722
+ let currentUrl = await this.getCurrentUrl();
723
+ debugLog(`[AmazonShoppingTool] Current URL: ${currentUrl}`);
724
+ // Check for sign-in redirect
725
+ if (currentUrl.includes('/ap/signin')) {
726
+ return {
727
+ operation: 'checkout',
728
+ success: false,
729
+ error: 'Password re-authentication required',
730
+ };
731
+ }
732
+ // Step 2.5: Handle "Continue to checkout" intermediate screen (BYG page)
733
+ debugLog('[AmazonShoppingTool] Step 2.5: Checking for Continue to checkout screen...');
734
+ const continueClickResult = (await this.evaluate(`
735
+ (() => {
736
+ const priorityOrder = ['input', 'button', 'a', 'span'];
737
+ for (const tagName of priorityOrder) {
738
+ const elements = document.querySelectorAll(tagName);
739
+ for (const el of elements) {
740
+ const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
741
+ if (text === 'continue to checkout') {
742
+ el.click();
743
+ return { clicked: true, tag: el.tagName, className: el.className, text: text };
744
+ }
745
+ }
746
+ }
747
+ return { clicked: false };
748
+ })()
749
+ `));
750
+ if (continueClickResult.clicked) {
751
+ debugLog(`[AmazonShoppingTool] Clicked Continue to checkout: <${continueClickResult.tag} class="${continueClickResult.className}">${continueClickResult.text}`);
752
+ await new Promise((resolve) => setTimeout(resolve, 5000));
753
+ currentUrl = await this.getCurrentUrl();
754
+ debugLog(`[AmazonShoppingTool] After Continue to checkout URL: ${currentUrl}`);
755
+ }
756
+ else {
757
+ debugLog('[AmazonShoppingTool] No Continue to checkout button found, continuing...');
758
+ }
759
+ // Check for sign-in redirect again
760
+ if (currentUrl.includes('/ap/signin')) {
761
+ return {
762
+ operation: 'checkout',
763
+ success: false,
764
+ error: 'Password re-authentication required',
765
+ };
766
+ }
767
+ // Step 3: Navigate through checkout steps (up to 5 pages: address, payment, review, etc.)
768
+ debugLog('[AmazonShoppingTool] Step 3: Navigating checkout steps...');
769
+ for (let attempt = 1; attempt <= 5; attempt++) {
770
+ currentUrl = await this.getCurrentUrl();
771
+ debugLog(`[AmazonShoppingTool] Step 3.${attempt}: URL = ${currentUrl}`);
772
+ // Check for sign-in redirect
773
+ if (currentUrl.includes('/ap/signin')) {
774
+ return {
775
+ operation: 'checkout',
776
+ success: false,
777
+ error: 'Password re-authentication required',
778
+ };
779
+ }
780
+ // Try to click "Use this payment method" button if visible (before Place your order)
781
+ const usePaymentMethodResult = (await this.evaluate(`
782
+ (() => {
783
+ // First try by specific IDs (most reliable)
784
+ const primaryBtn = document.querySelector('#checkout-primary-continue-button-id');
785
+ const secondaryBtn = document.querySelector('#checkout-secondary-continue-button-id');
786
+
787
+ // Check if either button contains "use this payment method" text (case-insensitive)
788
+ for (const btn of [primaryBtn, secondaryBtn]) {
789
+ if (btn) {
790
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
791
+ if (text.includes('use this payment method')) {
792
+ // Find the clickable input inside the button span
793
+ const input = btn.querySelector('input.a-button-input');
794
+ if (input) {
795
+ input.click();
796
+ return { clicked: true, tag: 'input', className: input.className, text: text, method: 'input-click' };
797
+ }
798
+ // Fallback to clicking the button span itself
799
+ btn.click();
800
+ return { clicked: true, tag: btn.tagName, className: btn.className, text: text, method: 'span-click' };
801
+ }
802
+ }
803
+ }
804
+
805
+ // Fallback: search all elements for "use this payment method" text (case-insensitive)
806
+ const priorityOrder = ['input', 'button', 'a', 'span'];
807
+ for (const tagName of priorityOrder) {
808
+ const elements = document.querySelectorAll(tagName);
809
+ for (const el of elements) {
810
+ const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
811
+ if (text.includes('use this payment method')) {
812
+ el.click();
813
+ return { clicked: true, tag: el.tagName, className: el.className, text: text, method: 'fallback' };
814
+ }
815
+ }
816
+ }
817
+ return { clicked: false };
818
+ })()
819
+ `));
820
+ if (usePaymentMethodResult.clicked) {
821
+ debugLog(`[AmazonShoppingTool] Clicked "Use this payment method": <${usePaymentMethodResult.tag} class="${usePaymentMethodResult.className}"> via ${usePaymentMethodResult.method}`);
822
+ // Wait for the page to process the payment method selection
823
+ await new Promise((resolve) => setTimeout(resolve, 4000));
824
+ // Continue to next iteration to check for Place your order button
825
+ continue;
826
+ }
827
+ // Try to click "Place your order" button
828
+ const placeOrderResult = (await this.evaluate(`
829
+ (() => {
830
+ const priorityOrder = ['input', 'button', 'a', 'span'];
831
+ for (const tagName of priorityOrder) {
832
+ const elements = document.querySelectorAll(tagName);
833
+ for (const el of elements) {
834
+ const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
835
+ if (text === 'place your order' || text === 'place order') {
836
+ el.click();
837
+ return { clicked: true, tag: el.tagName, className: el.className, text: text };
838
+ }
839
+ }
840
+ }
841
+ return { clicked: false };
842
+ })()
843
+ `));
844
+ if (placeOrderResult.clicked) {
845
+ debugLog(`[AmazonShoppingTool] Clicked Place Order: <${placeOrderResult.tag} class="${placeOrderResult.className}">${placeOrderResult.text}`);
846
+ // Wait for navigation to confirmation page
847
+ debugLog('[AmazonShoppingTool] Waiting for navigation...');
848
+ try {
849
+ await this.waitForNavigation(15000);
850
+ debugLog('[AmazonShoppingTool] Navigation complete');
851
+ }
852
+ catch {
853
+ debugLog('[AmazonShoppingTool] Navigation wait timed out, continuing...');
854
+ }
855
+ break; // Order placed, exit loop
856
+ }
857
+ // Try to click "Continue" button to advance to next checkout page
858
+ const continueResult = (await this.evaluate(`
859
+ (() => {
860
+ const priorityOrder = ['input', 'button', 'a', 'span'];
861
+ for (const tagName of priorityOrder) {
862
+ const elements = document.querySelectorAll(tagName);
863
+ for (const el of elements) {
864
+ const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
865
+ if (text === 'continue' || text === 'continue to checkout') {
866
+ el.click();
867
+ return { clicked: true, tag: el.tagName, className: el.className, text: text };
868
+ }
869
+ }
870
+ }
871
+ return { clicked: false };
872
+ })()
873
+ `));
874
+ if (continueResult.clicked) {
875
+ debugLog(`[AmazonShoppingTool] Clicked Continue: <${continueResult.tag} class="${continueResult.className}">${continueResult.text}`);
876
+ await new Promise((resolve) => setTimeout(resolve, 4000));
877
+ }
878
+ else {
879
+ debugLog('[AmazonShoppingTool] No actionable button found, moving to confirmation check...');
880
+ break;
881
+ }
882
+ }
883
+ // Step 4: Handle duplicate order warning
884
+ debugLog('[AmazonShoppingTool] Step 4: Checking page state...');
885
+ currentUrl = await this.getCurrentUrl();
886
+ debugLog(`[AmazonShoppingTool] Current URL: ${currentUrl}`);
887
+ if (currentUrl.includes('duplicateOrder')) {
888
+ debugLog('[AmazonShoppingTool] Detected duplicate order warning - clicking Place Order again...');
889
+ const duplicateResult = (await this.evaluate(`
890
+ (() => {
891
+ const priorityOrder = ['input', 'button', 'a', 'span'];
892
+ for (const tagName of priorityOrder) {
893
+ const elements = document.querySelectorAll(tagName);
894
+ for (const el of elements) {
895
+ const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
896
+ if (text === 'place your order' || text === 'place order') {
897
+ el.click();
898
+ return { clicked: true, tag: el.tagName, className: el.className, text: text };
899
+ }
900
+ }
901
+ }
902
+ return { clicked: false };
903
+ })()
904
+ `));
905
+ if (duplicateResult.clicked) {
906
+ debugLog(`[AmazonShoppingTool] Clicked Place Order to confirm duplicate: <${duplicateResult.tag} class="${duplicateResult.className}">${duplicateResult.text}`);
907
+ debugLog('[AmazonShoppingTool] Waiting for navigation...');
908
+ try {
909
+ await this.waitForNavigation(15000);
910
+ debugLog('[AmazonShoppingTool] Navigation complete');
911
+ }
912
+ catch {
913
+ debugLog('[AmazonShoppingTool] Navigation wait timed out, continuing...');
914
+ }
915
+ }
916
+ }
917
+ // Step 5: Check for confirmation
918
+ debugLog('[AmazonShoppingTool] Step 5: Checking for order confirmation...');
919
+ let finalUrl = await this.getCurrentUrl();
920
+ debugLog(`[AmazonShoppingTool] Current URL: ${finalUrl}`);
921
+ // If on intermediate page (payment verification, etc.), wait for another navigation
922
+ if (finalUrl.includes('/cpe/') || finalUrl.includes('executions')) {
923
+ debugLog('[AmazonShoppingTool] On intermediate page, waiting for redirect...');
924
+ try {
925
+ await this.waitForNavigation(30000);
926
+ finalUrl = await this.getCurrentUrl();
927
+ debugLog(`[AmazonShoppingTool] After redirect URL: ${finalUrl}`);
928
+ }
929
+ catch {
930
+ debugLog('[AmazonShoppingTool] No further redirect, continuing...');
931
+ }
932
+ }
933
+ debugLog(`[AmazonShoppingTool] Final URL: ${finalUrl}`);
934
+ // Extract order number from URL if available (purchaseId parameter)
935
+ const urlOrderMatch = finalUrl.match(/purchaseId=(\d{3}-\d{7}-\d{7})/);
936
+ const orderNumberFromUrl = urlOrderMatch?.[1];
937
+ if (orderNumberFromUrl) {
938
+ debugLog(`[AmazonShoppingTool] Order number from URL: ${orderNumberFromUrl}`);
939
+ }
940
+ const isConfirmationUrl = finalUrl.includes('thankyou') ||
941
+ finalUrl.includes('confirmation') ||
942
+ finalUrl.includes('order-details') ||
943
+ finalUrl.includes('your-orders') ||
944
+ finalUrl.includes('gp/buy/thankyou');
945
+ debugLog(`[AmazonShoppingTool] URL indicates confirmation: ${isConfirmationUrl}`);
946
+ // Extract confirmation info
947
+ const confirmInfo = (await this.evaluate(`
948
+ ((urlIsConfirmation) => {
949
+ const fullText = document.body.innerText;
950
+
951
+ // Skip navigation text - find where main content starts
952
+ const mainContentStart = fullText.indexOf('Order placed') !== -1 ? fullText.indexOf('Order placed') :
953
+ fullText.indexOf('Thank you') !== -1 ? fullText.indexOf('Thank you') :
954
+ fullText.indexOf('Arriving') !== -1 ? fullText.indexOf('Arriving') :
955
+ fullText.indexOf('Delivery') !== -1 ? fullText.indexOf('Delivery') :
956
+ 0;
957
+ const text = fullText.substring(mainContentStart);
958
+
959
+ // Check for various success indicators
960
+ const hasOrderPlaced = text.toLowerCase().includes("order placed");
961
+ const hasThankYou = text.toLowerCase().includes("thank you");
962
+ const hasConfirmed = text.toLowerCase().includes("order confirmed");
963
+ const hasOnItsWay = text.toLowerCase().includes("on its way");
964
+ const hasOrderNumber = /\\d{3}-\\d{7}-\\d{7}/.test(text);
965
+
966
+ const hasSuccess = hasOrderPlaced || hasThankYou || hasConfirmed || hasOnItsWay || hasOrderNumber;
967
+
968
+ // Extract order details
969
+ const orderMatch = text.match(/(\\d{3}-\\d{7}-\\d{7})/);
970
+
971
+ // Delivery date - look for day of week + date pattern
972
+ const dayDateMatch = text.match(/((?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),?\\s*(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[.\\s]*\\d{1,2})/i);
973
+ const tomorrowMatch = text.match(/(Tomorrow,?\\s*(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?[.\\s]*\\d{0,2})/i);
974
+ const arrivingMatch = text.match(/Arriving\\s+([\\w\\s,]+\\d{1,2})/i);
975
+ const deliveryMatch = dayDateMatch || tomorrowMatch || arrivingMatch;
976
+
977
+ // Order total - look near beginning, not in recommendations
978
+ // Note: Amazon's thank you page often doesn't show detailed pricing - that's on the order details page
979
+ const firstPart = text.substring(0, 1500);
980
+ const totalMatch = firstPart.match(/Order total[:\\s]*\\$([\\d,.]+)/i) ||
981
+ firstPart.match(/Grand total[:\\s]*\\$([\\d,.]+)/i);
982
+
983
+ // Subtotal, shipping, tax - must be specifically labeled (avoid Prime savings messages)
984
+ const subtotalMatch = firstPart.match(/(?:^|\\n)\\s*Subtotal[:\\s]*\\$([\\d,.]+)/im) ||
985
+ firstPart.match(/Items?\\s*\\(\\d+\\)[:\\s]*\\$([\\d,.]+)/i);
986
+ // Shipping cost - must be "Shipping:" or "Shipping cost" not "shipping fees" (which is Prime savings)
987
+ const shippingMatch = firstPart.match(/(?:Shipping cost|Shipping & handling)[:\\s]*\\$([\\d,.]+)/i) ||
988
+ firstPart.match(/(?:^|\\n)\\s*Shipping[:\\s]*\\$([\\d,.]+)/im);
989
+ const taxMatch = firstPart.match(/(?:^|\\n)\\s*(?:Estimated )?[Tt]ax[:\\s]*\\$([\\d,.]+)/m);
990
+
991
+ // Shipping address - look for address after "Shipping to" but before any price or newline
992
+ const addressMatch = text.match(/(?:Shipping to|Delivering to|Ship to)[:\\s]*([A-Z][^\\n\\$]{10,150})/i);
993
+
994
+ // Payment method - look for card info
995
+ const paymentMatch = text.match(/(Visa|Mastercard|Amex|American Express|Discover)[^\\n]*ending in (\\d{4})/i) ||
996
+ text.match(/Payment method[:\\s]*([^\\n]{5,40})/i);
997
+
998
+ // Items - look for product titles (be more conservative to avoid promotional content)
999
+ const items = [];
1000
+
1001
+ return {
1002
+ isSuccess: hasSuccess || urlIsConfirmation,
1003
+ hasOrderPlaced,
1004
+ hasThankYou,
1005
+ hasConfirmed,
1006
+ hasOnItsWay,
1007
+ hasOrderNumber,
1008
+ orderNumber: orderMatch?.[1],
1009
+ deliveryDate: deliveryMatch?.[1]?.trim(),
1010
+ total: totalMatch ? '$' + totalMatch[1] : undefined,
1011
+ subtotal: subtotalMatch ? '$' + subtotalMatch[1] : undefined,
1012
+ shippingCost: shippingMatch ? '$' + shippingMatch[1] : undefined,
1013
+ tax: taxMatch ? '$' + taxMatch[1] : undefined,
1014
+ address: addressMatch?.[1]?.trim(),
1015
+ paymentMethod: paymentMatch?.[0]?.trim(),
1016
+ items: items.length > 0 ? items : undefined,
1017
+ };
1018
+ })(${isConfirmationUrl})
1019
+ `));
1020
+ debugLog(`[AmazonShoppingTool] Success indicators: orderPlaced=${confirmInfo.hasOrderPlaced}, thankYou=${confirmInfo.hasThankYou}, confirmed=${confirmInfo.hasConfirmed}, onItsWay=${confirmInfo.hasOnItsWay}, hasOrderNumber=${confirmInfo.hasOrderNumber}`);
1021
+ debugLog(`[AmazonShoppingTool] Order number: ${confirmInfo.orderNumber || 'not found'}`);
1022
+ debugLog(`[AmazonShoppingTool] Delivery: ${confirmInfo.deliveryDate || 'not found'}`);
1023
+ debugLog(`[AmazonShoppingTool] Total: ${confirmInfo.total || 'not found'}`);
1024
+ debugLog(`[AmazonShoppingTool] Subtotal: ${confirmInfo.subtotal || 'not found'}`);
1025
+ debugLog(`[AmazonShoppingTool] Shipping: ${confirmInfo.shippingCost || 'not found'}`);
1026
+ debugLog(`[AmazonShoppingTool] Tax: ${confirmInfo.tax || 'not found'}`);
1027
+ debugLog(`[AmazonShoppingTool] Address: ${confirmInfo.address || 'not found'}`);
1028
+ debugLog(`[AmazonShoppingTool] Payment: ${confirmInfo.paymentMethod || 'not found'}`);
1029
+ debugLog(`[AmazonShoppingTool] Items: ${confirmInfo.items?.length || 0} found`);
1030
+ // Save debug state before returning
1031
+ await this.saveDebugState('checkout-final');
1032
+ // If URL indicates success OR text indicates success, consider it successful
1033
+ if (!confirmInfo.isSuccess && !isConfirmationUrl) {
1034
+ debugLog('[AmazonShoppingTool] ERROR: No success indicators found');
1035
+ return {
1036
+ operation: 'checkout',
1037
+ success: false,
1038
+ error: 'Order may not have been placed',
1039
+ };
1040
+ }
1041
+ debugLog('[AmazonShoppingTool] SUCCESS: Order confirmed!');
1042
+ // Take confirmation screenshot of the order
1043
+ const screenshotUrl = await this.takeScreenshotAndUpload('checkout-confirmation', false);
1044
+ return {
1045
+ operation: 'checkout',
1046
+ success: true,
1047
+ order_number: confirmInfo.orderNumber || orderNumberFromUrl,
1048
+ estimated_delivery: confirmInfo.deliveryDate,
1049
+ total: confirmInfo.total,
1050
+ subtotal: confirmInfo.subtotal,
1051
+ shipping_cost: confirmInfo.shippingCost,
1052
+ tax: confirmInfo.tax,
1053
+ shipping_address: confirmInfo.address,
1054
+ payment_method: confirmInfo.paymentMethod,
1055
+ items: confirmInfo.items,
1056
+ screenshot_url: screenshotUrl || undefined,
1057
+ error: '',
1058
+ };
1059
+ }
1060
+ /**
1061
+ * Save current DOM state to file for debugging
1062
+ * Only saves when DEBUG env var is set
1063
+ */
1064
+ async saveDebugState(label) {
1065
+ // Skip saving if DEBUG is not enabled
1066
+ if (!DEBUG) {
1067
+ return null;
1068
+ }
1069
+ try {
1070
+ const fs = await import('fs/promises');
1071
+ const htmlContent = (await this.evaluate(`document.documentElement.outerHTML`));
1072
+ const currentUrl = await this.getCurrentUrl();
1073
+ const timestamp = Date.now();
1074
+ const debugPath = `/tmp/amazon-debug-${label}-${timestamp}.html`;
1075
+ // Add URL as comment at top of file
1076
+ const contentWithUrl = `<!-- URL: ${currentUrl} -->\n${htmlContent}`;
1077
+ await fs.writeFile(debugPath, contentWithUrl);
1078
+ debugLog(`[AmazonShoppingTool] Saved debug DOM to: ${debugPath}`);
1079
+ return debugPath;
1080
+ }
1081
+ catch (e) {
1082
+ console.error('[AmazonShoppingTool] Failed to save debug state:', e);
1083
+ return null;
1084
+ }
1085
+ }
1086
+ /**
1087
+ * Search for products
1088
+ */
1089
+ async searchProducts(params) {
1090
+ debugLog(`[AmazonShoppingTool] Searching for: ${params.query}`);
1091
+ const searchUrl = `https://www.amazon.com/s?k=${encodeURIComponent(params.query)}`;
1092
+ await this.navigateTo(searchUrl);
1093
+ // Wait for results
1094
+ await this.waitForSelector('[data-component-type="s-search-result"]', 7000);
1095
+ const maxResults = params.max_results || 5;
1096
+ // Debug: Save page state before extracting results
1097
+ debugLog('[AmazonShoppingTool] Saving debug state before search extraction...');
1098
+ await this.saveDebugState('search-before-extract');
1099
+ // Debug: Log current URL and page title
1100
+ const currentSearchUrl = await this.getCurrentUrl();
1101
+ debugLog(`[AmazonShoppingTool] Search URL: ${currentSearchUrl}`);
1102
+ // Extract search results with enhanced debugging
1103
+ const searchData = (await this.evaluate(`
1104
+ (() => {
1105
+ const results = [];
1106
+ const items = document.querySelectorAll('[data-component-type="s-search-result"]');
1107
+ const maxResults = ${maxResults};
1108
+
1109
+ console.log('[AmazonSearch Debug] Found ' + items.length + ' search result items');
1110
+
1111
+ for (let i = 0; i < Math.min(items.length, maxResults); i++) {
1112
+ const item = items[i];
1113
+ const asin = item.getAttribute('data-asin');
1114
+ if (!asin) {
1115
+ console.log('[AmazonSearch Debug] Item ' + i + ' has no ASIN, skipping');
1116
+ continue;
1117
+ }
1118
+
1119
+ // Extract title - Amazon has TWO h2 elements:
1120
+ // 1. First h2: Brand name only (e.g., "Amazon Basics")
1121
+ // 2. Second h2: Full title with aria-label (inside anchor element)
1122
+ // We need to get the SECOND h2 which has the full title
1123
+ let title = 'Unknown Product';
1124
+ let titleSelectorUsed = '';
1125
+
1126
+ // Approach 1: Look for h2 with aria-label attribute (contains full title)
1127
+ const h2WithAriaLabel = item.querySelector('h2[aria-label]');
1128
+ if (h2WithAriaLabel) {
1129
+ const ariaLabel = h2WithAriaLabel.getAttribute('aria-label');
1130
+ if (ariaLabel && ariaLabel.length > 10) {
1131
+ // Remove "Sponsored Ad - " prefix if present
1132
+ title = ariaLabel.replace(/^Sponsored Ad - /i, '').trim();
1133
+ titleSelectorUsed = 'h2[aria-label]';
1134
+ }
1135
+ }
1136
+
1137
+ // Approach 2: Look for anchor > h2 (full title h2 is inside anchor)
1138
+ if (titleSelectorUsed === '') {
1139
+ const anchorWithH2 = item.querySelector('a > h2, a h2.a-text-normal');
1140
+ if (anchorWithH2) {
1141
+ const h2Text = anchorWithH2.textContent?.trim();
1142
+ if (h2Text && h2Text.length > 10) {
1143
+ title = h2Text;
1144
+ titleSelectorUsed = 'a > h2';
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ // Approach 3: Look for [data-cy="title-recipe"] anchor text
1150
+ if (titleSelectorUsed === '') {
1151
+ const titleRecipe = item.querySelector('[data-cy="title-recipe"] a.a-text-normal, [data-cy="title-recipe"] a.s-link-style');
1152
+ if (titleRecipe) {
1153
+ const linkText = titleRecipe.textContent?.trim();
1154
+ if (linkText && linkText.length > 10) {
1155
+ title = linkText;
1156
+ titleSelectorUsed = '[data-cy="title-recipe"] a';
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ // Approach 4: Fallback to image alt text (usually has full product name)
1162
+ if (titleSelectorUsed === '' || title.length < 20) {
1163
+ const imgEl = item.querySelector('.s-image');
1164
+ if (imgEl) {
1165
+ const altText = imgEl.getAttribute('alt');
1166
+ if (altText && altText.length > (title?.length || 0)) {
1167
+ // Remove "Sponsored Ad - " prefix if present
1168
+ title = altText.replace(/^Sponsored Ad - /i, '').trim();
1169
+ titleSelectorUsed = 'img[alt]';
1170
+ }
1171
+ }
1172
+ }
1173
+
1174
+ console.log('[AmazonSearch Debug] Item ' + i + ' ASIN=' + asin + ' title="' + title.substring(0, 100) + '" selector=' + titleSelectorUsed);
1175
+
1176
+ const priceEl = item.querySelector('.a-price .a-offscreen');
1177
+ const ratingEl = item.querySelector('.a-icon-alt');
1178
+ const reviewsEl = item.querySelector('.a-size-small .a-link-normal');
1179
+ const imageEl = item.querySelector('.s-image');
1180
+ const primeEl = item.querySelector('.s-prime, .aok-relative');
1181
+
1182
+ results.push({
1183
+ asin,
1184
+ title,
1185
+ price: priceEl?.textContent?.trim() || '',
1186
+ rating: ratingEl?.textContent?.match(/[0-9.]+/)?.[0] || '',
1187
+ reviews_count: reviewsEl?.textContent?.match(/[0-9,]+/)?.[0] || '',
1188
+ url: 'https://www.amazon.com/dp/' + asin,
1189
+ image: imageEl?.src || '',
1190
+ prime: !!primeEl,
1191
+ _debug_titleSelector: titleSelectorUsed
1192
+ });
1193
+ }
1194
+
1195
+ const totalEl = document.querySelector('.s-breadcrumb .a-color-state');
1196
+ const totalMatch = totalEl?.textContent?.match(/([0-9,]+)/);
1197
+
1198
+ return {
1199
+ results,
1200
+ totalResults: totalMatch ? parseInt(totalMatch[1].replace(/,/g, ''), 10) : results.length
1201
+ };
1202
+ })()
1203
+ `));
1204
+ // Log extracted results for debugging
1205
+ debugLog(`[AmazonShoppingTool] Extracted ${searchData.results.length} results`);
1206
+ searchData.results.forEach((r, i) => {
1207
+ debugLog(`[AmazonShoppingTool] Result ${i}: ASIN=${r.asin}, title="${r.title.substring(0, 50)}..."`);
1208
+ });
1209
+ return {
1210
+ operation: 'search',
1211
+ success: true,
1212
+ results: searchData.results,
1213
+ total_results: searchData.totalResults,
1214
+ error: '',
1215
+ };
1216
+ }
1217
+ /**
1218
+ * Get product details
1219
+ */
1220
+ async getProduct(params) {
1221
+ const asin = this.extractAsin(params.product_url);
1222
+ const productUrl = this.buildProductUrl(asin);
1223
+ debugLog(`[AmazonShoppingTool] Getting product details: ${asin}`);
1224
+ await this.navigateTo(productUrl);
1225
+ // Wait for product page
1226
+ await this.waitForSelector('#productTitle', 5000);
1227
+ // Extract product details
1228
+ const productData = (await this.evaluate(`
1229
+ (() => {
1230
+ const titleEl = document.querySelector('#productTitle');
1231
+ const priceEl = document.querySelector('.a-price .a-offscreen, #priceblock_ourprice, #priceblock_dealprice');
1232
+ const ratingEl = document.querySelector('#acrPopover .a-icon-alt');
1233
+ const reviewsEl = document.querySelector('#acrCustomerReviewText');
1234
+ const descEl = document.querySelector('#productDescription p, #feature-bullets');
1235
+ const availEl = document.querySelector('#availability span');
1236
+
1237
+ // Get feature bullets
1238
+ const features = [];
1239
+ document.querySelectorAll('#feature-bullets li span').forEach(el => {
1240
+ const text = el.textContent?.trim();
1241
+ if (text) features.push(text);
1242
+ });
1243
+
1244
+ // Get images
1245
+ const images = [];
1246
+ document.querySelectorAll('#altImages img').forEach(img => {
1247
+ const src = img.src?.replace(/._[^.]+_./, '._AC_SL1500_.');
1248
+ if (src && !src.includes('play-button')) images.push(src);
1249
+ });
1250
+
1251
+ return {
1252
+ asin: '${asin}',
1253
+ title: titleEl?.textContent?.trim() || 'Unknown Product',
1254
+ price: priceEl?.textContent?.trim() || '',
1255
+ rating: ratingEl?.textContent?.match(/[0-9.]+/)?.[0] || '',
1256
+ reviews_count: reviewsEl?.textContent?.match(/[0-9,]+/)?.[0] || '',
1257
+ description: descEl?.textContent?.trim() || '',
1258
+ features,
1259
+ availability: availEl?.textContent?.trim() || '',
1260
+ url: '${productUrl}',
1261
+ images,
1262
+ };
1263
+ })()
1264
+ `));
1265
+ return {
1266
+ operation: 'get_product',
1267
+ success: true,
1268
+ product: productData,
1269
+ error: '',
1270
+ };
1271
+ }
1272
+ /**
1273
+ * Take a screenshot operation - navigate to URL if provided, then capture screenshot
1274
+ */
1275
+ async takeScreenshot(params) {
1276
+ debugLog('[AmazonShoppingTool] Screenshot operation');
1277
+ // Navigate to URL if provided
1278
+ if (params.url) {
1279
+ debugLog(`[AmazonShoppingTool] Navigating to: ${params.url}`);
1280
+ await this.navigateTo(params.url);
1281
+ // Wait for page to load
1282
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1283
+ }
1284
+ // Take and upload screenshot
1285
+ const screenshotUrl = await this.takeScreenshotAndUpload('page', params.full_page);
1286
+ if (!screenshotUrl) {
1287
+ return {
1288
+ operation: 'screenshot',
1289
+ success: false,
1290
+ error: 'Failed to capture or upload screenshot',
1291
+ };
1292
+ }
1293
+ return {
1294
+ operation: 'screenshot',
1295
+ success: true,
1296
+ screenshot_url: screenshotUrl,
1297
+ error: '',
1298
+ };
1299
+ }
1300
+ }
1301
+ //# sourceMappingURL=amazon-shopping-tool.js.map