mcp-scraper 0.2.8 → 0.2.10

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 (54) hide show
  1. package/README.md +40 -3
  2. package/dist/bin/api-server.cjs +2073 -506
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/browser-agent-stdio-server.cjs +1 -1
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  7. package/dist/bin/browser-agent-stdio-server.js +2 -2
  8. package/dist/bin/mcp-scraper-cli.cjs +1276 -0
  9. package/dist/bin/mcp-scraper-cli.cjs.map +1 -0
  10. package/dist/bin/mcp-scraper-cli.d.cts +1 -0
  11. package/dist/bin/mcp-scraper-cli.d.ts +1 -0
  12. package/dist/bin/mcp-scraper-cli.js +476 -0
  13. package/dist/bin/mcp-scraper-cli.js.map +1 -0
  14. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +3 -3
  15. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  16. package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
  17. package/dist/bin/mcp-scraper-install.cjs +4 -1
  18. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  19. package/dist/bin/mcp-scraper-install.js +4 -1
  20. package/dist/bin/mcp-scraper-install.js.map +1 -1
  21. package/dist/bin/mcp-stdio-server.cjs +3 -3
  22. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  23. package/dist/bin/mcp-stdio-server.js +2 -2
  24. package/dist/{chunk-5HMOPP76.js → chunk-2OHP6WWF.js} +2 -2
  25. package/dist/{chunk-D4CJBZBY.js → chunk-7GCCOT3M.js} +372 -1
  26. package/dist/chunk-7GCCOT3M.js.map +1 -0
  27. package/dist/chunk-L6IS63WS.js +869 -0
  28. package/dist/chunk-L6IS63WS.js.map +1 -0
  29. package/dist/{chunk-IQOCZGJJ.js → chunk-ROS67BNV.js} +5 -5
  30. package/dist/chunk-WKWFO45O.js +7 -0
  31. package/dist/chunk-WKWFO45O.js.map +1 -0
  32. package/dist/{chunk-6NEXSNSA.js → chunk-YAKKRQRH.js} +4 -4
  33. package/dist/chunk-YAKKRQRH.js.map +1 -0
  34. package/dist/{db-YWCNHBLH.js → db-BVHYI57K.js} +38 -2
  35. package/dist/{server-BTTDFPSQ.js → server-USOIPC2I.js} +585 -266
  36. package/dist/server-USOIPC2I.js.map +1 -0
  37. package/dist/{worker-5O44YBF4.js → worker-TDJQ6TH3.js} +8 -8
  38. package/docs/specs/agent-ready-seo-packet-spec.md +237 -0
  39. package/docs/specs/cli-agent-wiring-spec.md +203 -0
  40. package/docs/specs/deferred-work-spec.md +12 -0
  41. package/docs/specs/local-competitive-audit-spec.md +312 -0
  42. package/docs/specs/scheduled-workflows-api-spec.md +304 -0
  43. package/docs/specs/seo-cli-growth-roadmap-spec.md +179 -0
  44. package/docs/specs/seo-workflow-runner-and-reports-spec.md +241 -0
  45. package/package.json +20 -19
  46. package/dist/chunk-6NEXSNSA.js.map +0 -1
  47. package/dist/chunk-D4CJBZBY.js.map +0 -1
  48. package/dist/chunk-I26QN7WQ.js +0 -7
  49. package/dist/chunk-I26QN7WQ.js.map +0 -1
  50. package/dist/server-BTTDFPSQ.js.map +0 -1
  51. /package/dist/{chunk-5HMOPP76.js.map → chunk-2OHP6WWF.js.map} +0 -0
  52. /package/dist/{chunk-IQOCZGJJ.js.map → chunk-ROS67BNV.js.map} +0 -0
  53. /package/dist/{db-YWCNHBLH.js.map → db-BVHYI57K.js.map} +0 -0
  54. /package/dist/{worker-5O44YBF4.js.map → worker-TDJQ6TH3.js.map} +0 -0
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ findWorkflowReport,
4
+ listWorkflowDefinitions,
5
+ listWorkflowReports,
6
+ openWorkflowReport,
7
+ runWorkflow,
8
+ workflowOutputBaseDir
9
+ } from "../chunk-L6IS63WS.js";
10
+ import {
11
+ PACKAGE_VERSION
12
+ } from "../chunk-WKWFO45O.js";
13
+
14
+ // src/cli/human-cli.ts
15
+ import { Command } from "commander";
16
+ import { mkdir as mkdir2, writeFile } from "fs/promises";
17
+ import { basename, join as join2 } from "path";
18
+
19
+ // src/cli/agent-config.ts
20
+ function apiKeyValue(options) {
21
+ return options.apiKey?.trim() || "sk_live_your_key";
22
+ }
23
+ function packageSpec(options) {
24
+ return options.packageSpec?.trim() || "mcp-scraper@latest";
25
+ }
26
+ function combinedNpxArgs(options = {}) {
27
+ return ["-y", "-p", packageSpec(options), "mcp-scraper-combined"];
28
+ }
29
+ function renderCodexConfig(options = {}) {
30
+ return [
31
+ "[mcp_servers.mcp-scraper]",
32
+ 'command = "npx"',
33
+ `args = ${JSON.stringify(combinedNpxArgs(options))}`,
34
+ `env = { MCP_SCRAPER_API_KEY = "${apiKeyValue(options)}" }`
35
+ ].join("\n");
36
+ }
37
+ function renderClaudeCommand(options = {}) {
38
+ return [
39
+ "claude mcp add mcp-scraper --scope user",
40
+ ` --env MCP_SCRAPER_API_KEY=${apiKeyValue(options)}`,
41
+ ` -- npx ${combinedNpxArgs(options).join(" ")}`
42
+ ].join(" \\\n");
43
+ }
44
+ function renderClaudeDesktopConfig(options = {}) {
45
+ return JSON.stringify({
46
+ mcpServers: {
47
+ "mcp-scraper": {
48
+ command: "npx",
49
+ args: combinedNpxArgs(options),
50
+ env: {
51
+ MCP_SCRAPER_API_KEY: apiKeyValue(options)
52
+ }
53
+ }
54
+ }
55
+ }, null, 2);
56
+ }
57
+ function renderAgentInstall(host, options = {}) {
58
+ const restart = "Restart the MCP client so it starts a fresh npx process.";
59
+ if (host === "codex") {
60
+ return [
61
+ "# Codex MCP config",
62
+ renderCodexConfig(options),
63
+ "",
64
+ restart
65
+ ].join("\n");
66
+ }
67
+ if (host === "claude") {
68
+ return [
69
+ "# Claude Code command",
70
+ renderClaudeCommand(options),
71
+ "",
72
+ restart
73
+ ].join("\n");
74
+ }
75
+ return [
76
+ "# Claude Desktop config",
77
+ renderClaudeDesktopConfig(options),
78
+ "",
79
+ "Desktop Extension: https://mcpscraper.dev/downloads/mcp-scraper.mcpb",
80
+ restart
81
+ ].join("\n");
82
+ }
83
+
84
+ // src/cli/doctor.ts
85
+ import { access, mkdir, readFile } from "fs/promises";
86
+ import { homedir } from "os";
87
+ import { join } from "path";
88
+ function status(ok, warn = false) {
89
+ if (ok) return "pass";
90
+ return warn ? "warn" : "fail";
91
+ }
92
+ function parseMajor(version) {
93
+ return Number(version.replace(/^v/, "").split(".")[0] ?? 0);
94
+ }
95
+ async function readKeyFile() {
96
+ const path = process.env.MCP_SCRAPER_KEY_PATH?.trim() || join(homedir(), ".mcp-scraper-key");
97
+ try {
98
+ const value = (await readFile(path, "utf8")).trim();
99
+ return value || null;
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ async function npmLatest(fetchImpl) {
105
+ try {
106
+ const res = await fetchImpl("https://registry.npmjs.org/mcp-scraper/latest", {
107
+ signal: AbortSignal.timeout(5e3)
108
+ });
109
+ if (!res.ok) return null;
110
+ const data = await res.json();
111
+ return data.version ?? null;
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ async function runDoctor(options = {}) {
117
+ const fetchImpl = options.fetchImpl ?? fetch;
118
+ const apiUrl = (options.apiUrl ?? process.env.MCP_SCRAPER_API_URL ?? "https://mcpscraper.dev").replace(/\/$/, "");
119
+ const outputDir = options.outputDir ?? process.env.MCP_SCRAPER_OUTPUT_DIR ?? join(homedir(), "Downloads", "mcp-scraper");
120
+ const configuredKey = options.apiKey?.trim() || process.env.MCP_SCRAPER_API_KEY?.trim() || await readKeyFile();
121
+ const latest = await npmLatest(fetchImpl);
122
+ const checks = [];
123
+ const nodeMajor = parseMajor(process.version);
124
+ checks.push({
125
+ id: "node",
126
+ label: "Node.js",
127
+ status: status(nodeMajor >= 20),
128
+ detail: process.version,
129
+ fix: nodeMajor >= 20 ? void 0 : "Install Node.js 20 or newer."
130
+ });
131
+ checks.push({
132
+ id: "package_version",
133
+ label: "Local package",
134
+ status: latest && latest !== PACKAGE_VERSION ? "warn" : "pass",
135
+ detail: latest ? `local ${PACKAGE_VERSION}, npm latest ${latest}` : `local ${PACKAGE_VERSION}, npm latest unavailable`,
136
+ fix: latest && latest !== PACKAGE_VERSION ? "Use mcp-scraper@latest and restart the MCP client." : void 0
137
+ });
138
+ checks.push({
139
+ id: "api_key",
140
+ label: "API key",
141
+ status: configuredKey ? "pass" : "warn",
142
+ detail: configuredKey ? "configured" : "not configured",
143
+ fix: configuredKey ? void 0 : "Set MCP_SCRAPER_API_KEY or pass --api-key."
144
+ });
145
+ try {
146
+ await mkdir(outputDir, { recursive: true });
147
+ await access(outputDir);
148
+ checks.push({ id: "output_dir", label: "Output directory", status: "pass", detail: outputDir });
149
+ } catch (err) {
150
+ checks.push({
151
+ id: "output_dir",
152
+ label: "Output directory",
153
+ status: "fail",
154
+ detail: outputDir,
155
+ fix: err instanceof Error ? err.message : "Create a writable output directory."
156
+ });
157
+ }
158
+ if (configuredKey) {
159
+ try {
160
+ const res = await fetchImpl(`${apiUrl}/me`, {
161
+ headers: { "x-api-key": configuredKey },
162
+ signal: AbortSignal.timeout(8e3)
163
+ });
164
+ checks.push({
165
+ id: "api_reachability",
166
+ label: "Hosted API",
167
+ status: res.ok ? "pass" : "fail",
168
+ detail: `${apiUrl}/me returned ${res.status}`,
169
+ fix: res.ok ? void 0 : "Verify the API key and account status."
170
+ });
171
+ } catch (err) {
172
+ checks.push({
173
+ id: "api_reachability",
174
+ label: "Hosted API",
175
+ status: "fail",
176
+ detail: err instanceof Error ? err.message : String(err),
177
+ fix: "Check network access and MCP_SCRAPER_API_URL."
178
+ });
179
+ }
180
+ } else {
181
+ checks.push({
182
+ id: "api_reachability",
183
+ label: "Hosted API",
184
+ status: "skip",
185
+ detail: "skipped because no API key is configured"
186
+ });
187
+ }
188
+ checks.push({
189
+ id: "mcp_config",
190
+ label: "MCP command",
191
+ status: "pass",
192
+ detail: `npx ${combinedNpxArgs().join(" ")}`,
193
+ fix: "Restart the MCP client after package updates."
194
+ });
195
+ return {
196
+ ok: checks.every((check) => check.status === "pass" || check.status === "skip"),
197
+ version: PACKAGE_VERSION,
198
+ npmLatest: latest,
199
+ checks,
200
+ recommendedConfig: {
201
+ command: "npx",
202
+ args: combinedNpxArgs(),
203
+ env: { MCP_SCRAPER_API_KEY: configuredKey ? "$MCP_SCRAPER_API_KEY" : "sk_live_your_key" }
204
+ }
205
+ };
206
+ }
207
+ function renderDoctor(output) {
208
+ const icon = { pass: "PASS", warn: "WARN", fail: "FAIL", skip: "SKIP" };
209
+ const lines = [
210
+ `mcp-scraper doctor v${output.version}`,
211
+ "",
212
+ ...output.checks.map((check) => {
213
+ const fix = check.fix ? `
214
+ Fix: ${check.fix}` : "";
215
+ return `[${icon[check.status]}] ${check.label}: ${check.detail}${fix}`;
216
+ }),
217
+ "",
218
+ `Recommended MCP command: npx ${output.recommendedConfig.args.join(" ")}`
219
+ ];
220
+ return lines.join("\n");
221
+ }
222
+
223
+ // src/cli/prompts.ts
224
+ var AGENT_PROMPTS = {
225
+ "agent-packet": [
226
+ "# MCP Scraper Agent Packet Prompt",
227
+ "",
228
+ "Use MCP Scraper as the evidence layer. Run the agent-packet workflow for the target keyword/domain, then treat `evidence.json`, `sources.csv`, and `competitors.csv` as source of truth.",
229
+ "",
230
+ "Do not invent citations. If a recommendation is not supported by the packet, mark it as an assumption. Turn the evidence into a concise SEO brief, an implementation task list, and content recommendations tied to source rows."
231
+ ].join("\n"),
232
+ "local-competitive-audit": [
233
+ "# MCP Scraper Local Competitive Audit Prompt",
234
+ "",
235
+ "Run the local-competitive-audit workflow for the niche and markets. Use the city summary, competitor CSV, and review-insight CSV to identify market difficulty, review themes, GBP category patterns, and weak competitors.",
236
+ "",
237
+ "Ground recommendations in Maps rank position, review count, star rating, categories, profile details, and review topics. Do not overstate heuristic opportunity scores."
238
+ ].join("\n"),
239
+ "directory-workflow": [
240
+ "# MCP Scraper Directory Workflow Prompt",
241
+ "",
242
+ "Use directory_workflow when the user wants cities selected by population and Google Maps candidates per city. Keep query and location separate. Preserve result_position, source_location, review stars/count, categories, and profile URLs for downstream CSV or directory use."
243
+ ].join("\n"),
244
+ "ai-citation-monitor": [
245
+ "# MCP Scraper AI Citation Monitor Prompt",
246
+ "",
247
+ "Use search_serp and harvest_paa to check whether the target brand/domain appears in AI Overview citations, organic results, People Also Ask sources, local pack, forums, and videos. Save evidence before summarizing trends or gaps."
248
+ ].join("\n"),
249
+ "serp-brief": [
250
+ "# MCP Scraper SERP Brief Prompt",
251
+ "",
252
+ "Use live SERP, PAA, AI Overview, forum, video, and source evidence to create a writer brief. Tie each recommended section to evidence rows and identify missing proof, entities, comparisons, and customer questions."
253
+ ].join("\n")
254
+ };
255
+ function listPrompts() {
256
+ return Object.keys(AGENT_PROMPTS).sort();
257
+ }
258
+ function renderPrompt(name) {
259
+ const prompt = AGENT_PROMPTS[name];
260
+ if (!prompt) throw new Error(`Unknown prompt "${name}". Available: ${listPrompts().join(", ")}`);
261
+ return prompt;
262
+ }
263
+
264
+ // src/cli/human-cli.ts
265
+ function numberOpt(value) {
266
+ if (value === void 0 || value === null || value === "") return void 0;
267
+ const parsed = Number(value);
268
+ return Number.isFinite(parsed) ? parsed : void 0;
269
+ }
270
+ function booleanOpt(value) {
271
+ if (value === void 0) return void 0;
272
+ if (typeof value === "boolean") return value;
273
+ if (value === "true") return true;
274
+ if (value === "false") return false;
275
+ return void 0;
276
+ }
277
+ function compactInput(input) {
278
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
279
+ }
280
+ function workflowInput(id, opts) {
281
+ if (id === "agent-packet") {
282
+ return compactInput({
283
+ keyword: opts.keyword,
284
+ domain: opts.domain,
285
+ location: opts.location,
286
+ maxQuestions: numberOpt(opts.maxQuestions),
287
+ includeSerp: opts.serp === false ? false : void 0,
288
+ includePaa: opts.paa === false ? false : void 0,
289
+ includeAiOverview: booleanOpt(opts.includeAiOverview),
290
+ returnPartial: opts.returnPartial === false ? false : void 0
291
+ });
292
+ }
293
+ if (id === "directory" || id === "local-competitive-audit") {
294
+ return compactInput({
295
+ query: opts.query,
296
+ state: opts.state,
297
+ minPopulation: numberOpt(opts.minPop ?? opts.minPopulation),
298
+ maxCities: numberOpt(opts.maxCities),
299
+ maxResultsPerCity: numberOpt(opts.perCity ?? opts.maxResultsPerCity),
300
+ concurrency: numberOpt(opts.concurrency),
301
+ proxyMode: opts.proxyMode,
302
+ hydrateTop: id === "local-competitive-audit" ? numberOpt(opts.hydrateTop) : void 0,
303
+ maxReviews: id === "local-competitive-audit" ? numberOpt(opts.reviews ?? opts.maxReviews) : void 0,
304
+ returnPartial: opts.returnPartial === false ? false : void 0
305
+ });
306
+ }
307
+ return compactInput(opts);
308
+ }
309
+ function writeOutput(data, json) {
310
+ if (json) {
311
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
312
+ `);
313
+ } else if (typeof data === "string") {
314
+ process.stdout.write(`${data}
315
+ `);
316
+ } else {
317
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
318
+ `);
319
+ }
320
+ }
321
+ function cadenceOpt(opts) {
322
+ if (opts.daily) return "daily";
323
+ if (opts.monthly) return "monthly";
324
+ return "weekly";
325
+ }
326
+ function apiOptions(opts) {
327
+ const apiKey = String(opts.apiKey ?? process.env.MCP_SCRAPER_API_KEY ?? "").trim();
328
+ if (!apiKey) throw new Error("MCP_SCRAPER_API_KEY is required. Pass --api-key or set the environment variable.");
329
+ return {
330
+ apiUrl: String(opts.apiUrl ?? process.env.MCP_SCRAPER_API_URL ?? "https://mcpscraper.dev").replace(/\/$/, ""),
331
+ apiKey
332
+ };
333
+ }
334
+ async function apiRequest(path, method, opts, body) {
335
+ const { apiUrl, apiKey } = apiOptions(opts);
336
+ const res = await fetch(`${apiUrl}${path}`, {
337
+ method,
338
+ headers: {
339
+ "Content-Type": "application/json",
340
+ "x-api-key": apiKey
341
+ },
342
+ body: body == null ? void 0 : JSON.stringify(body)
343
+ });
344
+ const data = await res.json().catch(() => ({}));
345
+ if (!res.ok) {
346
+ const message = typeof data.error === "string" ? data.error : `API request failed with ${res.status}`;
347
+ throw new Error(message);
348
+ }
349
+ return data;
350
+ }
351
+ function addWorkflowInputOptions(command) {
352
+ return command.option("--keyword <keyword>", "Agent packet keyword").option("--domain <domain>", "Target domain").option("--location <location>", "Target location").option("--max-questions <n>", "Maximum PAA questions").option("--no-serp", "Skip SERP evidence for agent packet").option("--no-paa", "Skip PAA evidence for agent packet").option("--query <query>", "Business category or workflow query").option("--state <state>", "US state").option("--min-pop <n>", "Minimum city population").option("--max-cities <n>", "Maximum selected cities").option("--per-city <n>", "Maps results per city").option("--concurrency <n>", "City search concurrency").option("--proxy-mode <mode>", "Proxy mode: location, configured, or none").option("--hydrate-top <n>", "Profiles to hydrate per city for competitive audits").option("--reviews <n>", "Review cards to collect per hydrated profile").option("--no-return-partial", "Fail instead of writing partial artifacts");
353
+ }
354
+ function buildHumanCli() {
355
+ const program = new Command();
356
+ program.name("mcp-scraper-cli").description("Human CLI for MCP Scraper setup, workflows, reports, and agent-ready SEO artifacts.").version(PACKAGE_VERSION);
357
+ program.command("doctor").description("Check local setup, API key, output directory, package version, and recommended MCP config.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--output-dir <path>", "Output directory to test").option("--json", "Print machine-readable JSON").action(async (opts) => {
358
+ const output = await runDoctor({ apiKey: opts.apiKey, apiUrl: opts.apiUrl, outputDir: opts.outputDir });
359
+ writeOutput(opts.json ? output : renderDoctor(output), opts.json);
360
+ if (!output.ok) process.exitCode = 1;
361
+ });
362
+ const agent = program.command("agent").description("Generate AI-agent install configs and workflow prompts.");
363
+ agent.command("install <host>").description("Print install/config instructions for codex, claude, or claude-desktop.").option("--api-key <key>", "API key to place in generated config").option("--package <spec>", "npm package spec", "mcp-scraper@latest").option("--json", "Print machine-readable JSON").action((host, opts) => {
364
+ const valid = ["codex", "claude", "claude-desktop"];
365
+ if (!valid.includes(host)) throw new Error(`Unknown host "${host}". Use: ${valid.join(", ")}`);
366
+ const text = renderAgentInstall(host, { apiKey: opts.apiKey, packageSpec: opts.package });
367
+ writeOutput(opts.json ? { host, text } : text, opts.json);
368
+ });
369
+ agent.command("prompt [name]").description("Print an agent prompt template.").option("--json", "Print machine-readable JSON").action((name, opts) => {
370
+ if (!name) {
371
+ writeOutput(opts.json ? { prompts: listPrompts() } : listPrompts().join("\n"), opts.json);
372
+ return;
373
+ }
374
+ const text = renderPrompt(name);
375
+ writeOutput(opts.json ? { name, text } : text, opts.json);
376
+ });
377
+ const workflow = program.command("workflow").description("Run named SEO workflows.");
378
+ workflow.command("list").description("List available workflows.").option("--json", "Print machine-readable JSON").action((opts) => {
379
+ const rows = listWorkflowDefinitions();
380
+ if (opts.json) writeOutput({ workflows: rows }, true);
381
+ else writeOutput(rows.map((row) => `${row.id} ${row.title}
382
+ ${row.description}`).join("\n"), false);
383
+ });
384
+ workflow.command("run <id>").description("Run a workflow and save local artifacts.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--output-dir <path>", "Workflow output directory").option("--json", "Print machine-readable JSON").option("--keyword <keyword>", "Agent packet keyword").option("--domain <domain>", "Target domain").option("--location <location>", "Target location").option("--max-questions <n>", "Maximum PAA questions").option("--no-serp", "Skip SERP evidence for agent packet").option("--no-paa", "Skip PAA evidence for agent packet").option("--query <query>", "Business category or workflow query").option("--state <state>", "US state").option("--min-pop <n>", "Minimum city population").option("--max-cities <n>", "Maximum selected cities").option("--per-city <n>", "Maps results per city").option("--concurrency <n>", "City search concurrency").option("--proxy-mode <mode>", "Proxy mode: location, configured, or none").option("--hydrate-top <n>", "Profiles to hydrate per city for competitive audits").option("--reviews <n>", "Review cards to collect per hydrated profile").option("--no-return-partial", "Fail instead of writing partial artifacts").action(async (id, opts) => {
385
+ const summary = await runWorkflow(id, workflowInput(id, opts), {
386
+ apiKey: opts.apiKey,
387
+ apiUrl: opts.apiUrl,
388
+ outputDir: opts.outputDir
389
+ });
390
+ if (opts.json) writeOutput(summary, true);
391
+ else {
392
+ writeOutput([
393
+ `${summary.title}: ${summary.status}`,
394
+ summary.summary,
395
+ summary.reportPath ? `Report: ${summary.reportPath}` : "",
396
+ summary.warnings.length ? `Warnings:
397
+ ${summary.warnings.map((w) => `- ${w}`).join("\n")}` : ""
398
+ ].filter(Boolean).join("\n"), false);
399
+ }
400
+ });
401
+ const report = program.command("report").description("List and open local workflow reports.");
402
+ report.command("list").description("List recent workflow reports.").option("--output-dir <path>", "Workflow output directory").option("--json", "Print machine-readable JSON").action(async (opts) => {
403
+ const reports = await listWorkflowReports(opts.outputDir);
404
+ writeOutput(opts.json ? { reports } : reports.map((r) => `${r.startedAt} ${r.workflow} ${r.status} ${r.reportPath ?? r.manifestPath}`).join("\n"), opts.json);
405
+ });
406
+ report.command("path [id]").description("Print a report path. Defaults to last.").option("--output-dir <path>", "Workflow output directory").option("--json", "Print machine-readable JSON").action(async (id = "last", opts) => {
407
+ const found = await findWorkflowReport(id, opts.outputDir);
408
+ if (!found?.reportPath) throw new Error(`No report found for "${id}"`);
409
+ writeOutput(opts.json ? { path: found.reportPath, run: found } : found.reportPath, opts.json);
410
+ });
411
+ report.command("open [id]").description("Open a report. Defaults to last.").option("--output-dir <path>", "Workflow output directory").option("--json", "Print machine-readable JSON instead of opening").action(async (id = "last", opts) => {
412
+ if (opts.json) {
413
+ const found = await findWorkflowReport(id, opts.outputDir);
414
+ if (!found?.reportPath) throw new Error(`No report found for "${id}"`);
415
+ writeOutput({ path: found.reportPath, run: found }, true);
416
+ return;
417
+ }
418
+ const path = await openWorkflowReport(id, opts.outputDir);
419
+ writeOutput(`Opened: ${path}`, false);
420
+ });
421
+ const schedule = program.command("schedule").description("Create and manage hosted workflow schedules.");
422
+ addWorkflowInputOptions(schedule.command("create <workflowId>").description("Create a recurring hosted workflow schedule.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--name <name>", "Schedule name").option("--daily", "Run daily").option("--weekly", "Run weekly").option("--monthly", "Run monthly").option("--timezone <tz>", "Schedule timezone", "UTC").option("--webhook <url>", "HTTPS webhook URL").option("--next-run-at <iso>", "First run time as an ISO timestamp").option("--json", "Print machine-readable JSON")).action(async (workflowId, opts) => {
423
+ const result = await apiRequest("/workflows/schedules", "POST", opts, {
424
+ workflowId,
425
+ name: opts.name,
426
+ input: workflowInput(workflowId, opts),
427
+ cadence: cadenceOpt(opts),
428
+ timezone: opts.timezone,
429
+ webhookUrl: opts.webhook,
430
+ nextRunAt: opts.nextRunAt
431
+ });
432
+ writeOutput(result, opts.json);
433
+ });
434
+ schedule.command("list").description("List hosted workflow schedules.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--json", "Print machine-readable JSON").action(async (opts) => {
435
+ const result = await apiRequest("/workflows/schedules", "GET", opts);
436
+ if (opts.json) writeOutput(result, true);
437
+ else writeOutput(result.schedules.map((scheduleRow) => `${scheduleRow.id} ${scheduleRow.status} ${scheduleRow.workflow_id} ${scheduleRow.next_run_at ?? ""}`).join("\n"), false);
438
+ });
439
+ schedule.command("pause <id>").description("Pause a hosted workflow schedule.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--json", "Print machine-readable JSON").action(async (id, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}`, "PATCH", opts, { status: "paused" }), opts.json));
440
+ schedule.command("resume <id>").description("Resume a hosted workflow schedule.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--json", "Print machine-readable JSON").action(async (id, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}`, "PATCH", opts, { status: "active" }), opts.json));
441
+ schedule.command("delete <id>").description("Delete a hosted workflow schedule.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--json", "Print machine-readable JSON").action(async (id, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}`, "DELETE", opts), opts.json));
442
+ schedule.command("run <id>").description("Run a hosted workflow schedule now.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--json", "Print machine-readable JSON").action(async (id, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}/run`, "POST", opts, {}), opts.json));
443
+ const runs = program.command("runs").description("Inspect and download hosted workflow runs.");
444
+ runs.command("list").description("List hosted workflow runs.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--json", "Print machine-readable JSON").action(async (opts) => {
445
+ const result = await apiRequest("/workflows/runs", "GET", opts);
446
+ if (opts.json) writeOutput(result, true);
447
+ else writeOutput(result.runs.map((run) => `${run.id} ${run.status} ${run.workflow_id} ${run.queued_at}`).join("\n"), false);
448
+ });
449
+ runs.command("status <id>").description("Show a hosted workflow run.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--json", "Print machine-readable JSON").action(async (id, opts) => writeOutput(await apiRequest(`/workflows/runs/${id}`, "GET", opts), opts.json));
450
+ runs.command("download <id>").description("Download hosted workflow run artifacts.").option("--api-key <key>", "MCP Scraper API key").option("--api-url <url>", "MCP Scraper API URL", "https://mcpscraper.dev").option("--output-dir <path>", "Download directory").option("--json", "Print machine-readable JSON").action(async (id, opts) => {
451
+ const result = await apiRequest(`/workflows/runs/${id}`, "GET", opts);
452
+ const { apiUrl, apiKey } = apiOptions(opts);
453
+ const outDir = join2(opts.outputDir ?? workflowOutputBaseDir(), "workflow-downloads", id);
454
+ await mkdir2(outDir, { recursive: true });
455
+ const downloaded = [];
456
+ for (const artifact of result.run.artifacts ?? []) {
457
+ const res = await fetch(`${apiUrl}/workflows/runs/${id}/artifacts/${artifact.id}`, { headers: { "x-api-key": apiKey } });
458
+ if (!res.ok) throw new Error(`Failed to download ${artifact.label}: HTTP ${res.status}`);
459
+ const file = join2(outDir, basename(artifact.path));
460
+ await writeFile(file, Buffer.from(await res.arrayBuffer()));
461
+ downloaded.push(file);
462
+ }
463
+ writeOutput(opts.json ? { runId: id, files: downloaded } : downloaded.join("\n"), opts.json);
464
+ });
465
+ return program;
466
+ }
467
+ async function runHumanCli(argv = process.argv) {
468
+ await buildHumanCli().parseAsync(argv);
469
+ }
470
+
471
+ // bin/mcp-scraper-cli.ts
472
+ runHumanCli().catch((err) => {
473
+ console.error(err instanceof Error ? err.message : String(err));
474
+ process.exit(1);
475
+ });
476
+ //# sourceMappingURL=mcp-scraper-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/human-cli.ts","../../src/cli/agent-config.ts","../../src/cli/doctor.ts","../../src/cli/prompts.ts","../../bin/mcp-scraper-cli.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport { basename, join } from 'node:path'\nimport { PACKAGE_VERSION } from '../version.js'\nimport { renderAgentInstall, type AgentHost } from './agent-config.js'\nimport { renderDoctor, runDoctor } from './doctor.js'\nimport { listPrompts, renderPrompt } from './prompts.js'\nimport { workflowOutputBaseDir } from '../workflows/artifact-writer.js'\nimport { findWorkflowReport, listWorkflowDefinitions, listWorkflowReports, openWorkflowReport, runWorkflow } from '../workflows/registry.js'\n\nfunction numberOpt(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') return undefined\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n}\n\nfunction booleanOpt(value: unknown): boolean | undefined {\n if (value === undefined) return undefined\n if (typeof value === 'boolean') return value\n if (value === 'true') return true\n if (value === 'false') return false\n return undefined\n}\n\nfunction compactInput(input: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined))\n}\n\nfunction workflowInput(id: string, opts: Record<string, unknown>): Record<string, unknown> {\n if (id === 'agent-packet') {\n return compactInput({\n keyword: opts.keyword,\n domain: opts.domain,\n location: opts.location,\n maxQuestions: numberOpt(opts.maxQuestions),\n includeSerp: opts.serp === false ? false : undefined,\n includePaa: opts.paa === false ? false : undefined,\n includeAiOverview: booleanOpt(opts.includeAiOverview),\n returnPartial: opts.returnPartial === false ? false : undefined,\n })\n }\n\n if (id === 'directory' || id === 'local-competitive-audit') {\n return compactInput({\n query: opts.query,\n state: opts.state,\n minPopulation: numberOpt(opts.minPop ?? opts.minPopulation),\n maxCities: numberOpt(opts.maxCities),\n maxResultsPerCity: numberOpt(opts.perCity ?? opts.maxResultsPerCity),\n concurrency: numberOpt(opts.concurrency),\n proxyMode: opts.proxyMode,\n hydrateTop: id === 'local-competitive-audit' ? numberOpt(opts.hydrateTop) : undefined,\n maxReviews: id === 'local-competitive-audit' ? numberOpt(opts.reviews ?? opts.maxReviews) : undefined,\n returnPartial: opts.returnPartial === false ? false : undefined,\n })\n }\n\n return compactInput(opts)\n}\n\nfunction writeOutput(data: unknown, json: boolean | undefined): void {\n if (json) {\n process.stdout.write(`${JSON.stringify(data, null, 2)}\\n`)\n } else if (typeof data === 'string') {\n process.stdout.write(`${data}\\n`)\n } else {\n process.stdout.write(`${JSON.stringify(data, null, 2)}\\n`)\n }\n}\n\nfunction cadenceOpt(opts: Record<string, unknown>): 'daily' | 'weekly' | 'monthly' {\n if (opts.daily) return 'daily'\n if (opts.monthly) return 'monthly'\n return 'weekly'\n}\n\nfunction apiOptions(opts: Record<string, unknown>): { apiUrl: string; apiKey: string } {\n const apiKey = String(opts.apiKey ?? process.env.MCP_SCRAPER_API_KEY ?? '').trim()\n if (!apiKey) throw new Error('MCP_SCRAPER_API_KEY is required. Pass --api-key or set the environment variable.')\n return {\n apiUrl: String(opts.apiUrl ?? process.env.MCP_SCRAPER_API_URL ?? 'https://mcpscraper.dev').replace(/\\/$/, ''),\n apiKey,\n }\n}\n\nasync function apiRequest<T>(path: string, method: string, opts: Record<string, unknown>, body?: unknown): Promise<T> {\n const { apiUrl, apiKey } = apiOptions(opts)\n const res = await fetch(`${apiUrl}${path}`, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n body: body == null ? undefined : JSON.stringify(body),\n })\n const data = await res.json().catch(() => ({}))\n if (!res.ok) {\n const message = typeof (data as { error?: unknown }).error === 'string'\n ? (data as { error: string }).error\n : `API request failed with ${res.status}`\n throw new Error(message)\n }\n return data as T\n}\n\nfunction addWorkflowInputOptions(command: Command): Command {\n return command\n .option('--keyword <keyword>', 'Agent packet keyword')\n .option('--domain <domain>', 'Target domain')\n .option('--location <location>', 'Target location')\n .option('--max-questions <n>', 'Maximum PAA questions')\n .option('--no-serp', 'Skip SERP evidence for agent packet')\n .option('--no-paa', 'Skip PAA evidence for agent packet')\n .option('--query <query>', 'Business category or workflow query')\n .option('--state <state>', 'US state')\n .option('--min-pop <n>', 'Minimum city population')\n .option('--max-cities <n>', 'Maximum selected cities')\n .option('--per-city <n>', 'Maps results per city')\n .option('--concurrency <n>', 'City search concurrency')\n .option('--proxy-mode <mode>', 'Proxy mode: location, configured, or none')\n .option('--hydrate-top <n>', 'Profiles to hydrate per city for competitive audits')\n .option('--reviews <n>', 'Review cards to collect per hydrated profile')\n .option('--no-return-partial', 'Fail instead of writing partial artifacts')\n}\n\nexport function buildHumanCli(): Command {\n const program = new Command()\n program\n .name('mcp-scraper-cli')\n .description('Human CLI for MCP Scraper setup, workflows, reports, and agent-ready SEO artifacts.')\n .version(PACKAGE_VERSION)\n\n program.command('doctor')\n .description('Check local setup, API key, output directory, package version, and recommended MCP config.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--output-dir <path>', 'Output directory to test')\n .option('--json', 'Print machine-readable JSON')\n .action(async (opts) => {\n const output = await runDoctor({ apiKey: opts.apiKey, apiUrl: opts.apiUrl, outputDir: opts.outputDir })\n writeOutput(opts.json ? output : renderDoctor(output), opts.json)\n if (!output.ok) process.exitCode = 1\n })\n\n const agent = program.command('agent').description('Generate AI-agent install configs and workflow prompts.')\n agent.command('install <host>')\n .description('Print install/config instructions for codex, claude, or claude-desktop.')\n .option('--api-key <key>', 'API key to place in generated config')\n .option('--package <spec>', 'npm package spec', 'mcp-scraper@latest')\n .option('--json', 'Print machine-readable JSON')\n .action((host: AgentHost, opts) => {\n const valid = ['codex', 'claude', 'claude-desktop']\n if (!valid.includes(host)) throw new Error(`Unknown host \"${host}\". Use: ${valid.join(', ')}`)\n const text = renderAgentInstall(host, { apiKey: opts.apiKey, packageSpec: opts.package })\n writeOutput(opts.json ? { host, text } : text, opts.json)\n })\n\n agent.command('prompt [name]')\n .description('Print an agent prompt template.')\n .option('--json', 'Print machine-readable JSON')\n .action((name: string | undefined, opts) => {\n if (!name) {\n writeOutput(opts.json ? { prompts: listPrompts() } : listPrompts().join('\\n'), opts.json)\n return\n }\n const text = renderPrompt(name)\n writeOutput(opts.json ? { name, text } : text, opts.json)\n })\n\n const workflow = program.command('workflow').description('Run named SEO workflows.')\n workflow.command('list')\n .description('List available workflows.')\n .option('--json', 'Print machine-readable JSON')\n .action((opts) => {\n const rows = listWorkflowDefinitions()\n if (opts.json) writeOutput({ workflows: rows }, true)\n else writeOutput(rows.map(row => `${row.id}\\t${row.title}\\n ${row.description}`).join('\\n'), false)\n })\n\n workflow.command('run <id>')\n .description('Run a workflow and save local artifacts.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--output-dir <path>', 'Workflow output directory')\n .option('--json', 'Print machine-readable JSON')\n .option('--keyword <keyword>', 'Agent packet keyword')\n .option('--domain <domain>', 'Target domain')\n .option('--location <location>', 'Target location')\n .option('--max-questions <n>', 'Maximum PAA questions')\n .option('--no-serp', 'Skip SERP evidence for agent packet')\n .option('--no-paa', 'Skip PAA evidence for agent packet')\n .option('--query <query>', 'Business category or workflow query')\n .option('--state <state>', 'US state')\n .option('--min-pop <n>', 'Minimum city population')\n .option('--max-cities <n>', 'Maximum selected cities')\n .option('--per-city <n>', 'Maps results per city')\n .option('--concurrency <n>', 'City search concurrency')\n .option('--proxy-mode <mode>', 'Proxy mode: location, configured, or none')\n .option('--hydrate-top <n>', 'Profiles to hydrate per city for competitive audits')\n .option('--reviews <n>', 'Review cards to collect per hydrated profile')\n .option('--no-return-partial', 'Fail instead of writing partial artifacts')\n .action(async (id: string, opts) => {\n const summary = await runWorkflow(id, workflowInput(id, opts), {\n apiKey: opts.apiKey,\n apiUrl: opts.apiUrl,\n outputDir: opts.outputDir,\n })\n if (opts.json) writeOutput(summary, true)\n else {\n writeOutput([\n `${summary.title}: ${summary.status}`,\n summary.summary,\n summary.reportPath ? `Report: ${summary.reportPath}` : '',\n summary.warnings.length ? `Warnings:\\n${summary.warnings.map(w => `- ${w}`).join('\\n')}` : '',\n ].filter(Boolean).join('\\n'), false)\n }\n })\n\n const report = program.command('report').description('List and open local workflow reports.')\n report.command('list')\n .description('List recent workflow reports.')\n .option('--output-dir <path>', 'Workflow output directory')\n .option('--json', 'Print machine-readable JSON')\n .action(async (opts) => {\n const reports = await listWorkflowReports(opts.outputDir)\n writeOutput(opts.json ? { reports } : reports.map(r => `${r.startedAt}\\t${r.workflow}\\t${r.status}\\t${r.reportPath ?? r.manifestPath}`).join('\\n'), opts.json)\n })\n report.command('path [id]')\n .description('Print a report path. Defaults to last.')\n .option('--output-dir <path>', 'Workflow output directory')\n .option('--json', 'Print machine-readable JSON')\n .action(async (id = 'last', opts) => {\n const found = await findWorkflowReport(id, opts.outputDir)\n if (!found?.reportPath) throw new Error(`No report found for \"${id}\"`)\n writeOutput(opts.json ? { path: found.reportPath, run: found } : found.reportPath, opts.json)\n })\n report.command('open [id]')\n .description('Open a report. Defaults to last.')\n .option('--output-dir <path>', 'Workflow output directory')\n .option('--json', 'Print machine-readable JSON instead of opening')\n .action(async (id = 'last', opts) => {\n if (opts.json) {\n const found = await findWorkflowReport(id, opts.outputDir)\n if (!found?.reportPath) throw new Error(`No report found for \"${id}\"`)\n writeOutput({ path: found.reportPath, run: found }, true)\n return\n }\n const path = await openWorkflowReport(id, opts.outputDir)\n writeOutput(`Opened: ${path}`, false)\n })\n\n const schedule = program.command('schedule').description('Create and manage hosted workflow schedules.')\n addWorkflowInputOptions(schedule.command('create <workflowId>')\n .description('Create a recurring hosted workflow schedule.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--name <name>', 'Schedule name')\n .option('--daily', 'Run daily')\n .option('--weekly', 'Run weekly')\n .option('--monthly', 'Run monthly')\n .option('--timezone <tz>', 'Schedule timezone', 'UTC')\n .option('--webhook <url>', 'HTTPS webhook URL')\n .option('--next-run-at <iso>', 'First run time as an ISO timestamp')\n .option('--json', 'Print machine-readable JSON'))\n .action(async (workflowId: string, opts) => {\n const result = await apiRequest('/workflows/schedules', 'POST', opts, {\n workflowId,\n name: opts.name,\n input: workflowInput(workflowId, opts),\n cadence: cadenceOpt(opts),\n timezone: opts.timezone,\n webhookUrl: opts.webhook,\n nextRunAt: opts.nextRunAt,\n })\n writeOutput(result, opts.json)\n })\n\n schedule.command('list')\n .description('List hosted workflow schedules.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--json', 'Print machine-readable JSON')\n .action(async (opts) => {\n const result = await apiRequest<{ schedules: Array<Record<string, unknown>> }>('/workflows/schedules', 'GET', opts)\n if (opts.json) writeOutput(result, true)\n else writeOutput(result.schedules.map(scheduleRow => `${scheduleRow.id}\\t${scheduleRow.status}\\t${scheduleRow.workflow_id}\\t${scheduleRow.next_run_at ?? ''}`).join('\\n'), false)\n })\n\n schedule.command('pause <id>')\n .description('Pause a hosted workflow schedule.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--json', 'Print machine-readable JSON')\n .action(async (id: string, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}`, 'PATCH', opts, { status: 'paused' }), opts.json))\n\n schedule.command('resume <id>')\n .description('Resume a hosted workflow schedule.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--json', 'Print machine-readable JSON')\n .action(async (id: string, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}`, 'PATCH', opts, { status: 'active' }), opts.json))\n\n schedule.command('delete <id>')\n .description('Delete a hosted workflow schedule.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--json', 'Print machine-readable JSON')\n .action(async (id: string, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}`, 'DELETE', opts), opts.json))\n\n schedule.command('run <id>')\n .description('Run a hosted workflow schedule now.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--json', 'Print machine-readable JSON')\n .action(async (id: string, opts) => writeOutput(await apiRequest(`/workflows/schedules/${id}/run`, 'POST', opts, {}), opts.json))\n\n const runs = program.command('runs').description('Inspect and download hosted workflow runs.')\n runs.command('list')\n .description('List hosted workflow runs.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--json', 'Print machine-readable JSON')\n .action(async (opts) => {\n const result = await apiRequest<{ runs: Array<Record<string, unknown>> }>('/workflows/runs', 'GET', opts)\n if (opts.json) writeOutput(result, true)\n else writeOutput(result.runs.map(run => `${run.id}\\t${run.status}\\t${run.workflow_id}\\t${run.queued_at}`).join('\\n'), false)\n })\n\n runs.command('status <id>')\n .description('Show a hosted workflow run.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--json', 'Print machine-readable JSON')\n .action(async (id: string, opts) => writeOutput(await apiRequest(`/workflows/runs/${id}`, 'GET', opts), opts.json))\n\n runs.command('download <id>')\n .description('Download hosted workflow run artifacts.')\n .option('--api-key <key>', 'MCP Scraper API key')\n .option('--api-url <url>', 'MCP Scraper API URL', 'https://mcpscraper.dev')\n .option('--output-dir <path>', 'Download directory')\n .option('--json', 'Print machine-readable JSON')\n .action(async (id: string, opts) => {\n const result = await apiRequest<{ run: { id: string; artifacts?: Array<{ id: string; label: string; path: string }> } }>(`/workflows/runs/${id}`, 'GET', opts)\n const { apiUrl, apiKey } = apiOptions(opts)\n const outDir = join(opts.outputDir ?? workflowOutputBaseDir(), 'workflow-downloads', id)\n await mkdir(outDir, { recursive: true })\n const downloaded: string[] = []\n for (const artifact of result.run.artifacts ?? []) {\n const res = await fetch(`${apiUrl}/workflows/runs/${id}/artifacts/${artifact.id}`, { headers: { 'x-api-key': apiKey } })\n if (!res.ok) throw new Error(`Failed to download ${artifact.label}: HTTP ${res.status}`)\n const file = join(outDir, basename(artifact.path))\n await writeFile(file, Buffer.from(await res.arrayBuffer()))\n downloaded.push(file)\n }\n writeOutput(opts.json ? { runId: id, files: downloaded } : downloaded.join('\\n'), opts.json)\n })\n\n return program\n}\n\nexport async function runHumanCli(argv = process.argv): Promise<void> {\n await buildHumanCli().parseAsync(argv)\n}\n","export type AgentHost = 'codex' | 'claude' | 'claude-desktop'\n\nexport interface AgentConfigOptions {\n apiKey?: string\n packageSpec?: string\n}\n\nfunction apiKeyValue(options: AgentConfigOptions): string {\n return options.apiKey?.trim() || 'sk_live_your_key'\n}\n\nfunction packageSpec(options: AgentConfigOptions): string {\n return options.packageSpec?.trim() || 'mcp-scraper@latest'\n}\n\nexport function combinedNpxArgs(options: AgentConfigOptions = {}): string[] {\n return ['-y', '-p', packageSpec(options), 'mcp-scraper-combined']\n}\n\nexport function renderCodexConfig(options: AgentConfigOptions = {}): string {\n return [\n '[mcp_servers.mcp-scraper]',\n 'command = \"npx\"',\n `args = ${JSON.stringify(combinedNpxArgs(options))}`,\n `env = { MCP_SCRAPER_API_KEY = \"${apiKeyValue(options)}\" }`,\n ].join('\\n')\n}\n\nexport function renderClaudeCommand(options: AgentConfigOptions = {}): string {\n return [\n 'claude mcp add mcp-scraper --scope user',\n ` --env MCP_SCRAPER_API_KEY=${apiKeyValue(options)}`,\n ` -- npx ${combinedNpxArgs(options).join(' ')}`,\n ].join(' \\\\\\n')\n}\n\nexport function renderClaudeDesktopConfig(options: AgentConfigOptions = {}): string {\n return JSON.stringify({\n mcpServers: {\n 'mcp-scraper': {\n command: 'npx',\n args: combinedNpxArgs(options),\n env: {\n MCP_SCRAPER_API_KEY: apiKeyValue(options),\n },\n },\n },\n }, null, 2)\n}\n\nexport function renderAgentInstall(host: AgentHost, options: AgentConfigOptions = {}): string {\n const restart = 'Restart the MCP client so it starts a fresh npx process.'\n if (host === 'codex') {\n return [\n '# Codex MCP config',\n renderCodexConfig(options),\n '',\n restart,\n ].join('\\n')\n }\n if (host === 'claude') {\n return [\n '# Claude Code command',\n renderClaudeCommand(options),\n '',\n restart,\n ].join('\\n')\n }\n return [\n '# Claude Desktop config',\n renderClaudeDesktopConfig(options),\n '',\n 'Desktop Extension: https://mcpscraper.dev/downloads/mcp-scraper.mcpb',\n restart,\n ].join('\\n')\n}\n\n","import { access, mkdir, readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { PACKAGE_VERSION } from '../version.js'\nimport { combinedNpxArgs } from './agent-config.js'\n\nexport type DoctorStatus = 'pass' | 'warn' | 'fail' | 'skip'\n\nexport interface DoctorCheck {\n id: string\n label: string\n status: DoctorStatus\n detail: string\n fix?: string\n}\n\nexport interface DoctorOptions {\n apiKey?: string\n apiUrl?: string\n outputDir?: string\n fetchImpl?: typeof fetch\n}\n\nexport interface DoctorOutput {\n ok: boolean\n version: string\n npmLatest: string | null\n checks: DoctorCheck[]\n recommendedConfig: {\n command: 'npx'\n args: string[]\n env: Record<string, string>\n }\n}\n\nfunction status(ok: boolean, warn = false): DoctorStatus {\n if (ok) return 'pass'\n return warn ? 'warn' : 'fail'\n}\n\nfunction parseMajor(version: string): number {\n return Number(version.replace(/^v/, '').split('.')[0] ?? 0)\n}\n\nasync function readKeyFile(): Promise<string | null> {\n const path = process.env.MCP_SCRAPER_KEY_PATH?.trim() || join(homedir(), '.mcp-scraper-key')\n try {\n const value = (await readFile(path, 'utf8')).trim()\n return value || null\n } catch {\n return null\n }\n}\n\nasync function npmLatest(fetchImpl: typeof fetch): Promise<string | null> {\n try {\n const res = await fetchImpl('https://registry.npmjs.org/mcp-scraper/latest', {\n signal: AbortSignal.timeout(5_000),\n })\n if (!res.ok) return null\n const data = await res.json() as { version?: string }\n return data.version ?? null\n } catch {\n return null\n }\n}\n\nexport async function runDoctor(options: DoctorOptions = {}): Promise<DoctorOutput> {\n const fetchImpl = options.fetchImpl ?? fetch\n const apiUrl = (options.apiUrl ?? process.env.MCP_SCRAPER_API_URL ?? 'https://mcpscraper.dev').replace(/\\/$/, '')\n const outputDir = options.outputDir ?? process.env.MCP_SCRAPER_OUTPUT_DIR ?? join(homedir(), 'Downloads', 'mcp-scraper')\n const configuredKey = options.apiKey?.trim() || process.env.MCP_SCRAPER_API_KEY?.trim() || await readKeyFile()\n const latest = await npmLatest(fetchImpl)\n\n const checks: DoctorCheck[] = []\n const nodeMajor = parseMajor(process.version)\n checks.push({\n id: 'node',\n label: 'Node.js',\n status: status(nodeMajor >= 20),\n detail: process.version,\n fix: nodeMajor >= 20 ? undefined : 'Install Node.js 20 or newer.',\n })\n\n checks.push({\n id: 'package_version',\n label: 'Local package',\n status: latest && latest !== PACKAGE_VERSION ? 'warn' : 'pass',\n detail: latest ? `local ${PACKAGE_VERSION}, npm latest ${latest}` : `local ${PACKAGE_VERSION}, npm latest unavailable`,\n fix: latest && latest !== PACKAGE_VERSION ? 'Use mcp-scraper@latest and restart the MCP client.' : undefined,\n })\n\n checks.push({\n id: 'api_key',\n label: 'API key',\n status: configuredKey ? 'pass' : 'warn',\n detail: configuredKey ? 'configured' : 'not configured',\n fix: configuredKey ? undefined : 'Set MCP_SCRAPER_API_KEY or pass --api-key.',\n })\n\n try {\n await mkdir(outputDir, { recursive: true })\n await access(outputDir)\n checks.push({ id: 'output_dir', label: 'Output directory', status: 'pass', detail: outputDir })\n } catch (err) {\n checks.push({\n id: 'output_dir',\n label: 'Output directory',\n status: 'fail',\n detail: outputDir,\n fix: err instanceof Error ? err.message : 'Create a writable output directory.',\n })\n }\n\n if (configuredKey) {\n try {\n const res = await fetchImpl(`${apiUrl}/me`, {\n headers: { 'x-api-key': configuredKey },\n signal: AbortSignal.timeout(8_000),\n })\n checks.push({\n id: 'api_reachability',\n label: 'Hosted API',\n status: res.ok ? 'pass' : 'fail',\n detail: `${apiUrl}/me returned ${res.status}`,\n fix: res.ok ? undefined : 'Verify the API key and account status.',\n })\n } catch (err) {\n checks.push({\n id: 'api_reachability',\n label: 'Hosted API',\n status: 'fail',\n detail: err instanceof Error ? err.message : String(err),\n fix: 'Check network access and MCP_SCRAPER_API_URL.',\n })\n }\n } else {\n checks.push({\n id: 'api_reachability',\n label: 'Hosted API',\n status: 'skip',\n detail: 'skipped because no API key is configured',\n })\n }\n\n checks.push({\n id: 'mcp_config',\n label: 'MCP command',\n status: 'pass',\n detail: `npx ${combinedNpxArgs().join(' ')}`,\n fix: 'Restart the MCP client after package updates.',\n })\n\n return {\n ok: checks.every(check => check.status === 'pass' || check.status === 'skip'),\n version: PACKAGE_VERSION,\n npmLatest: latest,\n checks,\n recommendedConfig: {\n command: 'npx',\n args: combinedNpxArgs(),\n env: { MCP_SCRAPER_API_KEY: configuredKey ? '$MCP_SCRAPER_API_KEY' : 'sk_live_your_key' },\n },\n }\n}\n\nexport function renderDoctor(output: DoctorOutput): string {\n const icon: Record<DoctorStatus, string> = { pass: 'PASS', warn: 'WARN', fail: 'FAIL', skip: 'SKIP' }\n const lines = [\n `mcp-scraper doctor v${output.version}`,\n '',\n ...output.checks.map(check => {\n const fix = check.fix ? `\\n Fix: ${check.fix}` : ''\n return `[${icon[check.status]}] ${check.label}: ${check.detail}${fix}`\n }),\n '',\n `Recommended MCP command: npx ${output.recommendedConfig.args.join(' ')}`,\n ]\n return lines.join('\\n')\n}\n\n","export const AGENT_PROMPTS: Record<string, string> = {\n 'agent-packet': [\n '# MCP Scraper Agent Packet Prompt',\n '',\n 'Use MCP Scraper as the evidence layer. Run the agent-packet workflow for the target keyword/domain, then treat `evidence.json`, `sources.csv`, and `competitors.csv` as source of truth.',\n '',\n 'Do not invent citations. If a recommendation is not supported by the packet, mark it as an assumption. Turn the evidence into a concise SEO brief, an implementation task list, and content recommendations tied to source rows.',\n ].join('\\n'),\n 'local-competitive-audit': [\n '# MCP Scraper Local Competitive Audit Prompt',\n '',\n 'Run the local-competitive-audit workflow for the niche and markets. Use the city summary, competitor CSV, and review-insight CSV to identify market difficulty, review themes, GBP category patterns, and weak competitors.',\n '',\n 'Ground recommendations in Maps rank position, review count, star rating, categories, profile details, and review topics. Do not overstate heuristic opportunity scores.',\n ].join('\\n'),\n 'directory-workflow': [\n '# MCP Scraper Directory Workflow Prompt',\n '',\n 'Use directory_workflow when the user wants cities selected by population and Google Maps candidates per city. Keep query and location separate. Preserve result_position, source_location, review stars/count, categories, and profile URLs for downstream CSV or directory use.',\n ].join('\\n'),\n 'ai-citation-monitor': [\n '# MCP Scraper AI Citation Monitor Prompt',\n '',\n 'Use search_serp and harvest_paa to check whether the target brand/domain appears in AI Overview citations, organic results, People Also Ask sources, local pack, forums, and videos. Save evidence before summarizing trends or gaps.',\n ].join('\\n'),\n 'serp-brief': [\n '# MCP Scraper SERP Brief Prompt',\n '',\n 'Use live SERP, PAA, AI Overview, forum, video, and source evidence to create a writer brief. Tie each recommended section to evidence rows and identify missing proof, entities, comparisons, and customer questions.',\n ].join('\\n'),\n}\n\nexport function listPrompts(): string[] {\n return Object.keys(AGENT_PROMPTS).sort()\n}\n\nexport function renderPrompt(name: string): string {\n const prompt = AGENT_PROMPTS[name]\n if (!prompt) throw new Error(`Unknown prompt \"${name}\". Available: ${listPrompts().join(', ')}`)\n return prompt\n}\n\n","#!/usr/bin/env node\nimport { runHumanCli } from '../src/cli/human-cli.js'\n\nrunHumanCli().catch((err) => {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n})\n\n"],"mappings":"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","names":["mkdir","join","join","mkdir"]}
@@ -138,7 +138,7 @@ var import_node_os2 = require("os");
138
138
  var import_node_path2 = require("path");
139
139
 
140
140
  // src/version.ts
141
- var PACKAGE_VERSION = "0.2.8";
141
+ var PACKAGE_VERSION = "0.2.10";
142
142
 
143
143
  // src/mcp/browser-agent-tool-schemas.ts
144
144
  var import_zod = require("zod");
@@ -1974,7 +1974,7 @@ var DirectoryWorkflowInputSchema = {
1974
1974
  concurrency: import_zod2.z.number().int().min(1).max(5).default(5).describe("How many city Maps searches to run in parallel. Use 5 for broad directory batches unless debugging."),
1975
1975
  includeZipGroups: import_zod2.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available. Set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath in local/test mode."),
1976
1976
  usZipsCsvPath: import_zod2.z.string().optional().describe("Local/test-only path to a US ZIPS CSV with state_abbr, zipcode, county, city columns, such as Lead Magician tools/analytics/data/uszips.csv. Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
1977
- saveCsv: import_zod2.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
1977
+ saveCsv: import_zod2.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
1978
1978
  proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode for every city Maps search. Use location by default for US city/state batches; retryable failures create a new residential proxy ID and new browser session for up to 5 attempts per city. Use configured for the server proxy ID, and none only for local direct-network debugging."),
1979
1979
  proxyZip: import_zod2.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its Lead Magician ZIP group or city/state location."),
1980
1980
  debug: import_zod2.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
@@ -2806,7 +2806,7 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
2806
2806
  }, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
2807
2807
  server2.registerTool("directory_workflow", {
2808
2808
  title: "Directory Workflow: Markets + Maps",
2809
- description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency. Use concurrency up to 5 for parallel city sessions. Keep proxyMode as location so each city search creates fresh residential proxy IDs and browser sessions; retryable city failures rotate to a new proxy and new browser session for up to 5 attempts. Saved CSV rows include source_location, result_position, business_name, review_stars, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. Structured city results include sanitized attempt telemetry. This workflow captures star ratings from Maps list cards, not profile review counts; use maps_place_intel only when a selected profile needs deeper review details. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
2809
+ description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency. Use concurrency up to 5 for parallel city sessions. Keep proxyMode as location so each city search creates fresh residential proxy IDs and browser sessions; retryable city failures rotate to a new proxy and new browser session for up to 5 attempts. Saved CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. Structured city results include sanitized attempt telemetry. Use maps_place_intel only when a selected profile needs deeper review topics, profile review count confirmation, or review cards. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
2810
2810
  inputSchema: DirectoryWorkflowInputSchema,
2811
2811
  outputSchema: DirectoryWorkflowOutputSchema,
2812
2812
  annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")