mcp-scraper 0.96.3 → 0.97.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +12 -18
  3. package/dist/actions-5X5CQAKO.js +1 -0
  4. package/dist/bin/api-server.js +1 -1
  5. package/dist/bin/mcp-scraper-cli.js +26 -27
  6. package/dist/bin/mcp-scraper-core.js +1 -1
  7. package/dist/bin/mcp-scraper-install.js +1 -1
  8. package/dist/bin/mcp-stdio-server.js +1 -1
  9. package/dist/bin/paa-harvest.js +1 -1
  10. package/dist/bundle-GS657HXG.js +1 -0
  11. package/dist/chunk-2L7CHN5N.js +4 -0
  12. package/dist/chunk-4AEW53RU.js +954 -0
  13. package/dist/chunk-4YZ74SJ6.js +1 -0
  14. package/dist/chunk-5RQWFYXY.js +930 -0
  15. package/dist/{chunk-U7BZ5KTE.js → chunk-5TQINRZN.js} +1 -1
  16. package/dist/{chunk-CFZXZKFT.js → chunk-5UQAL7HV.js} +1 -1
  17. package/dist/{chunk-GLCR4KJI.js → chunk-6D25Q5BR.js} +20 -14
  18. package/dist/chunk-B5Y26E3A.js +1 -0
  19. package/dist/chunk-C3JWX6KM.js +10 -0
  20. package/dist/{chunk-WP3QS4CJ.js → chunk-EYGA6LOU.js} +1 -1
  21. package/dist/{chunk-PANT6VEM.js → chunk-F3M7INSN.js} +7 -7
  22. package/dist/chunk-FLAWC5PF.js +5 -0
  23. package/dist/{chunk-WBJ6Y7WP.js → chunk-FM7BX2LU.js} +2 -2
  24. package/dist/chunk-IPQOBOFY.js +1 -0
  25. package/dist/chunk-J7XDXIB6.js +163 -0
  26. package/dist/chunk-JHDD6R3O.js +8 -0
  27. package/dist/chunk-KBZY2OGP.js +11 -0
  28. package/dist/{chunk-P55O4IDC.js → chunk-NQFTETVC.js} +1 -1
  29. package/dist/{chunk-3HXMW2UJ.js → chunk-P6CDR3RM.js} +1 -1
  30. package/dist/chunk-QCBCOEH5.js +1 -0
  31. package/dist/chunk-SJNSTO3C.js +42 -0
  32. package/dist/{chunk-MVMWPKRC.js → chunk-TZYITYL4.js} +1 -1
  33. package/dist/{chunk-JREMJRSS.js → chunk-UUJYICAJ.js} +1 -1
  34. package/dist/{chunk-DJT2547J.js → chunk-XNTAHMPA.js} +1 -1
  35. package/dist/chunk-ZDECILF6.js +16 -0
  36. package/dist/{chunk-42SLBGDI.js → chunk-ZNPGM3XE.js} +2 -2
  37. package/dist/compatibility-UPPKZ4KY.js +1 -0
  38. package/dist/{core-schema-423BSTE6.js → core-schema-Y47YFKH2.js} +7 -44
  39. package/dist/direct-completion-ZVPPEBDU.js +1 -0
  40. package/dist/dispatch-GRMMPKQD.js +1 -0
  41. package/dist/index.cjs +34 -34
  42. package/dist/index.js +1 -1
  43. package/dist/jobs-NRZHGQ7I.js +1 -0
  44. package/dist/queue-2VKW7SGH.js +1 -0
  45. package/dist/reconciliation-QC6SILLR.js +1 -0
  46. package/dist/{report-artifact-offload-6OOY2PTM.js → report-artifact-offload-PQNFDNPL.js} +1 -1
  47. package/dist/{server-74VGVXA4.js → server-EYMJROPX.js} +699 -1018
  48. package/dist/settlement-47EUTSGX.js +1 -0
  49. package/dist/vendor-errors-647LSH6H.js +1 -0
  50. package/dist/worker-AWJF7UYR.js +1 -0
  51. package/package.json +2 -2
  52. package/dist/actions-FKAX2PYT.js +0 -1
  53. package/dist/bundle-7JMACIIR.js +0 -1
  54. package/dist/chunk-2IATUJO6.js +0 -1
  55. package/dist/chunk-4KHDV5E7.js +0 -11
  56. package/dist/chunk-63VWBMYR.js +0 -3
  57. package/dist/chunk-7ZR5IO3B.js +0 -16
  58. package/dist/chunk-E7I3OQHQ.js +0 -203
  59. package/dist/chunk-JGGQ3UX5.js +0 -1
  60. package/dist/chunk-M5QHXNFZ.js +0 -10
  61. package/dist/chunk-MJOXIDKF.js +0 -935
  62. package/dist/chunk-OY24ERPV.js +0 -8
  63. package/dist/chunk-QYPSFZK3.js +0 -955
  64. package/dist/chunk-U4WIJZCX.js +0 -1
  65. package/dist/dispatch-E6VTNHHS.js +0 -1
  66. package/dist/queue-4XHYUV7J.js +0 -1
  67. package/dist/reconciliation-43CSC3XD.js +0 -1
  68. package/dist/settlement-I5IBIK6U.js +0 -1
  69. package/dist/worker-L5NISZQV.js +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ All notable changes to MCP Scraper are documented here. The format is based on [
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.97.1] - 2026-10-15
8
+
9
+ ### Fixed
10
+
11
+ - Keep upstream identities, session identifiers and service diagnostics out of public job and Maps responses and generated clients.
12
+ - Refund failed browser requests after an explicit connection rejection, and safely recover original holds without repeating work or issuing duplicate refunds.
13
+ - Recover failed search holds across supported search methods. Preserve uncertain connection outcomes for reconciliation.
14
+ - Mark Maps runs partial when requested fields are unavailable, and omit unsupported verification-challenge claims from failure summaries.
15
+
16
+ - Restore native background execution after an older deployment caused funded extraction jobs to remain pending. Preserve original job identities and billing during recovery.
17
+
18
+ ### Changed
19
+
20
+ - Allow temporary free X-Ray access across reports, collection, forms, and setup without changing saved trial dates or Thorbit links.
21
+
22
+ ## [0.97.0] - 2026-10-13
23
+
24
+ ### Removed
25
+
26
+ - **Breaking:** Remove recurring actions, workflow schedules, assistant reminders, automatic vault cleanup and their MCP/API/CLI controls. Use the retained tools and manual workflows for requested work; historical notes, results, billing records and credentials remain stored.
27
+ - Remove scheduling dashboards, run viewers, add-on pricing and current product guidance. Artifact templates move to their independent workspace and remain available through their existing tools.
28
+
29
+ ### Changed
30
+
31
+ - Host Memory and admitted background work through the existing native runtime. Preserve scoped search, full-file delivery, operation recovery, subscriptions and credit expiry; internal maintenance continues.
32
+ - Publish updated SDKs with scheduling methods removed and template methods in the artifacts namespace. Ordinary connected-service routes use `/connections`; existing connection transport aliases remain compatible.
33
+ - Align technical and business reports with the native worker while preserving customer costs, retries, refunds and client attribution.
34
+
7
35
  ## [0.96.3] - 2026-10-13
8
36
 
9
37
  ### Changed
@@ -2240,7 +2268,7 @@ All notable changes to MCP Scraper are documented here. The format is based on [
2240
2268
  - Write actions remain unavailable until the account owner explicitly enables them.
2241
2269
  - Provider-specific connection data is normalized into one agent-facing contract.
2242
2270
 
2243
- [Unreleased]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.96.3...HEAD
2271
+ [Unreleased]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.97.1...HEAD
2244
2272
  [0.95.2]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.95.1...v0.95.2
2245
2273
  [0.95.1]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.95.0...v0.95.1
2246
2274
  [0.95.0]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.94.4...v0.95.0
@@ -2414,3 +2442,7 @@ All notable changes to MCP Scraper are documented here. The format is based on [
2414
2442
  [0.96.2]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.96.1...v0.96.2
2415
2443
 
2416
2444
  [0.96.3]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.96.2...v0.96.3
2445
+
2446
+ [0.97.0]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.96.3...v0.97.0
2447
+
2448
+ [0.97.1]: https://github.com/VilovietaSEO/mcp-scraper/compare/v0.97.0...v0.97.1
package/README.md CHANGED
@@ -10,7 +10,7 @@ This repository owns the complete private product runtime while preserving indep
10
10
  deployment boundaries:
11
11
 
12
12
  - `/` — primary MCP/API server and public `mcp-scraper` package;
13
- - `apps/scheduler` — private scheduled execution and orchestration service;
13
+ - `apps/worker` — private native execution of admitted video and explicit Memory jobs;
14
14
  - `apps/web` — private Next.js product and marketing application;
15
15
  - `apps/memory` — hosted Memory service plus its independently publishable DB, Tools, and stdio packages.
16
16
 
@@ -85,20 +85,15 @@ npx -y -p mcp-scraper@latest mcp-scraper-cli report open last
85
85
 
86
86
  Workflow runs save `manifest.json`, `report.html`, CSVs, Markdown, and evidence JSON under `MCP_SCRAPER_OUTPUT_DIR/workflows` or `~/Downloads/mcp-scraper/workflows`. High-level workflow IDs are `directory`, `agent-packet`, `local-competitive-audit`, `map-comparison`, `serp-comparison`, `paa-expansion-brief`, and `ai-overview-language`.
87
87
 
88
- Run hosted workflow schedules and hosted run history:
88
+ Inspect hosted run history:
89
89
 
90
90
  ```bash
91
- npx -y -p mcp-scraper@latest mcp-scraper-cli schedule create local-competitive-audit --weekly --query roofers --state TN --min-pop 100000 --per-city 20 --webhook https://example.com/mcp-scraper-hook
92
- npx -y -p mcp-scraper@latest mcp-scraper-cli schedule list
93
- npx -y -p mcp-scraper@latest mcp-scraper-cli schedule run <schedule-id>
94
- npx -y -p mcp-scraper@latest mcp-scraper-cli schedule pause <schedule-id>
95
- npx -y -p mcp-scraper@latest mcp-scraper-cli schedule resume <schedule-id>
96
91
  npx -y -p mcp-scraper@latest mcp-scraper-cli runs list
97
92
  npx -y -p mcp-scraper@latest mcp-scraper-cli runs status <run-id>
98
93
  npx -y -p mcp-scraper@latest mcp-scraper-cli runs download <run-id>
99
94
  ```
100
95
 
101
- The hosted workflow API is mounted under `/workflows`: `GET /definitions`, `POST /run`, `GET /runs`, `GET /runs/:id`, `GET /runs/:id/artifacts/:artifactId`, `POST /schedules`, `GET /schedules`, `PATCH /schedules/:id`, `DELETE /schedules/:id`, and `POST /schedules/:id/run`. Scheduled dispatch is handled by `/workflows/cron/dispatch` and the main `/cron/tick` route when `CRON_SECRET` is configured. Current hosted artifacts are filesystem-backed for immediate run retrieval; durable public rollout should add S3/R2-compatible object storage.
96
+ The hosted workflow API is mounted under `/workflows`: `GET /definitions`, `POST /run`, `GET /runs`, `GET /runs/:id`, and `GET /runs/:id/artifacts/:artifactId`. Workflows run on explicit request. Internal maintenance resumes already-admitted jobs; it does not create user schedules.
102
97
 
103
98
  ## Build or enrich lead lists
104
99
 
@@ -177,7 +172,7 @@ Build the branded one-click bundle:
177
172
  npm run build:mcpb
178
173
  ```
179
174
 
180
- The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.96.3`, SHA-256 `78b3c9f6aaf5ad404315cbd66ec103928b5905a123eb13f633c520e4ecc1e708`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, API-key configuration field, and manually curated current-release message from the bundle manifest.
175
+ The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.97.1`, SHA-256 `2c98f521dd1cb860f08a6344febd9f7a4607606d9767085f899a0f834b5d88b1`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, API-key configuration field, and manually curated current-release message from the bundle manifest.
181
176
 
182
177
  The MCPB install exposes every tool — web-intelligence plus all `browser_*` tools — through the one `mcp-scraper` server.
183
178
 
@@ -296,20 +291,19 @@ Check `pagination.requestedPages`, `pagination.capturedPages`, and `pagination.p
296
291
  - `list_service_connections` — list this caller's tenant-owned Nango OAuth and official remote MCP connections, including verified provider-side account email/name when exposed, exact live reads, gated actions, permanently blocked administrative tools, credential transport, and schema-discovery metadata. Provider identity is distinct from the MCP Scraper login, and connections are never shared between customers.
297
292
  - `describe_service_connection_tool` — fetch the sanitized live MCP Tool definition for one tool listed on one tenant-owned connection, including its current callability, input schema, optional output schema, safe annotations, and schema hash. Use this before constructing provider-native arguments; provider functions stay behind the generic bridges instead of becoming dozens of permanent top-level tools.
298
293
  - `export_connected_service_data` — fetch a fresh Gmail, Google Calendar, Google Search Console, Zoom, Slack, Resend, or Meta dataset in one MCP call. Slack's `slack_channel_messages` dataset accepts a `channelId`, paginates top-level history, fetches threaded replies in bounded parallel batches, preserves file metadata, honors retry delays, and supports `allTime:true`; it never joins or changes the channel. Zoom's `zoom_transcripts` dataset resolves VTT files from recording metadata and downloads them through the authenticated connection without looping the separately rate-limited `get-meeting-transcript` function. Search Console's `search_console_performance` dataset walks accessible properties and bounded live Search Analytics pages with continuation. Small exports return inline; larger exports become private JSONL retained for seven days with exact `report_artifact_read` arguments plus an optional 15-minute human download URL. For relationship work, gather source evidence first: inspect existing People records, resolve the exact provider account, preserve provider provenance when writing a linked Communication, and never treat an export as permission to mutate the source account.
299
- - `export_search_console_table_data` — filter up to 50,000 Search Console rows already persisted by a scheduled `connection_sync` and create a private renewable JSONL artifact without calling Google again. Get the typed `gsc_performance_*` table name from `list_service_connections`, inspect it with `table-describe`, and use the same filters with `table-query` for interactive analysis.
294
+ - `export_search_console_table_data` — filter up to 50,000 Search Console historically stored Search Console rows and create a private renewable JSONL artifact without calling Google again. Get the typed `gsc_performance_*` table name from `list_service_connections`, inspect it with `table-describe`, and use the same filters with `table-query` for interactive analysis.
300
295
  - `renew_connected_data_download` — issue a fresh 15-minute signed URL for an unexpired private export artifact without pulling the provider again.
301
296
  - `read_service_connection` — run one small live read by exact allowlisted name across Nango OAuth or official remote MCP connections, including bounded Google Drive inventory, change, Doc, Sheet, and text-file tools. Do not loop it over a time range when `export_connected_service_data` supports that provider's collection.
302
297
  - `import_service_connection_to_memory` — run one exact approved read and upsert the bounded, redacted result into an existing ordinary Memory vault at a stable server-generated path. The snapshot is embedded for semantic search and explicitly marked as untrusted provider data. This is not account-wide pagination or continuous sync.
303
298
  - `call_service_connection_action` — run one exact mutation only after the account action switch is enabled. Google Drive, Search Console, Resend, GitHub, Gmail, Calendar, Slack, Zoom, Facebook, Google Analytics, X, and YouTube actions remain live-inventory, provider-policy, tenant, and audit gated; credential, OAuth-grant, and raw editor-session administration is never exposed.
304
- - `set_scheduled_action_connections` — bind exact tenant connections and exact tool grants to one scheduled action. Deterministic connection syncs are model-free and one schedule occurrence counts as one metered event even when provider pagination spans many pages.
305
299
 
306
- Connecting or reconnecting a provider and calling direct connected-account reads, gated writes, supported exports, or bounded Memory imports do not consume additional Credits. Scheduled occurrences cost 75 Credits each. Agent-mode runs also add 1.5× the model provider's actual reported cost; deterministic connection syncs pay only the 75-Credit run base. Separately metered scraper, browser, and media operations keep their normal rate-card charges. Scheduling has no separate subscription or monthly run quota.
300
+ Connected-account charges come from the canonical rate card and actual usage receipts. OAuth connect and reconnect do not consume Credits. Separately metered scraper, browser, and media operations keep their normal rates; inspect `get_credit_costs` before paid work.
307
301
 
308
- GitHub uses the same provider-neutral read/action bridges: 27 live reads cover repository discovery, issues, pull requests, commits, releases, and workflows; 20 live mutations remain account- and schedule-gated. The 47 Nango functions and expanded reconnect scopes are deployed. The current test token remains `public_repo` until owners approve its two pending organization requests, so private-repository and workflow parity is not yet claimed.
302
+ GitHub uses the same provider-neutral read/action bridges: 27 live reads cover repository discovery, issues, pull requests, commits, releases, and workflows; 20 live mutations remain account- and action-gated. The 47 Nango functions and expanded reconnect scopes are deployed. The current test token remains `public_repo` until owners approve its two pending organization requests, so private-repository and workflow parity is not yet claimed.
309
303
 
310
- Google Drive Phase 1 exposes ten bounded reads plus the separately gated `create-drive-folder` and `copy-drive-file` actions through those same bridges and through per-run Mastra toolsets. It is interactive tool support, not the later deterministic bulk-export/`connection_sync` phase; large binary delivery and broader Drive mutations remain unreleased.
304
+ Google Drive Phase 1 exposes ten bounded reads plus the separately gated `create-drive-folder` and `copy-drive-file` actions through those same bridges with explicit account and action permissions. It is interactive tool support, not a bulk-export feature; large binary delivery and broader Drive mutations remain unreleased.
311
305
 
312
- Google Search Console exposes eight bounded reads and eight gated property and sitemap mutations, including API-only batches for URL inspection, Search Analytics comparison, property management, and sitemap management. Batch calls return per-item receipts, run quota-consciously, and do not require database persistence; destructive batches default to dry-run previews and require explicit confirmation. Its optional deterministic `connection_sync` path backfills Search Analytics in bounded pages, persists raw records and checkpoints, mirrors normalized records into the selected Memory vault, and upserts performance rows into one indexed tenant-owned table per connection. The table supports exact/range/substring/in-list filtering by site, date, query, page, country, device, clicks, impressions, CTR, and position. Scheduled agent runs can instead receive only the exact Search Console tools granted to that schedule.
306
+ Google Search Console exposes eight bounded reads and eight gated property and sitemap mutations, including API-only batches for URL inspection, Search Analytics comparison, property management, and sitemap management. Batch calls return per-item receipts, run quota-consciously, and do not require database persistence; destructive batches default to dry-run previews and require explicit confirmation. Supported direct exports and Memory imports preserve returned records and checkpoints. Query tenant-owned tables with `table-describe` and `table-query`; the server does not create recurring syncs.
313
307
 
314
308
  ### Memory discovery and safe editing
315
309
 
@@ -353,9 +347,9 @@ The `mcp-scraper` server (and the MCPB bundle, which runs it) exposes both secti
353
347
 
354
348
  All MCP tools return `structuredContent` with the IDs, URLs, CSV paths, transcripts, browser session handles, replay paths, artifacts, recipe fields, or blueprint fields needed by the next step, plus readable text content for compatibility. Runtime `tools/list` omits output schemas so strict clients can register the complete catalog; the generated developer manifest retains every canonical output schema for validation and typed SDK generation. All tools carry MCP annotations; file-writing tools such as replay downloads and annotations state their filesystem side effects.
355
349
 
356
- The canonical tool inventory is generated at `docs/mcp-tool-manifest.generated.json`. The unified server exposes 367 tools: 241 scraper, browser, workflow, billing, and connected-service tools plus 126 durable-memory tools. Personal Assistant tools and routes are retired from active builds; their source history and persisted data remain recoverable from the archived pre-retirement branch. The scraper-side inventory includes complete Gmail selection, message, attachment, export, bulk-action, and Memory-import workflows; durable SERP, PAA, and single-page extraction starts and status; rendered site-content similarity; governed Local Sourcebook and Transparent Commons workflows; direct site-export reads; Editorial Reading Room and News Publisher templates; and production X-Ray setup, analytics, seven-model attribution, structured post-purchase surveys, reported impact, truthful view-evidence status, CRM policy and receipt, campaign, export, and scheduled-report tools. Provider setup remains absent until its authorization, ingestion, reconciliation, privacy, canary, cleanup, and deployment receipts are complete. Successful evidence-compiled Local Sourcebook revisions publish automatically to their canonical `localsourcebook.com` category profile and review URLs; administrator controls handle exceptional rejection or unpublishing. Release verification compares the exact local and hosted tool-name sets, not only the count.
350
+ The canonical tool inventory is generated at `docs/mcp-tool-manifest.generated.json`. The generated manifest is the authoritative current inventory across scraper, browser, manual workflow, billing, connected-service and durable Memory tools. Personal Assistant tools and routes are retired from active builds; their source history and persisted data remain recoverable from the archived pre-retirement branch. The scraper-side inventory includes complete Gmail selection, message, attachment, export, bulk-action, and Memory-import workflows; durable SERP, PAA, and single-page extraction starts and status; rendered site-content similarity; governed Local Sourcebook and Transparent Commons workflows; direct site-export reads; Editorial Reading Room and News Publisher templates; and production X-Ray setup, analytics, seven-model attribution, structured post-purchase surveys, reported impact, truthful view-evidence status, CRM policy and receipt, campaign, export, and report tools. Provider setup remains absent until its authorization, ingestion, reconciliation, privacy, canary, cleanup, and deployment receipts are complete. Successful evidence-compiled Local Sourcebook revisions publish automatically to their canonical `localsourcebook.com` category profile and review URLs; administrator controls handle exceptional rejection or unpublishing. Release verification compares the exact local and hosted tool-name sets, not only the count.
357
351
 
358
- For contract parity, stdio and MCPB memory calls invoke the matching public tool on the hosted MCP Scraper `/mcp` endpoint. The hosted aggregate runtime owns MCP Scraper-specific billing, scheduling, credential, and in-process cutover policy; its internal `/memory/mcp-call` bridge is a fallback to the standalone Memory service, not a second customer setup path. Existing direct Memory credentials remain compatible for one release, but all new customer setup uses the root endpoint and `MCP_SCRAPER_API_KEY`.
352
+ For contract parity, stdio and MCPB memory calls invoke the matching public tool on the hosted MCP Scraper `/mcp` endpoint. The hosted aggregate runtime owns MCP Scraper-specific billing, credential, and in-process cutover policy; its internal `/memory/mcp-call` bridge is a fallback to the standalone Memory service, not a second customer setup path. Existing direct Memory credentials remain compatible for one release, but all new customer setup uses the root endpoint and `MCP_SCRAPER_API_KEY`.
359
353
 
360
354
  The public REST registry in `src/api/public-rest-contract.ts` owns Maps search and directory run/status request and response schemas, headers, descriptions, and paths. Run `npm run contracts:rest` after changing one of those operations; `npm run contracts:rest:check` fails when the versioned artifact or the web docs projection is stale. The SDK imports `contracts/public-rest.v1.json`, projects it over its curated OpenAPI base, and regenerates Node and Python models together. Other public REST operations remain in that curated base until migrated.
361
355
 
@@ -412,7 +406,7 @@ The bundle runs `mcp-scraper` internally, so the user installs `MCP Scraper` onc
412
406
 
413
407
  ## Development
414
408
 
415
- Production deployments track `codex/mcp-ownership-tree` for the server, web app, Memory and scheduler. Their Vercel configurations admit this branch and disable automatic deployments from `main`. Before deploying server schema changes, run `npm run release:server:predeploy` against the production database. Customer request capture additionally requires `MCP_ANALYTICS_CAPTURE=1`; missing historical attribution stays unknown.
409
+ Production deployments track `codex/mcp-ownership-tree` for the server, web app, Memory and native worker. Their Vercel configurations admit this branch and disable automatic deployments from `main`. Before deploying server schema changes, run `npm run release:server:predeploy` against the production database. Customer request capture additionally requires `MCP_ANALYTICS_CAPTURE=1`; missing historical attribution stays unknown.
416
410
 
417
411
  See the [codebase layout and edit guide](docs/architecture/codebase-file-tree.md) for the populated tree, feature homes and compatibility entrypoints.
418
412
 
@@ -0,0 +1 @@
1
+ import{e as a,f as b,g as c,h as d,i as e,j as f,k as g,l as h}from"./chunk-5UQAL7HV.js";import"./chunk-FM7BX2LU.js";import"./chunk-NALXKTUU.js";import"./chunk-XNTAHMPA.js";import"./chunk-RDHGQ2WZ.js";import"./chunk-4YZ74SJ6.js";import"./chunk-JHDD6R3O.js";import"./chunk-SEZETSB3.js";import"./chunk-SDQW7IJN.js";import"./chunk-VVSHZV5S.js";import"./chunk-4ZVO6GX4.js";import"./chunk-WNUXMPVX.js";import"./chunk-EYGA6LOU.js";import"./chunk-OILOHQNL.js";export{d as GMAIL_ATTACHMENT_REF_TTL_MS,c as GMAIL_INLINE_BODY_BYTES,b as GMAIL_MAIN_CONTRACT_HASH,a as GMAIL_MAIN_CONTRACT_VERSION,h as GmailService,e as GmailServiceError,g as normalizeGmailMessage,f as parseGmailAddresses};
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  import{readFileSync as s}from"fs";function c(){try{for(let r of s(".env","utf8").split(`
3
- `)){let o=r.indexOf("=");if(o<1||r.trimStart().startsWith("#"))continue;let e=r.slice(0,o).trim();process.env[e]||(process.env[e]=r.slice(o+1).trim())}}catch{}}c();async function a(){let[{serve:r},{app:o},{startWorker:e},{migrate:i}]=await Promise.all([import("@hono/node-server"),import("../server-74VGVXA4.js"),import("../worker-L5NISZQV.js"),import("../core-schema-423BSTE6.js")]),n=parseInt(process.env.PORT??"3001");try{if(await i(),process.env.ANALYTICS_DATABASE_URL){let{migrateAnalytics:t}=await import("../migrations-3AKEC5QE.js");await t()}e(),r({fetch:o.fetch,port:n},t=>{console.log(`[server] http://localhost:${t.port}`),console.log(`[server] admin auth: ${process.env.ADMIN_KEY?"configured":"not configured"}`)})}catch(t){console.error("[startup] server preflight failed",t instanceof Error?t.name:"unknown_error"),process.exit(1)}}a();
3
+ `)){let o=r.indexOf("=");if(o<1||r.trimStart().startsWith("#"))continue;let e=r.slice(0,o).trim();process.env[e]||(process.env[e]=r.slice(o+1).trim())}}catch{}}c();async function a(){let[{serve:r},{app:o},{startWorker:e},{migrate:i}]=await Promise.all([import("@hono/node-server"),import("../server-EYMJROPX.js"),import("../worker-AWJF7UYR.js"),import("../core-schema-Y47YFKH2.js")]),n=parseInt(process.env.PORT??"3001");try{if(await i(),process.env.ANALYTICS_DATABASE_URL){let{migrateAnalytics:t}=await import("../migrations-3AKEC5QE.js");await t()}e(),r({fetch:o.fetch,port:n},t=>{console.log(`[server] http://localhost:${t.port}`),console.log(`[server] admin auth: ${process.env.ADMIN_KEY?"configured":"not configured"}`)})}catch(t){console.error("[startup] server preflight failed",t instanceof Error?t.name:"unknown_error"),process.exit(1)}}a();
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import{k as N,l as j,m as L,n as T,r as U}from"../chunk-4HZXATHB.js";import"../chunk-KJQXUZ4Y.js";import"../chunk-6N5EC7ZO.js";import{g as v,j as b,k as D,l as K,m as H}from"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import{c as _}from"../chunk-OILOHQNL.js";import{a as P}from"../chunk-JGGQ3UX5.js";import{Command as he}from"commander";import{spawn as te}from"child_process";import{mkdir as ke,writeFile as Pe}from"fs/promises";import{basename as Ce,join as X}from"path";function ie(e){return e.apiKey?.trim()||"sk_live_your_key"}function ae(e){return e.packageSpec?.trim()||"mcp-scraper@latest"}function A(e={}){return["-y","--package",ae(e),"mcp-scraper"]}function se(e){let n={MCP_SCRAPER_API_KEY:ie(e)},c=e.browserProfileName?.trim();return c&&(n.BROWSER_AGENT_PROFILE_NAME=c),e.browserProfileSaveChanges===!0&&(n.BROWSER_AGENT_PROFILE_SAVE_CHANGES="true"),n}function q(){return["mcp","remove","mcp-scraper","-s","user"]}function B(){return["mcp","get","mcp-scraper"]}function J(e){let n=e.match(/^\s*Command:\s*(.+?)\s*$/m)?.[1];if(!n)return null;let c=e.match(/^\s*Args:\s*(.*?)\s*$/m)?.[1]??"",i=c.length?c.split(/\s+/):[],p={},u=e.split(/^\s*Environment:\s*$/m)[1];if(u)for(let a of u.split(`
3
- `)){let l=a.match(/^\s{2,}([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);if(!l){if(a.trim().length&&!/^\s{2,}/.test(a))break;continue}p[l[1]]=l[2]}return{command:n,args:i,env:p}}function O(e){let n=["mcp","add","mcp-scraper","--scope","user"];for(let[c,i]of Object.entries(e.env))n.push("--env",`${c}=${i}`);return n.push("--",e.command,...e.args),n}function G(e={}){let n=["mcp","add","mcp-scraper","--scope","user"];for(let[c,i]of Object.entries(se(e)))n.push("--env",`${c}=${i}`);return n.push("--","npx",...A(e)),n}function I(e){if(e==="claude-code")return"claude";if(e==="claude"||D.hosts.some(n=>n.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 ce(e){return K(e==="claude"?"claude-code":e)}function W(e,n={}){let c=I(e),i=ce(c),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 c==="codex"?["# Codex MCP config",a,i.exactConfig,"",`Continuation: ${i.continuation}`,`Rollback: ${i.rollback}`,"",p].join(`
4
- `):c==="claude"?["# Claude Code command",a,i.exactConfig,"","# One-command Claude Code setup",u,"",`Continuation: ${i.continuation}`,`Rollback: ${i.rollback}`,"",p].join(`
5
- `):c==="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(`
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-IPQOBOFY.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
6
  `):[`# ${i.label} MCP config`,a,i.exactConfig,"",`Continuation: ${i.continuation}`,`Rollback: ${i.rollback}`,...i.restartRequired?["",p]:[]].join(`
7
- `)}import{access as pe,mkdir as ue,readFile as le}from"fs/promises";import{homedir as de}from"os";import{join as me}from"path";function fe(e,n=!1){return e?"pass":n?"warn":"fail"}function ge(e){return Number(e.replace(/^v/,"").split(".")[0]??0)}async function we(){let e=process.env.MCP_SCRAPER_KEY_PATH?.trim()||me(de(),".mcp-scraper-key");try{return(await le(e,"utf8")).trim()||null}catch{return null}}async function ye(e){try{let n=await e("https://registry.npmjs.org/mcp-scraper/latest",{signal:AbortSignal.timeout(5e3)});return n.ok?(await n.json()).version??null:null}catch{return null}}async function Y(e={}){let n=e.fetchImpl??fetch,c=(e.apiUrl??process.env.MCP_SCRAPER_API_URL??"https://mcpscraper.dev").replace(/\/$/,""),i=_(e.outputDir),p=e.apiKey?.trim()||process.env.MCP_SCRAPER_API_KEY?.trim()||await we(),u=await ye(n),a=[],l=ge(process.version);a.push({id:"node",label:"Node.js",status:fe(l>=20),detail:process.version,fix:l>=20?void 0:"Install Node.js 20 or newer."}),a.push({id:"package_version",label:"Local package",status:u&&u!==P?"warn":"pass",detail:u?`local ${P}, npm latest ${u}`:`local ${P}, npm latest unavailable`,fix:u&&u!==P?"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 ue(i,{recursive:!0}),await pe(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 n(`${c}/me`,{headers:{"x-api-key":p},signal:AbortSignal.timeout(8e3)});a.push({id:"api_reachability",label:"Hosted API",status:r.ok?"pass":"fail",detail:`${c}/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 ${A().join(" ")}`,fix:"Restart the MCP client after package updates."}),{ok:a.every(r=>r.status==="pass"||r.status==="skip"),version:P,npmLatest:u,checks:a,recommendedConfig:{command:"npx",args:A(),env:{MCP_SCRAPER_API_KEY:p?"$MCP_SCRAPER_API_KEY":"sk_live_your_key"}}}}function F(e){let n={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`[${n[i.status]}] ${i.label}: ${i.detail}${p}`}),"",`Recommended MCP command: npx ${e.recommendedConfig.args.join(" ")}`].join(`
9
- `)}var z={"xray-setup":H(),"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(`
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(`
10
10
  `),"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
11
  `),"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
12
  `),"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,26 +15,25 @@ import{k as N,l as j,m as L,n as T,r as U}from"../chunk-4HZXATHB.js";import"../c
15
15
  `),"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
16
  `),"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
17
  `),"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 x(){return Object.keys(z).sort()}function V(e){let n=z[e];if(!n)throw new Error(`Unknown prompt "${e}". Available: ${x().join(", ")}`);return n}function f(e){if(e==null||e==="")return;let n=Number(e);return Number.isFinite(n)?n:void 0}function Ae(e){if(e!==void 0){if(typeof e=="boolean")return e;if(e==="true")return!0;if(e==="false")return!1}}function $(e){return Object.fromEntries(Object.entries(e).filter(([,n])=>n!==void 0))}function Q(e,n){return $(e==="agent-packet"||e==="serp-comparison"||e==="ai-overview-language"?{keyword:n.keyword,domain:n.domain,url:n.url,location:n.location,maxResults:f(n.maxResults),maxQuestions:f(n.maxQuestions),extractTop:f(n.extractTop),includeSerp:n.serp===!1?!1:void 0,includePaa:n.paa===!1?!1:void 0,includeAiOverview:Ae(n.includeAiOverview),returnPartial:n.returnPartial===!1?!1:void 0}:e==="paa-expansion-brief"?{keyword:n.keyword,location:n.location,maxQuestions:f(n.maxQuestions),depth:f(n.depth),returnPartial:n.returnPartial===!1?!1:void 0}:e==="directory"||e==="local-competitive-audit"||e==="map-comparison"?{query:n.query,location:n.location,state:n.state,minPopulation:f(n.minPop??n.minPopulation),maxCities:f(n.maxCities),maxResultsPerCity:f(n.perCity??n.maxResultsPerCity??n.maxResults),concurrency:f(n.concurrency),proxyMode:n.proxyMode,hydrateTop:e==="local-competitive-audit"||e==="map-comparison"?f(n.hydrateTop):void 0,maxReviews:e==="local-competitive-audit"||e==="map-comparison"?f(n.reviews??n.maxReviews):void 0,returnPartial:n.returnPartial===!1?!1:void 0}:n)}function s(e,n){n?process.stdout.write(`${JSON.stringify(e,null,2)}
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)}
19
19
  `):typeof e=="string"?process.stdout.write(`${e}
20
20
  `):process.stdout.write(`${JSON.stringify(e,null,2)}
21
- `)}function Z(e){return e.replace(/sk_[A-Za-z0-9_-]+/g,"sk_***")}function S(e,n){return new Promise(c=>{let i=te(e,n,{stdio:["ignore","pipe","pipe"]}),p=[],u=[],a=!1,l=r=>{a||(a=!0,c(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=>{l({code:127,stdout:"",stderr:r.message})}),i.on("close",r=>{l({code:typeof r=="number"?r:1,stdout:Buffer.concat(p).toString("utf8"),stderr:Buffer.concat(u).toString("utf8")})})})}function ve(e){return e.daily?"daily":e.monthly?"monthly":"weekly"}function re(e){let n=String(e.apiKey??process.env.MCP_SCRAPER_API_KEY??"").trim();if(!n)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:n}}function ee(e){let n=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",c=process.platform==="win32"?["/c","start","",e]:[e];try{return te(n,c,{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function m(e,n,c,i){let{apiUrl:p,apiKey:u}=re(c),a=await fetch(`${p}${e}`,{method:n,headers:{"Content-Type":"application/json","x-api-key":u},body:i==null?void 0:JSON.stringify(i)}),l=await a.json().catch(()=>({}));if(!a.ok){let r=typeof l.error=="string"?l.error:`API request failed with ${a.status}`;throw new Error(r)}return l}function be(e){return e.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")}function Se(){let e=new he;e.name("mcp-scraper-cli").description("Human CLI for MCP Scraper setup, workflows, reports, and agent-ready SEO artifacts.").version(P),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 r=>{let t=await Y({apiKey:r.apiKey,apiUrl:r.apiUrl,outputDir:r.outputDir});s(r.json?t:F(t),r.json),t.ok||(process.exitCode=1)});let n=e.command("billing").description("Inspect billing and start checkout flows."),c=n.command("concurrency").description("Manage MCP Scraper concurrency packs.");c.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 r=>{let t=await m("/billing/credits","POST",r,{});if(r.json)s(t.concurrency,!0);else{let o=t.concurrency?.upgrade;s([`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(`
22
- `),!1)}}),c.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(r,t)=>{let o=await m("/billing/concurrency/terminal-checkout","POST",t,{quantity:Number(r)});if(t.json){s(o,!0);return}let d=!!o.checkout_url&&t.open!==!1&&ee(o.checkout_url);s([`Concurrency pack: ${o.price?.price_label??"$5/month for 2 extra browsers"}`,o.updated?"Concurrency pack quantity updated.":d?`Opened checkout: ${o.checkout_url}`:`Checkout URL: ${o.checkout_url}`,o.next_step??"Complete checkout, then retry the MCP request."].join(`
23
- `),!1)}),n.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(r,t)=>{let o=await m("/billing/subscribe/terminal-checkout","POST",t,{tier:String(r).toLowerCase()});if(t.json){s(o,!0);return}if(o.updated){s(o.message??`Switched to ${o.tier}.`,!1);return}let d=t.open!==!1&&!!o.checkout_url&&ee(o.checkout_url),C=o.billing_interval==="year"?"year":"month",h=o.amount_usd??o.monthly_usd,k=o.credits_per_interval??o.credits_per_month,g=C==="year"?"/yr":"/mo",w=C==="year"?"credits/yr":"credits/mo",y=[o.credits_never_expire?"credits never expire":null,o.includes_memory?"includes Memory Pro":null,o.intro].filter(Boolean).join(" \xB7 ");s([`Plan: ${o.label??r} \u2014 $${h}${g} \xB7 ${k?.toLocaleString()} ${w} \xB7 ${o.concurrency} concurrency${y?` \xB7 ${y}`:""}`,d?`Opened checkout: ${o.checkout_url}`:`Checkout URL: ${o.checkout_url}`,o.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(r,t)=>{let o=I(r),d=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(!d?.trim())throw new Error("MCP_SCRAPER_API_KEY is required for --apply. Set it in the environment or pass --api-key.");let h={apiKey:d,packageSpec:t.package,browserProfileName:t.browserProfile,browserProfileSaveChanges:t.saveBrowserProfileChanges},k=await S("claude",B()),g=k.code===0?J(k.stdout):null,w=await S("claude",q()),y=await S("claude",G(h));if(y.code!==0){let oe=Z([y.stderr,y.stdout].filter(Boolean).join(`
25
- `).trim()),R;throw w.code!==0?R="No existing entry was removed, so your configuration is unchanged.":g?R=(await S("claude",O(g))).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(g).join(" "):R="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.",oe||"No error output returned.",R,"Make sure Claude Code is installed and the `claude` command is on PATH."].join(`
27
- `))}let E=await S("claude",["mcp","list"]),M={host:"claude",applied:!0,replacedExisting:w.code===0,command:"npx",args:A(h),nextStep:"Fully exit Claude Code, start a new Claude terminal, then run: claude mcp list",list:Z([E.stdout,E.stderr].filter(Boolean).join(`
28
- `).trim())};if(t.json){s(M,!0);return}s(["Applied Claude Code MCP config: mcp-scraper",w.code===0?"Replaced existing mcp-scraper entry.":"No existing mcp-scraper entry found; added a new one.","Command: npx "+A(h).join(" "),t.browserProfile?`Browser profile: ${t.browserProfile}`:"","","Next step: fully exit Claude Code, start a new Claude terminal, then run:"," claude mcp list","",M.list?`Current Claude MCP list:
29
- ${M.list}`:""].filter(Boolean).join(`
30
- `),!1);return}let C=W(o,{apiKey:t.apiKey,packageSpec:t.package,browserProfileName:t.browserProfile,browserProfileSaveChanges:t.saveBrowserProfileChanges});s(t.json?{host:o,protocolVersion:v,protocolDigest:b,text:C}:C,t.json)}),i.command("prompt [name]").description("Print an agent prompt template.").option("--json","Print machine-readable JSON").action((r,t)=>{if(!r){s(t.json?{prompts:x()}:x().join(`
31
- `),t.json);return}let o=V(r);s(t.json?{name:r,...r==="xray-setup"?{protocolVersion:v,protocolDigest:b}:{},text:o}:o,t.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(r=>{let t=T();r.json?s({workflows:t},!0):s(t.map(o=>`${o.id} ${o.title}
32
- ${o.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(r,t)=>{let o=await U(r,Q(r,t),{apiKey:t.apiKey,apiUrl:t.apiUrl,outputDir:t.outputDir});t.json?s(o,!0):s([`${o.title}: ${o.status}`,o.summary,o.reportPath?`Report: ${o.reportPath}`:"",o.warnings.length?`Warnings:
34
- ${o.warnings.map(d=>`- ${d}`).join(`
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(`
35
35
  `)}`:""].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 r=>{let t=await N(r.outputDir);s(r.json?{reports:t}:t.map(o=>`${o.startedAt} ${o.workflow} ${o.status} ${o.reportPath??o.manifestPath}`).join(`
37
- `),r.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(r="last",t)=>{let o=await j(r,t.outputDir);if(!o?.reportPath)throw new Error(`No report found for "${r}"`);s(t.json?{path:o.reportPath,run:o}:o.reportPath,t.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(r="last",t)=>{if(t.json){let d=await j(r,t.outputDir);if(!d?.reportPath)throw new Error(`No report found for "${r}"`);s({path:d.reportPath,run:d},!0);return}let o=await L(r,t.outputDir);s(`Opened: ${o}`,!1)});let a=e.command("schedule").description("Create and manage hosted workflow schedules.");be(a.command("create <workflowId>").description("Create a recurring hosted workflow schedule.").option("--api-key <key>","MCP Scraper API key").option("--api-url <url>","MCP Scraper API URL","https://mcpscraper.dev").option("--name <name>","Schedule name").option("--daily","Run daily").option("--weekly","Run weekly").option("--monthly","Run monthly").option("--timezone <tz>","Schedule timezone","UTC").option("--webhook <url>","HTTPS webhook URL").option("--next-run-at <iso>","First run time as an ISO timestamp").option("--json","Print machine-readable JSON")).action(async(r,t)=>{let o=await m("/workflows/schedules","POST",t,{workflowId:r,name:t.name,input:Q(r,t),cadence:ve(t),timezone:t.timezone,webhookUrl:t.webhook,nextRunAt:t.nextRunAt});s(o,t.json)}),a.command("list").description("List hosted workflow schedules.").option("--api-key <key>","MCP Scraper API key").option("--api-url <url>","MCP Scraper API URL","https://mcpscraper.dev").option("--json","Print machine-readable JSON").action(async r=>{let t=await m("/workflows/schedules","GET",r);r.json?s(t,!0):s(t.schedules.map(o=>`${o.id} ${o.status} ${o.workflow_id} ${o.next_run_at??""}`).join(`
38
- `),!1)}),a.command("pause <id>").description("Pause a hosted workflow schedule.").option("--api-key <key>","MCP Scraper API key").option("--api-url <url>","MCP Scraper API URL","https://mcpscraper.dev").option("--json","Print machine-readable JSON").action(async(r,t)=>s(await m(`/workflows/schedules/${r}`,"PATCH",t,{status:"paused"}),t.json)),a.command("resume <id>").description("Resume a hosted workflow schedule.").option("--api-key <key>","MCP Scraper API key").option("--api-url <url>","MCP Scraper API URL","https://mcpscraper.dev").option("--json","Print machine-readable JSON").action(async(r,t)=>s(await m(`/workflows/schedules/${r}`,"PATCH",t,{status:"active"}),t.json)),a.command("delete <id>").description("Delete a hosted workflow schedule.").option("--api-key <key>","MCP Scraper API key").option("--api-url <url>","MCP Scraper API URL","https://mcpscraper.dev").option("--json","Print machine-readable JSON").action(async(r,t)=>s(await m(`/workflows/schedules/${r}`,"DELETE",t),t.json)),a.command("run <id>").description("Run a hosted workflow schedule now.").option("--api-key <key>","MCP Scraper API key").option("--api-url <url>","MCP Scraper API URL","https://mcpscraper.dev").option("--json","Print machine-readable JSON").action(async(r,t)=>s(await m(`/workflows/schedules/${r}/run`,"POST",t,{}),t.json));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 r=>{let t=await m("/workflows/runs","GET",r);r.json?s(t,!0):s(t.runs.map(o=>`${o.id} ${o.status} ${o.workflow_id} ${o.queued_at}`).join(`
39
- `),!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(r,t)=>s(await m(`/workflows/runs/${r}`,"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(r,t)=>{let o=await m(`/workflows/runs/${r}`,"GET",t),{apiUrl:d,apiKey:C}=re(t),h=X(t.outputDir??_(),"workflow-downloads",r);await ke(h,{recursive:!0});let k=[];for(let g of o.run.artifacts??[]){let w=await fetch(`${d}/workflows/runs/${r}/artifacts/${g.id}`,{headers:{"x-api-key":C}});if(!w.ok)throw new Error(`Failed to download ${g.label}: HTTP ${w.status}`);let y=X(h,Ce(g.path));await Pe(y,Buffer.from(await w.arrayBuffer())),k.push(y)}s(t.json?{runId:r,files:k}:k.join(`
40
- `),t.json)}),e}async function ne(e=process.argv){await Se().parseAsync(e)}ne().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});
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)});
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{a as e}from"../chunk-42SLBGDI.js";import"../chunk-QYPSFZK3.js";import"../chunk-QER2ELZP.js";import"../chunk-4KHDV5E7.js";import"../chunk-7POHNTVW.js";import"../chunk-MVMWPKRC.js";import"../chunk-63VWBMYR.js";import"../chunk-3F7AFM53.js";import"../chunk-RDHGQ2WZ.js";import"../chunk-2IATUJO6.js";import"../chunk-OY24ERPV.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-7ZR5IO3B.js";import"../chunk-WP3QS4CJ.js";import"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import"../chunk-OILOHQNL.js";import"../chunk-JGGQ3UX5.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{a as e}from"../chunk-ZNPGM3XE.js";import"../chunk-4AEW53RU.js";import"../chunk-TZYITYL4.js";import"../chunk-B5Y26E3A.js";import"../chunk-QER2ELZP.js";import"../chunk-QCBCOEH5.js";import"../chunk-7POHNTVW.js";import"../chunk-3F7AFM53.js";import"../chunk-KBZY2OGP.js";import"../chunk-RDHGQ2WZ.js";import"../chunk-4YZ74SJ6.js";import"../chunk-JHDD6R3O.js";import"../chunk-CNSVZTTT.js";import"../chunk-SEZETSB3.js";import"../chunk-SDQW7IJN.js";import"../chunk-K3VDGJNK.js";import"../chunk-7SR7MEU5.js";import"../chunk-ANQ4JJVI.js";import"../chunk-FLAWC5PF.js";import"../chunk-B4ZKPLSP.js";import"../chunk-VVSHZV5S.js";import"../chunk-PKQHHCO4.js";import"../chunk-4ZVO6GX4.js";import"../chunk-C7MGJUTU.js";import"../chunk-WNUXMPVX.js";import"../chunk-ZDECILF6.js";import"../chunk-EYGA6LOU.js";import"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import"../chunk-OILOHQNL.js";import"../chunk-IPQOBOFY.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:_});
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as s}from"../chunk-7ZR5IO3B.js";import"../chunk-WP3QS4CJ.js";import{a as e}from"../chunk-JGGQ3UX5.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(`
2
+ import{a as s}from"../chunk-ZDECILF6.js";import"../chunk-EYGA6LOU.js";import{a as e}from"../chunk-IPQOBOFY.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
3
  `)),process.exit(0));process.stdout.write(s({version:e,color:!r,apiKeyConfigured:!!process.env.MCP_SCRAPER_API_KEY?.trim()}));
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{a as r}from"../chunk-42SLBGDI.js";import"../chunk-QYPSFZK3.js";import"../chunk-QER2ELZP.js";import"../chunk-4KHDV5E7.js";import"../chunk-7POHNTVW.js";import"../chunk-MVMWPKRC.js";import"../chunk-63VWBMYR.js";import"../chunk-3F7AFM53.js";import"../chunk-RDHGQ2WZ.js";import"../chunk-2IATUJO6.js";import"../chunk-OY24ERPV.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-7ZR5IO3B.js";import"../chunk-WP3QS4CJ.js";import"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import"../chunk-OILOHQNL.js";import"../chunk-JGGQ3UX5.js";import"../chunk-HNNYXMSI.js";r();
2
+ import{a as r}from"../chunk-ZNPGM3XE.js";import"../chunk-4AEW53RU.js";import"../chunk-TZYITYL4.js";import"../chunk-B5Y26E3A.js";import"../chunk-QER2ELZP.js";import"../chunk-QCBCOEH5.js";import"../chunk-7POHNTVW.js";import"../chunk-3F7AFM53.js";import"../chunk-KBZY2OGP.js";import"../chunk-RDHGQ2WZ.js";import"../chunk-4YZ74SJ6.js";import"../chunk-JHDD6R3O.js";import"../chunk-CNSVZTTT.js";import"../chunk-SEZETSB3.js";import"../chunk-SDQW7IJN.js";import"../chunk-K3VDGJNK.js";import"../chunk-7SR7MEU5.js";import"../chunk-ANQ4JJVI.js";import"../chunk-FLAWC5PF.js";import"../chunk-B4ZKPLSP.js";import"../chunk-VVSHZV5S.js";import"../chunk-PKQHHCO4.js";import"../chunk-4ZVO6GX4.js";import"../chunk-C7MGJUTU.js";import"../chunk-WNUXMPVX.js";import"../chunk-ZDECILF6.js";import"../chunk-EYGA6LOU.js";import"../chunk-24YDQ3EO.js";import"../chunk-OLYPLX5C.js";import"../chunk-B3JRL45T.js";import"../chunk-OILOHQNL.js";import"../chunk-IPQOBOFY.js";import"../chunk-HNNYXMSI.js";r();
@@ -1,2 +1,2 @@
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{L as t}from"../chunk-6D25Q5BR.js";import"../chunk-C3JWX6KM.js";import{d as r}from"../chunk-B4ZKPLSP.js";import"../chunk-VVSHZV5S.js";import"../chunk-PKQHHCO4.js";import"../chunk-4ZVO6GX4.js";import"../chunk-C7MGJUTU.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();
@@ -0,0 +1 @@
1
+ import{j as b,k as c}from"./chunk-F3M7INSN.js";import"./chunk-JMZIDHYQ.js";import{F as a}from"./chunk-NQFTETVC.js";import"./chunk-KXJCGV3H.js";import"./chunk-C3JWX6KM.js";import"./chunk-CNSVZTTT.js";import"./chunk-SEZETSB3.js";import"./chunk-SDQW7IJN.js";import"./chunk-K3VDGJNK.js";import"./chunk-7SR7MEU5.js";import"./chunk-ANQ4JJVI.js";import"./chunk-PKQHHCO4.js";import"./chunk-4ZVO6GX4.js";import"./chunk-C7MGJUTU.js";import"./chunk-WNUXMPVX.js";import"./chunk-OLYPLX5C.js";import"./chunk-B3JRL45T.js";import"./chunk-OILOHQNL.js";import"./chunk-HNNYXMSI.js";export{b as MAX_ANALYZED_LINK_EDGES,a as SITE_EXTRACT_PAGE_CHUNK,c as assembleExtractArtifacts};
@@ -0,0 +1,4 @@
1
+ import{A as _,B as x,C as b,D as C,E as d,g as w}from"./chunk-SJNSTO3C.js";import{d as g,i as h,t as u}from"./chunk-ANQ4JJVI.js";import{c}from"./chunk-FLAWC5PF.js";import{a as f}from"./chunk-WNUXMPVX.js";import{c as y,d as l}from"./chunk-HNNYXMSI.js";var s=e=>`paa-execution:${e}`;async function I(e,t){if(e.options.executionProtocol!==1||e.options.directCompletionProtocol!==1||e.options.executionOwner!=="direct"||e.options.serpOnly===!0||!t)return null;let r=await u(s(e.id),e.user_id,"harvest_paa",e.id);if(!r||r.flags.intentVersion!==1||r.flags.purpose!=="paa-direct")return null;let i=r.flags,n=i.hold,o=i.options,p={intentVersion:1,purpose:"paa-direct",userId:i.userId,jobId:i.jobId,options:o,callbackUrl:i.callbackUrl,requestFingerprint:i.requestFingerprint,hold:n};if(i.userId!==e.user_id||i.jobId!==e.id||!o||n?.userId!==e.user_id||n.key!==`paa-harvest:${e.id}:hold`||n.operation!=="paa"||!Number.isSafeInteger(n.amountMc)||n.amountMc<=0||n.description!==`PAA harvest: ${o.query}`.slice(0,500)||e.options.executionRunId!==s(e.id)||e.options.billingDebitKey!==n.key||e.options.billingHoldMc!==n.amountMc||e.query!==o.query||e.callback_url!==i.callbackUrl||e.options.requestFingerprint!==i.requestFingerprint||e.options.paaLeaseToken!==t||l(Object.fromEntries(Object.keys(o).map(m=>[m,e.options[m]])))!==l(o)||r.fingerprint!==y(l(p)))throw Error("Direct completion intent conflicts");return{profile:"direct",recordToken:t,jobId:e.id,userId:e.user_id,tool:"harvest_paa",requestFingerprint:String(i.requestFingerprint),options:o,callbackUrl:e.callback_url,hold:n,refundOperation:"paa_refund",sourceSurface:"paa_harvest",deadlineAt:0}}async function a(e,t,r){r&&(await h(r).catch(()=>{}),t&&await x(t,r).catch(()=>{}));let i=await c(e.id,e.user_id)??e;return _(i,i.options.holdState==="settled"?"settled":t?"held":"unconfirmed")}async function N(e){let t;try{return t=await I(e.job,e.recordToken)??void 0,!t||e.claim.runId!==s(e.job.id)||e.claim.userId!==e.job.user_id?a(e.job):(await b(t,e.claim,e.result,e.amountMc,e.status,e.publicError??null,e.httpStatus??200,e.requiresPhysical??!0),await d(t,e.claim,!1))}catch{return a(e.job,t,e.claim)}}async function E(e){let t=await c(typeof e=="string"?e:e.id,typeof e=="string"?void 0:e.user_id);if(!t||t.options.directCompletionProtocol!==1)return null;let r,i=null;try{let n=await u(s(t.id),t.user_id,"harvest_paa",t.id);return!t.options.syncCapturedAt&&n?.phase==="claimed"?null:(r=await I(t,String(t.options.paaLeaseToken??""))??void 0,r?n?.phase==="published"&&t.options.executionState==="finalized"&&t.options.holdState==="settled"?C(t,!0):!t.options.syncCapturedAt||(i=await g(s(t.id),t.user_id),!i)?a(t,r):(await w(t,i),await d(r,i,!0,!0)):a(t))}catch{return a(t,r,i??void 0)}}async function R(e,t){return(await E(e))?.status===200}async function q(e=25){let t=(await f().execute({sql:`SELECT j.id,j.user_id FROM jobs j JOIN operation_runs r ON r.id='paa-execution:'||j.id AND r.user_id=j.user_id JOIN operation_executions x ON x.run_id=r.id
2
+ WHERE json_extract(j.options,'$.directCompletionProtocol')=1 AND json_extract(j.options,'$.syncCapturedAt') IS NOT NULL
3
+ AND (x.phase='unknown' OR (x.phase='effects_started' AND x.expires_at_ms<=CAST(strftime('%s','now') AS INTEGER)*1000))
4
+ ORDER BY j.created_at LIMIT ?`,args:[Math.max(1,Math.min(100,Math.floor(e)))]})).rows,r=0,i=0;for(let n of t){let o=await c(String(n.id),Number(n.user_id));if(o)try{(await E(o))?.status===200?r++:i++}catch{i++}}return{checked:t.length,finalized:r,pending:i}}export{N as a,E as b,R as c,q as d};