mcp-scraper 0.97.0 → 0.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +5 -2425
  2. package/README.md +16 -412
  3. package/dist/bin/api-server.js +3 -2
  4. package/dist/bin/mcp-scraper-cli.js +33 -27
  5. package/dist/bin/mcp-scraper-core.js +9 -1
  6. package/dist/bin/mcp-scraper-install.js +17 -2
  7. package/dist/bin/mcp-stdio-server.js +9 -1
  8. package/dist/bin/paa-harvest.js +8 -1
  9. package/dist/index.cjs +5 -21830
  10. package/dist/index.d.ts +14 -607
  11. package/dist/index.js +5 -39
  12. package/dist/transport.d.ts +35 -0
  13. package/package.json +11 -51
  14. package/THIRD_PARTY_NOTICES.html +0 -203
  15. package/dist/actions-NCZNPYEY.js +0 -1
  16. package/dist/artifacts-4ZDNP2JW.js +0 -1
  17. package/dist/artifacts-N62OPIT5.js +0 -1
  18. package/dist/artifacts-PSADD5OC.js +0 -1
  19. package/dist/artifacts-RUR3OHAK.js +0 -1
  20. package/dist/blob-store-GIH26EDO.js +0 -1
  21. package/dist/bundle-XR6XKYJM.js +0 -1
  22. package/dist/chunk-24YDQ3EO.js +0 -5
  23. package/dist/chunk-3F7AFM53.js +0 -1
  24. package/dist/chunk-4HZXATHB.js +0 -27
  25. package/dist/chunk-4P3LZNVA.js +0 -954
  26. package/dist/chunk-4ZVO6GX4.js +0 -1
  27. package/dist/chunk-55ZANTHL.js +0 -3
  28. package/dist/chunk-63VWBMYR.js +0 -3
  29. package/dist/chunk-6FAMBNHY.js +0 -1
  30. package/dist/chunk-6N5EC7ZO.js +0 -1
  31. package/dist/chunk-7POHNTVW.js +0 -1
  32. package/dist/chunk-7SR7MEU5.js +0 -62
  33. package/dist/chunk-A4UASMEQ.js +0 -1
  34. package/dist/chunk-AMYPXA2F.js +0 -930
  35. package/dist/chunk-ANQ4JJVI.js +0 -71
  36. package/dist/chunk-B3JRL45T.js +0 -1
  37. package/dist/chunk-B4ZKPLSP.js +0 -1
  38. package/dist/chunk-B5Y26E3A.js +0 -1
  39. package/dist/chunk-C7MGJUTU.js +0 -1
  40. package/dist/chunk-CNSVZTTT.js +0 -13
  41. package/dist/chunk-EYGA6LOU.js +0 -1
  42. package/dist/chunk-GLCR4KJI.js +0 -21722
  43. package/dist/chunk-HCK6PLM3.js +0 -3
  44. package/dist/chunk-HNNYXMSI.js +0 -1
  45. package/dist/chunk-IFPION5D.js +0 -121
  46. package/dist/chunk-JMZIDHYQ.js +0 -2
  47. package/dist/chunk-JNPM3U3L.js +0 -189
  48. package/dist/chunk-K3VDGJNK.js +0 -1
  49. package/dist/chunk-KJQXUZ4Y.js +0 -4
  50. package/dist/chunk-KXJCGV3H.js +0 -1
  51. package/dist/chunk-M5QHXNFZ.js +0 -10
  52. package/dist/chunk-MVMWPKRC.js +0 -2
  53. package/dist/chunk-NALXKTUU.js +0 -1
  54. package/dist/chunk-ND4WIBXT.js +0 -16
  55. package/dist/chunk-NNOX3FYG.js +0 -198
  56. package/dist/chunk-NULRGHJ4.js +0 -41
  57. package/dist/chunk-OILOHQNL.js +0 -2
  58. package/dist/chunk-OLYPLX5C.js +0 -1
  59. package/dist/chunk-OOB3RKWD.js +0 -4
  60. package/dist/chunk-OSKCHEVM.js +0 -218
  61. package/dist/chunk-PD7BI7TR.js +0 -12
  62. package/dist/chunk-PKQHHCO4.js +0 -20
  63. package/dist/chunk-PQRT4T2G.js +0 -159
  64. package/dist/chunk-QER2ELZP.js +0 -1
  65. package/dist/chunk-RDHGQ2WZ.js +0 -1
  66. package/dist/chunk-SDQW7IJN.js +0 -1
  67. package/dist/chunk-SEZETSB3.js +0 -1
  68. package/dist/chunk-U3NV72AL.js +0 -8
  69. package/dist/chunk-U4WIJZCX.js +0 -1
  70. package/dist/chunk-UIOSADPB.js +0 -1718
  71. package/dist/chunk-UNEJYZ4T.js +0 -102
  72. package/dist/chunk-VVSHZV5S.js +0 -73
  73. package/dist/chunk-WHVK6FUB.js +0 -11
  74. package/dist/chunk-WNUXMPVX.js +0 -1
  75. package/dist/chunk-XYUJTIPE.js +0 -203
  76. package/dist/chunk-YJPFEL2E.js +0 -1
  77. package/dist/chunk-Z4EHBBL4.js +0 -41
  78. package/dist/contracts-GUXGFCVV.js +0 -1
  79. package/dist/core-schema-Y47YFKH2.js +0 -899
  80. package/dist/dispatch-ILBBKOSW.js +0 -1
  81. package/dist/editorial-reading-room/assets/app.js +0 -433
  82. package/dist/editorial-reading-room/assets/index.html +0 -139
  83. package/dist/editorial-reading-room/assets/styles.css +0 -1091
  84. package/dist/index.d.cts +0 -607
  85. package/dist/media-artifacts-67CJWI7O.js +0 -1
  86. package/dist/metering-YHZ46GV4.js +0 -1
  87. package/dist/migrations-3AKEC5QE.js +0 -1
  88. package/dist/queue-VOGXQDPR.js +0 -1
  89. package/dist/reconciliation-OXBG7JA2.js +0 -1
  90. package/dist/report-artifact-offload-6OOY2PTM.js +0 -1
  91. package/dist/repository-CYXVMYAI.js +0 -1
  92. package/dist/repository-UKDA4IWP.js +0 -1
  93. package/dist/server-6HUBN3OR.js +0 -5547
  94. package/dist/settlement-3PS32LQO.js +0 -1
  95. package/dist/worker-NXRU3TT3.js +0 -1
  96. package/public/.well-known/xray-install.json +0 -183
  97. package/public/agent-install.md +0 -200
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import{k as E,l as j,m as N,n as D,r as L}from"../chunk-4HZXATHB.js";import"../chunk-KJQXUZ4Y.js";import"../chunk-6N5EC7ZO.js";import{g as v,j as b,k as U,l as T,m as K}from"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import{c as S}from"../chunk-OILOHQNL.js";import{a as h}from"../chunk-6FAMBNHY.js";import{Command as we}from"commander";import{spawn as Z}from"child_process";import{mkdir as ye,writeFile as he}from"fs/promises";import{basename as ke,join as X}from"path";function ne(e){return e.apiKey?.trim()||"sk_live_your_key"}function oe(e){return e.packageSpec?.trim()||"mcp-scraper@latest"}function P(e={}){return["-y","--package",oe(e),"mcp-scraper"]}function ie(e){let t={MCP_SCRAPER_API_KEY:ne(e)},s=e.browserProfileName?.trim();return s&&(t.BROWSER_AGENT_PROFILE_NAME=s),e.browserProfileSaveChanges===!0&&(t.BROWSER_AGENT_PROFILE_SAVE_CHANGES="true"),t}function H(){return["mcp","remove","mcp-scraper","-s","user"]}function B(){return["mcp","get","mcp-scraper"]}function q(e){let t=e.match(/^\s*Command:\s*(.+?)\s*$/m)?.[1];if(!t)return null;let s=e.match(/^\s*Args:\s*(.*?)\s*$/m)?.[1]??"",i=s.length?s.split(/\s+/):[],p={},u=e.split(/^\s*Environment:\s*$/m)[1];if(u)for(let a of u.split(`
3
- `)){let o=a.match(/^\s{2,}([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);if(!o){if(a.trim().length&&!/^\s{2,}/.test(a))break;continue}p[o[1]]=o[2]}return{command:t,args:i,env:p}}function O(e){let t=["mcp","add","mcp-scraper","--scope","user"];for(let[s,i]of Object.entries(e.env))t.push("--env",`${s}=${i}`);return t.push("--",e.command,...e.args),t}function G(e={}){let t=["mcp","add","mcp-scraper","--scope","user"];for(let[s,i]of Object.entries(ie(e)))t.push("--env",`${s}=${i}`);return t.push("--","npx",...P(e)),t}function M(e){if(e==="claude-code")return"claude";if(e==="claude"||U.hosts.some(t=>t.id===e))return e;throw new Error('Unknown host "'+e+'". Use: codex, claude, claude-code, claude-desktop, cursor, windsurf, cline, or user-action-only')}function ae(e){return T(e==="claude"?"claude-code":e)}function J(e,t={}){let s=M(e),i=ae(s),p="Restart the MCP client so it starts a fresh npx process.",u='MCP_SCRAPER_API_KEY="$MCP_SCRAPER_API_KEY" npx -y -p mcp-scraper@latest mcp-scraper-cli agent install claude --apply',a=`X-Ray install protocol: ${v} (${b})`;return s==="codex"?["# Codex MCP config",a,i.exactConfig,"",`Continuation: ${i.continuation}`,`Rollback: ${i.rollback}`,"",p].join(`
4
- `):s==="claude"?["# Claude Code command",a,i.exactConfig,"","# One-command Claude Code setup",u,"",`Continuation: ${i.continuation}`,`Rollback: ${i.rollback}`,"",p].join(`
5
- `):s==="claude-desktop"?["# Claude Desktop config",a,i.exactConfig,"","Desktop Extension: https://mcpscraper.dev/downloads/mcp-scraper.mcpb",`Continuation: ${i.continuation}`,`Rollback: ${i.rollback}`,p].join(`
6
- `):[`# ${i.label} MCP config`,a,i.exactConfig,"",`Continuation: ${i.continuation}`,`Rollback: ${i.rollback}`,...i.restartRequired?["",p]:[]].join(`
7
- `)}import{access as se,mkdir as ce,readFile as pe}from"fs/promises";import{homedir as ue}from"os";import{join as le}from"path";function de(e,t=!1){return e?"pass":t?"warn":"fail"}function me(e){return Number(e.replace(/^v/,"").split(".")[0]??0)}async function fe(){let e=process.env.MCP_SCRAPER_KEY_PATH?.trim()||le(ue(),".mcp-scraper-key");try{return(await pe(e,"utf8")).trim()||null}catch{return null}}async function ge(e){try{let t=await e("https://registry.npmjs.org/mcp-scraper/latest",{signal:AbortSignal.timeout(5e3)});return t.ok?(await t.json()).version??null:null}catch{return null}}async function Y(e={}){let t=e.fetchImpl??fetch,s=(e.apiUrl??process.env.MCP_SCRAPER_API_URL??"https://mcpscraper.dev").replace(/\/$/,""),i=S(e.outputDir),p=e.apiKey?.trim()||process.env.MCP_SCRAPER_API_KEY?.trim()||await fe(),u=await ge(t),a=[],o=me(process.version);a.push({id:"node",label:"Node.js",status:de(o>=20),detail:process.version,fix:o>=20?void 0:"Install Node.js 20 or newer."}),a.push({id:"package_version",label:"Local package",status:u&&u!==h?"warn":"pass",detail:u?`local ${h}, npm latest ${u}`:`local ${h}, npm latest unavailable`,fix:u&&u!==h?"Use mcp-scraper@latest and restart the MCP client.":void 0}),a.push({id:"api_key",label:"API key",status:p?"pass":"warn",detail:p?"configured":"not configured",fix:p?void 0:"Set MCP_SCRAPER_API_KEY or pass --api-key."});try{await ce(i,{recursive:!0}),await se(i),a.push({id:"output_dir",label:"Output directory",status:"pass",detail:i})}catch(r){a.push({id:"output_dir",label:"Output directory",status:"fail",detail:i,fix:r instanceof Error?r.message:"Create a writable output directory."})}if(p)try{let r=await t(`${s}/me`,{headers:{"x-api-key":p},signal:AbortSignal.timeout(8e3)});a.push({id:"api_reachability",label:"Hosted API",status:r.ok?"pass":"fail",detail:`${s}/me returned ${r.status}`,fix:r.ok?void 0:"Verify the API key and account status."})}catch(r){a.push({id:"api_reachability",label:"Hosted API",status:"fail",detail:r instanceof Error?r.message:String(r),fix:"Check network access and MCP_SCRAPER_API_URL."})}else a.push({id:"api_reachability",label:"Hosted API",status:"skip",detail:"skipped because no API key is configured"});return a.push({id:"mcp_config",label:"MCP command",status:"pass",detail:`npx ${P().join(" ")}`,fix:"Restart the MCP client after package updates."}),{ok:a.every(r=>r.status==="pass"||r.status==="skip"),version:h,npmLatest:u,checks:a,recommendedConfig:{command:"npx",args:P(),env:{MCP_SCRAPER_API_KEY:p?"$MCP_SCRAPER_API_KEY":"sk_live_your_key"}}}}function W(e){let t={pass:"PASS",warn:"WARN",fail:"FAIL",skip:"SKIP"};return[`mcp-scraper doctor v${e.version}`,"",...e.checks.map(i=>{let p=i.fix?`
8
- Fix: ${i.fix}`:"";return`[${t[i.status]}] ${i.label}: ${i.detail}${p}`}),"",`Recommended MCP command: npx ${e.recommendedConfig.args.join(" ")}`].join(`
9
- `)}var F={"xray-setup":K(),"agent-packet":["# MCP Scraper Agent Packet Prompt","","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.","","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."].join(`
2
+ import{Command as Fe}from"commander";import{spawn as ye}from"node:child_process";import{mkdir as ze,writeFile as Ze}from"node:fs/promises";import{basename as Qe,join as ge}from"node:path";var v="0.98.0";import{createHash as Ce}from"node:crypto";var C="3.0.0",W="analytics_setup_site",Re="Install X-Ray",X="Install X-Ray and tell me what drives customers",P=`Call ${W} with { "startUrl": "<site URL>", "goal": "${X}" }; on every later call, reuse the returned setupId and revision.`,D=JSON.stringify({mcpServers:{"mcp-scraper":{command:"npx",args:["-y","--package","mcp-scraper@latest","mcp-scraper"],env:{MCP_SCRAPER_API_KEY:"${env:MCP_SCRAPER_API_KEY}"}}}},null,2),_e=JSON.stringify({mcpServers:{"mcp-scraper":{url:"https://mcpscraper.dev/mcp"}}},null,2),G={schema:"https://mcpscraper.dev/schemas/xray-install-contract-v1.json",protocolVersion:C,product:"Thorbit X-Ray",triggerPhrases:[Re,"Install X-Ray on this site and tell me what drives customers"],canonicalUrls:{markdown:"https://mcpscraper.dev/agent-install.md",json:"https://mcpscraper.dev/.well-known/xray-install.json",hostedMcp:"https://mcpscraper.dev/mcp"},facade:{tool:W,initialCall:{startUrl:"<site URL>",goal:X},continuationRule:"Treat setupId plus revision as the durable continuation handle. Reuse both after approval, runtime publication, verification, retry, or return."},topologies:{local_stdio:{label:"Local npm MCP process",authority:"The client starts MCP Scraper locally. After the one-time Pixel insertion, X-Ray setup publishes only owner-confirmed runtime configuration.",secretHandling:"Set MCP_SCRAPER_API_KEY in the local environment or client secret field. Never paste it into chat or commit it."},hosted_remote:{label:"Hosted authenticated MCP",authority:"The hosted MCP inspects approved public pages and publishes owner-confirmed X-Ray runtime configuration. It cannot read or write a caller-local path.",secretHandling:"Use the MCP OAuth/connection flow. Never put a credential in a URL, prompt, or config shown in chat."}},hosts:[{id:"codex",label:"Codex",supportedTopologies:["local_stdio","hosted_remote"],mcpConfigMode:"toml",workspaceRead:!0,workspaceWrite:!0,restartRequired:!0,exactConfig:`[mcp_servers.mcp-scraper]
3
+ command = "npx"
4
+ args = ["-y", "--package", "mcp-scraper@latest", "mcp-scraper"]
5
+ env_vars = ["MCP_SCRAPER_API_KEY"]`,continuation:P,rollback:"Remove the mcp_servers.mcp-scraper table, then restart Codex. Pixel removal is a separate owner-controlled site action."},{id:"claude-code",label:"Claude Code",supportedTopologies:["local_stdio","hosted_remote"],mcpConfigMode:"command",workspaceRead:!0,workspaceWrite:!0,restartRequired:!0,exactConfig:'claude mcp add mcp-scraper --scope user --env MCP_SCRAPER_API_KEY="$MCP_SCRAPER_API_KEY" -- npx -y --package mcp-scraper@latest mcp-scraper',continuation:P,rollback:"Run: claude mcp remove mcp-scraper -s user. Pixel removal is a separate owner-controlled site action."},{id:"claude-desktop",label:"Claude Desktop",supportedTopologies:["local_stdio"],mcpConfigMode:"desktop_extension",workspaceRead:!1,workspaceWrite:!1,restartRequired:!0,exactConfig:"Install https://mcpscraper.dev/downloads/mcp-scraper.mcpb and enter the API key only in Claude Desktop's protected configuration field.",continuation:P,rollback:"Remove the MCP Scraper extension in Claude Desktop. Pixel removal is a separate owner-controlled site action."},{id:"cursor",label:"Cursor",supportedTopologies:["local_stdio","hosted_remote"],mcpConfigMode:"json",workspaceRead:!0,workspaceWrite:!0,restartRequired:!0,exactConfig:D,continuation:P,rollback:"Remove mcp-scraper from .cursor/mcp.json (or the user MCP settings), then restart Cursor."},{id:"windsurf",label:"Windsurf",supportedTopologies:["local_stdio"],mcpConfigMode:"json",workspaceRead:!0,workspaceWrite:!0,restartRequired:!0,exactConfig:D,continuation:P,rollback:"Remove mcp-scraper from the Windsurf MCP configuration, then restart Windsurf."},{id:"cline",label:"Cline",supportedTopologies:["local_stdio"],mcpConfigMode:"json",workspaceRead:!0,workspaceWrite:!0,restartRequired:!0,exactConfig:D,continuation:P,rollback:"Remove mcp-scraper from Cline MCP Servers, then restart the extension host."},{id:"user-action-only",label:"User-action-only MCP host",supportedTopologies:["hosted_remote"],mcpConfigMode:"handoff_only",workspaceRead:!1,workspaceWrite:!1,restartRequired:!1,exactConfig:_e,continuation:P,rollback:"Disconnect MCP Scraper from the host. Pixel removal remains an owner-controlled site action."}],tagBootstrap:{requirement:"one_global_pixel_once",ongoingConfiguration:"xray_runtime_manifest",sourceAccessRequiredAfterInstall:!1,supportedInsertionPaths:["manual","tag_manager","cms_plugin","coding_host"]},workflow:["Resolve Thorbit X-Ray entitlement and the target site without consuming MCP Scraper Credits.","Inspect the whole approved public site; enumerate distinct renderers, routes, and Pixel coverage.","Infer the business model and customer outcome, then propose one privacy-safe measurement plan.","If the Pixel is absent, return the canonical one-time tag and exact uncovered surfaces; do not claim ongoing control there.","Publish an approved measurement revision as a digest-bound runtime manifest; source or repository access is not required.","For SaaS, verify the measurement foundation: global tag coverage, ad-source capture, lifecycle events, currency and timezone, consent, and observed outcome events.","Optionally publish a structured post-purchase survey after approved disclosure evidence; reported influence remains separate from deterministic touches.","Compare observed attribution with customer-reported influence only with explicit weights totaling 1,000,000 micros and a named methodology version.","Keep view-through reporting unavailable until event-level provider evidence can join to an eligible X-Ray subject; aggregate impressions never qualify.","Verify route coverage, consent, PageViews, manifest convergence, bounded rule matches, and persisted definition/version evidence.","Require a separate exact-revision approval before any canary or real-visitor experiment activation.","Return first customer-journey answers when sufficient, or exact evidence-readiness thresholds when not.","On \u201Cimprove\u201D, propose one highest-value uncovered outcome, require approval when needed, and reverify."],safetyRules:["Never invent or hand-author tracking code; use the canonical product-authored Pixel snippet returned by analytics_setup_site.","Never ask for or print API keys, OAuth tokens, signed URLs, cookies, or other credentials.","Never call ad platforms directly or silently activate events, definitions, funnels, or delivery destinations.","Never treat a CTA click as a customer, sale, or revenue event without verified outcome evidence.","Never claim full-domain installation while an in-scope route or renderer is missing, excluded without approval, or unverifiable.","Never treat event-name presence as verification; require persisted definition ID and version evidence.","Never accept survey invitation tokens or exchange codes through MCP inputs or prompts.","Never treat customer-reported influence as identity, CRM mutation authority, ad-activation evidence, or a deterministic touch."],terminalStates:["complete","tag_required","input_required","approval_required","runtime_convergence_required","not_ready","error"]};function U(e){return Array.isArray(e)?`[${e.map(U).join(",")}]`:e&&typeof e=="object"?`{${Object.entries(e).sort(([r],[i])=>r.localeCompare(i)).map(([r,i])=>`${JSON.stringify(r)}:${U(i)}`).join(",")}}`:JSON.stringify(e)}function be(){return U(G)}var R=`sha256:${Ce("sha256").update(be(),"utf8").digest("hex")}`,q={...G,protocolDigest:R};function V(e){let r=q.hosts.find(i=>i.id===e);if(!r)throw new Error(`Unknown X-Ray host: ${e}`);return r}function F(){return["# Install X-Ray","",`Protocol: ${C} (${R})`,"",`Use ${W} as the single conversational setup facade. Ask for the target site URL if it is missing, then call it with the goal \u201C${X}\u201D.`,"","Continue the same setup with its setupId and revision. Confirm one global Pixel across all public rendering surfaces; present one measurement plan; publish it as a digest-bound runtime manifest after exact-revision approval; verify manifest and rule versions; then return first customer-journey answers or explicit readiness thresholds. For SaaS, verify the provider-neutral measurement foundation. Browser events never prove paid revenue. GitHub or repository access is not required after the tag is installed.","","Do not request pasted credentials, invent tracking code, silently activate measurement, call ad platforms directly, or claim customers/revenue from CTA clicks."].join(`
6
+ `)}function Ae(e){return e.apiKey?.trim()||"sk_live_your_key"}function Se(e){return e.packageSpec?.trim()||"mcp-scraper@latest"}function _(e={}){return["-y","--package",Se(e),"mcp-scraper"]}function xe(e){let r={MCP_SCRAPER_API_KEY:Ae(e)},i=e.browserProfileName?.trim();return i&&(r.BROWSER_AGENT_PROFILE_NAME=i),e.browserProfileSaveChanges===!0&&(r.BROWSER_AGENT_PROFILE_SAVE_CHANGES="true"),r}function z(){return["mcp","remove","mcp-scraper","-s","user"]}function Z(){return["mcp","get","mcp-scraper"]}function Q(e){let r=e.match(/^\s*Command:\s*(.+?)\s*$/m)?.[1];if(!r)return null;let i=e.match(/^\s*Args:\s*(.*?)\s*$/m)?.[1]??"",n=i.length?i.split(/\s+/):[],a={},c=e.split(/^\s*Environment:\s*$/m)[1];if(c)for(let l of c.split(`
7
+ `)){let s=l.match(/^\s{2,}([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);if(!s){if(l.trim().length&&!/^\s{2,}/.test(l))break;continue}a[s[1]]=s[2]}return{command:r,args:n,env:a}}function K(e){let r=["mcp","add","mcp-scraper","--scope","user"];for(let[i,n]of Object.entries(e.env))r.push("--env",`${i}=${n}`);return r.push("--",e.command,...e.args),r}function ee(e={}){let r=["mcp","add","mcp-scraper","--scope","user"];for(let[i,n]of Object.entries(xe(e)))r.push("--env",`${i}=${n}`);return r.push("--","npx",..._(e)),r}function H(e){if(e==="claude-code")return"claude";if(e==="claude"||q.hosts.some(r=>r.id===e))return e;throw new Error('Unknown host "'+e+'". Use: codex, claude, claude-code, claude-desktop, cursor, windsurf, cline, or user-action-only')}function Ie(e){return V(e==="claude"?"claude-code":e)}function re(e,r={}){let i=H(e),n=Ie(i),a="Restart the MCP client so it starts a fresh npx process.",c='MCP_SCRAPER_API_KEY="$MCP_SCRAPER_API_KEY" npx -y -p mcp-scraper@latest mcp-scraper-cli agent install claude --apply',l=`X-Ray install protocol: ${C} (${R})`;return i==="codex"?["# Codex MCP config",l,n.exactConfig,"",`Continuation: ${n.continuation}`,`Rollback: ${n.rollback}`,"",a].join(`
8
+ `):i==="claude"?["# Claude Code command",l,n.exactConfig,"","# One-command Claude Code setup",c,"",`Continuation: ${n.continuation}`,`Rollback: ${n.rollback}`,"",a].join(`
9
+ `):i==="claude-desktop"?["# Claude Desktop config",l,n.exactConfig,"","Desktop Extension: https://mcpscraper.dev/downloads/mcp-scraper.mcpb",`Continuation: ${n.continuation}`,`Rollback: ${n.rollback}`,a].join(`
10
+ `):[`# ${n.label} MCP config`,l,n.exactConfig,"",`Continuation: ${n.continuation}`,`Rollback: ${n.rollback}`,...n.restartRequired?["",a]:[]].join(`
11
+ `)}import{homedir as $e}from"node:os";import{join as Ee}from"node:path";function k(e){return e?.trim()||process.env.MCP_SCRAPER_OUTPUT_DIR?.trim()||Ee($e(),"Downloads","mcp-scraper")}import{access as Oe,mkdir as Me,readFile as je}from"node:fs/promises";import{homedir as Te}from"node:os";import{join as Ne}from"node:path";function Le(e,r=!1){return e?"pass":r?"warn":"fail"}function De(e){return Number(e.replace(/^v/,"").split(".")[0]??0)}async function Ue(){let e=process.env.MCP_SCRAPER_KEY_PATH?.trim()||Ne(Te(),".mcp-scraper-key");try{return(await je(e,"utf8")).trim()||null}catch{return null}}async function We(e){try{let r=await e("https://registry.npmjs.org/mcp-scraper/latest",{signal:AbortSignal.timeout(5e3)});return r.ok?(await r.json()).version??null:null}catch{return null}}async function te(e={}){let r=e.fetchImpl??fetch,i=(e.apiUrl??process.env.MCP_SCRAPER_API_URL??"https://mcpscraper.dev").replace(/\/$/,""),n=k(e.outputDir),a=e.apiKey?.trim()||process.env.MCP_SCRAPER_API_KEY?.trim()||await Ue(),c=await We(r),l=[],s=De(process.version);l.push({id:"node",label:"Node.js",status:Le(s>=20),detail:process.version,fix:s>=20?void 0:"Install Node.js 20 or newer."}),l.push({id:"package_version",label:"Local package",status:c&&c!==v?"warn":"pass",detail:c?`local ${v}, npm latest ${c}`:`local ${v}, npm latest unavailable`,fix:c&&c!==v?"Use mcp-scraper@latest and restart the MCP client.":void 0}),l.push({id:"api_key",label:"API key",status:a?"pass":"warn",detail:a?"configured":"not configured",fix:a?void 0:"Set MCP_SCRAPER_API_KEY or pass --api-key."});try{await Me(n,{recursive:!0}),await Oe(n),l.push({id:"output_dir",label:"Output directory",status:"pass",detail:n})}catch(t){l.push({id:"output_dir",label:"Output directory",status:"fail",detail:n,fix:t instanceof Error?t.message:"Create a writable output directory."})}if(a)try{let t=await r(`${i}/me`,{headers:{"x-api-key":a},signal:AbortSignal.timeout(8e3)});l.push({id:"api_reachability",label:"Hosted API",status:t.ok?"pass":"fail",detail:`${i}/me returned ${t.status}`,fix:t.ok?void 0:"Verify the API key and account status."})}catch(t){l.push({id:"api_reachability",label:"Hosted API",status:"fail",detail:t instanceof Error?t.message:String(t),fix:"Check network access and MCP_SCRAPER_API_URL."})}else l.push({id:"api_reachability",label:"Hosted API",status:"skip",detail:"skipped because no API key is configured"});return l.push({id:"mcp_config",label:"MCP command",status:"pass",detail:`npx ${_().join(" ")}`,fix:"Restart the MCP client after package updates."}),{ok:l.every(t=>t.status==="pass"||t.status==="skip"),version:v,npmLatest:c,checks:l,recommendedConfig:{command:"npx",args:_(),env:{MCP_SCRAPER_API_KEY:a?"$MCP_SCRAPER_API_KEY":"sk_live_your_key"}}}}function ne(e){let r={pass:"PASS",warn:"WARN",fail:"FAIL",skip:"SKIP"};return[`mcp-scraper doctor v${e.version}`,"",...e.checks.map(n=>{let a=n.fix?`
12
+ Fix: ${n.fix}`:"";return`[${r[n.status]}] ${n.label}: ${n.detail}${a}`}),"",`Recommended MCP command: npx ${e.recommendedConfig.args.join(" ")}`].join(`
13
+ `)}var oe={"xray-setup":F(),"agent-packet":["# MCP Scraper Agent Packet Prompt","","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.","","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."].join(`
10
14
  `),"local-competitive-audit":["# MCP Scraper Local Competitive Audit Prompt","","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.","","Ground recommendations in Maps rank position, review count, star rating, categories, profile details, and review topics. Do not overstate heuristic opportunity scores."].join(`
11
15
  `),"directory-workflow":["# MCP Scraper Directory Workflow Prompt","","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."].join(`
12
16
  `),"map-comparison":["# MCP Scraper Maps Comparison Prompt","","Run the map-comparison workflow when the user wants to compare local Maps competitors in a city or across selected markets. Use `maps-results.csv`, `map-comparison.csv`, and `profile-insights.csv` as source of truth.","","Ground recommendations in result_position, review_stars, review_count, category, website presence, review topics, and profile attributes. Treat review gaps and missing websites as opportunity signals, not guaranteed ranking factors."].join(`
@@ -15,25 +19,27 @@ import{k as E,l as j,m as N,n as D,r as L}from"../chunk-4HZXATHB.js";import"../c
15
19
  `),"ai-overview-language":["# MCP Scraper AI Overview Language Prompt","","Run the ai-overview-language workflow when the user wants to know how to phrase content for AI Overview inclusion. Use `claim-patterns.csv`, `language-guidance.csv`, `ai-overview-citations.csv`, and PAA follow-ups as evidence.","","Recommend concise answer blocks, criteria/step language, citation hooks, and follow-up sections. Do not claim the target will be cited; frame the output as evidence-based language guidance."].join(`
16
20
  `),"ai-citation-monitor":["# MCP Scraper AI Citation Monitor Prompt","","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."].join(`
17
21
  `),"serp-brief":["# MCP Scraper SERP Brief Prompt","","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."].join(`
18
- `)};function R(){return Object.keys(F).sort()}function V(e){let t=F[e];if(!t)throw new Error(`Unknown prompt "${e}". Available: ${R().join(", ")}`);return t}function d(e){if(e==null||e==="")return;let t=Number(e);return Number.isFinite(t)?t:void 0}function Pe(e){if(e!==void 0){if(typeof e=="boolean")return e;if(e==="true")return!0;if(e==="false")return!1}}function x(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0))}function Ce(e,t){return x(e==="agent-packet"||e==="serp-comparison"||e==="ai-overview-language"?{keyword:t.keyword,domain:t.domain,url:t.url,location:t.location,maxResults:d(t.maxResults),maxQuestions:d(t.maxQuestions),extractTop:d(t.extractTop),includeSerp:t.serp===!1?!1:void 0,includePaa:t.paa===!1?!1:void 0,includeAiOverview:Pe(t.includeAiOverview),returnPartial:t.returnPartial===!1?!1:void 0}:e==="paa-expansion-brief"?{keyword:t.keyword,location:t.location,maxQuestions:d(t.maxQuestions),depth:d(t.depth),returnPartial:t.returnPartial===!1?!1:void 0}:e==="directory"||e==="local-competitive-audit"||e==="map-comparison"?{query:t.query,location:t.location,state:t.state,minPopulation:d(t.minPop??t.minPopulation),maxCities:d(t.maxCities),maxResultsPerCity:d(t.perCity??t.maxResultsPerCity??t.maxResults),concurrency:d(t.concurrency),proxyMode:t.proxyMode,hydrateTop:e==="local-competitive-audit"||e==="map-comparison"?d(t.hydrateTop):void 0,maxReviews:e==="local-competitive-audit"||e==="map-comparison"?d(t.reviews??t.maxReviews):void 0,returnPartial:t.returnPartial===!1?!1:void 0}:t)}function c(e,t){t?process.stdout.write(`${JSON.stringify(e,null,2)}
22
+ `)};function O(){return Object.keys(oe).sort()}function ie(e){let r=oe[e];if(!r)throw new Error(`Unknown prompt "${e}". Available: ${O().join(", ")}`);return r}import{mkdir as S,readFile as qe,stat as Ke,writeFile as x}from"node:fs/promises";import{existsSync as He}from"node:fs";import{platform as Be}from"node:os";import{dirname as M,join as b}from"node:path";import{execFile as Ye}from"node:child_process";function Xe(e){if(e==null)return"";let r=String(e);return/[",\n\r]/.test(r)?`"${r.replace(/"/g,'""')}"`:r}function se(e,r){return[e.join(","),...r.map(i=>e.map(n=>Xe(i[n])).join(","))].join(`
23
+ `)+`
24
+ `}function ae(e){return e.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"")}async function ce(e){if((process.env.NODE_ENV==="test"||process.env.VITEST)&&process.env.MCP_SCRAPER_ENABLE_MEMORY_LIBRARY_SINK_IN_TESTS!=="1")return;let r=process.env.MCP_SCRAPER_API_KEY?.trim(),i=(process.env.MCP_SCRAPER_BASE_URL??"https://mcpscraper.dev").replace(/\/$/,"");if(r)try{let n=await fetch(`${i}/memory/mcp-call`,{method:"POST",headers:{"content-type":"application/json","x-api-key":r},body:JSON.stringify({toolName:"libraryIngestTool",args:e})}),a=await n.json().catch(()=>null);(!n.ok||a?.ok===!1)&&console.warn("[memory-library-sink] ingest not accepted:",n.status,a?.error??"")}catch(n){console.warn("[memory-library-sink] ingest failed:",n?.message)}}function le(){return new Date().toISOString().replace(/[:.]/g,"-")}function B(e){return ae(e).replace(/^-+|-+$/g,"").slice(0,80)||"run"}var j=class e{constructor(r,i,n,a,c,l,s){this.workflowId=r;this.title=i;this.runId=n;this.baseDir=a;this.runDir=c;this.startedAt=l;this.input=s}workflowId;title;runId;baseDir;runDir;startedAt;input;artifacts=[];static async create(r,i,n,a,c){let l=new Date().toISOString(),s=c?.trim()||`${le()}-${B(r)}-${Math.random().toString(36).slice(2,8)}`,t=String(n.keyword??n.query??n.domain??n.state??r),o=k(a),u=b(o,"workflows",r,`${le()}-${B(t)}-${B(s).slice(0,20)}`);return await S(u,{recursive:!0}),new e(r,i,s,o,u,l,n)}async remember(r,i,n,a){let c=await Ke(n).then(l=>l.size).catch(()=>{});return this.artifacts.push({kind:r,label:i,path:n,bytes:c,rows:a}),await ce({title:`${this.title} ${i}`,content:`artifact: ${n}`,source:`mcp-scraper-workflow:${this.workflowId}:${this.runId}`}),n}async writeJson(r,i,n){let a=b(this.runDir,i);return await S(M(a),{recursive:!0}),await x(a,JSON.stringify(n,null,2),"utf8"),this.remember("json",r,a)}async writeText(r,i,n,a="markdown"){let c=b(this.runDir,i);return await S(M(c),{recursive:!0}),await x(c,n,"utf8"),this.remember(a,r,c)}async writeCsv(r,i,n,a){let c=b(this.runDir,i);return await S(M(c),{recursive:!0}),await x(c,se(n,a),"utf8"),this.remember("csv",r,c,a.length)}async writeHtml(r,i,n){return this.writeText(r,i,n,"html_report")}async writeManifest(r,i,n,a){let c={workflow:this.workflowId,title:this.title,runId:this.runId,status:r,startedAt:this.startedAt,completedAt:r==="running"?null:new Date().toISOString(),input:this.input,artifacts:this.artifacts,warnings:n,errors:a,counts:i},l=b(this.runDir,"manifest.json");return await x(l,JSON.stringify(c,null,2),"utf8"),await Je(c,l,this.baseDir),l}};function pe(e=k()){return b(e,"workflows","index.json")}async function ue(e){let r=pe(e);if(!He(r))return{runs:[]};try{return JSON.parse(await qe(r,"utf8"))}catch{return{runs:[]}}}async function Je(e,r,i){if(e.status==="running")return;let n=pe(i),a=await ue(i),c=e.artifacts.find(s=>s.kind==="html_report")?.path??null,l={workflow:e.workflow,runId:e.runId,status:e.status,startedAt:e.startedAt,reportPath:c,manifestPath:r,summary:`${e.title} (${e.status})`};a.runs=[l,...a.runs.filter(s=>s.runId!==e.runId)].slice(0,200),await S(M(n),{recursive:!0}),await x(n,JSON.stringify(a,null,2),"utf8")}async function T(e){return(await ue(k(e))).runs}async function I(e,r){let i=await T(r);return e==="last"?i[0]??null:i.find(n=>n.runId===e)??null}async function Y(e,r){let i=await I(e,r);if(!i?.reportPath)throw new Error(`No report found for "${e}"`);return Be()==="darwin"&&await new Promise((n,a)=>{Ye("open",[i.reportPath],c=>c?a(c):n())}),i.reportPath}var de=[{id:"directory",title:"Directory Workflow",description:"Select city markets and export Google Maps business candidates."},{id:"get-leads",title:"Get Leads",description:"Build an outreach-ready local lead list: Google Maps search for a niche in a market, confirm review counts + booking URLs, then visit each business website (home + contact) to harvest email and social links. An opt-in SERP fallback can recover exact-domain public email occurrences after bounded pages are empty. Saves a leads CSV."},{id:"agent-packet",title:"Agent-Ready SEO Packet",description:"Create an evidence folder for AI agents from live SERP/PAA/AI search surfaces."},{id:"local-competitive-audit",title:"Local Competitive Audit",description:"Audit local Maps competitors, categories, review counts, and review themes across city markets."},{id:"map-comparison",title:"Maps Comparison",description:"Compare Google Maps competitors by rank, reviews, stars, categories, websites, and profile/review signals."},{id:"serp-comparison",title:"SERP Comparison",description:"Compare ranking pages, SERP features, PAA evidence, AI Overview citations, and page-level content gaps."},{id:"paa-expansion-brief",title:"PAA Expansion Brief",description:"Expand People Also Ask questions into an evidence-backed writer brief, section map, and source table."},{id:"ai-overview-language",title:"AI Overview Language Brief",description:"Turn AI Overview, citation, PAA, and ranking-page evidence into answer-block and citation-hook guidance."}];import{basename as Ve}from"node:path";function me(){return de}async function fe(e,r,i={}){let n=i.apiKey?.trim()||process.env.MCP_SCRAPER_API_KEY?.trim();if(!n)throw new Error("MCP_SCRAPER_API_KEY is required");let a=(i.apiUrl||process.env.MCP_SCRAPER_API_URL||"https://mcpscraper.dev").replace(/\/$/,""),c=new URL(a);if(!["http:","https:"].includes(c.protocol)||c.username||c.password||c.search||c.hash)throw new Error("Invalid workflow endpoint");if(!/^[a-zA-Z0-9_-]{1,128}$/.test(e))throw new Error("Invalid workflow identity");let l=i.fetchImpl??fetch,s=async(m,f)=>{let d=await l(a+m,{method:f===void 0?"GET":"POST",headers:{...i.headers,"x-api-key":n,...f===void 0?{}:{"content-type":"application/json"}},body:f===void 0?void 0:JSON.stringify(f),signal:i.signal});if(!d.ok)throw new Error(`Workflow HTTP ${d.status}; inspect the original run before retrying`);return d.json()},t=await s("/workflows/run",{workflowId:e,input:r}),o=0;for(;t.done===!1&&typeof t.run?.id=="string";){if(++o>50)throw new Error(`Workflow ${t.run.id} has more steps than this client supports`);t=await s(`/workflows/runs/${encodeURIComponent(t.run.id)}/step`,{})}if(!t.summary)throw new Error(`Workflow ${t.run?.id??e} has no terminal summary; inspect the saved run before retrying`);if(!/^[a-zA-Z0-9_-]{1,128}$/.test(e)||!/^[a-zA-Z0-9_-]{1,128}$/.test(String(t.run?.id||"")))throw new Error("Invalid workflow result identity");let u=t.summary,y=await j.create(e,u.title,r,i.outputDir,t.run?.id),g=null;for(let m of t.run?.artifacts??[]){if(typeof m.id!="string")continue;let f=await l(`${a}/workflows/runs/${encodeURIComponent(t.run.id)}/artifacts/${encodeURIComponent(m.id)}`,{headers:{"x-api-key":n},signal:i.signal});if(!f.ok)throw new Error(`Workflow result download failed; use runs download ${t.run.id}`);let d=Ve(String(m.path||m.id)).replace(/[^\w.\-]/g,"_");if(!d||d==="."||d==="..")throw new Error("Invalid workflow artifact name");let w=await y.writeText(String(m.label||d),d,await f.text(),m.kind);m.kind==="html_report"&&(g=w)}return await y.writeManifest(u.status,u.counts||{},u.warnings||[],u.errors||[]),{...u,reportPath:g}}function h(e){if(e==null||e==="")return;let r=Number(e);return Number.isFinite(r)?r:void 0}function er(e){if(e!==void 0){if(typeof e=="boolean")return e;if(e==="true")return!0;if(e==="false")return!1}}function N(e){return Object.fromEntries(Object.entries(e).filter(([,r])=>r!==void 0))}function rr(e,r){return N(e==="agent-packet"||e==="serp-comparison"||e==="ai-overview-language"?{keyword:r.keyword,domain:r.domain,url:r.url,location:r.location,maxResults:h(r.maxResults),maxQuestions:h(r.maxQuestions),extractTop:h(r.extractTop),includeSerp:r.serp===!1?!1:void 0,includePaa:r.paa===!1?!1:void 0,includeAiOverview:er(r.includeAiOverview),returnPartial:r.returnPartial===!1?!1:void 0}:e==="paa-expansion-brief"?{keyword:r.keyword,location:r.location,maxQuestions:h(r.maxQuestions),depth:h(r.depth),returnPartial:r.returnPartial===!1?!1:void 0}:e==="directory"||e==="local-competitive-audit"||e==="map-comparison"?{query:r.query,location:r.location,state:r.state,minPopulation:h(r.minPop??r.minPopulation),maxCities:h(r.maxCities),maxResultsPerCity:h(r.perCity??r.maxResultsPerCity??r.maxResults),concurrency:h(r.concurrency),proxyMode:r.proxyMode,hydrateTop:e==="local-competitive-audit"||e==="map-comparison"?h(r.hydrateTop):void 0,maxReviews:e==="local-competitive-audit"||e==="map-comparison"?h(r.reviews??r.maxReviews):void 0,returnPartial:r.returnPartial===!1?!1:void 0}:r)}function p(e,r){r?process.stdout.write(`${JSON.stringify(e,null,2)}
19
25
  `):typeof e=="string"?process.stdout.write(`${e}
20
26
  `):process.stdout.write(`${JSON.stringify(e,null,2)}
21
- `)}function z(e){return e.replace(/sk_[A-Za-z0-9_-]+/g,"sk_***")}function A(e,t){return new Promise(s=>{let i=Z(e,t,{stdio:["ignore","pipe","pipe"]}),p=[],u=[],a=!1,o=r=>{a||(a=!0,s(r))};i.stdout.on("data",r=>p.push(Buffer.from(r))),i.stderr.on("data",r=>u.push(Buffer.from(r))),i.on("error",r=>{o({code:127,stdout:"",stderr:r.message})}),i.on("close",r=>{o({code:typeof r=="number"?r:1,stdout:Buffer.concat(p).toString("utf8"),stderr:Buffer.concat(u).toString("utf8")})})})}function ee(e){let t=String(e.apiKey??process.env.MCP_SCRAPER_API_KEY??"").trim();if(!t)throw new Error("MCP_SCRAPER_API_KEY is required. Pass --api-key or set the environment variable.");return{apiUrl:String(e.apiUrl??process.env.MCP_SCRAPER_API_URL??"https://mcpscraper.dev").replace(/\/$/,""),apiKey:t}}function Q(e){let t=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",s=process.platform==="win32"?["/c","start","",e]:[e];try{return Z(t,s,{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function C(e,t,s,i){let{apiUrl:p,apiKey:u}=ee(s),a=await fetch(`${p}${e}`,{method:t,headers:{"Content-Type":"application/json","x-api-key":u},body:i==null?void 0:JSON.stringify(i)}),o=await a.json().catch(()=>({}));if(!a.ok){let r=typeof o.error=="string"?o.error:`API request failed with ${a.status}`;throw new Error(r)}return o}function ve(){let e=new we;e.name("mcp-scraper-cli").description("Human CLI for MCP Scraper setup, workflows, reports, and agent-ready SEO artifacts.").version(h),e.command("doctor").description("Check 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 o=>{let r=await Y({apiKey:o.apiKey,apiUrl:o.apiUrl,outputDir:o.outputDir});c(o.json?r:W(r),o.json),r.ok||(process.exitCode=1)});let t=e.command("billing").description("Inspect billing and start checkout flows."),s=t.command("concurrency").description("Manage MCP Scraper concurrency packs.");s.command("info").description("Show current concurrency limit and concurrency-pack price.").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 o=>{let r=await C("/billing/credits","POST",o,{});if(o.json)c(r.concurrency,!0);else{let n=r.concurrency?.upgrade;c([`Current limit: ${r.concurrency?.current_limit??"unknown"} concurrent operations`,`Extra slots: ${r.concurrency?.current_extra_slots??"unknown"}`,`Concurrency pack: ${n?.price_label??"$5/month for 2 extra browsers"}`,`Upgrade command: ${n?.terminal_command??"mcp-scraper-cli billing concurrency checkout"}`].join(`
22
- `),!1)}}),s.command("checkout").description("Create or update a hosted Stripe checkout for concurrency packs (two browsers per pack).").argument("[quantity]","Desired pack quantity","1").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").option("--no-open","Print the checkout URL without opening a browser").action(async(o,r)=>{let n=await C("/billing/concurrency/terminal-checkout","POST",r,{quantity:Number(o)});if(r.json){c(n,!0);return}let l=!!n.checkout_url&&r.open!==!1&&Q(n.checkout_url);c([`Concurrency pack: ${n.price?.price_label??"$5/month for 2 extra browsers"}`,n.updated?"Concurrency pack quantity updated.":l?`Opened checkout: ${n.checkout_url}`:`Checkout URL: ${n.checkout_url}`,n.next_step??"Complete checkout, then retry the MCP request."].join(`
23
- `),!1)}),t.command("subscribe <tier>").description("Subscribe to a plan (starter | growth | scale) via a hosted Stripe checkout link.").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").option("--no-open","Print the checkout URL without opening a browser").action(async(o,r)=>{let n=await C("/billing/subscribe/terminal-checkout","POST",r,{tier:String(o).toLowerCase()});if(r.json){c(n,!0);return}if(n.updated){c(n.message??`Switched to ${n.tier}.`,!1);return}let l=r.open!==!1&&!!n.checkout_url&&Q(n.checkout_url),k=n.billing_interval==="year"?"year":"month",w=n.amount_usd??n.monthly_usd,y=n.credits_per_interval??n.credits_per_month,m=k==="year"?"/yr":"/mo",f=k==="year"?"credits/yr":"credits/mo",g=[n.credits_never_expire?"credits never expire":null,n.includes_memory?"includes Memory Pro":null,n.intro].filter(Boolean).join(" \xB7 ");c([`Plan: ${n.label??o} \u2014 $${w}${m} \xB7 ${y?.toLocaleString()} ${f} \xB7 ${n.concurrency} concurrency${g?` \xB7 ${g}`:""}`,l?`Opened checkout: ${n.checkout_url}`:`Checkout URL: ${n.checkout_url}`,n.next_step??"Complete payment in the browser."].join(`
24
- `),!1)});let i=e.command("agent").description("Generate AI-agent install configs and workflow prompts.");i.command("install <host>").description("Print or apply install/config instructions for Codex, Claude, Cursor, Windsurf, Cline, or a handoff-only MCP host.").option("--api-key <key>","API key to place in generated config").option("--package <spec>","npm package spec","mcp-scraper@latest").option("--browser-profile <name>","Default saved browser profile for browser_open sessions.").option("--save-browser-profile-changes","Persist cookies and browser storage back to the named browser profile when sessions close").option("--apply","Apply the config to this client when supported. For Claude Code, this upserts the user-scope mcp-scraper server.").option("--json","Print machine-readable JSON").action(async(o,r)=>{let n=M(o),l=r.apiKey??process.env.MCP_SCRAPER_API_KEY;if(r.apply){if(n!=="claude")throw new Error('--apply is currently supported for Claude Code only. Use host "claude" or "claude-code".');if(!l?.trim())throw new Error("MCP_SCRAPER_API_KEY is required for --apply. Set it in the environment or pass --api-key.");let w={apiKey:l,packageSpec:r.package,browserProfileName:r.browserProfile,browserProfileSaveChanges:r.saveBrowserProfileChanges},y=await A("claude",B()),m=y.code===0?q(y.stdout):null,f=await A("claude",H()),g=await A("claude",G(w));if(g.code!==0){let te=z([g.stderr,g.stdout].filter(Boolean).join(`
25
- `).trim()),_;throw f.code!==0?_="No existing entry was removed, so your configuration is unchanged.":m?_=(await A("claude",O(m))).code===0?"Your previous mcp-scraper entry was restored; nothing was lost.":`WARNING: the previous mcp-scraper entry was removed and could NOT be restored. Re-add it with:
26
- claude `+O(m).join(" "):_="WARNING: the previous mcp-scraper entry was removed and could not be captured for rollback. Re-add it manually.",new Error(["Claude Code MCP registration failed.",te||"No error output returned.",_,"Make sure Claude Code is installed and the `claude` command is on PATH."].join(`
27
- `))}let I=await A("claude",["mcp","list"]),$={host:"claude",applied:!0,replacedExisting:f.code===0,command:"npx",args:P(w),nextStep:"Fully exit Claude Code, start a new Claude terminal, then run: claude mcp list",list:z([I.stdout,I.stderr].filter(Boolean).join(`
28
- `).trim())};if(r.json){c($,!0);return}c(["Applied Claude Code MCP config: mcp-scraper",f.code===0?"Replaced existing mcp-scraper entry.":"No existing mcp-scraper entry found; added a new one.","Command: npx "+P(w).join(" "),r.browserProfile?`Browser profile: ${r.browserProfile}`:"","","Next step: fully exit Claude Code, start a new Claude terminal, then run:"," claude mcp list","",$.list?`Current Claude MCP list:
29
- ${$.list}`:""].filter(Boolean).join(`
30
- `),!1);return}let k=J(n,{apiKey:r.apiKey,packageSpec:r.package,browserProfileName:r.browserProfile,browserProfileSaveChanges:r.saveBrowserProfileChanges});c(r.json?{host:n,protocolVersion:v,protocolDigest:b,text:k}:k,r.json)}),i.command("prompt [name]").description("Print an agent prompt template.").option("--json","Print machine-readable JSON").action((o,r)=>{if(!o){c(r.json?{prompts:R()}:R().join(`
31
- `),r.json);return}let n=V(o);c(r.json?{name:o,...o==="xray-setup"?{protocolVersion:v,protocolDigest:b}:{},text:n}:n,r.json)});let p=e.command("workflow").description("Run named SEO workflows.");p.command("list").description("List available workflows.").option("--json","Print machine-readable JSON").action(o=>{let r=D();o.json?c({workflows:r},!0):c(r.map(n=>`${n.id} ${n.title}
32
- ${n.description}`).join(`
33
- `),!1)}),p.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("--url <url>","Target page URL").option("--location <location>","Target location").option("--max-results <n>","Maximum SERP or Maps results").option("--max-questions <n>","Maximum PAA questions").option("--extract-top <n>","Top result pages or citation pages to extract").option("--depth <n>","PAA expansion depth").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(o,r)=>{let n=await L(o,Ce(o,r),{apiKey:r.apiKey,apiUrl:r.apiUrl,outputDir:r.outputDir});r.json?c(n,!0):c([`${n.title}: ${n.status}`,n.summary,n.reportPath?`Report: ${n.reportPath}`:"",n.warnings.length?`Warnings:
34
- ${n.warnings.map(l=>`- ${l}`).join(`
27
+ `)}function we(e){return e.replace(/sk_[A-Za-z0-9_-]+/g,"sk_***")}function $(e,r){return new Promise(i=>{let n=ye(e,r,{stdio:["ignore","pipe","pipe"]}),a=[],c=[],l=!1,s=t=>{l||(l=!0,i(t))};n.stdout.on("data",t=>a.push(Buffer.from(t))),n.stderr.on("data",t=>c.push(Buffer.from(t))),n.on("error",t=>{s({code:127,stdout:"",stderr:t.message})}),n.on("close",t=>{s({code:typeof t=="number"?t:1,stdout:Buffer.concat(a).toString("utf8"),stderr:Buffer.concat(c).toString("utf8")})})})}function ke(e){let r=String(e.apiKey??process.env.MCP_SCRAPER_API_KEY??"").trim();if(!r)throw new Error("MCP_SCRAPER_API_KEY is required. Pass --api-key or set the environment variable.");return{apiUrl:String(e.apiUrl??process.env.MCP_SCRAPER_API_URL??"https://mcpscraper.dev").replace(/\/$/,""),apiKey:r}}function he(e){let r=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",i=process.platform==="win32"?["/c","start","",e]:[e];try{return ye(r,i,{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function A(e,r,i,n){let{apiUrl:a,apiKey:c}=ke(i),l=await fetch(`${a}${e}`,{method:r,headers:{"Content-Type":"application/json","x-api-key":c},body:n==null?void 0:JSON.stringify(n)}),s=await l.json().catch(()=>({}));if(!l.ok){let t=typeof s.error=="string"?s.error:`API request failed with ${l.status}`;throw new Error(t)}return s}function tr(){let e=new Fe;e.name("mcp-scraper-cli").description("Human CLI for MCP Scraper setup, workflows, reports, and agent-ready SEO artifacts.").version(v),e.command("doctor").description("Check 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 s=>{let t=await te({apiKey:s.apiKey,apiUrl:s.apiUrl,outputDir:s.outputDir});p(s.json?t:ne(t),s.json),t.ok||(process.exitCode=1)});let r=e.command("billing").description("Inspect billing and start checkout flows."),i=r.command("concurrency").description("Manage MCP Scraper concurrency packs.");i.command("info").description("Show current concurrency limit and concurrency-pack price.").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 s=>{let t=await A("/billing/credits","POST",s,{});if(s.json)p(t.concurrency,!0);else{let o=t.concurrency?.upgrade;p([`Current limit: ${t.concurrency?.current_limit??"unknown"} concurrent operations`,`Extra slots: ${t.concurrency?.current_extra_slots??"unknown"}`,`Concurrency pack: ${o?.price_label??"$5/month for 2 extra browsers"}`,`Upgrade command: ${o?.terminal_command??"mcp-scraper-cli billing concurrency checkout"}`].join(`
28
+ `),!1)}}),i.command("checkout").description("Create or update a hosted Stripe checkout for concurrency packs (two browsers per pack).").argument("[quantity]","Desired pack quantity","1").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").option("--no-open","Print the checkout URL without opening a browser").action(async(s,t)=>{let o=await A("/billing/concurrency/terminal-checkout","POST",t,{quantity:Number(s)});if(t.json){p(o,!0);return}let u=!!o.checkout_url&&t.open!==!1&&he(o.checkout_url);p([`Concurrency pack: ${o.price?.price_label??"$5/month for 2 extra browsers"}`,o.updated?"Concurrency pack quantity updated.":u?`Opened checkout: ${o.checkout_url}`:`Checkout URL: ${o.checkout_url}`,o.next_step??"Complete checkout, then retry the MCP request."].join(`
29
+ `),!1)}),r.command("subscribe <tier>").description("Subscribe to a plan (starter | growth | scale) via a hosted Stripe checkout link.").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").option("--no-open","Print the checkout URL without opening a browser").action(async(s,t)=>{let o=await A("/billing/subscribe/terminal-checkout","POST",t,{tier:String(s).toLowerCase()});if(t.json){p(o,!0);return}if(o.updated){p(o.message??`Switched to ${o.tier}.`,!1);return}let u=t.open!==!1&&!!o.checkout_url&&he(o.checkout_url),y=o.billing_interval==="year"?"year":"month",g=o.amount_usd??o.monthly_usd,m=o.credits_per_interval??o.credits_per_month,f=y==="year"?"/yr":"/mo",d=y==="year"?"credits/yr":"credits/mo",w=[o.credits_never_expire?"credits never expire":null,o.includes_memory?"includes Memory Pro":null,o.intro].filter(Boolean).join(" \xB7 ");p([`Plan: ${o.label??s} \u2014 $${g}${f} \xB7 ${m?.toLocaleString()} ${d} \xB7 ${o.concurrency} concurrency${w?` \xB7 ${w}`:""}`,u?`Opened checkout: ${o.checkout_url}`:`Checkout URL: ${o.checkout_url}`,o.next_step??"Complete payment in the browser."].join(`
30
+ `),!1)});let n=e.command("agent").description("Generate AI-agent install configs and workflow prompts.");n.command("install <host>").description("Print or apply install/config instructions for Codex, Claude, Cursor, Windsurf, Cline, or a handoff-only MCP host.").option("--api-key <key>","API key to place in generated config").option("--package <spec>","npm package spec","mcp-scraper@latest").option("--browser-profile <name>","Default saved browser profile for browser_open sessions.").option("--save-browser-profile-changes","Persist cookies and browser storage back to the named browser profile when sessions close").option("--apply","Apply the config to this client when supported. For Claude Code, this upserts the user-scope mcp-scraper server.").option("--json","Print machine-readable JSON").action(async(s,t)=>{let o=H(s),u=t.apiKey??process.env.MCP_SCRAPER_API_KEY;if(t.apply){if(o!=="claude")throw new Error('--apply is currently supported for Claude Code only. Use host "claude" or "claude-code".');if(!u?.trim())throw new Error("MCP_SCRAPER_API_KEY is required for --apply. Set it in the environment or pass --api-key.");let g={apiKey:u,packageSpec:t.package,browserProfileName:t.browserProfile,browserProfileSaveChanges:t.saveBrowserProfileChanges},m=await $("claude",Z()),f=m.code===0?Q(m.stdout):null,d=await $("claude",z()),w=await $("claude",ee(g));if(w.code!==0){let Pe=we([w.stderr,w.stdout].filter(Boolean).join(`
31
+ `).trim()),E;throw d.code!==0?E="No existing entry was removed, so your configuration is unchanged.":f?E=(await $("claude",K(f))).code===0?"Your previous mcp-scraper entry was restored; nothing was lost.":`WARNING: the previous mcp-scraper entry was removed and could NOT be restored. Re-add it with:
32
+ claude `+K(f).join(" "):E="WARNING: the previous mcp-scraper entry was removed and could not be captured for rollback. Re-add it manually.",new Error(["Claude Code MCP registration failed.",Pe||"No error output returned.",E,"Make sure Claude Code is installed and the `claude` command is on PATH."].join(`
33
+ `))}let J=await $("claude",["mcp","list"]),L={host:"claude",applied:!0,replacedExisting:d.code===0,command:"npx",args:_(g),nextStep:"Fully exit Claude Code, start a new Claude terminal, then run: claude mcp list",list:we([J.stdout,J.stderr].filter(Boolean).join(`
34
+ `).trim())};if(t.json){p(L,!0);return}p(["Applied Claude Code MCP config: mcp-scraper",d.code===0?"Replaced existing mcp-scraper entry.":"No existing mcp-scraper entry found; added a new one.","Command: npx "+_(g).join(" "),t.browserProfile?`Browser profile: ${t.browserProfile}`:"","","Next step: fully exit Claude Code, start a new Claude terminal, then run:"," claude mcp list","",L.list?`Current Claude MCP list:
35
+ ${L.list}`:""].filter(Boolean).join(`
36
+ `),!1);return}let y=re(o,{apiKey:t.apiKey,packageSpec:t.package,browserProfileName:t.browserProfile,browserProfileSaveChanges:t.saveBrowserProfileChanges});p(t.json?{host:o,protocolVersion:C,protocolDigest:R,text:y}:y,t.json)}),n.command("prompt [name]").description("Print an agent prompt template.").option("--json","Print machine-readable JSON").action((s,t)=>{if(!s){p(t.json?{prompts:O()}:O().join(`
37
+ `),t.json);return}let o=ie(s);p(t.json?{name:s,...s==="xray-setup"?{protocolVersion:C,protocolDigest:R}:{},text:o}:o,t.json)});let a=e.command("workflow").description("Run named SEO workflows.");a.command("list").description("List available workflows.").option("--json","Print machine-readable JSON").action(s=>{let t=me();s.json?p({workflows:t},!0):p(t.map(o=>`${o.id} ${o.title}
38
+ ${o.description}`).join(`
39
+ `),!1)}),a.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("--url <url>","Target page URL").option("--location <location>","Target location").option("--max-results <n>","Maximum SERP or Maps results").option("--max-questions <n>","Maximum PAA questions").option("--extract-top <n>","Top result pages or citation pages to extract").option("--depth <n>","PAA expansion depth").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(s,t)=>{let o=await fe(s,rr(s,t),{apiKey:t.apiKey,apiUrl:t.apiUrl,outputDir:t.outputDir});t.json?p(o,!0):p([`${o.title}: ${o.status}`,o.summary,o.reportPath?`Report: ${o.reportPath}`:"",o.warnings.length?`Warnings:
40
+ ${o.warnings.map(u=>`- ${u}`).join(`
35
41
  `)}`:""].filter(Boolean).join(`
36
- `),!1)});let u=e.command("report").description("List and open local workflow reports.");u.command("list").description("List recent workflow reports.").option("--output-dir <path>","Workflow output directory").option("--json","Print machine-readable JSON").action(async o=>{let r=await E(o.outputDir);c(o.json?{reports:r}:r.map(n=>`${n.startedAt} ${n.workflow} ${n.status} ${n.reportPath??n.manifestPath}`).join(`
37
- `),o.json)}),u.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(o="last",r)=>{let n=await j(o,r.outputDir);if(!n?.reportPath)throw new Error(`No report found for "${o}"`);c(r.json?{path:n.reportPath,run:n}:n.reportPath,r.json)}),u.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(o="last",r)=>{if(r.json){let l=await j(o,r.outputDir);if(!l?.reportPath)throw new Error(`No report found for "${o}"`);c({path:l.reportPath,run:l},!0);return}let n=await N(o,r.outputDir);c(`Opened: ${n}`,!1)});let a=e.command("runs").description("Inspect and download hosted workflow runs.");return a.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 o=>{let r=await C("/workflows/runs","GET",o);o.json?c(r,!0):c(r.runs.map(n=>`${n.id} ${n.status} ${n.workflow_id} ${n.queued_at}`).join(`
38
- `),!1)}),a.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(o,r)=>c(await C(`/workflows/runs/${o}`,"GET",r),r.json)),a.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(o,r)=>{let n=await C(`/workflows/runs/${o}`,"GET",r),{apiUrl:l,apiKey:k}=ee(r),w=X(r.outputDir??S(),"workflow-downloads",o);await ye(w,{recursive:!0});let y=[];for(let m of n.run.artifacts??[]){let f=await fetch(`${l}/workflows/runs/${o}/artifacts/${m.id}`,{headers:{"x-api-key":k}});if(!f.ok)throw new Error(`Failed to download ${m.label}: HTTP ${f.status}`);let g=X(w,ke(m.path));await he(g,Buffer.from(await f.arrayBuffer())),y.push(g)}c(r.json?{runId:o,files:y}:y.join(`
39
- `),r.json)}),e}async function re(e=process.argv){await ve().parseAsync(e)}re().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});
42
+ `),!1)});let c=e.command("report").description("List and open local workflow reports.");c.command("list").description("List recent workflow reports.").option("--output-dir <path>","Workflow output directory").option("--json","Print machine-readable JSON").action(async s=>{let t=await T(s.outputDir);p(s.json?{reports:t}:t.map(o=>`${o.startedAt} ${o.workflow} ${o.status} ${o.reportPath??o.manifestPath}`).join(`
43
+ `),s.json)}),c.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(s="last",t)=>{let o=await I(s,t.outputDir);if(!o?.reportPath)throw new Error(`No report found for "${s}"`);p(t.json?{path:o.reportPath,run:o}:o.reportPath,t.json)}),c.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(s="last",t)=>{if(t.json){let u=await I(s,t.outputDir);if(!u?.reportPath)throw new Error(`No report found for "${s}"`);p({path:u.reportPath,run:u},!0);return}let o=await Y(s,t.outputDir);p(`Opened: ${o}`,!1)});let l=e.command("runs").description("Inspect and download hosted workflow runs.");return l.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 s=>{let t=await A("/workflows/runs","GET",s);s.json?p(t,!0):p(t.runs.map(o=>`${o.id} ${o.status} ${o.workflow_id} ${o.queued_at}`).join(`
44
+ `),!1)}),l.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(s,t)=>p(await A(`/workflows/runs/${s}`,"GET",t),t.json)),l.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(s,t)=>{let o=await A(`/workflows/runs/${s}`,"GET",t),{apiUrl:u,apiKey:y}=ke(t),g=ge(t.outputDir??k(),"workflow-downloads",s);await ze(g,{recursive:!0});let m=[];for(let f of o.run.artifacts??[]){let d=await fetch(`${u}/workflows/runs/${s}/artifacts/${f.id}`,{headers:{"x-api-key":y}});if(!d.ok)throw new Error(`Failed to download ${f.label}: HTTP ${d.status}`);let w=ge(g,Qe(f.path));await Ze(w,Buffer.from(await d.arrayBuffer())),m.push(w)}p(t.json?{runId:s,files:m}:m.join(`
45
+ `),t.json)}),e}async function ve(e=process.argv){await tr().parseAsync(e)}ve().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import{a as e}from"../chunk-OOB3RKWD.js";import"../chunk-4P3LZNVA.js";import"../chunk-QER2ELZP.js";import"../chunk-WHVK6FUB.js";import"../chunk-7POHNTVW.js";import"../chunk-MVMWPKRC.js";import"../chunk-63VWBMYR.js";import"../chunk-3F7AFM53.js";import"../chunk-RDHGQ2WZ.js";import"../chunk-B5Y26E3A.js";import"../chunk-U3NV72AL.js";import"../chunk-CNSVZTTT.js";import"../chunk-K3VDGJNK.js";import"../chunk-7SR7MEU5.js";import"../chunk-ANQ4JJVI.js";import"../chunk-SEZETSB3.js";import"../chunk-SDQW7IJN.js";import"../chunk-B4ZKPLSP.js";import"../chunk-VVSHZV5S.js";import"../chunk-C7MGJUTU.js";import"../chunk-PKQHHCO4.js";import"../chunk-4ZVO6GX4.js";import"../chunk-WNUXMPVX.js";import"../chunk-ND4WIBXT.js";import"../chunk-EYGA6LOU.js";import"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import"../chunk-OILOHQNL.js";import"../chunk-6FAMBNHY.js";import"../chunk-HNNYXMSI.js";var _=["harvest_paa","search_serp","extract_url","diff_page","map_site_urls","map_wayback_snapshots","extract_site","analyze_site_similarity","audit_site","check_site_export","site_export_read","site_export_image","archive_read","youtube_harvest","youtube_transcribe","facebook_page_intel","facebook_ad_search","reddit_thread","reddit_trending","video_frame_analysis","video_frame_analysis_status","facebook_ad_transcribe","google_ads_search","google_ads_page_intel","google_ads_transcribe","facebook_video_transcribe","instagram_profile_content","instagram_media_download","maps_place_intel","maps_search","trustpilot_reviews","g2_reviews","capture_serp_snapshot","capture_serp_page_snapshots"];e({toolsets:new Set(["paa","serp"]),allowedToolNames:_});
2
+ import{createInterface as w}from"node:readline";import{readFileSync as R}from"node:fs";import{homedir as v}from"node:os";import{join as P}from"node:path";var m=class{#r;endpoint;fetchImpl;#e;protocolVersion;nextId=0;constructor(e={}){if(this.#r=e.apiKey?.trim()||process.env.MCP_SCRAPER_API_KEY?.trim()||"",!this.#r)throw new Error("MCP_SCRAPER_API_KEY is required");let i=e.baseUrl?.trim()||process.env.MCP_SCRAPER_BASE_URL?.trim()||process.env.MCP_BASE_URL?.trim()||"https://mcpscraper.dev",t=new URL(i);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid MCP Scraper endpoint");t.pathname=t.pathname.replace(/\/$/,"")+(t.pathname.replace(/\/$/,"").endsWith("/mcp")?"":"/mcp"),this.endpoint=t.href,this.fetchImpl=e.fetchImpl??fetch}async relay(e,i,t){if(e.method==="initialize"&&typeof e.params?.protocolVersion=="string"&&(this.protocolVersion=e.params.protocolVersion),e.method==="server/discover"){let s=e.params?._meta?.["io.modelcontextprotocol/protocolVersion"];typeof s=="string"&&(this.protocolVersion=s)}let o=await this.fetchImpl(this.endpoint,{method:"POST",headers:{"content-type":"application/json",accept:"application/json, text/event-stream","x-api-key":this.#r,...e.method?{"mcp-method":e.method}:{},...this.#e?{"mcp-session-id":this.#e}:{},...this.protocolVersion?{"mcp-protocol-version":this.protocolVersion}:{}},body:JSON.stringify(e),signal:t});if(!o.ok)throw new Error(`MCP Scraper HTTP ${o.status}`);if(this.#e=o.headers.get("mcp-session-id")??this.#e,e.method==="initialize"&&typeof e.params?.protocolVersion=="string"&&(this.protocolVersion=e.params.protocolVersion),o.status===202||o.status===204)return null;if(!o.headers.get("content-type")?.includes("text/event-stream"))return await o.json();if(!o.body)throw new Error("MCP Scraper returned an empty event stream");let d=o.body.getReader(),a=new TextDecoder,r="",_=[],c=()=>{if(!_.length)return null;let s=JSON.parse(_.join(`
3
+ `));return _=[],s.id===e.id&&("result"in s||"error"in s)?s:(i?.(s),null)};try{for(;;){let{done:s,value:n}=await d.read();r+=a.decode(n,{stream:!s}),s&&r&&!r.endsWith(`
4
+ `)&&(r+=`
5
+ `);let l;for(;(l=r.indexOf(`
6
+ `))>=0;){let p=r.slice(0,l).replace(/\r$/,"");if(r=r.slice(l+1),p===""){let h=c();if(h)return h}else p.startsWith("data:")&&_.push(p.slice(5).replace(/^ /,""))}if(s){let p=c();if(p)return p;break}}}finally{await d.cancel().catch(()=>{}),d.releaseLock()}throw new Error("MCP Scraper event stream ended without a response")}async request(e,i){let t=await this.relay({jsonrpc:"2.0",id:++this.nextId,method:e,params:i});if(!t)throw new Error("MCP Scraper returned no response");if(t.error)throw new Error(typeof t.error.message=="string"?t.error.message:"MCP Scraper request failed");return t.result}callTool(e,i={}){return this.request("tools/call",{name:e,arguments:i})}};var f="0.98.0";function g(u){if(process.argv.includes("--help")||process.argv.includes("-h")||!process.argv.includes("--stdio")&&process.stdin.isTTY&&process.stdout.isTTY){process.stdout.write(`MCP Scraper ${f}
7
+ Authenticated hosted MCP client. Set MCP_SCRAPER_API_KEY and connect with --stdio.
8
+ `);return}let e=process.env.MCP_SCRAPER_API_KEY?.trim();if(!e){for(let a of[process.env.MCP_SCRAPER_KEY_PATH,P(v(),".mcp-scraper-key")])if(a)try{if(e=R(a,"utf8").trim(),e)break}catch{}}let i;try{i=new m({apiKey:e})}catch{process.stderr.write(`MCP_SCRAPER_API_KEY is required
9
+ `),process.exitCode=1;return}let t=new Map,o=a=>process.stdout.write(JSON.stringify(a)+`
10
+ `),d=w({input:process.stdin,crlfDelay:1/0});d.on("line",a=>{let r;try{r=JSON.parse(a)}catch{o({jsonrpc:"2.0",id:null,error:{code:-32700,message:"Invalid JSON"}});return}let _=r&&r.id!=null&&("result"in r||"error"in r);if(!r||r.jsonrpc!=="2.0"||typeof r.method!="string"&&!_){o({jsonrpc:"2.0",id:r?.id??null,error:{code:-32600,message:"Invalid request"}});return}if(u&&r.method==="tools/call"&&!u.has(String(r.params?.name))){o({jsonrpc:"2.0",id:r.id,error:{code:-32601,message:"Tool is not available in this client profile"}});return}let c=r.id,s=new AbortController;c!=null&&t.set(c,s),i.relay(r,o,s.signal).then(n=>{if(!(c==null||!n)){if(u&&r.method==="tools/list"&&n.result&&typeof n.result=="object"){let l=n.result;Array.isArray(l.tools)&&(l.tools=l.tools.filter(p=>u.has(p.name)))}o(n)}}).catch(n=>{c!=null&&o({jsonrpc:"2.0",id:c,error:{code:-32e3,message:n instanceof Error&&/^MCP Scraper HTTP \d+$/.test(n.message)?n.message:"MCP Scraper transport failed; inspect the original job before retrying"}})}).finally(()=>{c!=null&&t.delete(c)})}),d.on("close",()=>{for(let a of t.values())a.abort()})}var y=["harvest_paa","search_serp","extract_url","diff_page","map_site_urls","map_wayback_snapshots","extract_site","analyze_site_similarity","audit_site","check_site_export","site_export_read","site_export_image","archive_read","youtube_harvest","youtube_transcribe","facebook_page_intel","facebook_ad_search","reddit_thread","reddit_trending","video_frame_analysis","video_frame_analysis_status","facebook_ad_transcribe","google_ads_search","google_ads_page_intel","google_ads_transcribe","facebook_video_transcribe","instagram_profile_content","instagram_media_download","maps_place_intel","maps_search","trustpilot_reviews","g2_reviews","capture_serp_snapshot","capture_serp_page_snapshots"];g(new Set(y));
@@ -1,3 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import{a as s}from"../chunk-ND4WIBXT.js";import"../chunk-EYGA6LOU.js";import{a as e}from"../chunk-6FAMBNHY.js";var r=process.argv.includes("--no-color")||process.env.NO_COLOR!==void 0||process.env.FORCE_COLOR==="0"||!process.stdout.isTTY,n=process.argv.includes("--help")||process.argv.includes("-h");n&&(process.stdout.write(["Usage: mcp-scraper-install [--no-color]","","Prints the branded MCP Scraper terminal install card and copyable install commands.","mcp-scraper prints the same card in a human terminal and runs as the MCP stdio server in clients.",""].join(`
3
- `)),process.exit(0));process.stdout.write(s({version:e,color:!r,apiKeyConfigured:!!process.env.MCP_SCRAPER_API_KEY?.trim()}));
2
+ var i={schemaVersion:1,ratePolicy:{version:"serp-page-2026-09-24",effectiveDate:"2026-09-24T00:00:00.000Z"},costEvidence:{id:"serp-page-rate-basis-2026-09",status:"owner-approved"},operationCosts:{serp:{credits:20,unit:"per page (ordinary search)",label:"SERP search",notes:"search_serp light mode returns one page of organic positions, URLs, titles, and descriptions by default. Unfiltered one-page full mode returns available same-page SERP features. Either mode requests two pages only when pages:2 is explicit. Unfiltered light searches cost 20 credits per delivered page, or 35 credits per delivered page if the backup supplies the result. One-page full searches cost 35 credits. Setting recency to week or month applies Google's date filter through a managed browser and returns organic results only. It costs 35 credits for one page; filtered pages:2 follows Google's page-two control and costs 70 credits when both pages are delivered. Unfiltered searches use the managed browser only as the last backup. Unfiltered two-page full searches return organic results only for 40 credits, with rich features marked unsupported. If only page 1 is delivered, the search is marked partial and billed for one delivered page. A temporary 35-credit-per-requested-page hold is settled to the delivered rate. Location, language, device, and optional module fields do not change ordinary searches. Saved browser identity and capture_serp_snapshot searches retain their 60-credit base price; optional page snapshots add 1 credit per attempted URL."},paa:{credits:10,unit:"per question (+400 credit base)",label:"PAA harvest",notes:"Returns original-query PAA questions with answers and sources plus organic results and Google entity IDs. Optional same-page SERP modules are flag-controlled. Billed 400 credit base + 10 per question actually returned (unused estimate refunded)."},page_scrape:{credits:1,unit:"per page",label:"Page crawl / extract",notes:"Applies to both single-URL extraction and per-page site crawls."},listing_create:{credits:10,unit:"per new listing",label:"Directory or wiki listing creation",notes:"Fixed total price for a new Local Sourcebook directory listing or Transparent Commons wiki entity. Sourcebook acquisition is included."},listing_edit:{credits:2,unit:"per existing listing revision",label:"Directory or wiki listing edit",notes:"Fixed total price for refreshing an existing Local Sourcebook listing or revising an existing Transparent Commons entity."},url_map:{credits:5,unit:"per mapping operation",label:"Site URL mapping",notes:"Flat rate for the full /map-urls call regardless of URL count discovered."},yt_channel:{credits:2,unit:"per call",label:"YouTube search / channel harvest"},yt_transcription:{credits:200,unit:"base + 2/min of video",label:"YouTube transcription",notes:"Flat 200-credit base plus 2 credits per minute of video, reconciled to the real video length after transcription (e.g. a 10-minute video is about 220 credits)."},fb_ad:{credits:7,unit:"per call",label:"Facebook search / ad lookup"},maps_search:{credits:5,unit:"per search",label:"Maps business search",notes:"Returns up to 50 Google Maps business/profile candidates. Use maps_place_intel to hydrate selected businesses."},maps_place:{credits:60,unit:"per business",label:"Maps business lookup",notes:"Maps place runs reserve the 60-credit base plus the declared review maximum before browsing. Completed runs settle to the base plus unique saved reviews; stopped attempts are refunded."},maps_review:{credits:1,unit:"per review card",label:"Maps review",notes:"One credit per unique saved review on a completed Maps place run. The declared maximum is held before browsing; unused credits are refunded."},fb_search:{credits:8,unit:"per search",label:"Facebook ad library search",notes:"Browser automation to search Facebook Ads Library by keyword."},google_ads_search:{credits:6,unit:"per search",label:"Google Ads Transparency search",notes:"Browser automation to find advertisers in Google Ads Transparency Center by domain or name."},google_ads_intel:{credits:2,unit:"per call",label:"Google Ads Transparency advertiser intel",notes:"Lists and hydrates an advertiser's creatives with image URLs and video references."},google_ads_transcribe:{credits:200,unit:"base + 2/min of video",label:"Google ad video transcription",notes:"Flat 200-credit base plus 2 credits per minute of video, reconciled to the real video length after transcription (e.g. a 10-minute video is about 220 credits)."},fb_transcribe:{credits:200,unit:"base + 2/min of video",label:"Facebook video / ad transcription",notes:"Flat 200-credit base plus 2 credits per minute of video, reconciled to the real video length after transcription (e.g. a 10-minute video is about 220 credits)."},fb_reels_inventory:{credits:4,unit:"per profile scan",label:"Facebook Reel URL inventory",notes:"Collects canonical Reel URLs from a Facebook profile. Logged-out scans are capped at 60 URLs; saved browser profiles can request larger inventories."},tiktok_transcribe:{credits:200,unit:"base + 2/min of video",label:"TikTok video transcription",notes:"Transcribes a public TikTok video from its playback media. Flat 200-credit base plus 2 credits per minute, reconciled after transcription."},instagram_profile:{credits:4,unit:"per profile scan",label:"Instagram profile content discovery",notes:"Browser extraction of public Instagram profile grid links. Complete history may require a logged-in profile."},instagram_media:{credits:4,unit:"per post or reel",label:"Instagram media download",notes:"Extracts post text, image URLs, and reel audio/video tracks, with local downloads when the server can write files."},instagram_transcribe:{credits:200,unit:"base + 2/min of video",label:"Instagram media transcription",notes:"Whisper transcription of selected Instagram audio/video media. Flat 200-credit base plus 2 credits per minute of video, reconciled to the real video length after transcription (e.g. a 10-minute video is about 220 credits)."},browser_minute:{credits:120,unit:"per minute of use",label:"Interactive browser session",notes:"Metered per minute of use for the whole time the browser session is open. Close the session to stop the meter; abandoned sessions are auto-closed after a short idle window."},reddit_thread:{credits:30,unit:"per thread",label:"Reddit thread base lookup",notes:"Base lookup for the post itself. Comments billed separately per comment at reddit_comment rate. Refunded if the thread cannot be retrieved."},reddit_comment:{credits:2,unit:"per comment",label:"Reddit comment",notes:"Charged per comment actually extracted, billed down automatically if the balance runs out mid-thread."},video_analysis:{credits:6667,unit:"per 120 frames (max 480)",label:"Video breakdown (frame-by-frame + transcript)",notes:"Full video breakdown: samples frames across a video up to 60 minutes, analyzes visuals and audio, then produces a summary, pacing, topic outline, key points, hook, style, and replication guidance. Reserves 6667 Credits per requested 120-frame tier, up to 480 frames. Unused frame tiers and failed-run frame charges are refunded. Incurred analysis-model usage is billed separately, including usage before a failure; terminal status checks settle the original run."},trustpilot_reviews:{credits:5,unit:"per call",label:"Trustpilot review harvest",notes:"Base lookup. Reviews billed separately per card at trustpilot_review rate. Refunded if no reviews are found."},trustpilot_review:{credits:1,unit:"per review card",label:"Trustpilot review card",notes:"Charged per review actually extracted, billed down automatically if the balance runs out mid-page."},g2_reviews:{credits:5,unit:"per call",label:"G2 review harvest",notes:"Base lookup. Reviews billed separately per card at g2_review rate. Refunded if no reviews are found."},g2_review:{credits:2,unit:"per review card",label:"G2 review card",notes:"Charged per review actually extracted (each card carries up to 3 Q&A sections), billed down automatically if the balance runs out mid-page."},diff_page:{credits:1,unit:"per check",label:"Page change check",notes:"Same cost as a single page extract \u2014 one scrape is performed per check, then compared against your last stored snapshot for that URL."}},publicTiers:[{tier:"starter",label:"Starter",monthlyUsd:12,credits:8e4,concurrency:3},{tier:"growth",label:"Growth",monthlyUsd:40,credits:266667,concurrency:10},{tier:"scale",label:"Scale",monthlyUsd:100,credits:8e5,concurrency:20}],credits:{mcPerCredit:100,freeSignupCredits:0},inventory:{totalTools:347,scraperTools:233,memoryTools:114},concurrencyPack:{ratePolicyVersion:"2026-08-14.concurrency-pack-v1",monthlyUsd:5,currency:"usd",interval:"month",billingUnit:"pack",slotsPerPack:2},scaleCreditPack:{ratePolicyVersion:"2026-09-21.scale-credit-pack-v1",eligibleTier:"scale",incrementUsd:10,creditsPerIncrement:8e4,maxQuantity:100,currency:"usd",expiryMonths:3},addons:{memory:{monthlyUsd:19,quotaGb:5,freeQuotaGb:.01},connectedAccounts:{schemaVersion:2,ratePolicyVersion:"2026-08-03.connected-usage-v3",effectiveAt:"2026-08-03T00:00:00.000Z",entitlement:"paid_plan_add_on",connection:{billingMode:"flat_recurring_credits",amountCredits:15e3,interval:"month",unit:"active_connection",proration:"none"},usage:{billingMode:"credits",settlementMode:"authoritative_receipt",rounding:"aggregate_then_ceil_to_mc",functionRun:{credits:2,mc:200,unit:"per_function_run"},proxyRequest:{credits:2,mc:200,unit:"per_proxy_request"},compute:{creditsPerSecond:5,mcPerSecond:500,sourceUnit:"millisecond",unit:"per_compute_second",formula:"ceil(computeMilliseconds * mcPerSecond / 1000)"}}}}};var n={message:"Public packages now contain authenticated hosted-service clients only. Existing MCP connections remain compatible; local library execution migrates to hosted tools and public result-delivery handles."};var c={reset:"\x1B[0m",cyan:"\x1B[36m",lime:"\x1B[32m",amber:"\x1B[33m",red:"\x1B[31m",muted:"\x1B[90m",bold:"\x1B[1m"};function r(o,e,a){return a?`${c[e]}${o}${c.reset}`:o}function t(o,e,a){let s=o.padEnd(9," ");return` ${r(s,"muted",a)} ${e.join(r(" . ","muted",a))}`}function l(o){let e=o.color??!0,a=o.apiKeyConfigured?"$MCP_SCRAPER_API_KEY":"sk_live_your_key",s=String.raw`
3
+ __ __ ____ ____
4
+ | \/ |/ ___| _ \
5
+ | |\/| | | | |_) |
6
+ | | | | |___| __/
7
+ |_| |_|\____|_|
8
+
9
+ ____ ____ ____ _ ____ _____ ____
10
+ / ___| / ___| _ \ / \ | _ \| ____| _ \
11
+ \___ \| | | |_) | / _ \ | |_) | _| | |_) |
12
+ ___) | |___| _ < / ___ \| __/| |___| _ <
13
+ |____/ \____|_| \_\/_/ \_\_| |_____|_| \_\
14
+ `,p=[`MCP_SCRAPER_API_KEY=${a} npx -y -p mcp-scraper@latest \\`," mcp-scraper-cli agent install claude --apply"].join(`
15
+ `),u=["[mcp_servers.mcp-scraper]",'command = "npx"','args = ["-y", "-p", "mcp-scraper@latest", "mcp-scraper"]',`env = { MCP_SCRAPER_API_KEY = "${a}" }`].join(`
16
+ `);return[r(`mcp-scraper v${o.version}`,"bold",e),r("> mcp-scraper-install","muted",e),r(s,"amber",e),`${r("MCP Scraper Agent","cyan",e)} . v${o.version} . mcpscraper.dev`,"1/1 install surfaces ready",r(`Newest in v${o.version}: ${n.message}`,"lime",e),"",`${r("Tools","cyan",e)} ${r(`(${i.inventory.totalTools} MCP tools)`,"muted",e)}`,t("search",["harvest_paa","search_serp","maps_search","maps_place_intel"],e),t("extract",["extract_url","map_site_urls","extract_site","audit_site","directory_workflow"],e),t("build",["create_editorial_reading_room","rank_tracker_workflow","portable HTML"],e),t("media",["youtube_harvest","youtube_transcribe","facebook_reels_inventory","facebook_ad_search","facebook_page_intel","facebook_ad_transcribe","facebook_video_transcribe","tiktok_video_transcribe","instagram_profile_content","instagram_media_download","reddit_thread"],e),t("browser",["serp_identity_create","serp_identity_list","browser_open","browser_profile_connect","browser_profile_list","browser_close","browser_screenshot","browser_read","browser_locate","browser_replay_mark","browser_replay_annotate"],e),t("connect",["list_service_connections","describe_service_connection_tool","import_service_connection_to_memory","export_connected_service_data","renew_connected_data_download","read_service_connection","call_service_connection_action"],e),t("commons",["commons_search_entities","commons_get_entity_linkset","commons_prepare_entity","commons_submit_entity","commons_prepare_publication","commons_claim_publication","commons_publish_editorial","commons_get_publication"],e),t("account",["credits_info","reports","MCP resources"],e),t("memory",["memory-put","memory-get","memory-search","list-vaults","record-fact","list_artifact_templates"],e),`${r("Workflows","cyan",e)} ${r("(MCP + CLI + API)","muted",e)}`,t("route",["workflow_list","workflow_suggest","workflow_run","workflow_step","workflow_status","workflow_artifact_read"],e),t("seo",["directory","agent-packet","competitive audit","map/serp comparison","PAA/AIO briefs"],e),"",r("Usage tips:","amber",e),"Run mcp-scraper-install for this visible card. Run mcp-scraper-cli for setup utilities and subcommands.","Run mcp-scraper in a human terminal to print this card; MCP clients get the same command as a silent stdio server.","Explicit card command: npx -y -p mcp-scraper@latest mcp-scraper-install","Hosted browser sessions use direct/no-proxy egress by default.","Customer auth setup: run browser_profile_connect, send the watch_url, let the user sign in, then call browser_profile_list until AUTHENTICATED.","Connected account ranges: call export_connected_service_data once. It handles Gmail, Calendar, Zoom, and Resend pagination; do not loop read_service_connection over individual records.","Connected account RAG: call import_service_connection_to_memory for one bounded approved read. It writes a redacted, untrusted snapshot to a stable Memory path and embeds it for search.","Stack logins / reconnect: run browser_profile_connect again with the same profile name and another domain to add accounts or refresh a login.","Start with workflow_suggest for broad jobs like market analysis, ICP research, CRO audits, brand briefs, content gaps, and AI visibility.","For MCP clients, use mcp-scraper so one install can mix SERP, Maps, browser, reports, and saved MCP resources.","If you hit the concurrency limit, add 2 browsers for $5/month with mcp-scraper-cli billing concurrency checkout.","",`${r("Ready.","lime",e)} Install the combined MCP server with one command:`,"",r("Setup doctor","amber",e),"npx -y -p mcp-scraper@latest mcp-scraper-cli doctor","",r("Hosted profile setup","amber",e),'In your MCP client, call browser_profile_connect with email="seo@example.com" and domain="chatgpt.com".',"Give the returned watch_url to the user. After they sign in, call browser_profile_list, then browser_open with the returned profile. Add more logins by calling browser_profile_connect again with the same profile and a new domain.","",r("Claude Code one-command setup","amber",e),p,"Then fully exit Claude Code and open a new Claude terminal. Check with: claude mcp list","",r("Codex config","amber",e),u,"",r("Claude Desktop Extension","amber",e),"Download: https://mcpscraper.dev/downloads/mcp-scraper.mcpb","",r("Safety note:","muted",e),"mcp-scraper prints this card only when stdin/stdout are an interactive TTY. In MCP clients it writes only JSON-RPC to stdout.","Use --stdio or MCP_SCRAPER_FORCE_STDIO=1 to force server mode from a terminal.",""].join(`
17
+ `)}var d="0.98.0";var m=process.argv.includes("--no-color")||process.env.NO_COLOR!==void 0||process.env.FORCE_COLOR==="0"||!process.stdout.isTTY,b=process.argv.includes("--help")||process.argv.includes("-h");b&&(process.stdout.write(["Usage: mcp-scraper-install [--no-color]","","Prints the branded MCP Scraper terminal install card and copyable install commands.","mcp-scraper prints the same card in a human terminal and runs as the MCP stdio server in clients.",""].join(`
18
+ `)),process.exit(0));process.stdout.write(l({version:d,color:!m,apiKeyConfigured:!!process.env.MCP_SCRAPER_API_KEY?.trim()}));
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import{a as r}from"../chunk-OOB3RKWD.js";import"../chunk-4P3LZNVA.js";import"../chunk-QER2ELZP.js";import"../chunk-WHVK6FUB.js";import"../chunk-7POHNTVW.js";import"../chunk-MVMWPKRC.js";import"../chunk-63VWBMYR.js";import"../chunk-3F7AFM53.js";import"../chunk-RDHGQ2WZ.js";import"../chunk-B5Y26E3A.js";import"../chunk-U3NV72AL.js";import"../chunk-CNSVZTTT.js";import"../chunk-K3VDGJNK.js";import"../chunk-7SR7MEU5.js";import"../chunk-ANQ4JJVI.js";import"../chunk-SEZETSB3.js";import"../chunk-SDQW7IJN.js";import"../chunk-B4ZKPLSP.js";import"../chunk-VVSHZV5S.js";import"../chunk-C7MGJUTU.js";import"../chunk-PKQHHCO4.js";import"../chunk-4ZVO6GX4.js";import"../chunk-WNUXMPVX.js";import"../chunk-ND4WIBXT.js";import"../chunk-EYGA6LOU.js";import"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import"../chunk-OILOHQNL.js";import"../chunk-6FAMBNHY.js";import"../chunk-HNNYXMSI.js";r();
2
+ import{createInterface as P}from"node:readline";import{readFileSync as w}from"node:fs";import{homedir as R}from"node:os";import{join as C}from"node:path";var f=class{#r;endpoint;fetchImpl;#e;protocolVersion;nextId=0;constructor(e={}){if(this.#r=e.apiKey?.trim()||process.env.MCP_SCRAPER_API_KEY?.trim()||"",!this.#r)throw new Error("MCP_SCRAPER_API_KEY is required");let i=e.baseUrl?.trim()||process.env.MCP_SCRAPER_BASE_URL?.trim()||process.env.MCP_BASE_URL?.trim()||"https://mcpscraper.dev",t=new URL(i);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid MCP Scraper endpoint");t.pathname=t.pathname.replace(/\/$/,"")+(t.pathname.replace(/\/$/,"").endsWith("/mcp")?"":"/mcp"),this.endpoint=t.href,this.fetchImpl=e.fetchImpl??fetch}async relay(e,i,t){if(e.method==="initialize"&&typeof e.params?.protocolVersion=="string"&&(this.protocolVersion=e.params.protocolVersion),e.method==="server/discover"){let s=e.params?._meta?.["io.modelcontextprotocol/protocolVersion"];typeof s=="string"&&(this.protocolVersion=s)}let n=await this.fetchImpl(this.endpoint,{method:"POST",headers:{"content-type":"application/json",accept:"application/json, text/event-stream","x-api-key":this.#r,...e.method?{"mcp-method":e.method}:{},...this.#e?{"mcp-session-id":this.#e}:{},...this.protocolVersion?{"mcp-protocol-version":this.protocolVersion}:{}},body:JSON.stringify(e),signal:t});if(!n.ok)throw new Error(`MCP Scraper HTTP ${n.status}`);if(this.#e=n.headers.get("mcp-session-id")??this.#e,e.method==="initialize"&&typeof e.params?.protocolVersion=="string"&&(this.protocolVersion=e.params.protocolVersion),n.status===202||n.status===204)return null;if(!n.headers.get("content-type")?.includes("text/event-stream"))return await n.json();if(!n.body)throw new Error("MCP Scraper returned an empty event stream");let d=n.body.getReader(),c=new TextDecoder,r="",u=[],a=()=>{if(!u.length)return null;let s=JSON.parse(u.join(`
3
+ `));return u=[],s.id===e.id&&("result"in s||"error"in s)?s:(i?.(s),null)};try{for(;;){let{done:s,value:o}=await d.read();r+=c.decode(o,{stream:!s}),s&&r&&!r.endsWith(`
4
+ `)&&(r+=`
5
+ `);let l;for(;(l=r.indexOf(`
6
+ `))>=0;){let p=r.slice(0,l).replace(/\r$/,"");if(r=r.slice(l+1),p===""){let m=a();if(m)return m}else p.startsWith("data:")&&u.push(p.slice(5).replace(/^ /,""))}if(s){let p=a();if(p)return p;break}}}finally{await d.cancel().catch(()=>{}),d.releaseLock()}throw new Error("MCP Scraper event stream ended without a response")}async request(e,i){let t=await this.relay({jsonrpc:"2.0",id:++this.nextId,method:e,params:i});if(!t)throw new Error("MCP Scraper returned no response");if(t.error)throw new Error(typeof t.error.message=="string"?t.error.message:"MCP Scraper request failed");return t.result}callTool(e,i={}){return this.request("tools/call",{name:e,arguments:i})}};var g="0.98.0";function y(h){if(process.argv.includes("--help")||process.argv.includes("-h")||!process.argv.includes("--stdio")&&process.stdin.isTTY&&process.stdout.isTTY){process.stdout.write(`MCP Scraper ${g}
7
+ Authenticated hosted MCP client. Set MCP_SCRAPER_API_KEY and connect with --stdio.
8
+ `);return}let e=process.env.MCP_SCRAPER_API_KEY?.trim();if(!e){for(let c of[process.env.MCP_SCRAPER_KEY_PATH,C(R(),".mcp-scraper-key")])if(c)try{if(e=w(c,"utf8").trim(),e)break}catch{}}let i;try{i=new f({apiKey:e})}catch{process.stderr.write(`MCP_SCRAPER_API_KEY is required
9
+ `),process.exitCode=1;return}let t=new Map,n=c=>process.stdout.write(JSON.stringify(c)+`
10
+ `),d=P({input:process.stdin,crlfDelay:1/0});d.on("line",c=>{let r;try{r=JSON.parse(c)}catch{n({jsonrpc:"2.0",id:null,error:{code:-32700,message:"Invalid JSON"}});return}let u=r&&r.id!=null&&("result"in r||"error"in r);if(!r||r.jsonrpc!=="2.0"||typeof r.method!="string"&&!u){n({jsonrpc:"2.0",id:r?.id??null,error:{code:-32600,message:"Invalid request"}});return}if(h&&r.method==="tools/call"&&!h.has(String(r.params?.name))){n({jsonrpc:"2.0",id:r.id,error:{code:-32601,message:"Tool is not available in this client profile"}});return}let a=r.id,s=new AbortController;a!=null&&t.set(a,s),i.relay(r,n,s.signal).then(o=>{if(!(a==null||!o)){if(h&&r.method==="tools/list"&&o.result&&typeof o.result=="object"){let l=o.result;Array.isArray(l.tools)&&(l.tools=l.tools.filter(p=>h.has(p.name)))}n(o)}}).catch(o=>{a!=null&&n({jsonrpc:"2.0",id:a,error:{code:-32e3,message:o instanceof Error&&/^MCP Scraper HTTP \d+$/.test(o.message)?o.message:"MCP Scraper transport failed; inspect the original job before retrying"}})}).finally(()=>{a!=null&&t.delete(a)})}),d.on("close",()=>{for(let c of t.values())c.abort()})}y();
@@ -1,2 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{K as t}from"../chunk-GLCR4KJI.js";import"../chunk-M5QHXNFZ.js";import{d as r}from"../chunk-B4ZKPLSP.js";import"../chunk-VVSHZV5S.js";import"../chunk-C7MGJUTU.js";import"../chunk-PKQHHCO4.js";import"../chunk-4ZVO6GX4.js";import"../chunk-WNUXMPVX.js";import"../chunk-6N5EC7ZO.js";import"../chunk-HNNYXMSI.js";import{Command as s,Option as a}from"commander";var i=new s;i.name("paa-harvest").description("Recursively extract Google People Also Ask questions").requiredOption("-q, --query <query>","Seed query").option("-l, --location <location>",'Location name (e.g. "austin" or "Austin,Texas,United States")').option("--gl <gl>","Google country code","us").option("--hl <hl>","Google language code","en").option("-d, --depth <depth>","BFS depth (1-30)","3").option("-m, --max-questions <n>","Max questions to harvest","100").option("-o, --output <dir>","Output directory","./paa-output").option("-f, --format <format>","Output format: json, csv, or both","both").option("--headless","Run browser in headless mode",!1).option("--profile <dir>","Persistent browser profile directory").option("--proxy <url>","Proxy server URL").option("--browser-api-key <key>","Browser service API key (or set BROWSER_SERVICE_API_KEY env var)").addOption(new a("--\u006b\u0065\u0072\u006e\u0065\u006c-api-key <key>").hideHelp()).action(async e=>{try{let o=await t({query:e.query,location:e.location,gl:e.gl,hl:e.hl,depth:parseInt(e.depth,10),maxQuestions:parseInt(e.maxQuestions,10),outputDir:e.output,format:e.format,headless:e.headless,profileDir:e.profile,proxy:e.proxy,\u006b\u0065\u0072\u006e\u0065\u006cApiKey:e.browserApiKey??e.\u006b\u0065\u0072\u006e\u0065\u006cApiKey??r()});console.log(JSON.stringify({totalQuestions:o.totalQuestions,outputDir:o.stats.seed}))}catch(o){console.error(o instanceof Error?o.message:String(o)),process.exit(1)}});async function n(){await i.parseAsync()}n();
2
+ import{Command as w}from"commander";import{mkdir as y,writeFile as v}from"node:fs/promises";import{join as P}from"node:path";var c=class{#t;endpoint;fetchImpl;#e;protocolVersion;nextId=0;constructor(e={}){if(this.#t=e.apiKey?.trim()||process.env.MCP_SCRAPER_API_KEY?.trim()||"",!this.#t)throw new Error("MCP_SCRAPER_API_KEY is required");let i=e.baseUrl?.trim()||process.env.MCP_SCRAPER_BASE_URL?.trim()||process.env.MCP_BASE_URL?.trim()||"https://mcpscraper.dev",t=new URL(i);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid MCP Scraper endpoint");t.pathname=t.pathname.replace(/\/$/,"")+(t.pathname.replace(/\/$/,"").endsWith("/mcp")?"":"/mcp"),this.endpoint=t.href,this.fetchImpl=e.fetchImpl??fetch}async relay(e,i,t){if(e.method==="initialize"&&typeof e.params?.protocolVersion=="string"&&(this.protocolVersion=e.params.protocolVersion),e.method==="server/discover"){let n=e.params?._meta?.["io.modelcontextprotocol/protocolVersion"];typeof n=="string"&&(this.protocolVersion=n)}let o=await this.fetchImpl(this.endpoint,{method:"POST",headers:{"content-type":"application/json",accept:"application/json, text/event-stream","x-api-key":this.#t,...e.method?{"mcp-method":e.method}:{},...this.#e?{"mcp-session-id":this.#e}:{},...this.protocolVersion?{"mcp-protocol-version":this.protocolVersion}:{}},body:JSON.stringify(e),signal:t});if(!o.ok)throw new Error(`MCP Scraper HTTP ${o.status}`);if(this.#e=o.headers.get("mcp-session-id")??this.#e,e.method==="initialize"&&typeof e.params?.protocolVersion=="string"&&(this.protocolVersion=e.params.protocolVersion),o.status===202||o.status===204)return null;if(!o.headers.get("content-type")?.includes("text/event-stream"))return await o.json();if(!o.body)throw new Error("MCP Scraper returned an empty event stream");let l=o.body.getReader(),f=new TextDecoder,s="",p=[],d=()=>{if(!p.length)return null;let n=JSON.parse(p.join(`
3
+ `));return p=[],n.id===e.id&&("result"in n||"error"in n)?n:(i?.(n),null)};try{for(;;){let{done:n,value:g}=await l.read();s+=f.decode(g,{stream:!n}),n&&s&&!s.endsWith(`
4
+ `)&&(s+=`
5
+ `);let u;for(;(u=s.indexOf(`
6
+ `))>=0;){let a=s.slice(0,u).replace(/\r$/,"");if(s=s.slice(u+1),a===""){let h=d();if(h)return h}else a.startsWith("data:")&&p.push(a.slice(5).replace(/^ /,""))}if(n){let a=d();if(a)return a;break}}}finally{await l.cancel().catch(()=>{}),l.releaseLock()}throw new Error("MCP Scraper event stream ended without a response")}async request(e,i){let t=await this.relay({jsonrpc:"2.0",id:++this.nextId,method:e,params:i});if(!t)throw new Error("MCP Scraper returned no response");if(t.error)throw new Error(typeof t.error.message=="string"?t.error.message:"MCP Scraper request failed");return t.result}callTool(e,i={}){return this.request("tools/call",{name:e,arguments:i})}};function m(r,e){return new c(e).callTool("harvest_paa",{...r})}var x=new w().name("paa-harvest").description("Harvest PAA through your authenticated MCP Scraper account").requiredOption("-q, --query <query>").option("-l, --location <location>").option("--gl <gl>","Country","us").option("--hl <hl>","Language","en").option("-d, --depth <depth>","Expansion depth","3").option("-m, --max-questions <n>","Maximum questions","100").option("-o, --output <dir>").option("-f, --format <format>","Public result format","json").action(async r=>{if(r.format!=="json")throw new Error("Use JSON delivery handles for complete results; legacy local CSV export is no longer supported");let e=await m({query:r.query,location:r.location,gl:r.gl,hl:r.hl,depth:Number(r.depth),maxQuestions:Number(r.maxQuestions)});r.output&&(await y(r.output,{recursive:!0}),await v(P(r.output,"result.json"),JSON.stringify(e,null,2)+`
7
+ `)),process.stdout.write(JSON.stringify(e)+`
8
+ `),e.isError&&(process.exitCode=1)});x.parseAsync().catch(r=>{process.stderr.write((r instanceof Error?r.message:"Harvest failed")+`
9
+ `),process.exitCode=1});