firecrawl-cli 1.9.7 → 1.9.8
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.
- package/dist/commands/browser.d.ts.map +1 -1
- package/dist/commands/browser.js +5 -3
- package/dist/commands/browser.js.map +1 -1
- package/dist/commands/experimental/index.d.ts.map +1 -1
- package/dist/commands/experimental/index.js +45 -4
- package/dist/commands/experimental/index.js.map +1 -1
- package/dist/commands/experimental/workflows/company-directories.d.ts +11 -0
- package/dist/commands/experimental/workflows/company-directories.d.ts.map +1 -0
- package/dist/commands/experimental/workflows/company-directories.js +245 -0
- package/dist/commands/experimental/workflows/company-directories.js.map +1 -0
- package/dist/commands/experimental/workflows/competitive-intel.d.ts +11 -0
- package/dist/commands/experimental/workflows/competitive-intel.d.ts.map +1 -0
- package/dist/commands/experimental/workflows/competitive-intel.js +226 -0
- package/dist/commands/experimental/workflows/competitive-intel.js.map +1 -0
- package/dist/commands/experimental/workflows/dashboard-reporting.d.ts +11 -0
- package/dist/commands/experimental/workflows/dashboard-reporting.d.ts.map +1 -0
- package/dist/commands/experimental/workflows/dashboard-reporting.js +254 -0
- package/dist/commands/experimental/workflows/dashboard-reporting.js.map +1 -0
- package/dist/commands/experimental/workflows/knowledge-ingest.d.ts +12 -0
- package/dist/commands/experimental/workflows/knowledge-ingest.d.ts.map +1 -0
- package/dist/commands/experimental/workflows/knowledge-ingest.js +251 -0
- package/dist/commands/experimental/workflows/knowledge-ingest.js.map +1 -0
- package/dist/commands/experimental/workflows/lead-gen.d.ts +11 -0
- package/dist/commands/experimental/workflows/lead-gen.d.ts.map +1 -0
- package/dist/commands/experimental/workflows/lead-gen.js +257 -0
- package/dist/commands/experimental/workflows/lead-gen.js.map +1 -0
- package/dist/commands/experimental/workflows/market-research.d.ts +11 -0
- package/dist/commands/experimental/workflows/market-research.d.ts.map +1 -0
- package/dist/commands/experimental/workflows/market-research.js +254 -0
- package/dist/commands/experimental/workflows/market-research.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Workflow: Knowledge Base Ingestion
|
|
4
|
+
*
|
|
5
|
+
* Navigates auth-gated documentation portals using saved browser profiles,
|
|
6
|
+
* paginates through articles and sections, and extracts everything into
|
|
7
|
+
* structured JSON. Built for portals that require login, have pagination,
|
|
8
|
+
* or use JS-heavy rendering that static scraping can't handle.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.register = register;
|
|
45
|
+
const backends_1 = require("../backends");
|
|
46
|
+
const shared_1 = require("../shared");
|
|
47
|
+
// ─── Input gathering ────────────────────────────────────────────────────────
|
|
48
|
+
async function gatherInputs(prefill) {
|
|
49
|
+
if (prefill?.url) {
|
|
50
|
+
return {
|
|
51
|
+
url: prefill.url,
|
|
52
|
+
profile: '',
|
|
53
|
+
format: 'json',
|
|
54
|
+
maxPages: '100',
|
|
55
|
+
context: '',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const { input, select } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
|
|
59
|
+
const url = await input({
|
|
60
|
+
message: 'URL of the docs portal or knowledge base?',
|
|
61
|
+
validate: (0, shared_1.validateRequired)('URL'),
|
|
62
|
+
});
|
|
63
|
+
const profile = await input({
|
|
64
|
+
message: 'Browser profile for auth? (leave blank for public/anonymous access)',
|
|
65
|
+
default: '',
|
|
66
|
+
});
|
|
67
|
+
const format = await select({
|
|
68
|
+
message: 'Output format?',
|
|
69
|
+
choices: [
|
|
70
|
+
{
|
|
71
|
+
name: 'Structured JSON (articles with metadata)',
|
|
72
|
+
value: 'json',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'Markdown files (one per article, .firecrawl/ convention)',
|
|
76
|
+
value: 'markdown',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'Single merged file (all content in one document)',
|
|
80
|
+
value: 'merged',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
const maxPages = await input({
|
|
85
|
+
message: 'Max pages to extract?',
|
|
86
|
+
default: '100',
|
|
87
|
+
});
|
|
88
|
+
const context = await input({
|
|
89
|
+
message: 'Any specific sections or topics to focus on? (leave blank for everything)',
|
|
90
|
+
default: '',
|
|
91
|
+
});
|
|
92
|
+
return { url, profile, format, maxPages, context };
|
|
93
|
+
}
|
|
94
|
+
// ─── System prompt ──────────────────────────────────────────────────────────
|
|
95
|
+
function buildSystemPrompt(opts) {
|
|
96
|
+
const profileBlock = opts.profile
|
|
97
|
+
? `\n### Authentication\n\nUse the saved browser profile \`${opts.profile}\` to access auth-gated content:\n\`\`\`bash\nfirecrawl browser "open <url>" --profile ${opts.profile}\n\`\`\`\nAfter the first \`open\` with \`--profile\`, subsequent browser commands don't need the flag.`
|
|
98
|
+
: '';
|
|
99
|
+
const outputInstructions = {
|
|
100
|
+
json: `Save results to \`knowledge-base.json\` in the current directory. Tell the user the file path when done.
|
|
101
|
+
|
|
102
|
+
Use this schema:
|
|
103
|
+
\`\`\`json
|
|
104
|
+
{
|
|
105
|
+
"source": "string (portal name)",
|
|
106
|
+
"url": "string (base URL)",
|
|
107
|
+
"extractedAt": "ISO-8601",
|
|
108
|
+
"totalArticles": 0,
|
|
109
|
+
"sections": [
|
|
110
|
+
{
|
|
111
|
+
"name": "string",
|
|
112
|
+
"articles": [
|
|
113
|
+
{
|
|
114
|
+
"title": "string",
|
|
115
|
+
"url": "string",
|
|
116
|
+
"section": "string",
|
|
117
|
+
"content": "string (full markdown content)",
|
|
118
|
+
"metadata": {
|
|
119
|
+
"lastUpdated": "string",
|
|
120
|
+
"author": "string",
|
|
121
|
+
"tags": ["string"]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
\`\`\``,
|
|
129
|
+
markdown: `Save each article as a separate markdown file following the .firecrawl/ convention:
|
|
130
|
+
\`\`\`
|
|
131
|
+
.firecrawl/<hostname>/<path>/index.md
|
|
132
|
+
\`\`\`
|
|
133
|
+
|
|
134
|
+
Each file should have frontmatter:
|
|
135
|
+
\`\`\`yaml
|
|
136
|
+
---
|
|
137
|
+
title: "Article Title"
|
|
138
|
+
url: "https://..."
|
|
139
|
+
section: "Section Name"
|
|
140
|
+
lastUpdated: "date if available"
|
|
141
|
+
---
|
|
142
|
+
\`\`\`
|
|
143
|
+
|
|
144
|
+
Also create \`.firecrawl/index.md\` as a table of contents. Tell the user the output path when done.`,
|
|
145
|
+
merged: `Save all content to a single \`knowledge-base.md\` file in the current directory with a table of contents at the top. Each article should be a section with its title as a heading. Tell the user the file path when done.`,
|
|
146
|
+
};
|
|
147
|
+
return `You are a knowledge base ingestion agent powered by Firecrawl. You use a real cloud browser to navigate documentation portals -- including auth-gated ones -- paginate through all articles, and extract content into structured formats.
|
|
148
|
+
|
|
149
|
+
## STEP 1: Launch Browser and Open Live View
|
|
150
|
+
|
|
151
|
+
Before anything else, launch a browser session so the user can watch:
|
|
152
|
+
|
|
153
|
+
\`\`\`bash
|
|
154
|
+
firecrawl browser launch-session --json
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
Extract the \`interactiveLiveViewUrl\` from the JSON output and open it (NOT the regular \`liveViewUrl\` -- the interactive one lets the user click and interact):
|
|
158
|
+
|
|
159
|
+
\`\`\`bash
|
|
160
|
+
open "<interactiveLiveViewUrl>" # macOS
|
|
161
|
+
xdg-open "<interactiveLiveViewUrl>" # Linux
|
|
162
|
+
\`\`\`
|
|
163
|
+
|
|
164
|
+
If the \`open\` command fails, print the URL clearly.
|
|
165
|
+
${profileBlock}
|
|
166
|
+
|
|
167
|
+
## STEP 2: Map the Portal Structure
|
|
168
|
+
|
|
169
|
+
1. Open the portal's main page / table of contents / sidebar
|
|
170
|
+
2. Snapshot to see the navigation structure
|
|
171
|
+
3. Identify all sections, categories, or sidebar nav items
|
|
172
|
+
4. Build a list of all article URLs to visit
|
|
173
|
+
|
|
174
|
+
If the portal has a sitemap or API docs index, use that. Otherwise, click through sidebar/nav items to discover pages.
|
|
175
|
+
|
|
176
|
+
\`\`\`bash
|
|
177
|
+
firecrawl browser "open <url>"
|
|
178
|
+
firecrawl browser "snapshot"
|
|
179
|
+
firecrawl browser "scrape"
|
|
180
|
+
\`\`\`
|
|
181
|
+
|
|
182
|
+
Also try \`firecrawl map <url>\` to discover URLs programmatically -- combine with browser navigation for auth-gated content.
|
|
183
|
+
|
|
184
|
+
## STEP 3: Extract Articles
|
|
185
|
+
|
|
186
|
+
For each article/page:
|
|
187
|
+
|
|
188
|
+
1. **Navigate** to the article URL
|
|
189
|
+
2. **Wait** for content to fully render (some portals are JS-heavy)
|
|
190
|
+
3. **Scrape** the full page content as markdown
|
|
191
|
+
4. **Extract metadata** -- title, section, last updated date, author, tags
|
|
192
|
+
5. **Handle pagination** within articles (multi-page docs, "Next" buttons)
|
|
193
|
+
6. **Navigate** to the next article
|
|
194
|
+
|
|
195
|
+
### Pagination strategies:
|
|
196
|
+
- **Sidebar navigation**: Click through each sidebar item systematically
|
|
197
|
+
- **"Next article" links**: Follow sequential article links
|
|
198
|
+
- **Paginated lists**: Click page numbers or "Load More"
|
|
199
|
+
- **Infinite scroll**: Scroll down and snapshot to load more items
|
|
200
|
+
- **Search/filter**: If the portal has search, use it to find specific sections
|
|
201
|
+
|
|
202
|
+
### Browser commands:
|
|
203
|
+
\`\`\`bash
|
|
204
|
+
firecrawl browser "open <url>"
|
|
205
|
+
firecrawl browser "snapshot"
|
|
206
|
+
firecrawl browser "click @<ref>"
|
|
207
|
+
firecrawl browser "scroll down"
|
|
208
|
+
firecrawl browser "scrape"
|
|
209
|
+
\`\`\`
|
|
210
|
+
|
|
211
|
+
## Limits
|
|
212
|
+
|
|
213
|
+
Extract up to ${opts.maxPages} pages. Prioritize breadth (cover all sections) over depth (every sub-article) if you're approaching the limit.
|
|
214
|
+
|
|
215
|
+
## Output Format
|
|
216
|
+
|
|
217
|
+
${outputInstructions[opts.format]}
|
|
218
|
+
|
|
219
|
+
## Quality Guidelines
|
|
220
|
+
|
|
221
|
+
- Preserve code examples, tables, and formatting
|
|
222
|
+
- Strip navigation chrome, headers, footers -- extract only article content
|
|
223
|
+
- Note any pages that failed to load or were access-restricted
|
|
224
|
+
- Track progress: print "Extracted X/Y articles..." periodically
|
|
225
|
+
|
|
226
|
+
Do everything sequentially. Start immediately.`;
|
|
227
|
+
}
|
|
228
|
+
// ─── Command registration ───────────────────────────────────────────────────
|
|
229
|
+
function register(parentCmd, backend) {
|
|
230
|
+
const config = backends_1.BACKENDS[backend];
|
|
231
|
+
parentCmd
|
|
232
|
+
.command('knowledge-ingest')
|
|
233
|
+
.description('Extract auth-gated docs portals into structured JSON or markdown')
|
|
234
|
+
.argument('[url]', 'URL of the docs portal or knowledge base')
|
|
235
|
+
.option('-y, --yes', 'Auto-approve all tool permissions')
|
|
236
|
+
.action(async (url, options) => {
|
|
237
|
+
const inputs = await gatherInputs(url ? { url } : undefined);
|
|
238
|
+
const parts = [`Ingest knowledge base from: ${inputs.url}`];
|
|
239
|
+
if (inputs.context)
|
|
240
|
+
parts.push(`Focus on: ${inputs.context}`);
|
|
241
|
+
const userMessage = parts.join('. ') + '.';
|
|
242
|
+
const skipPermissions = true;
|
|
243
|
+
console.log(`\nLaunching ${config.displayName}...\n`);
|
|
244
|
+
(0, backends_1.launchAgent)(backend, buildSystemPrompt({
|
|
245
|
+
profile: inputs.profile,
|
|
246
|
+
format: inputs.format,
|
|
247
|
+
maxPages: inputs.maxPages,
|
|
248
|
+
}), userMessage, skipPermissions);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=knowledge-ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-ingest.js","sourceRoot":"","sources":["../../../../src/commands/experimental/workflows/knowledge-ingest.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0NH,4BAgCC;AAvPD,0CAAkE;AAClE,sCAA6C;AAY7C,+EAA+E;AAE/E,KAAK,UAAU,YAAY,CAAC,OAA0B;IACpD,IAAI,OAAO,EAAE,GAAG,EAAE,CAAC;QACjB,OAAO;YACL,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IAE5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC;QACtB,OAAO,EAAE,2CAA2C;QACpD,QAAQ,EAAE,IAAA,yBAAgB,EAAC,KAAK,CAAC;KAClC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC;QAC1B,OAAO,EACL,qEAAqE;QACvE,OAAO,EAAE,EAAE;KACZ,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC;QAC1B,OAAO,EAAE,gBAAgB;QACzB,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,0CAA0C;gBAChD,KAAK,EAAE,MAAM;aACd;YACD;gBACE,IAAI,EAAE,0DAA0D;gBAChE,KAAK,EAAE,UAAU;aAClB;YACD;gBACE,IAAI,EAAE,kDAAkD;gBACxD,KAAK,EAAE,QAAQ;aAChB;SACF;KACF,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC;QAC3B,OAAO,EAAE,uBAAuB;QAChC,OAAO,EAAE,KAAK;KACf,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC;QAC1B,OAAO,EACL,2EAA2E;QAC7E,OAAO,EAAE,EAAE;KACZ,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AACrD,CAAC;AAED,+EAA+E;AAE/E,SAAS,iBAAiB,CAAC,IAI1B;IACC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO;QAC/B,CAAC,CAAC,2DAA2D,IAAI,CAAC,OAAO,0FAA0F,IAAI,CAAC,OAAO,yGAAyG;QACxR,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,kBAAkB,GAA2B;QACjD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BH;QACH,QAAQ,EAAE;;;;;;;;;;;;;;;qGAeuF;QACjG,MAAM,EAAE,4NAA4N;KACrO,CAAC;IAEF,OAAO;;;;;;;;;;;;;;;;;;EAkBP,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAgDE,IAAI,CAAC,QAAQ;;;;EAI3B,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC;;;;;;;;;+CASc,CAAC;AAChD,CAAC;AAED,+EAA+E;AAE/E,SAAgB,QAAQ,CAAC,SAAkB,EAAE,OAAgB;IAC3D,MAAM,MAAM,GAAG,mBAAQ,CAAC,OAAO,CAAC,CAAC;IAEjC,SAAS;SACN,OAAO,CAAC,kBAAkB,CAAC;SAC3B,WAAW,CACV,kEAAkE,CACnE;SACA,QAAQ,CAAC,OAAO,EAAE,0CAA0C,CAAC;SAC7D,MAAM,CAAC,WAAW,EAAE,mCAAmC,CAAC;SACxD,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAE7D,MAAM,KAAK,GAAG,CAAC,+BAA+B,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5D,IAAI,MAAM,CAAC,OAAO;YAChB,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;QAE3C,MAAM,eAAe,GAAG,IAAI,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,CAAC,WAAW,OAAO,CAAC,CAAC;QAEtD,IAAA,sBAAW,EACT,OAAO,EACP,iBAAiB,CAAC;YAChB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC,EACF,WAAW,EACX,eAAe,CAChB,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow: Lead Generation
|
|
3
|
+
*
|
|
4
|
+
* Uses a cloud browser to fill search forms on prospect databases (Apollo,
|
|
5
|
+
* LinkedIn Sales Nav, ZoomInfo, etc.), apply filters, paginate through
|
|
6
|
+
* results, and extract contact details at scale into structured formats.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { type Backend } from '../backends';
|
|
10
|
+
export declare function register(parentCmd: Command, backend: Backend): void;
|
|
11
|
+
//# sourceMappingURL=lead-gen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-gen.d.ts","sourceRoot":"","sources":["../../../../src/commands/experimental/workflows/lead-gen.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,OAAO,EAAyB,MAAM,aAAa,CAAC;AAoOlE,wBAAgB,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAyCnE"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Workflow: Lead Generation
|
|
4
|
+
*
|
|
5
|
+
* Uses a cloud browser to fill search forms on prospect databases (Apollo,
|
|
6
|
+
* LinkedIn Sales Nav, ZoomInfo, etc.), apply filters, paginate through
|
|
7
|
+
* results, and extract contact details at scale into structured formats.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.register = register;
|
|
44
|
+
const backends_1 = require("../backends");
|
|
45
|
+
const shared_1 = require("../shared");
|
|
46
|
+
// ─── Input gathering ────────────────────────────────────────────────────────
|
|
47
|
+
async function gatherInputs(prefill) {
|
|
48
|
+
if (prefill?.target) {
|
|
49
|
+
return {
|
|
50
|
+
target: prefill.target,
|
|
51
|
+
source: 'auto',
|
|
52
|
+
profile: '',
|
|
53
|
+
maxLeads: '50',
|
|
54
|
+
output: 'json',
|
|
55
|
+
context: '',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const { input, select } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
|
|
59
|
+
const target = await input({
|
|
60
|
+
message: 'Describe your ideal prospects (e.g., "CTOs at Series B fintech startups in NYC")',
|
|
61
|
+
validate: (0, shared_1.validateRequired)('Target description'),
|
|
62
|
+
});
|
|
63
|
+
const source = await select({
|
|
64
|
+
message: 'Where should the agent search?',
|
|
65
|
+
choices: [
|
|
66
|
+
{ name: 'Auto-detect best sources', value: 'auto' },
|
|
67
|
+
{ name: 'Apollo.io', value: 'apollo' },
|
|
68
|
+
{ name: 'LinkedIn (requires profile)', value: 'linkedin' },
|
|
69
|
+
{ name: 'Crunchbase', value: 'crunchbase' },
|
|
70
|
+
{ name: 'Custom URL / database', value: 'custom' },
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
let sourceValue = source;
|
|
74
|
+
if (source === 'custom') {
|
|
75
|
+
sourceValue = await input({
|
|
76
|
+
message: 'Enter the database URL:',
|
|
77
|
+
validate: (0, shared_1.validateRequired)('URL'),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const profile = await input({
|
|
81
|
+
message: 'Browser profile for auth? (leave blank for anonymous access)',
|
|
82
|
+
default: '',
|
|
83
|
+
});
|
|
84
|
+
const maxLeads = await input({
|
|
85
|
+
message: 'How many leads to extract?',
|
|
86
|
+
default: '50',
|
|
87
|
+
});
|
|
88
|
+
const output = await select({
|
|
89
|
+
message: 'Output format?',
|
|
90
|
+
choices: [
|
|
91
|
+
{ name: 'JSON (structured, CRM-ready)', value: 'json' },
|
|
92
|
+
{ name: 'CSV (spreadsheet/CRM import)', value: 'csv' },
|
|
93
|
+
{ name: 'Print to terminal', value: 'terminal' },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
const context = await input({
|
|
97
|
+
message: 'Any other criteria? (e.g., "must have email", "exclude consultants")',
|
|
98
|
+
default: '',
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
target,
|
|
102
|
+
source: sourceValue,
|
|
103
|
+
profile,
|
|
104
|
+
maxLeads,
|
|
105
|
+
output,
|
|
106
|
+
context,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// ─── System prompt ──────────────────────────────────────────────────────────
|
|
110
|
+
function buildSystemPrompt(opts) {
|
|
111
|
+
const sourceUrls = {
|
|
112
|
+
apollo: 'https://app.apollo.io',
|
|
113
|
+
linkedin: 'https://www.linkedin.com/sales',
|
|
114
|
+
crunchbase: 'https://www.crunchbase.com/discover/people',
|
|
115
|
+
};
|
|
116
|
+
const sourceHint = opts.source === 'auto'
|
|
117
|
+
? 'Start by searching the web to find the best prospect databases for this target audience. Try Apollo.io, LinkedIn, Crunchbase, industry directories, or any relevant databases.'
|
|
118
|
+
: `Start at: ${sourceUrls[opts.source] || opts.source}`;
|
|
119
|
+
const profileBlock = opts.profile
|
|
120
|
+
? `\n### Authentication\n\nUse the saved browser profile \`${opts.profile}\`:\n\`\`\`bash\nfirecrawl browser "open <url>" --profile ${opts.profile}\n\`\`\`\nAfter the first \`open\` with \`--profile\`, subsequent browser commands don't need the flag.`
|
|
121
|
+
: '';
|
|
122
|
+
const outputInstructions = {
|
|
123
|
+
terminal: 'Print the lead list to the terminal as a formatted markdown table.',
|
|
124
|
+
json: `Save results to \`leads.json\` in the current directory. Tell the user the file path when done.
|
|
125
|
+
|
|
126
|
+
Use this schema:
|
|
127
|
+
\`\`\`json
|
|
128
|
+
{
|
|
129
|
+
"query": "string (target description)",
|
|
130
|
+
"source": "string (database used)",
|
|
131
|
+
"extractedAt": "ISO-8601",
|
|
132
|
+
"totalLeads": 0,
|
|
133
|
+
"leads": [
|
|
134
|
+
{
|
|
135
|
+
"name": "string",
|
|
136
|
+
"title": "string",
|
|
137
|
+
"company": "string",
|
|
138
|
+
"companyUrl": "string",
|
|
139
|
+
"location": "string",
|
|
140
|
+
"email": "string (if available)",
|
|
141
|
+
"linkedin": "string (if available)",
|
|
142
|
+
"phone": "string (if available)",
|
|
143
|
+
"industry": "string",
|
|
144
|
+
"companySize": "string",
|
|
145
|
+
"fundingStage": "string",
|
|
146
|
+
"notes": "string",
|
|
147
|
+
"profileUrl": "string (source listing URL)"
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
\`\`\``,
|
|
152
|
+
csv: `Save results to \`leads.csv\` in the current directory with columns: name, title, company, companyUrl, location, email, linkedin, phone, industry, companySize, fundingStage, notes, profileUrl. Tell the user the file path when done.`,
|
|
153
|
+
};
|
|
154
|
+
return `You are a lead generation agent powered by Firecrawl. You use a real cloud browser to search prospect databases, fill in filters, paginate through results, and extract structured contact data.
|
|
155
|
+
|
|
156
|
+
## STEP 1: Launch Browser and Open Live View
|
|
157
|
+
|
|
158
|
+
Before anything else, launch a browser session so the user can watch:
|
|
159
|
+
|
|
160
|
+
\`\`\`bash
|
|
161
|
+
firecrawl browser launch-session --json
|
|
162
|
+
\`\`\`
|
|
163
|
+
|
|
164
|
+
Extract the \`interactiveLiveViewUrl\` from the JSON output and open it (NOT the regular \`liveViewUrl\` -- the interactive one lets the user click and interact):
|
|
165
|
+
|
|
166
|
+
\`\`\`bash
|
|
167
|
+
open "<interactiveLiveViewUrl>" # macOS
|
|
168
|
+
xdg-open "<interactiveLiveViewUrl>" # Linux
|
|
169
|
+
\`\`\`
|
|
170
|
+
|
|
171
|
+
If the \`open\` command fails, print the URL clearly.
|
|
172
|
+
${profileBlock}
|
|
173
|
+
|
|
174
|
+
## STEP 2: Navigate to Prospect Database
|
|
175
|
+
|
|
176
|
+
${sourceHint}
|
|
177
|
+
|
|
178
|
+
## STEP 3: Apply Filters and Search
|
|
179
|
+
|
|
180
|
+
1. **Snapshot** the page to see available search/filter controls
|
|
181
|
+
2. **Fill search forms** -- type the target criteria into search bars
|
|
182
|
+
3. **Apply filters** -- click dropdowns, checkboxes, sliders for:
|
|
183
|
+
- Job title / role
|
|
184
|
+
- Company size
|
|
185
|
+
- Industry / sector
|
|
186
|
+
- Location / geography
|
|
187
|
+
- Funding stage
|
|
188
|
+
- Technologies used
|
|
189
|
+
4. **Execute the search** -- click "Search" or wait for auto-results
|
|
190
|
+
|
|
191
|
+
### Browser commands:
|
|
192
|
+
\`\`\`bash
|
|
193
|
+
firecrawl browser "open <url>"
|
|
194
|
+
firecrawl browser "snapshot"
|
|
195
|
+
firecrawl browser "click @<ref>"
|
|
196
|
+
firecrawl browser "type @<ref> <text>"
|
|
197
|
+
firecrawl browser "scroll down"
|
|
198
|
+
firecrawl browser "scrape"
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
## STEP 4: Extract and Paginate
|
|
202
|
+
|
|
203
|
+
After filtering:
|
|
204
|
+
|
|
205
|
+
1. **Snapshot** the results to see lead listings
|
|
206
|
+
2. **Extract** visible data from each lead (name, title, company, etc.)
|
|
207
|
+
3. **Click into profiles** if the listing doesn't have enough detail
|
|
208
|
+
4. **Navigate back** to the results list
|
|
209
|
+
5. **Paginate** -- click "Next", page numbers, or scroll for more
|
|
210
|
+
6. Repeat until you've collected ~${opts.maxLeads} leads or exhausted results
|
|
211
|
+
|
|
212
|
+
### Tips:
|
|
213
|
+
- Some databases show partial info (e.g., masked emails) -- extract what's visible
|
|
214
|
+
- If you get rate-limited or CAPTCHAed, note it and move to the next result
|
|
215
|
+
- Track progress: print "Extracted X/${opts.maxLeads} leads..." periodically
|
|
216
|
+
- Deduplicate leads that appear in multiple pages
|
|
217
|
+
|
|
218
|
+
## Output Format
|
|
219
|
+
|
|
220
|
+
${outputInstructions[opts.output]}
|
|
221
|
+
|
|
222
|
+
## Important
|
|
223
|
+
|
|
224
|
+
- Only extract publicly visible or legitimately accessible data
|
|
225
|
+
- Note any fields that were partially masked or unavailable
|
|
226
|
+
- If a source requires paid access for full data, note what's behind the paywall
|
|
227
|
+
|
|
228
|
+
Do everything sequentially. Start immediately.`;
|
|
229
|
+
}
|
|
230
|
+
// ─── Command registration ───────────────────────────────────────────────────
|
|
231
|
+
function register(parentCmd, backend) {
|
|
232
|
+
const config = backends_1.BACKENDS[backend];
|
|
233
|
+
parentCmd
|
|
234
|
+
.command('lead-gen')
|
|
235
|
+
.description('Extract prospect contact details from databases at scale via browser')
|
|
236
|
+
.argument('[target...]', 'Describe ideal prospects (e.g., "CTOs at B2B SaaS startups")')
|
|
237
|
+
.option('-y, --yes', 'Auto-approve all tool permissions')
|
|
238
|
+
.action(async (targetParts, options) => {
|
|
239
|
+
const prefillTarget = targetParts.length > 0 ? targetParts.join(' ') : undefined;
|
|
240
|
+
const inputs = await gatherInputs(prefillTarget ? { target: prefillTarget } : undefined);
|
|
241
|
+
const parts = [`Find leads matching: ${inputs.target}`];
|
|
242
|
+
if (inputs.source !== 'auto')
|
|
243
|
+
parts.push(`Search on: ${inputs.source}`);
|
|
244
|
+
if (inputs.context)
|
|
245
|
+
parts.push(inputs.context);
|
|
246
|
+
const userMessage = parts.join('. ') + '.';
|
|
247
|
+
const skipPermissions = true;
|
|
248
|
+
console.log(`\nLaunching ${config.displayName}...\n`);
|
|
249
|
+
(0, backends_1.launchAgent)(backend, buildSystemPrompt({
|
|
250
|
+
source: inputs.source,
|
|
251
|
+
profile: inputs.profile,
|
|
252
|
+
maxLeads: inputs.maxLeads,
|
|
253
|
+
output: inputs.output,
|
|
254
|
+
}), userMessage, skipPermissions);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=lead-gen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-gen.js","sourceRoot":"","sources":["../../../../src/commands/experimental/workflows/lead-gen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow: Market Research
|
|
3
|
+
*
|
|
4
|
+
* Browses financial portals, earnings pages, and market data sites using a
|
|
5
|
+
* cloud browser. Interacts with charts, filters, and dropdowns to extract
|
|
6
|
+
* earnings data, market metrics, and financial comparisons across companies.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { type Backend } from '../backends';
|
|
10
|
+
export declare function register(parentCmd: Command, backend: Backend): void;
|
|
11
|
+
//# sourceMappingURL=market-research.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"market-research.d.ts","sourceRoot":"","sources":["../../../../src/commands/experimental/workflows/market-research.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,OAAO,EAAyB,MAAM,aAAa,CAAC;AA8NlE,wBAAgB,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAoCnE"}
|