@lobu/cli 6.0.1 → 6.1.1

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 (217) hide show
  1. package/README.md +20 -27
  2. package/dist/bundled-skills/lobu/SKILL.md +11 -11
  3. package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +26 -0
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +1 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/desired-state.js +4 -4
  10. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  11. package/dist/commands/agent.d.ts +7 -0
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +65 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/chat.d.ts +12 -9
  16. package/dist/commands/chat.d.ts.map +1 -1
  17. package/dist/commands/chat.js +117 -56
  18. package/dist/commands/chat.js.map +1 -1
  19. package/dist/commands/dev.d.ts +15 -7
  20. package/dist/commands/dev.d.ts.map +1 -1
  21. package/dist/commands/dev.js +79 -44
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/doctor.d.ts +1 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +136 -0
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/eval.d.ts +8 -0
  28. package/dist/commands/eval.d.ts.map +1 -1
  29. package/dist/commands/eval.js +56 -1
  30. package/dist/commands/eval.js.map +1 -1
  31. package/dist/commands/init.d.ts +20 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +332 -183
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/link.d.ts +11 -0
  36. package/dist/commands/link.d.ts.map +1 -0
  37. package/dist/commands/link.js +28 -0
  38. package/dist/commands/link.js.map +1 -0
  39. package/dist/commands/login.d.ts.map +1 -1
  40. package/dist/commands/login.js +14 -2
  41. package/dist/commands/login.js.map +1 -1
  42. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  43. package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
  44. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  45. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  46. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  47. package/dist/commands/memory/_lib/mcp.js +24 -12
  48. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  49. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  50. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  51. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  52. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  53. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  54. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  55. package/dist/commands/memory/_lib/schema.d.ts +1 -1
  56. package/dist/commands/memory/_lib/schema.js +1 -1
  57. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  58. package/dist/commands/memory/_lib/seed-cmd.js +5 -6
  59. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  60. package/dist/commands/memory/run.d.ts.map +1 -1
  61. package/dist/commands/memory/run.js +2 -2
  62. package/dist/commands/memory/run.js.map +1 -1
  63. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  64. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  65. package/dist/commands/platforms/platform-prompts.js +54 -8
  66. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  67. package/dist/commands/telemetry.d.ts +10 -0
  68. package/dist/commands/telemetry.d.ts.map +1 -0
  69. package/dist/commands/telemetry.js +68 -0
  70. package/dist/commands/telemetry.js.map +1 -0
  71. package/dist/commands/whoami.d.ts.map +1 -1
  72. package/dist/commands/whoami.js +1 -1
  73. package/dist/commands/whoami.js.map +1 -1
  74. package/dist/connectors/README.md +534 -0
  75. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  76. package/dist/connectors/browser-scraper-utils.ts +214 -0
  77. package/dist/connectors/capterra.ts +273 -0
  78. package/dist/connectors/g2.ts +286 -0
  79. package/dist/connectors/github.ts +1553 -0
  80. package/dist/connectors/glassdoor.ts +291 -0
  81. package/dist/connectors/gmaps.ts +197 -0
  82. package/dist/connectors/google_calendar.ts +631 -0
  83. package/dist/connectors/google_gmail.ts +751 -0
  84. package/dist/connectors/google_photos.ts +776 -0
  85. package/dist/connectors/google_play.ts +342 -0
  86. package/dist/connectors/hackernews.ts +471 -0
  87. package/dist/connectors/index.ts +23 -0
  88. package/dist/connectors/ios_appstore.ts +226 -0
  89. package/dist/connectors/linkedin.ts +471 -0
  90. package/dist/connectors/microsoft_outlook.ts +410 -0
  91. package/dist/connectors/producthunt.ts +471 -0
  92. package/dist/connectors/reddit.ts +600 -0
  93. package/dist/connectors/rss.ts +448 -0
  94. package/dist/connectors/spotify.ts +590 -0
  95. package/dist/connectors/trustpilot.ts +199 -0
  96. package/dist/connectors/website.ts +629 -0
  97. package/dist/connectors/whatsapp.ts +1073 -0
  98. package/dist/connectors/x.ts +526 -0
  99. package/dist/connectors/youtube.ts +666 -0
  100. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  101. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  102. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  103. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  104. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  105. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  106. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  107. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  108. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  109. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  110. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  111. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  112. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  113. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  114. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  115. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  116. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  117. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  118. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  119. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  120. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  121. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  122. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  123. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  124. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  125. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  126. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  127. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  128. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  129. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  130. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  131. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  132. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  133. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  134. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  135. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  136. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  137. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  138. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  139. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  140. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  141. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  142. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  143. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  144. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  145. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +147 -23
  148. package/dist/index.js.map +1 -1
  149. package/dist/internal/api-client.d.ts +4 -8
  150. package/dist/internal/api-client.d.ts.map +1 -1
  151. package/dist/internal/api-client.js +1 -1
  152. package/dist/internal/api-client.js.map +1 -1
  153. package/dist/internal/context.js +2 -2
  154. package/dist/internal/context.js.map +1 -1
  155. package/dist/internal/credentials.d.ts.map +1 -1
  156. package/dist/internal/credentials.js +6 -1
  157. package/dist/internal/credentials.js.map +1 -1
  158. package/dist/internal/index.d.ts +2 -3
  159. package/dist/internal/index.d.ts.map +1 -1
  160. package/dist/internal/index.js +2 -2
  161. package/dist/internal/index.js.map +1 -1
  162. package/dist/internal/oauth.d.ts +6 -5
  163. package/dist/internal/oauth.d.ts.map +1 -1
  164. package/dist/internal/oauth.js +2 -2
  165. package/dist/internal/project-link.d.ts +10 -0
  166. package/dist/internal/project-link.d.ts.map +1 -0
  167. package/dist/internal/project-link.js +48 -0
  168. package/dist/internal/project-link.js.map +1 -0
  169. package/dist/providers.json +2 -2
  170. package/dist/server.bundle.mjs +3090 -4321
  171. package/dist/start-local.bundle.mjs +71481 -0
  172. package/dist/templates/README.md.tmpl +10 -11
  173. package/package.json +14 -12
  174. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  175. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  176. package/dist/__tests__/chat.integration.test.js +0 -337
  177. package/dist/__tests__/chat.integration.test.js.map +0 -1
  178. package/dist/__tests__/dev.test.d.ts +0 -2
  179. package/dist/__tests__/dev.test.d.ts.map +0 -1
  180. package/dist/__tests__/dev.test.js +0 -25
  181. package/dist/__tests__/dev.test.js.map +0 -1
  182. package/dist/__tests__/init-memory.test.d.ts +0 -2
  183. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  184. package/dist/__tests__/init-memory.test.js +0 -45
  185. package/dist/__tests__/init-memory.test.js.map +0 -1
  186. package/dist/__tests__/token.test.d.ts +0 -2
  187. package/dist/__tests__/token.test.d.ts.map +0 -1
  188. package/dist/__tests__/token.test.js +0 -52
  189. package/dist/__tests__/token.test.js.map +0 -1
  190. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  191. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  192. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  193. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  194. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  195. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  196. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  197. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  198. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  199. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  200. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  201. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  202. package/dist/commands/apply.d.ts +0 -3
  203. package/dist/commands/apply.d.ts.map +0 -1
  204. package/dist/commands/apply.js +0 -5
  205. package/dist/commands/apply.js.map +0 -1
  206. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  207. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  208. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  209. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  210. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  211. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  212. package/dist/internal/__tests__/api-client.test.js +0 -95
  213. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  214. package/dist/internal/__tests__/context.test.d.ts +0 -2
  215. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  216. package/dist/internal/__tests__/context.test.js +0 -77
  217. package/dist/internal/__tests__/context.test.js.map +0 -1
@@ -0,0 +1,286 @@
1
+ /**
2
+ * G2 Connector (V1 runtime)
3
+ *
4
+ * Scrapes B2B software reviews from G2.com using browser rendering with stealth mode.
5
+ */
6
+
7
+ import {
8
+ type ActionContext,
9
+ type ActionResult,
10
+ type ConnectorDefinition,
11
+ ConnectorRuntime,
12
+ calculateEngagementScore,
13
+ type EventEnvelope,
14
+ type SyncContext,
15
+ type SyncResult,
16
+ } from '@lobu/connector-sdk';
17
+ import {
18
+ handleCookieConsent,
19
+ openStealthBrowser,
20
+ validateUrlDomain,
21
+ withBrowserErrorCapture,
22
+ } from './browser-scraper-utils.ts';
23
+
24
+ interface G2Review {
25
+ rating: number;
26
+ title: string;
27
+ text: string;
28
+ author: string;
29
+ jobTitle: string;
30
+ industry: string;
31
+ companySize: string;
32
+ date: string;
33
+ badges: string[];
34
+ reviewUrl: string;
35
+ helpfulCount: number;
36
+ }
37
+
38
+ interface G2Checkpoint {
39
+ last_sync_at?: string;
40
+ pages_crawled?: number;
41
+ }
42
+
43
+ const configSchema = {
44
+ type: 'object',
45
+ required: ['product_url'],
46
+ properties: {
47
+ product_url: {
48
+ type: 'string',
49
+ description: 'Full G2 product review URL e.g. https://www.g2.com/products/confluence/reviews',
50
+ },
51
+ lookback_days: {
52
+ type: 'integer',
53
+ minimum: 1,
54
+ maximum: 730,
55
+ default: 365,
56
+ description: 'Number of days to look back for reviews (default 365)',
57
+ },
58
+ },
59
+ };
60
+
61
+ export default class G2Connector extends ConnectorRuntime {
62
+ readonly definition: ConnectorDefinition = {
63
+ key: 'g2',
64
+ name: 'G2',
65
+ description: 'Scrapes B2B software reviews from G2.com.',
66
+ version: '1.0.0',
67
+ faviconDomain: 'g2.com',
68
+ authSchema: {
69
+ methods: [{ type: 'none' }],
70
+ },
71
+ feeds: {
72
+ reviews: {
73
+ key: 'reviews',
74
+ name: 'Product Reviews',
75
+ description: 'Scrape reviews for a G2 product listing.',
76
+ configSchema,
77
+ eventKinds: {
78
+ review: {
79
+ description: 'A G2 B2B software review',
80
+ metadataSchema: {
81
+ type: 'object',
82
+ properties: {
83
+ rating: { type: 'number', description: 'Star rating (0-5)' },
84
+ helpful_count: { type: 'number' },
85
+ job_title: { type: 'string', description: 'Reviewer job title' },
86
+ industry: { type: 'string', description: 'Reviewer industry' },
87
+ company_size: { type: 'string', description: 'Reviewer company size' },
88
+ badges: { type: 'array', items: { type: 'string' }, description: 'Review badges' },
89
+ },
90
+ },
91
+ },
92
+ },
93
+ },
94
+ },
95
+ optionsSchema: configSchema,
96
+ };
97
+
98
+ async sync(ctx: SyncContext): Promise<SyncResult> {
99
+ const productUrl = ctx.config.product_url as string;
100
+
101
+ if (!productUrl?.match(/^https:\/\/www\.g2\.com\/products\/[^/]+\/reviews/)) {
102
+ return {
103
+ events: [],
104
+ checkpoint: ctx.checkpoint,
105
+ metadata: { items_found: 0, error: 'Invalid product_url' },
106
+ };
107
+ }
108
+ validateUrlDomain(productUrl, 'g2.com');
109
+
110
+ // Extract product key from URL for origin_id generation
111
+ const productMatch = productUrl.match(/\/products\/([^/]+)/);
112
+ const productKey = productMatch ? productMatch[1] : 'unknown';
113
+
114
+ const baseUrl = productUrl;
115
+ const allEvents: EventEnvelope[] = [];
116
+
117
+ const session = await openStealthBrowser({ cdpUrl: 'auto' });
118
+
119
+ return withBrowserErrorCapture(session, 'g2-sync', async (page) => {
120
+ const maxPages = 5;
121
+
122
+ for (let pageNum = 1; pageNum <= maxPages; pageNum++) {
123
+ const pageUrl = pageNum === 1 ? baseUrl : `${baseUrl}?page=${pageNum}`;
124
+
125
+ await page.goto(pageUrl, { waitUntil: 'domcontentloaded' });
126
+
127
+ if (pageNum === 1) {
128
+ await handleCookieConsent(page, '#onetrust-accept-btn-handler');
129
+ }
130
+
131
+ // Wait for review cards to load
132
+ try {
133
+ await page.waitForSelector('[itemprop="review"]', { timeout: 10000 });
134
+ } catch {
135
+ // No reviews found on this page — stop paginating
136
+ break;
137
+ }
138
+
139
+ // Extract reviews from the page
140
+ const reviews: G2Review[] = await page.evaluate(() => {
141
+ const results: G2Review[] = [];
142
+ const reviewCards = document.querySelectorAll('[itemprop="review"]');
143
+
144
+ reviewCards.forEach((card) => {
145
+ try {
146
+ // Extract author name from meta tag
147
+ const authorMeta = card.querySelector('[itemprop="author"] meta[itemprop="name"]');
148
+ const author = authorMeta?.getAttribute('content') || 'Anonymous';
149
+
150
+ // Extract author details from sibling divs with elv-text-subtle class
151
+ const authorContainer = card.querySelector('[itemprop="author"]');
152
+ const parentDiv = authorContainer?.closest('.elv-gap-2')?.parentElement;
153
+ const detailDivs = parentDiv
154
+ ? Array.from(parentDiv.querySelectorAll('.elv-text-subtle'))
155
+ : [];
156
+
157
+ // Parse author details (job title, industry, company size)
158
+ let jobTitle = '';
159
+ let industry = '';
160
+ let companySize = '';
161
+
162
+ if (detailDivs.length >= 3) {
163
+ jobTitle = detailDivs[0]?.textContent?.trim() || '';
164
+ industry = detailDivs[1]?.textContent?.trim() || '';
165
+ companySize = detailDivs[2]?.textContent?.trim() || '';
166
+ } else if (detailDivs.length === 2) {
167
+ jobTitle = detailDivs[0]?.textContent?.trim() || '';
168
+ companySize = detailDivs[1]?.textContent?.trim() || '';
169
+ } else if (detailDivs.length === 1) {
170
+ companySize = detailDivs[0]?.textContent?.trim() || '';
171
+ }
172
+
173
+ // Extract date from meta tag
174
+ const dateMeta = card.querySelector('meta[itemprop="datePublished"]');
175
+ const dateStr = dateMeta?.getAttribute('content') || '';
176
+
177
+ // Extract rating
178
+ const ratingMeta = card.querySelector('[itemprop="ratingValue"]');
179
+ const rating = ratingMeta ? parseFloat(ratingMeta.getAttribute('content') || '0') : 0;
180
+
181
+ // Extract review title
182
+ const titleDiv = card.querySelector('[itemprop="name"] .elv-font-bold');
183
+ const title = titleDiv?.textContent?.trim().replace(/^"|"$/g, '') || '';
184
+
185
+ // Extract review body - use innerText to preserve visual spacing/newlines
186
+ const reviewBodyEl = card.querySelector('[itemprop="reviewBody"]');
187
+ const reviewBody = (reviewBodyEl as HTMLElement)?.innerText?.trim() || '';
188
+
189
+ // Extract badges
190
+ const badgeEls = card.querySelectorAll(
191
+ '[class*="badge"], [class*="tag"], .elv-rounded-sm.elv-border'
192
+ );
193
+ const badges = Array.from(badgeEls)
194
+ .map((el) => el.textContent?.trim())
195
+ .filter((text): text is string => !!text && text.length < 50 && text.length > 3);
196
+
197
+ // Extract review URL
198
+ const linkEl = card.querySelector('a[href*="survey_responses"]');
199
+ const href = linkEl?.getAttribute('href') || '';
200
+ const reviewUrl = href
201
+ ? href.startsWith('http')
202
+ ? href
203
+ : `https://www.g2.com${href}`
204
+ : '';
205
+
206
+ // Skip reviews with minimal content
207
+ if ((reviewBody || '').length < 50) return;
208
+
209
+ results.push({
210
+ rating,
211
+ title,
212
+ text: reviewBody,
213
+ author,
214
+ jobTitle,
215
+ industry,
216
+ companySize,
217
+ date: dateStr,
218
+ badges: badges.slice(0, 10),
219
+ reviewUrl,
220
+ helpfulCount: 0,
221
+ });
222
+ } catch (e) {
223
+ console.error('[G2Connector] Error parsing review card:', e);
224
+ }
225
+ });
226
+
227
+ return results;
228
+ });
229
+
230
+ // Transform reviews to EventEnvelope format
231
+ for (const review of reviews) {
232
+ const event: EventEnvelope = {
233
+ origin_id: `g2-${productKey}-${review.date || 'nodate'}-${review.author.replace(/\s+/g, '-')}`,
234
+ title: review.title,
235
+ payload_text: review.text,
236
+ author_name: review.author,
237
+ occurred_at: review.date ? new Date(review.date) : new Date(),
238
+ origin_type: 'review',
239
+ score: calculateEngagementScore('g2', {
240
+ rating: review.rating,
241
+ helpful_count: 0,
242
+ }),
243
+ source_url: review.reviewUrl || baseUrl,
244
+ metadata: {
245
+ rating: review.rating,
246
+ helpful_count: review.helpfulCount,
247
+ job_title: review.jobTitle,
248
+ industry: review.industry,
249
+ company_size: review.companySize,
250
+ badges: review.badges,
251
+ },
252
+ };
253
+ allEvents.push(event);
254
+ }
255
+
256
+ // If this page had no reviews, stop paginating
257
+ if (reviews.length === 0) break;
258
+
259
+ // Delay between pages
260
+ if (pageNum < maxPages) {
261
+ await new Promise((resolve) => setTimeout(resolve, 2000));
262
+ }
263
+
264
+ // Rate limit
265
+ await new Promise((resolve) => setTimeout(resolve, 6000));
266
+ }
267
+
268
+ const newCheckpoint: G2Checkpoint = {
269
+ last_sync_at: new Date().toISOString(),
270
+ pages_crawled: Math.min(5, allEvents.length > 0 ? Math.ceil(allEvents.length / 10) : 0),
271
+ };
272
+
273
+ return {
274
+ events: allEvents,
275
+ checkpoint: newCheckpoint as Record<string, unknown>,
276
+ metadata: {
277
+ items_found: allEvents.length,
278
+ },
279
+ };
280
+ });
281
+ }
282
+
283
+ async execute(_ctx: ActionContext): Promise<ActionResult> {
284
+ return { success: false, error: 'Actions not supported' };
285
+ }
286
+ }