@bubblelab/bubble-core 0.1.222 → 0.1.225

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 (169) hide show
  1. package/dist/bubble-bundle.d.ts +182 -182
  2. package/dist/bubble-trigger/index.d.ts +2 -0
  3. package/dist/bubble-trigger/index.d.ts.map +1 -0
  4. package/dist/bubble-trigger/index.js +2 -0
  5. package/dist/bubble-trigger/index.js.map +1 -0
  6. package/dist/bubble-trigger/types.d.ts +87 -0
  7. package/dist/bubble-trigger/types.d.ts.map +1 -0
  8. package/dist/bubble-trigger/types.js +14 -0
  9. package/dist/bubble-trigger/types.js.map +1 -0
  10. package/dist/bubbles/service-bubble/agi-inc.d.ts +80 -80
  11. package/dist/bubbles/service-bubble/ai-agent.d.ts +74 -74
  12. package/dist/bubbles/service-bubble/airtable.d.ts +102 -102
  13. package/dist/bubbles/service-bubble/apify/actors/google-maps-scraper.d.ts +2 -2
  14. package/dist/bubbles/service-bubble/apify/actors/instagram-hashtag-scraper.d.ts +6 -6
  15. package/dist/bubbles/service-bubble/apify/actors/instagram-scraper.d.ts +20 -20
  16. package/dist/bubbles/service-bubble/apify/actors/linkedin-posts-search.d.ts +26 -26
  17. package/dist/bubbles/service-bubble/apify/actors/linkedin-profile-detail.d.ts +90 -90
  18. package/dist/bubbles/service-bubble/apify/actors/linkedin-profile-posts.d.ts +70 -70
  19. package/dist/bubbles/service-bubble/apify/actors/tiktok-scraper.d.ts +16 -16
  20. package/dist/bubbles/service-bubble/apify/actors/twitter-scraper.d.ts +37 -37
  21. package/dist/bubbles/service-bubble/apify/actors/youtube-scraper.d.ts +16 -16
  22. package/dist/bubbles/service-bubble/apify/apify-scraper.schema.d.ts +243 -243
  23. package/dist/bubbles/service-bubble/apify/apify.d.ts +30 -30
  24. package/dist/bubbles/service-bubble/ashby/ashby.d.ts +64 -64
  25. package/dist/bubbles/service-bubble/ashby/ashby.schema.d.ts +86 -86
  26. package/dist/bubbles/service-bubble/assembled/assembled.d.ts +42 -42
  27. package/dist/bubbles/service-bubble/assembled/assembled.schema.d.ts +42 -42
  28. package/dist/bubbles/service-bubble/attio/attio.d.ts +34 -34
  29. package/dist/bubbles/service-bubble/attio/attio.schema.d.ts +34 -34
  30. package/dist/bubbles/service-bubble/browserbase/browserbase.d.ts +31 -31
  31. package/dist/bubbles/service-bubble/browserbase/browserbase.schema.d.ts +33 -33
  32. package/dist/bubbles/service-bubble/confluence/confluence.d.ts +24 -24
  33. package/dist/bubbles/service-bubble/confluence/confluence.schema.d.ts +24 -24
  34. package/dist/bubbles/service-bubble/crustdata/crustdata.d.ts +126 -126
  35. package/dist/bubbles/service-bubble/crustdata/crustdata.schema.d.ts +158 -158
  36. package/dist/bubbles/service-bubble/eleven-labs.d.ts +28 -28
  37. package/dist/bubbles/service-bubble/firecrawl.d.ts +834 -834
  38. package/dist/bubbles/service-bubble/followupboss.d.ts +144 -144
  39. package/dist/bubbles/service-bubble/fullenrich/fullenrich.d.ts +68 -68
  40. package/dist/bubbles/service-bubble/fullenrich/fullenrich.schema.d.ts +108 -108
  41. package/dist/bubbles/service-bubble/github.d.ts +144 -144
  42. package/dist/bubbles/service-bubble/gmail.d.ts +240 -240
  43. package/dist/bubbles/service-bubble/google-calendar.d.ts +138 -138
  44. package/dist/bubbles/service-bubble/google-drive.d.ts +52 -52
  45. package/dist/bubbles/service-bubble/google-sheets/google-sheets.d.ts +44 -44
  46. package/dist/bubbles/service-bubble/google-sheets/google-sheets.schema.d.ts +48 -48
  47. package/dist/bubbles/service-bubble/google-sheets.d.ts +1811 -0
  48. package/dist/bubbles/service-bubble/google-sheets.d.ts.map +1 -0
  49. package/dist/bubbles/service-bubble/google-sheets.js +904 -0
  50. package/dist/bubbles/service-bubble/google-sheets.js.map +1 -0
  51. package/dist/bubbles/service-bubble/hello-world.d.ts +8 -8
  52. package/dist/bubbles/service-bubble/http.d.ts +18 -18
  53. package/dist/bubbles/service-bubble/hubspot/hubspot.d.ts +8 -8
  54. package/dist/bubbles/service-bubble/hubspot/hubspot.schema.d.ts +8 -8
  55. package/dist/bubbles/service-bubble/insforge-db.d.ts +16 -16
  56. package/dist/bubbles/service-bubble/jira/jira.d.ts +60 -60
  57. package/dist/bubbles/service-bubble/jira/jira.schema.d.ts +62 -62
  58. package/dist/bubbles/service-bubble/linear/linear.d.ts +32 -32
  59. package/dist/bubbles/service-bubble/linear/linear.schema.d.ts +32 -32
  60. package/dist/bubbles/service-bubble/notion/notion.d.ts +682 -682
  61. package/dist/bubbles/service-bubble/notion/property-schemas.d.ts +16 -16
  62. package/dist/bubbles/service-bubble/postgresql.d.ts +16 -16
  63. package/dist/bubbles/service-bubble/posthog/posthog.d.ts +24 -24
  64. package/dist/bubbles/service-bubble/posthog/posthog.schema.d.ts +30 -30
  65. package/dist/bubbles/service-bubble/pylon/index.d.ts +3 -0
  66. package/dist/bubbles/service-bubble/pylon/index.d.ts.map +1 -0
  67. package/dist/bubbles/service-bubble/pylon/index.js +3 -0
  68. package/dist/bubbles/service-bubble/pylon/index.js.map +1 -0
  69. package/dist/bubbles/service-bubble/pylon/pylon.d.ts +435 -0
  70. package/dist/bubbles/service-bubble/pylon/pylon.d.ts.map +1 -0
  71. package/dist/bubbles/service-bubble/pylon/pylon.js +375 -0
  72. package/dist/bubbles/service-bubble/pylon/pylon.js.map +1 -0
  73. package/dist/bubbles/service-bubble/pylon/pylon.schema.d.ts +408 -0
  74. package/dist/bubbles/service-bubble/pylon/pylon.schema.d.ts.map +1 -0
  75. package/dist/bubbles/service-bubble/pylon/pylon.schema.js +249 -0
  76. package/dist/bubbles/service-bubble/pylon/pylon.schema.js.map +1 -0
  77. package/dist/bubbles/service-bubble/ramp/ramp.d.ts +28 -28
  78. package/dist/bubbles/service-bubble/ramp/ramp.schema.d.ts +28 -28
  79. package/dist/bubbles/service-bubble/resend.d.ts +28 -28
  80. package/dist/bubbles/service-bubble/s3/s3.d.ts +10 -10
  81. package/dist/bubbles/service-bubble/s3/s3.schema.d.ts +10 -10
  82. package/dist/bubbles/service-bubble/sendsafely/sendsafely.d.ts +10 -10
  83. package/dist/bubbles/service-bubble/sendsafely/sendsafely.schema.d.ts +12 -12
  84. package/dist/bubbles/service-bubble/slab/slab.d.ts +38 -38
  85. package/dist/bubbles/service-bubble/slab/slab.schema.d.ts +44 -44
  86. package/dist/bubbles/service-bubble/slack/slack.d.ts +558 -558
  87. package/dist/bubbles/service-bubble/slack.d.ts +5869 -0
  88. package/dist/bubbles/service-bubble/slack.d.ts.map +1 -0
  89. package/dist/bubbles/service-bubble/slack.js +1536 -0
  90. package/dist/bubbles/service-bubble/slack.js.map +1 -0
  91. package/dist/bubbles/service-bubble/storage.d.ts +20 -20
  92. package/dist/bubbles/service-bubble/stripe/stripe.d.ts +157 -157
  93. package/dist/bubbles/service-bubble/stripe/stripe.schema.d.ts +183 -183
  94. package/dist/bubbles/service-bubble/telegram.d.ts +1561 -1561
  95. package/dist/bubbles/service-bubble/xero/xero.d.ts +30 -30
  96. package/dist/bubbles/service-bubble/xero/xero.schema.d.ts +30 -30
  97. package/dist/bubbles/service-bubble/zendesk/zendesk.d.ts +93 -93
  98. package/dist/bubbles/service-bubble/zendesk/zendesk.schema.d.ts +93 -93
  99. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.d.ts +19 -19
  100. package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.d.ts +21 -21
  101. package/dist/bubbles/tool-bubble/browser-tools/_shared/schema.d.ts +2 -2
  102. package/dist/bubbles/tool-bubble/browser-tools/linkedin-accept-invitations/schema.d.ts +2 -2
  103. package/dist/bubbles/tool-bubble/browser-tools/linkedin-accept-invitations/tool.d.ts +2 -2
  104. package/dist/bubbles/tool-bubble/browser-tools/linkedin-connection/schema.d.ts +2 -2
  105. package/dist/bubbles/tool-bubble/browser-tools/linkedin-connection/tool.d.ts +2 -2
  106. package/dist/bubbles/tool-bubble/browser-tools/linkedin-received-invitations/schema.d.ts +2 -2
  107. package/dist/bubbles/tool-bubble/browser-tools/linkedin-received-invitations/tool.d.ts +2 -2
  108. package/dist/bubbles/tool-bubble/browser-tools/linkedin-sent-invitations/schema.d.ts +2 -2
  109. package/dist/bubbles/tool-bubble/browser-tools/linkedin-sent-invitations/tool.d.ts +2 -2
  110. package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.d.ts +20 -20
  111. package/dist/bubbles/tool-bubble/chart-js-tool.d.ts +26 -26
  112. package/dist/bubbles/tool-bubble/code-edit-tool.d.ts +4 -4
  113. package/dist/bubbles/tool-bubble/company-enrichment-tool.d.ts +62 -62
  114. package/dist/bubbles/tool-bubble/get-bubble-details-tool.d.ts +4 -4
  115. package/dist/bubbles/tool-bubble/get-trigger-detail-tool.d.ts +4 -4
  116. package/dist/bubbles/tool-bubble/google-maps-tool.d.ts +4 -4
  117. package/dist/bubbles/tool-bubble/instagram-tool.d.ts +14 -14
  118. package/dist/bubbles/tool-bubble/linkedin-connection-tool/index.d.ts +3 -0
  119. package/dist/bubbles/tool-bubble/linkedin-connection-tool/index.d.ts.map +1 -0
  120. package/dist/bubbles/tool-bubble/linkedin-connection-tool/index.js +3 -0
  121. package/dist/bubbles/tool-bubble/linkedin-connection-tool/index.js.map +1 -0
  122. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.d.ts +160 -0
  123. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.d.ts.map +1 -0
  124. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.js +706 -0
  125. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.js.map +1 -0
  126. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.schema.d.ts +93 -0
  127. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.schema.d.ts.map +1 -0
  128. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.schema.js +50 -0
  129. package/dist/bubbles/tool-bubble/linkedin-connection-tool/linkedin-connection-tool.schema.js.map +1 -0
  130. package/dist/bubbles/tool-bubble/linkedin-tool.d.ts +570 -570
  131. package/dist/bubbles/tool-bubble/list-airtable-bases-tool.d.ts +4 -4
  132. package/dist/bubbles/tool-bubble/list-airtable-tables-tool.d.ts +4 -4
  133. package/dist/bubbles/tool-bubble/list-bubbles-tool.d.ts +4 -4
  134. package/dist/bubbles/tool-bubble/list-capabilities-tool.d.ts +4 -4
  135. package/dist/bubbles/tool-bubble/people-search-tool.d.ts +58 -58
  136. package/dist/bubbles/tool-bubble/reddit-scrape-tool.d.ts +32 -32
  137. package/dist/bubbles/tool-bubble/research-agent-tool.d.ts +4 -4
  138. package/dist/bubbles/tool-bubble/sql-query-tool.d.ts +8 -8
  139. package/dist/bubbles/tool-bubble/tiktok-tool.d.ts +88 -88
  140. package/dist/bubbles/tool-bubble/tool-template.d.ts +4 -4
  141. package/dist/bubbles/tool-bubble/twitter-tool.d.ts +188 -188
  142. package/dist/bubbles/tool-bubble/web-crawl-tool.d.ts +22 -22
  143. package/dist/bubbles/tool-bubble/web-extract-tool.d.ts +8 -8
  144. package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts +8 -8
  145. package/dist/bubbles/tool-bubble/web-search-tool.d.ts +8 -8
  146. package/dist/bubbles/tool-bubble/yc-scraper-tool.d.ts +26 -26
  147. package/dist/bubbles/tool-bubble/youtube-tool.d.ts +38 -38
  148. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts +114 -0
  149. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts.map +1 -0
  150. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js +777 -0
  151. package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js.map +1 -0
  152. package/dist/bubbles/workflow-bubble/database-analyzer.workflow.d.ts +4 -4
  153. package/dist/bubbles/workflow-bubble/generate-document.workflow.d.ts +40 -40
  154. package/dist/bubbles/workflow-bubble/parse-document.workflow.d.ts +8 -8
  155. package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts +72 -72
  156. package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.d.ts +32 -32
  157. package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.d.ts +32 -32
  158. package/dist/bubbles/workflow-bubble/slack-formatter-agent.d.ts +104 -104
  159. package/dist/bubbles/workflow-bubble/slack-notifier.workflow.d.ts +8 -8
  160. package/dist/bubbles.json +1 -1
  161. package/dist/types/ai-models.d.ts +4 -0
  162. package/dist/types/ai-models.d.ts.map +1 -0
  163. package/dist/types/ai-models.js +16 -0
  164. package/dist/types/ai-models.js.map +1 -0
  165. package/dist/utils/param-helper.d.ts +2 -0
  166. package/dist/utils/param-helper.d.ts.map +1 -0
  167. package/dist/utils/param-helper.js +5 -0
  168. package/dist/utils/param-helper.js.map +1 -0
  169. package/package.json +2 -2
@@ -0,0 +1,706 @@
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 { LinkedInConnectionToolParamsSchema, LinkedInConnectionToolResultSchema, } from './linkedin-connection-tool.schema.js';
5
+ // Debug logging helper - only logs when DEBUG_BROWSER_BASE env var is set
6
+ const DEBUG = process.env.DEBUG_BROWSER_BASE;
7
+ function debugLog(...args) {
8
+ if (DEBUG) {
9
+ console.log(...args);
10
+ }
11
+ }
12
+ /**
13
+ * LinkedIn Connection Tool
14
+ *
15
+ * A tool bubble for automating LinkedIn connection requests.
16
+ * Handles both profile types:
17
+ * - Profiles with direct "Connect" button
18
+ * - Profiles where "Connect" is under the "More" dropdown
19
+ *
20
+ * Features:
21
+ * - Send connection requests to LinkedIn profiles
22
+ * - Add optional personalized notes
23
+ * - Handle various profile layouts
24
+ *
25
+ * Required Credentials:
26
+ * - LINKEDIN_CRED: Browser session credential with LinkedIn cookies
27
+ *
28
+ * Security:
29
+ * - Uses BrowserBase cloud browsers (isolated)
30
+ * - Credentials are encrypted at rest
31
+ * - Session data is not persisted beyond operation
32
+ */
33
+ export class LinkedInConnectionTool extends ToolBubble {
34
+ static bubbleName = 'linkedin-connection-tool';
35
+ static schema = LinkedInConnectionToolParamsSchema;
36
+ static resultSchema = LinkedInConnectionToolResultSchema;
37
+ static shortDescription = 'LinkedIn connection automation - send connection requests with optional notes';
38
+ static longDescription = `
39
+ LinkedIn Connection Tool for automating connection requests.
40
+
41
+ Features:
42
+ - Send connection requests to LinkedIn profiles
43
+ - Add optional personalized notes (up to 300 characters)
44
+ - Handles profiles with direct Connect button
45
+ - Handles profiles where Connect is under "More" dropdown
46
+
47
+ Required Credentials:
48
+ - LINKEDIN_CRED: Browser session credential (authenticate via browser session)
49
+
50
+ Note: The tool operates using authenticated browser sessions to ensure security.
51
+ `;
52
+ static alias = 'linkedin';
53
+ static type = 'tool';
54
+ sessionId = null;
55
+ contextId = null;
56
+ cookies = null;
57
+ constructor(params = { operation: 'send_connection', profile_url: '' }, context) {
58
+ super(params, context);
59
+ }
60
+ /**
61
+ * Choose the credential to use for LinkedIn operations
62
+ */
63
+ chooseCredential() {
64
+ const { credentials } = this.params;
65
+ if (!credentials || typeof credentials !== 'object') {
66
+ return undefined;
67
+ }
68
+ return credentials[CredentialType.LINKEDIN_CRED];
69
+ }
70
+ /**
71
+ * Parse the LINKEDIN_CRED to extract contextId and cookies
72
+ */
73
+ parseBrowserSessionData() {
74
+ const credential = this.chooseCredential();
75
+ if (!credential) {
76
+ return null;
77
+ }
78
+ try {
79
+ const jsonString = Buffer.from(credential, 'base64').toString('utf-8');
80
+ const parsed = JSON.parse(jsonString);
81
+ const validated = BrowserSessionDataSchema.safeParse(parsed);
82
+ if (validated.success) {
83
+ return validated.data;
84
+ }
85
+ console.error('[LinkedInConnectionTool] Invalid credential format:', validated.error);
86
+ return null;
87
+ }
88
+ catch (error) {
89
+ console.error('[LinkedInConnectionTool] Failed to parse credential:', error);
90
+ return null;
91
+ }
92
+ }
93
+ /**
94
+ * Start a browser session using BrowserBase
95
+ */
96
+ async startBrowserSession() {
97
+ debugLog('[LinkedInConnectionTool] Starting browser session');
98
+ if (this.sessionId) {
99
+ return this.sessionId;
100
+ }
101
+ const sessionData = this.parseBrowserSessionData();
102
+ if (sessionData) {
103
+ this.contextId = sessionData.contextId;
104
+ this.cookies = sessionData.cookies;
105
+ debugLog(`[LinkedInConnectionTool] Loaded session data: contextId=${this.contextId}, cookies=${this.cookies.length}`);
106
+ }
107
+ else {
108
+ debugLog('[LinkedInConnectionTool] No LINKEDIN_CRED found, creating new context');
109
+ }
110
+ const startsession_browserbase = new BrowserBaseBubble({
111
+ operation: 'start_session',
112
+ context_id: this.contextId || undefined,
113
+ cookies: this.cookies || undefined,
114
+ credentials: this.params.credentials,
115
+ }, this.context, 'startsession_browserbase');
116
+ const result = await startsession_browserbase.action();
117
+ if (!result.data.success || !result.data.session_id) {
118
+ throw new Error(result.data.error || 'Failed to start browser session');
119
+ }
120
+ this.sessionId = result.data.session_id;
121
+ if (result.data.context_id) {
122
+ this.contextId = result.data.context_id;
123
+ }
124
+ debugLog(`[LinkedInConnectionTool] Browser session started: ${this.sessionId}, context: ${this.contextId}`);
125
+ if (this.context?.logger && result.data.debug_url) {
126
+ this.context.logger.logBrowserSessionStart(this.sessionId, result.data.debug_url, this.context.variableId);
127
+ }
128
+ return this.sessionId;
129
+ }
130
+ /**
131
+ * End the browser session
132
+ */
133
+ async endBrowserSession() {
134
+ if (!this.sessionId)
135
+ return;
136
+ const sessionIdToEnd = this.sessionId;
137
+ try {
138
+ const endsession_browserbase = new BrowserBaseBubble({
139
+ operation: 'end_session',
140
+ session_id: sessionIdToEnd,
141
+ }, this.context, 'endsession_browserbase');
142
+ await endsession_browserbase.action();
143
+ debugLog(`[LinkedInConnectionTool] Browser session ended: ${sessionIdToEnd}`);
144
+ }
145
+ catch (error) {
146
+ console.error('[LinkedInConnectionTool] Error ending session:', error);
147
+ }
148
+ finally {
149
+ if (this.context?.logger) {
150
+ this.context.logger.logBrowserSessionEnd(sessionIdToEnd, this.context.variableId);
151
+ }
152
+ this.sessionId = null;
153
+ }
154
+ }
155
+ /**
156
+ * Navigate to a URL
157
+ */
158
+ async navigateTo(url) {
159
+ if (!this.sessionId) {
160
+ throw new Error('No active browser session');
161
+ }
162
+ const navigate_browserbase = new BrowserBaseBubble({
163
+ operation: 'navigate',
164
+ session_id: this.sessionId,
165
+ url,
166
+ wait_until: 'domcontentloaded',
167
+ timeout: 30000,
168
+ }, this.context, 'navigate_browserbase');
169
+ const result = await navigate_browserbase.action();
170
+ if (!result.data.success) {
171
+ throw new Error(result.data.error || 'Navigation failed');
172
+ }
173
+ }
174
+ /**
175
+ * Evaluate JavaScript in page
176
+ */
177
+ async evaluate(script) {
178
+ if (!this.sessionId) {
179
+ throw new Error('No active browser session');
180
+ }
181
+ const evaluate_browserbase = new BrowserBaseBubble({
182
+ operation: 'evaluate',
183
+ session_id: this.sessionId,
184
+ script,
185
+ }, this.context, 'evaluate_browserbase');
186
+ const result = await evaluate_browserbase.action();
187
+ if (!result.data.success) {
188
+ throw new Error(result.data.error || 'Script evaluation failed');
189
+ }
190
+ return result.data.result;
191
+ }
192
+ /**
193
+ * Type text into an input field
194
+ */
195
+ async typeText(selector, text) {
196
+ if (!this.sessionId) {
197
+ throw new Error('No active browser session');
198
+ }
199
+ const type_browserbase = new BrowserBaseBubble({
200
+ operation: 'type',
201
+ session_id: this.sessionId,
202
+ selector,
203
+ text,
204
+ delay: 50,
205
+ }, this.context, 'type_browserbase');
206
+ const result = await type_browserbase.action();
207
+ return result.data.success;
208
+ }
209
+ /**
210
+ * Get current page URL
211
+ */
212
+ async getCurrentUrl() {
213
+ const result = (await this.evaluate(`window.location.href`));
214
+ return result;
215
+ }
216
+ /**
217
+ * Poll for an element using evaluate with retries
218
+ * More flexible than waitForSelector as it can use custom JS logic
219
+ */
220
+ async pollForElement(checkScript, options = {}) {
221
+ const { maxAttempts = 15, intervalMs = 1000, description = 'element', } = options;
222
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
223
+ const found = await this.evaluate(checkScript);
224
+ if (found) {
225
+ debugLog(`[LinkedInConnectionTool] Found ${description} on attempt ${attempt}/${maxAttempts}`);
226
+ return true;
227
+ }
228
+ debugLog(`[LinkedInConnectionTool] Waiting for ${description}... attempt ${attempt}/${maxAttempts}`);
229
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
230
+ }
231
+ debugLog(`[LinkedInConnectionTool] ${description} not found after ${maxAttempts} attempts`);
232
+ return false;
233
+ }
234
+ /**
235
+ * Wait for page to be ready by checking for key LinkedIn profile elements
236
+ */
237
+ async waitForProfilePageReady() {
238
+ // Poll for either: profile name (h1), connect button, or "More" button
239
+ // This indicates the page has loaded enough for interaction
240
+ const checkScript = `
241
+ (() => {
242
+ // Check for profile name
243
+ const h1 = document.querySelector('h1');
244
+ if (h1 && h1.textContent?.trim()) return true;
245
+
246
+ // Check for Connect button
247
+ const buttons = document.querySelectorAll('button');
248
+ for (const btn of buttons) {
249
+ const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
250
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
251
+ if (ariaLabel.includes('connect') || text === 'connect') return true;
252
+ if (ariaLabel === 'more actions' || btn.id?.includes('profile-overflow-action')) return true;
253
+ }
254
+ return false;
255
+ })()
256
+ `;
257
+ return this.pollForElement(checkScript, {
258
+ maxAttempts: 15,
259
+ intervalMs: 1500,
260
+ description: 'profile page elements',
261
+ });
262
+ }
263
+ /**
264
+ * Wait for modal/dialog to appear after clicking Connect
265
+ */
266
+ async waitForConnectionModal() {
267
+ const checkScript = `
268
+ (() => {
269
+ const buttons = document.querySelectorAll('button');
270
+ for (const btn of buttons) {
271
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
272
+ // Look for "Add a note" or "Send without a note" buttons in the modal
273
+ if (text.includes('add a note') || text.includes('send without')) return true;
274
+ }
275
+ return false;
276
+ })()
277
+ `;
278
+ return this.pollForElement(checkScript, {
279
+ maxAttempts: 8,
280
+ intervalMs: 1500,
281
+ description: 'connection modal',
282
+ });
283
+ }
284
+ /**
285
+ * Wait for dropdown menu to appear after clicking "More"
286
+ */
287
+ async waitForDropdownMenu() {
288
+ const checkScript = `
289
+ (() => {
290
+ // Check for dropdown content being visible
291
+ const dropdownItems = document.querySelectorAll('.artdeco-dropdown__item[role="button"], .artdeco-dropdown__content-inner [role="button"]');
292
+ return dropdownItems.length > 0;
293
+ })()
294
+ `;
295
+ return this.pollForElement(checkScript, {
296
+ maxAttempts: 8,
297
+ intervalMs: 1000,
298
+ description: 'dropdown menu',
299
+ });
300
+ }
301
+ /**
302
+ * Save current DOM state to file for debugging
303
+ * Only saves when DEBUG env var is set
304
+ */
305
+ async saveDebugState(label) {
306
+ if (!DEBUG) {
307
+ return null;
308
+ }
309
+ try {
310
+ const fs = await import('fs/promises');
311
+ const htmlContent = (await this.evaluate(`document.documentElement.outerHTML`));
312
+ const currentUrl = await this.getCurrentUrl();
313
+ const timestamp = Date.now();
314
+ const debugPath = `/tmp/linkedin-debug-${label}-${timestamp}.html`;
315
+ // Add URL as comment at top of file
316
+ const contentWithUrl = `<!-- URL: ${currentUrl} -->\n${htmlContent}`;
317
+ await fs.writeFile(debugPath, contentWithUrl);
318
+ debugLog(`[LinkedInConnectionTool] Saved debug DOM to: ${debugPath}`);
319
+ return debugPath;
320
+ }
321
+ catch (e) {
322
+ console.error('[LinkedInConnectionTool] Failed to save debug state:', e);
323
+ return null;
324
+ }
325
+ }
326
+ async performAction() {
327
+ try {
328
+ await this.startBrowserSession();
329
+ return await this.sendConnection();
330
+ }
331
+ catch (error) {
332
+ console.error('[LinkedInConnectionTool] Error:', error);
333
+ return {
334
+ operation: 'send_connection',
335
+ success: false,
336
+ error: error instanceof Error ? error.message : 'Unknown error occurred',
337
+ };
338
+ }
339
+ finally {
340
+ await this.endBrowserSession();
341
+ }
342
+ }
343
+ /**
344
+ * Send a connection request to a LinkedIn profile
345
+ */
346
+ async sendConnection() {
347
+ const { profile_url, message } = this.params;
348
+ debugLog(`[LinkedInConnectionTool] Sending connection to: ${profile_url}`);
349
+ // Navigate to profile page
350
+ await this.navigateTo(profile_url);
351
+ // Wait for page to be ready using polling instead of fixed timeout
352
+ const pageReady = await this.waitForProfilePageReady();
353
+ if (!pageReady) {
354
+ debugLog('[LinkedInConnectionTool] Profile page did not load in time, trying to continue anyway');
355
+ }
356
+ // Save debug state after page load
357
+ await this.saveDebugState('01-after-page-load');
358
+ // Extract profile info
359
+ const profileInfo = await this.extractProfileInfo();
360
+ debugLog('[LinkedInConnectionTool] Profile info:', profileInfo);
361
+ // Try to find and click the Connect button
362
+ // Strategy 1: Direct Connect button (visible on profile)
363
+ let connectClicked = false;
364
+ // Save debug state before attempting to click Connect
365
+ await this.saveDebugState('02-before-connect-click');
366
+ // Look for direct Connect button with various selectors
367
+ const directConnectResult = (await this.evaluate(`
368
+ (() => {
369
+ // Look for primary Connect button
370
+ const connectButtons = document.querySelectorAll('button');
371
+ for (const btn of connectButtons) {
372
+ const ariaLabel = btn.getAttribute('aria-label') || '';
373
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
374
+
375
+ // Check for "Invite X to connect" aria-label or "Connect" text
376
+ if (ariaLabel.toLowerCase().includes('connect') || text === 'connect') {
377
+ // Make sure it's a primary action button, not in a dropdown
378
+ if (btn.classList.contains('artdeco-button--primary') ||
379
+ btn.closest('.pvs-profile-actions') ||
380
+ btn.closest('.pv-top-card-v2-ctas')) {
381
+ btn.click();
382
+ return { clicked: true, method: 'direct', ariaLabel, text };
383
+ }
384
+ }
385
+ }
386
+ return { clicked: false };
387
+ })()
388
+ `));
389
+ if (directConnectResult.clicked) {
390
+ connectClicked = true;
391
+ debugLog(`[LinkedInConnectionTool] Clicked direct Connect button: ${directConnectResult.method}`);
392
+ }
393
+ // Strategy 2: Connect is under "More" dropdown
394
+ if (!connectClicked) {
395
+ debugLog('[LinkedInConnectionTool] Direct Connect not found, trying More dropdown...');
396
+ // Click the "More" button
397
+ const moreButtonResult = (await this.evaluate(`
398
+ (() => {
399
+ // Look for More button - specifically the profile overflow action button
400
+ // It has id like "ember67-profile-overflow-action" and aria-label="More actions"
401
+ const buttons = document.querySelectorAll('button');
402
+ for (const btn of buttons) {
403
+ const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
404
+ const btnId = btn.id || '';
405
+
406
+ // Profile overflow button has specific ID pattern and aria-label
407
+ if (btnId.includes('profile-overflow-action') ||
408
+ (ariaLabel === 'more actions' && btn.classList.contains('artdeco-dropdown__trigger'))) {
409
+ btn.click();
410
+ return { clicked: true, id: btnId, ariaLabel };
411
+ }
412
+ }
413
+ return { clicked: false };
414
+ })()
415
+ `));
416
+ if (moreButtonResult.clicked) {
417
+ debugLog(`[LinkedInConnectionTool] Clicked More button: id="${moreButtonResult.id}", aria-label="${moreButtonResult.ariaLabel}"`);
418
+ // Wait for dropdown menu to appear using polling
419
+ await this.waitForDropdownMenu();
420
+ // Save debug state after clicking More button
421
+ await this.saveDebugState('03-after-more-dropdown-open');
422
+ // Now look for Connect in the dropdown
423
+ const dropdownConnectResult = (await this.evaluate(`
424
+ (() => {
425
+ // LinkedIn uses div[role="button"] for dropdown items
426
+ // Look for Connect option with aria-label containing "connect"
427
+ const dropdownItems = document.querySelectorAll('.artdeco-dropdown__item[role="button"], .artdeco-dropdown__content-inner [role="button"]');
428
+ for (const item of dropdownItems) {
429
+ const ariaLabel = (item.getAttribute('aria-label') || '').toLowerCase();
430
+ const text = (item.innerText || item.textContent || '').trim().toLowerCase();
431
+
432
+ // Check aria-label like "Invite X to connect" or text "Connect"
433
+ if (ariaLabel.includes('connect') || text === 'connect') {
434
+ item.click();
435
+ return { clicked: true, ariaLabel, text };
436
+ }
437
+ }
438
+
439
+ return { clicked: false };
440
+ })()
441
+ `));
442
+ if (dropdownConnectResult.clicked) {
443
+ connectClicked = true;
444
+ debugLog(`[LinkedInConnectionTool] Clicked Connect from dropdown: aria-label="${dropdownConnectResult.ariaLabel}", text="${dropdownConnectResult.text}"`);
445
+ }
446
+ else {
447
+ debugLog('[LinkedInConnectionTool] Connect option not found in dropdown');
448
+ }
449
+ }
450
+ else {
451
+ debugLog('[LinkedInConnectionTool] More button not found');
452
+ }
453
+ }
454
+ if (!connectClicked) {
455
+ // Save debug state before reporting failure
456
+ await this.saveDebugState('04-connect-not-found');
457
+ return {
458
+ operation: 'send_connection',
459
+ success: false,
460
+ profile: profileInfo || undefined,
461
+ error: 'Could not find Connect button. Profile may already be connected or connection requests may be restricted.',
462
+ };
463
+ }
464
+ // Wait for connection modal to appear using polling
465
+ const modalReady = await this.waitForConnectionModal();
466
+ if (!modalReady) {
467
+ debugLog('[LinkedInConnectionTool] Connection modal did not appear, trying to continue anyway');
468
+ }
469
+ // Check if we need to add a note or send without note
470
+ if (message) {
471
+ debugLog('[LinkedInConnectionTool] Adding note to connection request');
472
+ // Click "Add a note" button
473
+ const addNoteResult = (await this.evaluate(`
474
+ (() => {
475
+ const buttons = document.querySelectorAll('button');
476
+ for (const btn of buttons) {
477
+ const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
478
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
479
+
480
+ if (ariaLabel.includes('add a note') || text.includes('add a note')) {
481
+ btn.click();
482
+ return { clicked: true };
483
+ }
484
+ }
485
+ return { clicked: false };
486
+ })()
487
+ `));
488
+ if (addNoteResult.clicked) {
489
+ debugLog('[LinkedInConnectionTool] Clicked Add a note button');
490
+ // Wait for textarea to appear
491
+ const textareaReady = await this.pollForElement(`!!document.querySelector('#custom-message')`, { maxAttempts: 8, intervalMs: 500, description: 'note textarea' });
492
+ if (!textareaReady) {
493
+ debugLog('[LinkedInConnectionTool] Note textarea did not appear');
494
+ }
495
+ // Type the message into the textarea
496
+ const typed = await this.typeText('#custom-message', message);
497
+ if (!typed) {
498
+ // Fallback: try evaluate to set value
499
+ await this.evaluate(`
500
+ (() => {
501
+ const textarea = document.querySelector('#custom-message');
502
+ if (textarea) {
503
+ textarea.value = ${JSON.stringify(message)};
504
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
505
+ return true;
506
+ }
507
+ return false;
508
+ })()
509
+ `);
510
+ }
511
+ debugLog('[LinkedInConnectionTool] Typed note message');
512
+ }
513
+ // Wait for Send button to be ready
514
+ await this.pollForElement(`(() => {
515
+ const buttons = document.querySelectorAll('button');
516
+ for (const btn of buttons) {
517
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
518
+ if (text === 'send' && btn.classList.contains('artdeco-button--primary')) return true;
519
+ }
520
+ return false;
521
+ })()`, { maxAttempts: 5, intervalMs: 500, description: 'Send button' });
522
+ // Click Send button (after adding note)
523
+ const sendResult = (await this.evaluate(`
524
+ (() => {
525
+ const buttons = document.querySelectorAll('button');
526
+ for (const btn of buttons) {
527
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
528
+
529
+ // Look for Send button (primary)
530
+ if (text === 'send' && btn.classList.contains('artdeco-button--primary')) {
531
+ btn.click();
532
+ return { clicked: true, text };
533
+ }
534
+ }
535
+ return { clicked: false };
536
+ })()
537
+ `));
538
+ if (!sendResult.clicked) {
539
+ return {
540
+ operation: 'send_connection',
541
+ success: false,
542
+ profile: profileInfo || undefined,
543
+ error: 'Could not find Send button to complete connection request.',
544
+ };
545
+ }
546
+ debugLog('[LinkedInConnectionTool] Clicked Send button');
547
+ }
548
+ else {
549
+ // No message - click "Send without a note" button
550
+ debugLog('[LinkedInConnectionTool] No message provided, clicking Send without a note');
551
+ const sendWithoutNoteResult = (await this.evaluate(`
552
+ (() => {
553
+ const buttons = document.querySelectorAll('button');
554
+ for (const btn of buttons) {
555
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
556
+
557
+ // Look for "Send without a note" button
558
+ if (text.includes('send without a note') || text.includes('send without note')) {
559
+ btn.click();
560
+ return { clicked: true, text };
561
+ }
562
+ }
563
+ return { clicked: false };
564
+ })()
565
+ `));
566
+ if (!sendWithoutNoteResult.clicked) {
567
+ return {
568
+ operation: 'send_connection',
569
+ success: false,
570
+ profile: profileInfo || undefined,
571
+ error: 'Could not find Send without a note button to complete connection request.',
572
+ };
573
+ }
574
+ debugLog('[LinkedInConnectionTool] Clicked Send without a note button');
575
+ }
576
+ // Wait for request to be processed by checking if modal closed or "Not now" prompt appears
577
+ await this.pollForElement(`(() => {
578
+ // Check if modal has closed (no more Send buttons visible)
579
+ const sendButtons = document.querySelectorAll('button');
580
+ for (const btn of sendButtons) {
581
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
582
+ if (text === 'send' || text.includes('send without')) return false;
583
+ // Also check for "Not now" prompt (success indicator)
584
+ if (text === 'not now') return true;
585
+ }
586
+ return true; // Modal closed
587
+ })()`, {
588
+ maxAttempts: 8,
589
+ intervalMs: 1500,
590
+ description: 'connection request completion',
591
+ });
592
+ // Handle "Not now" prompt if it appears (for adding to address book)
593
+ await this.evaluate(`
594
+ (() => {
595
+ const buttons = document.querySelectorAll('button');
596
+ for (const btn of buttons) {
597
+ const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
598
+ if (text === 'not now') {
599
+ btn.click();
600
+ return true;
601
+ }
602
+ }
603
+ return false;
604
+ })()
605
+ `);
606
+ return {
607
+ operation: 'send_connection',
608
+ success: true,
609
+ message: `Connection request sent to ${profileInfo?.name || 'profile'}`,
610
+ profile: profileInfo || undefined,
611
+ error: '',
612
+ };
613
+ }
614
+ /**
615
+ * Extract profile information from the current page
616
+ */
617
+ async extractProfileInfo() {
618
+ try {
619
+ const info = (await this.evaluate(`
620
+ (() => {
621
+ // Get profile name - try multiple strategies
622
+ let name = '';
623
+
624
+ // Strategy 1: h1 element (has obfuscated class but also standard classes)
625
+ const h1El = document.querySelector('h1');
626
+ if (h1El) {
627
+ name = h1El.textContent?.trim() || '';
628
+ }
629
+
630
+ // Strategy 2: Profile picture alt/title attribute
631
+ if (!name) {
632
+ const imgEl = document.querySelector('img.pv-top-card-profile-picture__image--show') ||
633
+ document.querySelector('img[class*="pv-top-card-profile-picture"]');
634
+ if (imgEl) {
635
+ name = imgEl.getAttribute('alt') || imgEl.getAttribute('title') || '';
636
+ }
637
+ }
638
+
639
+ // Strategy 3: Extract from Connect button aria-label "Invite X to connect"
640
+ if (!name) {
641
+ const connectBtn = document.querySelector('button[aria-label*="to connect"]');
642
+ if (connectBtn) {
643
+ const ariaLabel = connectBtn.getAttribute('aria-label') || '';
644
+ const match = ariaLabel.match(/Invite (.+) to connect/i);
645
+ if (match) {
646
+ name = match[1];
647
+ }
648
+ }
649
+ }
650
+
651
+ // Get headline - div with text-body-medium break-words and data-generated-suggestion-target
652
+ let headline = '';
653
+ const headlineEl = document.querySelector('div.text-body-medium.break-words[data-generated-suggestion-target]') ||
654
+ document.querySelector('div.text-body-medium.break-words');
655
+ if (headlineEl) {
656
+ headline = headlineEl.textContent?.trim() || '';
657
+ }
658
+
659
+ // Get location - span before Contact info link
660
+ let location = '';
661
+ const contactInfoLink = document.querySelector('a[href*="contact-info"]');
662
+ if (contactInfoLink) {
663
+ // Location is in a sibling or parent span
664
+ const parentSpan = contactInfoLink.closest('span');
665
+ if (parentSpan && parentSpan.previousElementSibling) {
666
+ location = parentSpan.previousElementSibling.textContent?.trim() || '';
667
+ }
668
+ }
669
+
670
+ // Fallback: look for location pattern in spans
671
+ if (!location) {
672
+ const spans = document.querySelectorAll('span');
673
+ for (const span of spans) {
674
+ const text = span.textContent?.trim() || '';
675
+ // Location usually contains comma-separated place names
676
+ if (text.includes(',') && !text.includes('@') && !text.includes('|') &&
677
+ text.length < 100 && text.length > 5) {
678
+ // Check if it looks like a location (contains country/city patterns)
679
+ if (/(?:United|Kingdom|States|England|Germany|France|India|Canada|Australia|California|New York|London|Manchester)/i.test(text)) {
680
+ location = text;
681
+ break;
682
+ }
683
+ }
684
+ }
685
+ }
686
+
687
+ return {
688
+ name,
689
+ headline,
690
+ location,
691
+ profile_url: window.location.href
692
+ };
693
+ })()
694
+ `));
695
+ if (!info.name) {
696
+ return null;
697
+ }
698
+ return info;
699
+ }
700
+ catch (error) {
701
+ console.error('[LinkedInConnectionTool] Error extracting profile info:', error);
702
+ return null;
703
+ }
704
+ }
705
+ }
706
+ //# sourceMappingURL=linkedin-connection-tool.js.map