lobster-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +389 -0
  2. package/dist/agent/core.js +1013 -0
  3. package/dist/agent/core.js.map +1 -0
  4. package/dist/agent/index.js +1027 -0
  5. package/dist/agent/index.js.map +1 -0
  6. package/dist/brain/index.js +60 -0
  7. package/dist/brain/index.js.map +1 -0
  8. package/dist/browser/dom/index.js +1096 -0
  9. package/dist/browser/dom/index.js.map +1 -0
  10. package/dist/browser/index.js +2034 -0
  11. package/dist/browser/index.js.map +1 -0
  12. package/dist/browser/manager.js +86 -0
  13. package/dist/browser/manager.js.map +1 -0
  14. package/dist/browser/page-adapter.js +1345 -0
  15. package/dist/browser/page-adapter.js.map +1 -0
  16. package/dist/cascade/index.js +138 -0
  17. package/dist/cascade/index.js.map +1 -0
  18. package/dist/config/index.js +110 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/config/schema.js +66 -0
  21. package/dist/config/schema.js.map +1 -0
  22. package/dist/discover/index.js +545 -0
  23. package/dist/discover/index.js.map +1 -0
  24. package/dist/index.js +5529 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/lib.js +4206 -0
  27. package/dist/lib.js.map +1 -0
  28. package/dist/llm/client.js +379 -0
  29. package/dist/llm/client.js.map +1 -0
  30. package/dist/llm/index.js +397 -0
  31. package/dist/llm/index.js.map +1 -0
  32. package/dist/llm/openai-client.js +214 -0
  33. package/dist/llm/openai-client.js.map +1 -0
  34. package/dist/output/index.js +93 -0
  35. package/dist/output/index.js.map +1 -0
  36. package/dist/pipeline/index.js +802 -0
  37. package/dist/pipeline/index.js.map +1 -0
  38. package/dist/router/decision.js +80 -0
  39. package/dist/router/decision.js.map +1 -0
  40. package/dist/router/index.js +3443 -0
  41. package/dist/router/index.js.map +1 -0
  42. package/dist/types/index.js +23 -0
  43. package/dist/types/index.js.map +1 -0
  44. package/logo.svg +11 -0
  45. package/package.json +65 -0
@@ -0,0 +1,545 @@
1
+ // src/discover/explore.ts
2
+ import { writeFileSync, mkdirSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ // src/utils/logger.ts
6
+ import chalk from "chalk";
7
+ var log = {
8
+ info: (msg) => console.log(chalk.blue("\u2139"), msg),
9
+ success: (msg) => console.log(chalk.green("\u2713"), msg),
10
+ warn: (msg) => console.log(chalk.yellow("\u26A0"), msg),
11
+ error: (msg) => console.error(chalk.red("\u2717"), msg),
12
+ debug: (msg) => {
13
+ if (process.env.LOBSTER_DEBUG) console.log(chalk.gray("\u22EF"), msg);
14
+ },
15
+ step: (n, msg) => console.log(chalk.cyan(`[${n}]`), msg),
16
+ dim: (msg) => console.log(chalk.dim(msg))
17
+ };
18
+
19
+ // src/discover/explore.ts
20
+ var SITE_ALIASES = {
21
+ "x.com": "twitter",
22
+ "twitter.com": "twitter",
23
+ "news.ycombinator.com": "hackernews",
24
+ "old.reddit.com": "reddit",
25
+ "www.reddit.com": "reddit",
26
+ "bilibili.com": "bilibili",
27
+ "www.bilibili.com": "bilibili",
28
+ "zhihu.com": "zhihu",
29
+ "www.zhihu.com": "zhihu"
30
+ };
31
+ var FIELD_ROLES = {
32
+ title: ["title", "name", "headline", "subject", "text", "caption"],
33
+ url: ["url", "link", "href", "permalink", "uri", "web_url"],
34
+ author: ["author", "user", "creator", "owner", "by", "username", "screen_name", "display_name"],
35
+ score: ["score", "points", "likes", "upvotes", "karma", "vote_count", "favorite_count", "retweet_count"],
36
+ time: ["time", "date", "created", "created_at", "timestamp", "published", "updated_at", "posted_at"],
37
+ description: ["description", "summary", "snippet", "excerpt", "body", "content", "selftext"],
38
+ image: ["image", "thumbnail", "avatar", "icon", "photo", "cover", "poster"],
39
+ id: ["id", "uid", "pid", "mid", "aid", "bvid"]
40
+ };
41
+ var VOLATILE_PARAMS = /* @__PURE__ */ new Set([
42
+ "_",
43
+ "t",
44
+ "ts",
45
+ "timestamp",
46
+ "nonce",
47
+ "rand",
48
+ "random",
49
+ "callback",
50
+ "jsonp",
51
+ "_t",
52
+ "__t"
53
+ ]);
54
+ function normalizeUrlPattern(urlStr) {
55
+ try {
56
+ const u = new URL(urlStr);
57
+ const parts = u.pathname.split("/");
58
+ const normalized = parts.map((p) => {
59
+ if (!p) return p;
60
+ if (/^\d+$/.test(p)) return "{id}";
61
+ if (/^[a-f0-9]{8,}$/i.test(p)) return "{hex}";
62
+ if (/^BV[a-zA-Z0-9]+$/.test(p)) return "{bvid}";
63
+ if (/^[a-z0-9]{20,}$/i.test(p)) return "{token}";
64
+ return p;
65
+ });
66
+ return u.origin + normalized.join("/");
67
+ } catch {
68
+ return urlStr;
69
+ }
70
+ }
71
+ function detectAuth(url, headers) {
72
+ const indicators = [];
73
+ if (url.includes("signature") || url.includes("sign=") || url.includes("sig=")) indicators.push("signature");
74
+ if (url.includes("token=") || url.includes("access_token=")) indicators.push("token");
75
+ if (url.includes("api_key=") || url.includes("apikey=")) indicators.push("api_key");
76
+ if (headers) {
77
+ if (headers["authorization"]?.startsWith("Bearer")) indicators.push("bearer");
78
+ if (headers["x-csrf-token"] || headers["x-xsrf-token"]) indicators.push("csrf");
79
+ }
80
+ return indicators;
81
+ }
82
+ function analyzeResponseBody(body) {
83
+ if (!body || typeof body !== "object") {
84
+ return { hasItems: false, itemCount: 0, fields: [], fieldRoles: {} };
85
+ }
86
+ let items = null;
87
+ if (Array.isArray(body)) {
88
+ items = body;
89
+ } else {
90
+ const obj = body;
91
+ for (const key of ["data", "results", "items", "list", "entries", "records", "hits", "posts", "articles", "stories"]) {
92
+ const val = obj[key];
93
+ if (Array.isArray(val) && val.length > 0) {
94
+ items = val;
95
+ break;
96
+ }
97
+ if (val && typeof val === "object" && !Array.isArray(val)) {
98
+ for (const subKey of ["items", "list", "data", "results", "entries"]) {
99
+ const subVal = val[subKey];
100
+ if (Array.isArray(subVal) && subVal.length > 0) {
101
+ items = subVal;
102
+ break;
103
+ }
104
+ }
105
+ if (items) break;
106
+ }
107
+ }
108
+ }
109
+ if (!items || items.length === 0) {
110
+ return { hasItems: false, itemCount: 0, fields: [], fieldRoles: {} };
111
+ }
112
+ const firstItem = items[0];
113
+ if (!firstItem || typeof firstItem !== "object") {
114
+ return { hasItems: true, itemCount: items.length, fields: [], fieldRoles: {} };
115
+ }
116
+ const fields = Object.keys(firstItem);
117
+ const fieldRoles = {};
118
+ for (const field of fields) {
119
+ const lower = field.toLowerCase();
120
+ for (const [role, patterns] of Object.entries(FIELD_ROLES)) {
121
+ if (patterns.some((p) => lower.includes(p))) {
122
+ fieldRoles[field] = role;
123
+ break;
124
+ }
125
+ }
126
+ }
127
+ return { hasItems: true, itemCount: items.length, fields, fieldRoles };
128
+ }
129
+ function scoreEndpoint(ep) {
130
+ let score = 0;
131
+ if (ep.contentType.includes("json")) score += 10;
132
+ if (ep.hasItems) score += 5;
133
+ if (ep.itemCount > 3) score += 3;
134
+ if (ep.itemCount > 10) score += 2;
135
+ if (Object.keys(ep.fieldRoles).length > 2) score += 3;
136
+ if (ep.url.includes("/api/")) score += 5;
137
+ if (ep.url.includes("/v1/") || ep.url.includes("/v2/") || ep.url.includes("/v3/")) score += 3;
138
+ const path = new URL(ep.url).pathname.toLowerCase();
139
+ if (/search|query|find/.test(path)) score += 4;
140
+ if (/hot|trending|popular|top|feed|timeline/.test(path)) score += 4;
141
+ if (/list|index|all|latest|recent/.test(path)) score += 3;
142
+ if (ep.queryParams.some((p) => /search|keyword|query|q/.test(p))) score += 3;
143
+ if (ep.queryParams.some((p) => /page|offset|cursor|limit|count|num/.test(p))) score += 2;
144
+ if (ep.method === "GET") score += 2;
145
+ if (ep.authIndicators.includes("signature")) score -= 2;
146
+ if (/\.(js|css|png|jpg|gif|svg|woff|ico)/.test(ep.url)) score -= 30;
147
+ if (/analytics|tracking|pixel|beacon|log\b/.test(ep.url)) score -= 20;
148
+ return score;
149
+ }
150
+ function inferCapabilities(endpoints) {
151
+ const caps = [];
152
+ for (const ep of endpoints) {
153
+ const path = ep.url.toLowerCase();
154
+ if (/search|query|find/.test(path) && !caps.includes("search")) caps.push("search");
155
+ if (/hot|trending|popular/.test(path) && !caps.includes("hot")) caps.push("hot");
156
+ if (/feed|timeline|home/.test(path) && !caps.includes("feed")) caps.push("feed");
157
+ if (/detail|item\/\{|article\/\{|post\/\{/.test(ep.pattern) && !caps.includes("detail")) caps.push("detail");
158
+ if (/comment|reply|discuss/.test(path) && !caps.includes("comments")) caps.push("comments");
159
+ if (/user|profile|me\b|account/.test(path) && !caps.includes("me")) caps.push("me");
160
+ if (/favorite|bookmark|saved|like/.test(path) && !caps.includes("favorites")) caps.push("favorites");
161
+ if (/history|watch|read/.test(path) && !caps.includes("history")) caps.push("history");
162
+ }
163
+ return caps;
164
+ }
165
+ async function smartAutoScroll(page, attempts) {
166
+ for (let i = 0; i < attempts; i++) {
167
+ const scrolled = await page.evaluate(`
168
+ (async () => {
169
+ const lastHeight = document.body.scrollHeight;
170
+ window.scrollTo(0, lastHeight);
171
+
172
+ // Wait for new content via MutationObserver or timeout
173
+ const result = await new Promise((resolve) => {
174
+ let timeoutId;
175
+ const observer = new MutationObserver(() => {
176
+ if (document.body.scrollHeight > lastHeight) {
177
+ clearTimeout(timeoutId);
178
+ observer.disconnect();
179
+ setTimeout(() => resolve(true), 100);
180
+ }
181
+ });
182
+ observer.observe(document.body, { childList: true, subtree: true });
183
+ timeoutId = setTimeout(() => { observer.disconnect(); resolve(false); }, 2000);
184
+ });
185
+ return result;
186
+ })()
187
+ `);
188
+ if (!scrolled) break;
189
+ }
190
+ }
191
+ async function interactiveFuzz(page, maxButtons) {
192
+ await page.evaluate(`
193
+ (async () => {
194
+ const clickTargets = [];
195
+ const selectors = [
196
+ 'button:not([disabled])',
197
+ '[role="tab"]',
198
+ '[role="button"]',
199
+ '.tab', '.nav-link', '.dropdown-toggle',
200
+ 'a[data-toggle]', '[data-bs-toggle]',
201
+ ];
202
+
203
+ for (const sel of selectors) {
204
+ for (const el of document.querySelectorAll(sel)) {
205
+ const rect = el.getBoundingClientRect();
206
+ if (rect.width > 0 && rect.height > 0 &&
207
+ rect.top >= 0 && rect.top < window.innerHeight * 2) {
208
+ const text = el.textContent?.trim()?.slice(0, 40) || '';
209
+ // Skip destructive-looking buttons
210
+ if (/delete|remove|logout|sign.?out|cancel|close/i.test(text)) continue;
211
+ clickTargets.push(el);
212
+ }
213
+ }
214
+ }
215
+
216
+ // Click up to N targets with delays
217
+ const max = Math.min(${maxButtons}, clickTargets.length);
218
+ for (let i = 0; i < max; i++) {
219
+ try {
220
+ clickTargets[i].click();
221
+ await new Promise(r => setTimeout(r, 800));
222
+ } catch {}
223
+ }
224
+ })()
225
+ `);
226
+ await page.wait(1.5);
227
+ }
228
+ async function recoverMissingBodies(page, endpoints) {
229
+ const needsRecovery = endpoints.filter(
230
+ (ep) => !ep.hasItems && ep.contentType.includes("json") && ep.score > 5
231
+ );
232
+ if (needsRecovery.length === 0) return;
233
+ const urls = needsRecovery.map((ep) => ep.url).slice(0, 8);
234
+ const bodies = await page.evaluate(`
235
+ (async () => {
236
+ const urls = ${JSON.stringify(urls)};
237
+ const results = [];
238
+ for (const url of urls) {
239
+ try {
240
+ const resp = await fetch(url, { credentials: 'include' });
241
+ if (resp.ok) {
242
+ const json = await resp.json();
243
+ results.push(json);
244
+ } else {
245
+ results.push(null);
246
+ }
247
+ } catch { results.push(null); }
248
+ }
249
+ return results;
250
+ })()
251
+ `);
252
+ if (!bodies) return;
253
+ for (let i = 0; i < urls.length; i++) {
254
+ if (!bodies[i]) continue;
255
+ const ep = needsRecovery[i];
256
+ const analysis = analyzeResponseBody(bodies[i]);
257
+ if (analysis.hasItems) {
258
+ ep.hasItems = analysis.hasItems;
259
+ ep.itemCount = analysis.itemCount;
260
+ ep.fields = analysis.fields;
261
+ ep.fieldRoles = analysis.fieldRoles;
262
+ ep.score = scoreEndpoint(ep);
263
+ }
264
+ }
265
+ }
266
+ function writeArtifacts(dir, result) {
267
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
268
+ writeFileSync(join(dir, "manifest.json"), JSON.stringify({
269
+ site: result.site,
270
+ domain: result.domain,
271
+ framework: result.framework,
272
+ strategy: result.strategy,
273
+ capabilities: result.capabilities,
274
+ endpointCount: result.endpoints.length,
275
+ exploredAt: (/* @__PURE__ */ new Date()).toISOString()
276
+ }, null, 2));
277
+ writeFileSync(join(dir, "endpoints.json"), JSON.stringify(
278
+ result.endpoints.map((ep) => ({
279
+ url: ep.url,
280
+ pattern: ep.pattern,
281
+ method: ep.method,
282
+ status: ep.status,
283
+ score: ep.score,
284
+ hasItems: ep.hasItems,
285
+ itemCount: ep.itemCount,
286
+ fields: ep.fields,
287
+ fieldRoles: ep.fieldRoles,
288
+ queryParams: ep.queryParams,
289
+ authIndicators: ep.authIndicators
290
+ })),
291
+ null,
292
+ 2
293
+ ));
294
+ writeFileSync(join(dir, "capabilities.json"), JSON.stringify(
295
+ result.capabilities.map((cap) => {
296
+ const matchingEndpoints = result.endpoints.filter((ep) => {
297
+ const path = ep.url.toLowerCase();
298
+ if (cap === "search") return /search|query|find/.test(path);
299
+ if (cap === "hot") return /hot|trending|popular/.test(path);
300
+ if (cap === "feed") return /feed|timeline|home/.test(path);
301
+ return false;
302
+ });
303
+ return {
304
+ name: cap,
305
+ description: `${cap} capability`,
306
+ endpoint: matchingEndpoints[0]?.pattern || null,
307
+ strategy: result.strategy,
308
+ confidence: matchingEndpoints.length > 0 ? 0.8 : 0.5,
309
+ recommendedColumns: matchingEndpoints[0]?.fields?.slice(0, 6) || []
310
+ };
311
+ }),
312
+ null,
313
+ 2
314
+ ));
315
+ const authSummary = {};
316
+ for (const ep of result.endpoints) {
317
+ for (const ind of ep.authIndicators) {
318
+ if (!authSummary[ind]) authSummary[ind] = [];
319
+ authSummary[ind].push(ep.pattern);
320
+ }
321
+ }
322
+ writeFileSync(join(dir, "auth.json"), JSON.stringify(authSummary, null, 2));
323
+ if (result.stores && result.stores.length > 0) {
324
+ writeFileSync(join(dir, "stores.json"), JSON.stringify(result.stores, null, 2));
325
+ }
326
+ log.success(`Artifacts written to ${dir}/`);
327
+ }
328
+ async function exploreSite(page, url, options) {
329
+ const parsedUrl = new URL(url);
330
+ const domain = parsedUrl.hostname;
331
+ const site = SITE_ALIASES[domain] || domain.replace(/^www\./, "").split(".")[0];
332
+ log.info(`Exploring ${url}...`);
333
+ await page.installInterceptor("");
334
+ await page.goto(url);
335
+ await page.wait(options?.wait || 3);
336
+ if (options?.scroll !== false) {
337
+ log.debug("Smart auto-scrolling to trigger lazy-loaded APIs...");
338
+ await smartAutoScroll(page, options?.scrollAttempts || 4);
339
+ }
340
+ if (options?.fuzz !== false) {
341
+ log.debug("Fuzzing interactive elements...");
342
+ await interactiveFuzz(page, options?.maxButtons || 12);
343
+ }
344
+ const rawRequests = await page.getInterceptedRequests();
345
+ const seen = /* @__PURE__ */ new Set();
346
+ const endpoints = [];
347
+ for (const raw of rawRequests) {
348
+ if (!raw?.url || !raw?.status) continue;
349
+ if (raw.status < 200 || raw.status >= 400) continue;
350
+ const pattern = normalizeUrlPattern(raw.url);
351
+ const dedupeKey = `${raw.method || "GET"}:${pattern}`;
352
+ if (seen.has(dedupeKey)) continue;
353
+ seen.add(dedupeKey);
354
+ let queryParams = [];
355
+ try {
356
+ const u = new URL(raw.url);
357
+ queryParams = [...u.searchParams.keys()].filter((k) => !VOLATILE_PARAMS.has(k));
358
+ } catch {
359
+ }
360
+ const authIndicators = detectAuth(raw.url);
361
+ const bodyAnalysis = analyzeResponseBody(raw.body);
362
+ const ep = {
363
+ url: raw.url,
364
+ pattern,
365
+ method: raw.method || "GET",
366
+ status: raw.status,
367
+ contentType: "json",
368
+ queryParams,
369
+ ...bodyAnalysis,
370
+ authIndicators
371
+ };
372
+ endpoints.push({ ...ep, score: scoreEndpoint(ep) });
373
+ }
374
+ log.debug("Recovering missing response bodies...");
375
+ await recoverMissingBodies(page, endpoints);
376
+ endpoints.sort((a, b) => b.score - a.score);
377
+ const framework = await page.evaluate(`
378
+ (() => {
379
+ const app = document.querySelector('#app');
380
+ if (window.__NEXT_DATA__) return 'nextjs';
381
+ if (window.__NUXT__) return 'nuxt';
382
+ if (app && app.__vue_app__) {
383
+ const gp = app.__vue_app__.config?.globalProperties;
384
+ if (gp?.$pinia) return 'vue+pinia';
385
+ if (gp?.$store) return 'vue+vuex';
386
+ return 'vue';
387
+ }
388
+ if (app && app.__vue__) return 'vue2';
389
+ if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) return 'react';
390
+ if (document.querySelector('[data-reactroot]') || document.querySelector('#__next') || document.querySelector('#root')?.['_reactRootContainer']) return 'react';
391
+ if (window.angular || document.querySelector('[ng-version]')) return 'angular';
392
+ if (window.__svelte_meta) return 'svelte';
393
+ return 'unknown';
394
+ })()
395
+ `).catch(() => "unknown");
396
+ const stores = await page.evaluate(`
397
+ (() => {
398
+ const results = [];
399
+ const app = document.querySelector('#app');
400
+
401
+ // Pinia via __vue_app__
402
+ if (app && app.__vue_app__) {
403
+ try {
404
+ const pinia = app.__vue_app__.config?.globalProperties?.$pinia;
405
+ if (pinia && pinia._s) {
406
+ pinia._s.forEach((store, id) => {
407
+ const actions = Object.keys(store).filter(k =>
408
+ typeof store[k] === 'function' && !k.startsWith('$') && !k.startsWith('_')
409
+ );
410
+ const stateKeys = Object.keys(store).filter(k =>
411
+ typeof store[k] !== 'function' && !k.startsWith('$') && !k.startsWith('_')
412
+ );
413
+ results.push({ name: id, type: 'pinia', actions: actions.slice(0, 20), stateKeys: stateKeys.slice(0, 20) });
414
+ });
415
+ }
416
+ } catch {}
417
+
418
+ // Vuex via __vue_app__
419
+ try {
420
+ const store = app.__vue_app__.config?.globalProperties?.$store;
421
+ if (store && store._actions) {
422
+ const actions = Object.keys(store._actions);
423
+ results.push({ name: 'vuex', type: 'vuex', actions: actions.slice(0, 20) });
424
+ }
425
+ } catch {}
426
+ }
427
+
428
+ // Legacy Pinia global
429
+ if (results.length === 0 && window.__pinia) {
430
+ try {
431
+ const pinia = window.__pinia;
432
+ for (const [id, store] of pinia._s || []) {
433
+ const actions = Object.keys(store).filter(k => typeof store[k] === 'function' && !k.startsWith('$') && !k.startsWith('_'));
434
+ results.push({ name: id, type: 'pinia', actions: actions.slice(0, 20) });
435
+ }
436
+ } catch {}
437
+ }
438
+
439
+ return results;
440
+ })()
441
+ `).catch(() => []);
442
+ let strategy = "public";
443
+ if (endpoints.length > 0) {
444
+ const topEp = endpoints[0];
445
+ if (topEp.authIndicators.includes("signature")) strategy = "intercept";
446
+ else if (topEp.authIndicators.includes("bearer") || topEp.authIndicators.includes("csrf")) strategy = "header";
447
+ else if (endpoints.some((e) => !e.hasItems) && endpoints.some((e) => e.hasItems)) strategy = "cookie";
448
+ } else {
449
+ strategy = "cookie";
450
+ }
451
+ const capabilities = inferCapabilities(endpoints);
452
+ const result = {
453
+ site,
454
+ domain,
455
+ endpoints: endpoints.slice(0, 30),
456
+ strategy,
457
+ framework,
458
+ stores: stores.length > 0 ? stores : void 0,
459
+ capabilities
460
+ };
461
+ const outputDir = options?.outputDir || join(process.cwd(), ".lobster", "explore", site);
462
+ writeArtifacts(outputDir, result);
463
+ result.artifactDir = outputDir;
464
+ return result;
465
+ }
466
+
467
+ // src/discover/synthesize.ts
468
+ import yaml from "js-yaml";
469
+ function synthesizeAdapter(result, goal) {
470
+ const topEndpoints = result.endpoints.filter((e) => e.score > 0).slice(0, 3);
471
+ if (topEndpoints.length === 0) {
472
+ return `# No API endpoints discovered for ${result.site}
473
+ # Try using: lobster agent "your task" --url https://${result.domain}`;
474
+ }
475
+ const endpoint = topEndpoints[0];
476
+ let name = goal || "data";
477
+ if (!goal) {
478
+ const path = new URL(endpoint.url).pathname.toLowerCase();
479
+ if (/search|query/.test(path)) name = "search";
480
+ else if (/hot|trending|popular/.test(path)) name = "hot";
481
+ else if (/feed|timeline|home/.test(path)) name = "feed";
482
+ else if (/top|best|rank/.test(path)) name = "top";
483
+ }
484
+ const args = {
485
+ limit: { type: "int", default: 20 }
486
+ };
487
+ for (const param of endpoint.queryParams) {
488
+ if (/search|keyword|query|q/.test(param)) {
489
+ args[param] = { required: true, positional: true, help: "Search query" };
490
+ } else if (/page|offset|cursor/.test(param)) {
491
+ args[param] = { type: "int", default: 1 };
492
+ } else if (/limit|count|num|size/.test(param)) {
493
+ }
494
+ }
495
+ const columns = [];
496
+ const mapTemplate = {};
497
+ for (const [field, role] of Object.entries(endpoint.fieldRoles)) {
498
+ if (["title", "url", "author", "score", "time", "description"].includes(role)) {
499
+ columns.push(role);
500
+ mapTemplate[role] = `\${{ item.${field} }}`;
501
+ }
502
+ }
503
+ if (columns.length === 0) {
504
+ for (const field of endpoint.fields.slice(0, 5)) {
505
+ columns.push(field);
506
+ mapTemplate[field] = `\${{ item.${field} }}`;
507
+ }
508
+ }
509
+ const pipeline = [];
510
+ if (result.strategy === "public" && !endpoint.authIndicators.length) {
511
+ pipeline.push({ fetch: endpoint.url });
512
+ } else {
513
+ pipeline.push({ navigate: `https://${result.domain}` });
514
+ pipeline.push({
515
+ evaluate: `(async () => { const r = await fetch(${JSON.stringify(endpoint.url)}, {credentials:'include'}); return r.json(); })()`
516
+ });
517
+ }
518
+ if (endpoint.hasItems && endpoint.itemCount > 0) {
519
+ for (const path of ["data", "results", "items", "list", "data.items", "data.list"]) {
520
+ pipeline.push({ select: path });
521
+ break;
522
+ }
523
+ }
524
+ if (Object.keys(mapTemplate).length > 0) {
525
+ pipeline.push({ map: mapTemplate });
526
+ }
527
+ pipeline.push({ limit: "${{ args.limit }}" });
528
+ const adapter = {
529
+ site: result.site,
530
+ name,
531
+ description: `${name} from ${result.domain}`,
532
+ domain: result.domain,
533
+ strategy: result.strategy,
534
+ browser: result.strategy !== "public",
535
+ args,
536
+ pipeline,
537
+ columns: columns.length > 0 ? columns : void 0
538
+ };
539
+ return yaml.dump(adapter, { indent: 2, lineWidth: 120 });
540
+ }
541
+ export {
542
+ exploreSite,
543
+ synthesizeAdapter
544
+ };
545
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/discover/explore.ts","../../src/utils/logger.ts","../../src/discover/synthesize.ts"],"sourcesContent":["/**\n * Site exploration: navigate, capture network, analyze APIs, infer capabilities.\n *\n * Based on OpenCLI's explore module with:\n * - URL pattern normalization (/123 → /{id})\n * - Auth detection (bearer, CSRF, signature)\n * - Response body analysis (item arrays, field roles)\n * - Framework & store detection\n * - Artifact generation\n */\n\nimport { writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { IPage } from '../types/page.js';\nimport { log } from '../utils/logger.js';\n\n// ── Known site aliases ──\nconst SITE_ALIASES: Record<string, string> = {\n 'x.com': 'twitter', 'twitter.com': 'twitter',\n 'news.ycombinator.com': 'hackernews',\n 'old.reddit.com': 'reddit', 'www.reddit.com': 'reddit',\n 'bilibili.com': 'bilibili', 'www.bilibili.com': 'bilibili',\n 'zhihu.com': 'zhihu', 'www.zhihu.com': 'zhihu',\n};\n\n// ── Field role mapping ──\nconst FIELD_ROLES: Record<string, string[]> = {\n title: ['title', 'name', 'headline', 'subject', 'text', 'caption'],\n url: ['url', 'link', 'href', 'permalink', 'uri', 'web_url'],\n author: ['author', 'user', 'creator', 'owner', 'by', 'username', 'screen_name', 'display_name'],\n score: ['score', 'points', 'likes', 'upvotes', 'karma', 'vote_count', 'favorite_count', 'retweet_count'],\n time: ['time', 'date', 'created', 'created_at', 'timestamp', 'published', 'updated_at', 'posted_at'],\n description: ['description', 'summary', 'snippet', 'excerpt', 'body', 'content', 'selftext'],\n image: ['image', 'thumbnail', 'avatar', 'icon', 'photo', 'cover', 'poster'],\n id: ['id', 'uid', 'pid', 'mid', 'aid', 'bvid'],\n};\n\n// ── Volatile query params to ignore ──\nconst VOLATILE_PARAMS = new Set([\n '_', 't', 'ts', 'timestamp', 'nonce', 'rand', 'random',\n 'callback', 'jsonp', '_t', '__t',\n]);\n\nexport interface EndpointInfo {\n url: string;\n pattern: string;\n method: string;\n status: number;\n contentType: string;\n queryParams: string[];\n hasItems: boolean;\n itemCount: number;\n fields: string[];\n fieldRoles: Record<string, string>;\n authIndicators: string[];\n score: number;\n}\n\nexport interface ExploreResult {\n site: string;\n domain: string;\n endpoints: EndpointInfo[];\n strategy: string;\n framework?: string;\n stores?: { name: string; type: string; actions: string[] }[];\n capabilities: string[];\n artifactDir?: string;\n}\n\nexport interface ExploreOptions {\n wait?: number;\n scroll?: boolean;\n fuzz?: boolean;\n outputDir?: string;\n maxButtons?: number;\n scrollAttempts?: number;\n}\n\n/**\n * Normalize a URL path to a pattern:\n * /users/123/posts → /users/{id}/posts\n * /item/abc123def → /item/{hex}\n */\nfunction normalizeUrlPattern(urlStr: string): string {\n try {\n const u = new URL(urlStr);\n const parts = u.pathname.split('/');\n const normalized = parts.map((p) => {\n if (!p) return p;\n if (/^\\d+$/.test(p)) return '{id}';\n if (/^[a-f0-9]{8,}$/i.test(p)) return '{hex}';\n if (/^BV[a-zA-Z0-9]+$/.test(p)) return '{bvid}';\n if (/^[a-z0-9]{20,}$/i.test(p)) return '{token}';\n return p;\n });\n return u.origin + normalized.join('/');\n } catch {\n return urlStr;\n }\n}\n\n/**\n * Detect auth indicators in request headers/URL.\n */\nfunction detectAuth(url: string, headers?: Record<string, string>): string[] {\n const indicators: string[] = [];\n if (url.includes('signature') || url.includes('sign=') || url.includes('sig=')) indicators.push('signature');\n if (url.includes('token=') || url.includes('access_token=')) indicators.push('token');\n if (url.includes('api_key=') || url.includes('apikey=')) indicators.push('api_key');\n if (headers) {\n if (headers['authorization']?.startsWith('Bearer')) indicators.push('bearer');\n if (headers['x-csrf-token'] || headers['x-xsrf-token']) indicators.push('csrf');\n }\n return indicators;\n}\n\n/**\n * Analyze a JSON response body to find item arrays and extract fields.\n */\nfunction analyzeResponseBody(body: unknown): {\n hasItems: boolean;\n itemCount: number;\n fields: string[];\n fieldRoles: Record<string, string>;\n} {\n if (!body || typeof body !== 'object') {\n return { hasItems: false, itemCount: 0, fields: [], fieldRoles: {} };\n }\n\n // Find the item array — could be at root or nested\n let items: unknown[] | null = null;\n\n if (Array.isArray(body)) {\n items = body;\n } else {\n // Search common nested paths: data, results, items, list, entries, records, hits\n const obj = body as Record<string, unknown>;\n for (const key of ['data', 'results', 'items', 'list', 'entries', 'records', 'hits', 'posts', 'articles', 'stories']) {\n const val = obj[key];\n if (Array.isArray(val) && val.length > 0) {\n items = val;\n break;\n }\n // One level deeper: data.items, data.list, etc.\n if (val && typeof val === 'object' && !Array.isArray(val)) {\n for (const subKey of ['items', 'list', 'data', 'results', 'entries']) {\n const subVal = (val as Record<string, unknown>)[subKey];\n if (Array.isArray(subVal) && subVal.length > 0) {\n items = subVal;\n break;\n }\n }\n if (items) break;\n }\n }\n }\n\n if (!items || items.length === 0) {\n return { hasItems: false, itemCount: 0, fields: [], fieldRoles: {} };\n }\n\n // Extract fields from first item\n const firstItem = items[0];\n if (!firstItem || typeof firstItem !== 'object') {\n return { hasItems: true, itemCount: items.length, fields: [], fieldRoles: {} };\n }\n\n const fields = Object.keys(firstItem as Record<string, unknown>);\n\n // Map fields to semantic roles\n const fieldRoles: Record<string, string> = {};\n for (const field of fields) {\n const lower = field.toLowerCase();\n for (const [role, patterns] of Object.entries(FIELD_ROLES)) {\n if (patterns.some((p) => lower.includes(p))) {\n fieldRoles[field] = role;\n break;\n }\n }\n }\n\n return { hasItems: true, itemCount: items.length, fields, fieldRoles };\n}\n\n/**\n * Score an endpoint for relevance.\n */\nfunction scoreEndpoint(ep: Omit<EndpointInfo, 'score'>): number {\n let score = 0;\n\n // Content type\n if (ep.contentType.includes('json')) score += 10;\n\n // Response analysis\n if (ep.hasItems) score += 5;\n if (ep.itemCount > 3) score += 3;\n if (ep.itemCount > 10) score += 2;\n if (Object.keys(ep.fieldRoles).length > 2) score += 3;\n\n // URL patterns\n if (ep.url.includes('/api/')) score += 5;\n if (ep.url.includes('/v1/') || ep.url.includes('/v2/') || ep.url.includes('/v3/')) score += 3;\n const path = new URL(ep.url).pathname.toLowerCase();\n if (/search|query|find/.test(path)) score += 4;\n if (/hot|trending|popular|top|feed|timeline/.test(path)) score += 4;\n if (/list|index|all|latest|recent/.test(path)) score += 3;\n\n // Query params\n if (ep.queryParams.some((p) => /search|keyword|query|q/.test(p))) score += 3;\n if (ep.queryParams.some((p) => /page|offset|cursor|limit|count|num/.test(p))) score += 2;\n\n // Method\n if (ep.method === 'GET') score += 2;\n\n // Auth complexity penalty\n if (ep.authIndicators.includes('signature')) score -= 2;\n\n // Penalize static assets\n if (/\\.(js|css|png|jpg|gif|svg|woff|ico)/.test(ep.url)) score -= 30;\n\n // Penalize tracking/analytics\n if (/analytics|tracking|pixel|beacon|log\\b/.test(ep.url)) score -= 20;\n\n return score;\n}\n\n/**\n * Infer capabilities from discovered endpoints.\n */\nfunction inferCapabilities(endpoints: EndpointInfo[]): string[] {\n const caps: string[] = [];\n for (const ep of endpoints) {\n const path = ep.url.toLowerCase();\n if (/search|query|find/.test(path) && !caps.includes('search')) caps.push('search');\n if (/hot|trending|popular/.test(path) && !caps.includes('hot')) caps.push('hot');\n if (/feed|timeline|home/.test(path) && !caps.includes('feed')) caps.push('feed');\n if (/detail|item\\/\\{|article\\/\\{|post\\/\\{/.test(ep.pattern) && !caps.includes('detail')) caps.push('detail');\n if (/comment|reply|discuss/.test(path) && !caps.includes('comments')) caps.push('comments');\n if (/user|profile|me\\b|account/.test(path) && !caps.includes('me')) caps.push('me');\n if (/favorite|bookmark|saved|like/.test(path) && !caps.includes('favorites')) caps.push('favorites');\n if (/history|watch|read/.test(path) && !caps.includes('history')) caps.push('history');\n }\n return caps;\n}\n\n/**\n * Smart auto-scroll with MutationObserver-based lazy-load detection.\n * Waits for new DOM nodes to appear after each scroll instead of fixed delays.\n */\nasync function smartAutoScroll(page: IPage, attempts: number): Promise<void> {\n for (let i = 0; i < attempts; i++) {\n const scrolled = await page.evaluate<boolean>(`\n (async () => {\n const lastHeight = document.body.scrollHeight;\n window.scrollTo(0, lastHeight);\n\n // Wait for new content via MutationObserver or timeout\n const result = await new Promise((resolve) => {\n let timeoutId;\n const observer = new MutationObserver(() => {\n if (document.body.scrollHeight > lastHeight) {\n clearTimeout(timeoutId);\n observer.disconnect();\n setTimeout(() => resolve(true), 100);\n }\n });\n observer.observe(document.body, { childList: true, subtree: true });\n timeoutId = setTimeout(() => { observer.disconnect(); resolve(false); }, 2000);\n });\n return result;\n })()\n `);\n if (!scrolled) break; // No new content loaded, stop scrolling\n }\n}\n\n/**\n * Interactive fuzzing — click buttons/tabs to trigger hidden API calls.\n * Clicks up to maxButtons interactive elements that look like data triggers.\n */\nasync function interactiveFuzz(page: IPage, maxButtons: number): Promise<void> {\n await page.evaluate(`\n (async () => {\n const clickTargets = [];\n const selectors = [\n 'button:not([disabled])',\n '[role=\"tab\"]',\n '[role=\"button\"]',\n '.tab', '.nav-link', '.dropdown-toggle',\n 'a[data-toggle]', '[data-bs-toggle]',\n ];\n\n for (const sel of selectors) {\n for (const el of document.querySelectorAll(sel)) {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0 &&\n rect.top >= 0 && rect.top < window.innerHeight * 2) {\n const text = el.textContent?.trim()?.slice(0, 40) || '';\n // Skip destructive-looking buttons\n if (/delete|remove|logout|sign.?out|cancel|close/i.test(text)) continue;\n clickTargets.push(el);\n }\n }\n }\n\n // Click up to N targets with delays\n const max = Math.min(${maxButtons}, clickTargets.length);\n for (let i = 0; i < max; i++) {\n try {\n clickTargets[i].click();\n await new Promise(r => setTimeout(r, 800));\n } catch {}\n }\n })()\n `);\n await page.wait(1.5);\n}\n\n/**\n * Re-fetch JSON endpoints whose response body was missing from interception.\n * Uses an iframe to avoid CORS issues (same-origin cookies).\n */\nasync function recoverMissingBodies(\n page: IPage,\n endpoints: EndpointInfo[],\n): Promise<void> {\n const needsRecovery = endpoints.filter(\n (ep) => !ep.hasItems && ep.contentType.includes('json') && ep.score > 5,\n );\n\n if (needsRecovery.length === 0) return;\n\n const urls = needsRecovery.map((ep) => ep.url).slice(0, 8);\n\n const bodies = await page.evaluate<(unknown | null)[]>(`\n (async () => {\n const urls = ${JSON.stringify(urls)};\n const results = [];\n for (const url of urls) {\n try {\n const resp = await fetch(url, { credentials: 'include' });\n if (resp.ok) {\n const json = await resp.json();\n results.push(json);\n } else {\n results.push(null);\n }\n } catch { results.push(null); }\n }\n return results;\n })()\n `);\n\n if (!bodies) return;\n\n for (let i = 0; i < urls.length; i++) {\n if (!bodies[i]) continue;\n const ep = needsRecovery[i];\n const analysis = analyzeResponseBody(bodies[i]);\n if (analysis.hasItems) {\n ep.hasItems = analysis.hasItems;\n ep.itemCount = analysis.itemCount;\n ep.fields = analysis.fields;\n ep.fieldRoles = analysis.fieldRoles;\n ep.score = scoreEndpoint(ep);\n }\n }\n}\n\n/**\n * Write exploration artifacts to disk.\n */\nfunction writeArtifacts(dir: string, result: ExploreResult): void {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n // manifest.json — site metadata\n writeFileSync(join(dir, 'manifest.json'), JSON.stringify({\n site: result.site,\n domain: result.domain,\n framework: result.framework,\n strategy: result.strategy,\n capabilities: result.capabilities,\n endpointCount: result.endpoints.length,\n exploredAt: new Date().toISOString(),\n }, null, 2));\n\n // endpoints.json — all discovered endpoints with scores/fields\n writeFileSync(join(dir, 'endpoints.json'), JSON.stringify(\n result.endpoints.map((ep) => ({\n url: ep.url,\n pattern: ep.pattern,\n method: ep.method,\n status: ep.status,\n score: ep.score,\n hasItems: ep.hasItems,\n itemCount: ep.itemCount,\n fields: ep.fields,\n fieldRoles: ep.fieldRoles,\n queryParams: ep.queryParams,\n authIndicators: ep.authIndicators,\n })),\n null, 2,\n ));\n\n // capabilities.json — inferred CLI commands\n writeFileSync(join(dir, 'capabilities.json'), JSON.stringify(\n result.capabilities.map((cap) => {\n const matchingEndpoints = result.endpoints.filter((ep) => {\n const path = ep.url.toLowerCase();\n if (cap === 'search') return /search|query|find/.test(path);\n if (cap === 'hot') return /hot|trending|popular/.test(path);\n if (cap === 'feed') return /feed|timeline|home/.test(path);\n return false;\n });\n return {\n name: cap,\n description: `${cap} capability`,\n endpoint: matchingEndpoints[0]?.pattern || null,\n strategy: result.strategy,\n confidence: matchingEndpoints.length > 0 ? 0.8 : 0.5,\n recommendedColumns: matchingEndpoints[0]?.fields?.slice(0, 6) || [],\n };\n }),\n null, 2,\n ));\n\n // auth.json — auth indicators\n const authSummary: Record<string, string[]> = {};\n for (const ep of result.endpoints) {\n for (const ind of ep.authIndicators) {\n if (!authSummary[ind]) authSummary[ind] = [];\n authSummary[ind].push(ep.pattern);\n }\n }\n writeFileSync(join(dir, 'auth.json'), JSON.stringify(authSummary, null, 2));\n\n // stores.json — Vue/React stores if detected\n if (result.stores && result.stores.length > 0) {\n writeFileSync(join(dir, 'stores.json'), JSON.stringify(result.stores, null, 2));\n }\n\n log.success(`Artifacts written to ${dir}/`);\n}\n\nexport async function exploreSite(\n page: IPage,\n url: string,\n options?: ExploreOptions,\n): Promise<ExploreResult> {\n const parsedUrl = new URL(url);\n const domain = parsedUrl.hostname;\n const site = SITE_ALIASES[domain] || domain.replace(/^www\\./, '').split('.')[0];\n\n log.info(`Exploring ${url}...`);\n\n // Install network interceptor before navigation\n await page.installInterceptor('');\n await page.goto(url);\n await page.wait(options?.wait || 3);\n\n // Smart auto-scroll with MutationObserver lazy-load detection\n if (options?.scroll !== false) {\n log.debug('Smart auto-scrolling to trigger lazy-loaded APIs...');\n await smartAutoScroll(page, options?.scrollAttempts || 4);\n }\n\n // Interactive fuzzing — click buttons/tabs to discover hidden APIs\n if (options?.fuzz !== false) {\n log.debug('Fuzzing interactive elements...');\n await interactiveFuzz(page, options?.maxButtons || 12);\n }\n\n // Capture intercepted network requests\n const rawRequests = await page.getInterceptedRequests();\n\n // Analyze each request\n const seen = new Set<string>();\n const endpoints: EndpointInfo[] = [];\n\n for (const raw of rawRequests as any[]) {\n if (!raw?.url || !raw?.status) continue;\n if (raw.status < 200 || raw.status >= 400) continue;\n\n const pattern = normalizeUrlPattern(raw.url);\n const dedupeKey = `${raw.method || 'GET'}:${pattern}`;\n if (seen.has(dedupeKey)) continue;\n seen.add(dedupeKey);\n\n // Extract query params\n let queryParams: string[] = [];\n try {\n const u = new URL(raw.url);\n queryParams = [...u.searchParams.keys()].filter((k) => !VOLATILE_PARAMS.has(k));\n } catch {}\n\n const authIndicators = detectAuth(raw.url);\n const bodyAnalysis = analyzeResponseBody(raw.body);\n\n const ep: Omit<EndpointInfo, 'score'> = {\n url: raw.url,\n pattern,\n method: raw.method || 'GET',\n status: raw.status,\n contentType: 'json',\n queryParams,\n ...bodyAnalysis,\n authIndicators,\n };\n\n endpoints.push({ ...ep, score: scoreEndpoint(ep) });\n }\n\n // Response body recovery — re-fetch endpoints that had missing bodies\n log.debug('Recovering missing response bodies...');\n await recoverMissingBodies(page, endpoints);\n\n endpoints.sort((a, b) => b.score - a.score);\n\n // Detect framework\n const framework = await page.evaluate<string>(`\n (() => {\n const app = document.querySelector('#app');\n if (window.__NEXT_DATA__) return 'nextjs';\n if (window.__NUXT__) return 'nuxt';\n if (app && app.__vue_app__) {\n const gp = app.__vue_app__.config?.globalProperties;\n if (gp?.$pinia) return 'vue+pinia';\n if (gp?.$store) return 'vue+vuex';\n return 'vue';\n }\n if (app && app.__vue__) return 'vue2';\n if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) return 'react';\n if (document.querySelector('[data-reactroot]') || document.querySelector('#__next') || document.querySelector('#root')?.['_reactRootContainer']) return 'react';\n if (window.angular || document.querySelector('[ng-version]')) return 'angular';\n if (window.__svelte_meta) return 'svelte';\n return 'unknown';\n })()\n `).catch(() => 'unknown');\n\n // Detect stores (Pinia/Vuex) — improved detection via __vue_app__\n const stores = await page.evaluate<{ name: string; type: string; actions: string[] }[]>(`\n (() => {\n const results = [];\n const app = document.querySelector('#app');\n\n // Pinia via __vue_app__\n if (app && app.__vue_app__) {\n try {\n const pinia = app.__vue_app__.config?.globalProperties?.$pinia;\n if (pinia && pinia._s) {\n pinia._s.forEach((store, id) => {\n const actions = Object.keys(store).filter(k =>\n typeof store[k] === 'function' && !k.startsWith('$') && !k.startsWith('_')\n );\n const stateKeys = Object.keys(store).filter(k =>\n typeof store[k] !== 'function' && !k.startsWith('$') && !k.startsWith('_')\n );\n results.push({ name: id, type: 'pinia', actions: actions.slice(0, 20), stateKeys: stateKeys.slice(0, 20) });\n });\n }\n } catch {}\n\n // Vuex via __vue_app__\n try {\n const store = app.__vue_app__.config?.globalProperties?.$store;\n if (store && store._actions) {\n const actions = Object.keys(store._actions);\n results.push({ name: 'vuex', type: 'vuex', actions: actions.slice(0, 20) });\n }\n } catch {}\n }\n\n // Legacy Pinia global\n if (results.length === 0 && window.__pinia) {\n try {\n const pinia = window.__pinia;\n for (const [id, store] of pinia._s || []) {\n const actions = Object.keys(store).filter(k => typeof store[k] === 'function' && !k.startsWith('$') && !k.startsWith('_'));\n results.push({ name: id, type: 'pinia', actions: actions.slice(0, 20) });\n }\n } catch {}\n }\n\n return results;\n })()\n `).catch(() => []);\n\n // Infer strategy\n let strategy = 'public';\n if (endpoints.length > 0) {\n const topEp = endpoints[0];\n if (topEp.authIndicators.includes('signature')) strategy = 'intercept';\n else if (topEp.authIndicators.includes('bearer') || topEp.authIndicators.includes('csrf')) strategy = 'header';\n else if (endpoints.some((e) => !e.hasItems) && endpoints.some((e) => e.hasItems)) strategy = 'cookie';\n } else {\n strategy = 'cookie';\n }\n\n const capabilities = inferCapabilities(endpoints);\n\n const result: ExploreResult = {\n site,\n domain,\n endpoints: endpoints.slice(0, 30),\n strategy,\n framework,\n stores: stores.length > 0 ? stores : undefined,\n capabilities,\n };\n\n // Write artifacts to disk if outputDir specified\n const outputDir = options?.outputDir || join(process.cwd(), '.lobster', 'explore', site);\n writeArtifacts(outputDir, result);\n result.artifactDir = outputDir;\n\n return result;\n}\n","import chalk from 'chalk';\n\nexport const log = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✓'), msg),\n warn: (msg: string) => console.log(chalk.yellow('⚠'), msg),\n error: (msg: string) => console.error(chalk.red('✗'), msg),\n debug: (msg: string) => {\n if (process.env.LOBSTER_DEBUG) console.log(chalk.gray('⋯'), msg);\n },\n step: (n: number, msg: string) => console.log(chalk.cyan(`[${n}]`), msg),\n dim: (msg: string) => console.log(chalk.dim(msg)),\n};\n","import yaml from 'js-yaml';\nimport type { ExploreResult } from './explore.js';\n\n/**\n * Generate adapter YAML from explore results.\n * Uses field role mapping to auto-detect column names.\n */\nexport function synthesizeAdapter(result: ExploreResult, goal?: string): string {\n const topEndpoints = result.endpoints.filter((e) => e.score > 0).slice(0, 3);\n if (topEndpoints.length === 0) {\n return `# No API endpoints discovered for ${result.site}\\n# Try using: lobster agent \"your task\" --url https://${result.domain}`;\n }\n\n const endpoint = topEndpoints[0];\n\n // Infer command name from goal or endpoint URL\n let name = goal || 'data';\n if (!goal) {\n const path = new URL(endpoint.url).pathname.toLowerCase();\n if (/search|query/.test(path)) name = 'search';\n else if (/hot|trending|popular/.test(path)) name = 'hot';\n else if (/feed|timeline|home/.test(path)) name = 'feed';\n else if (/top|best|rank/.test(path)) name = 'top';\n }\n\n // Build args from query params\n const args: Record<string, unknown> = {\n limit: { type: 'int', default: 20 },\n };\n for (const param of endpoint.queryParams) {\n if (/search|keyword|query|q/.test(param)) {\n args[param] = { required: true, positional: true, help: 'Search query' };\n } else if (/page|offset|cursor/.test(param)) {\n args[param] = { type: 'int', default: 1 };\n } else if (/limit|count|num|size/.test(param)) {\n // Already have limit\n }\n }\n\n // Build columns from field roles\n const columns: string[] = [];\n const mapTemplate: Record<string, string> = {};\n\n for (const [field, role] of Object.entries(endpoint.fieldRoles)) {\n if (['title', 'url', 'author', 'score', 'time', 'description'].includes(role)) {\n columns.push(role);\n mapTemplate[role] = `\\${{ item.${field} }}`;\n }\n }\n\n // Fallback columns if no roles detected\n if (columns.length === 0) {\n for (const field of endpoint.fields.slice(0, 5)) {\n columns.push(field);\n mapTemplate[field] = `\\${{ item.${field} }}`;\n }\n }\n\n // Build pipeline\n const pipeline: Record<string, unknown>[] = [];\n\n if (result.strategy === 'public' && !endpoint.authIndicators.length) {\n pipeline.push({ fetch: endpoint.url });\n } else {\n // Browser-based fetch with credentials\n pipeline.push({ navigate: `https://${result.domain}` });\n pipeline.push({\n evaluate: `(async () => { const r = await fetch(${JSON.stringify(endpoint.url)}, {credentials:'include'}); return r.json(); })()`,\n });\n }\n\n // Add select step if items are nested\n if (endpoint.hasItems && endpoint.itemCount > 0) {\n // Try to find the array path — check common patterns\n for (const path of ['data', 'results', 'items', 'list', 'data.items', 'data.list']) {\n pipeline.push({ select: path });\n break;\n }\n }\n\n if (Object.keys(mapTemplate).length > 0) {\n pipeline.push({ map: mapTemplate });\n }\n\n pipeline.push({ limit: '${{ args.limit }}' });\n\n const adapter = {\n site: result.site,\n name,\n description: `${name} from ${result.domain}`,\n domain: result.domain,\n strategy: result.strategy,\n browser: result.strategy !== 'public',\n args,\n pipeline,\n columns: columns.length > 0 ? columns : undefined,\n };\n\n return yaml.dump(adapter, { indent: 2, lineWidth: 120 });\n}\n"],"mappings":"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","names":[]}