mcp-scraper 0.39.0 → 0.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +382 -44
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +6 -6
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +4 -4
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +3 -3
- package/dist/{chunk-DO6WGZRS.js → chunk-3PIWJS6Y.js} +2 -2
- package/dist/{chunk-YUE46JGV.js → chunk-7XBBFBYY.js} +2 -2
- package/dist/{chunk-AFY6DNL7.js → chunk-GUVKHCKE.js} +13 -3
- package/dist/chunk-GUVKHCKE.js.map +1 -0
- package/dist/{chunk-TJRCAP7C.js → chunk-LP6E462I.js} +8 -8
- package/dist/chunk-LP6E462I.js.map +1 -0
- package/dist/{chunk-4WAE4AV5.js → chunk-NVXNEOUQ.js} +2 -2
- package/dist/{chunk-3HOANNNR.js → chunk-X2LKCX6H.js} +2 -2
- package/dist/{chunk-ATWQRW3K.js → chunk-XORPNO3Z.js} +2 -2
- package/dist/chunk-ZUJLSICT.js +7 -0
- package/dist/chunk-ZUJLSICT.js.map +1 -0
- package/dist/{db-6AJW47NF.js → db-W3CP562I.js} +2 -2
- package/dist/{extract-bundle-CWG35SKV.js → extract-bundle-K4PG3RZJ.js} +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{location-data-repository-BGKZF7DV.js → location-data-repository-RLQX6SNM.js} +3 -3
- package/dist/{server-BXNVNAMT.js → server-KM3CFWCF.js} +373 -52
- package/dist/server-KM3CFWCF.js.map +1 -0
- package/dist/{site-extract-repository-QZKZSQLQ.js → site-extract-repository-2SMMFKKL.js} +3 -3
- package/dist/{worker-YTIMPE57.js → worker-FXAGFYOE.js} +5 -5
- package/package.json +7 -7
- package/dist/chunk-6NWL3MVU.js +0 -7
- package/dist/chunk-6NWL3MVU.js.map +0 -1
- package/dist/chunk-AFY6DNL7.js.map +0 -1
- package/dist/chunk-TJRCAP7C.js.map +0 -1
- package/dist/server-BXNVNAMT.js.map +0 -1
- package/docs/adr/0001-in-page-graphql-interception-for-anti-bot-scraping.md +0 -58
- package/docs/adr/0002-hybrid-smart-rag-vault-retrieval.md +0 -62
- package/docs/adr/0003-waive-unrecoverable-scheduled-model-cost.md +0 -22
- package/docs/adr/2026-07-28-owned-aug-webinar-registration.md +0 -61
- package/docs/adr/README.md +0 -13
- package/docs/final-tooling-spec.md +0 -206
- package/docs/hosted-location-data.md +0 -108
- package/docs/kernel-proxy-future-enhancements.md +0 -80
- package/docs/mcp-tool-craft-lint.generated.md +0 -186
- package/docs/mcp-tool-design-guide.md +0 -225
- package/docs/mcp-tool-manifest.generated.json +0 -23305
- package/docs/mcp-tool-quality-spec.md +0 -240
- package/docs/oauth-legal-review.md +0 -38
- package/docs/seo-crawl-report-spec.md +0 -287
- package/docs/specs/api-forge-spec.md +0 -234
- package/docs/specs/connected-services-control-plane-decoupling-spec.md +0 -1044
- package/docs/specs/deferred-work-spec.md +0 -86
- package/docs/specs/google-drive-bulk-access-and-mcp-schema-passthrough-spec.md +0 -1689
- package/docs/specs/kernel-stealth-captcha-test-matrix.md +0 -278
- package/docs/specs/main-mcp-integration-ownership-spec.md +0 -1164
- package/docs/specs/mcp-tool-definition-quality-audit-spec.md +0 -1602
- package/docs/specs/meta-ad-creative-media-resolution-spec.md +0 -31
- package/docs/specs/multimodal-image-memory-architecture-spec.md +0 -1022
- package/docs/specs/oauth-mcp-spec.md +0 -213
- package/docs/specs/plan-visibility-and-management-roadmap.md +0 -293
- package/docs/specs/query-fanout-transport-contract-fix.md +0 -45
- package/docs/specs/relationship-workspace-ai-behavior-plan.md +0 -26
- package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +0 -995
- package/docs/tool-catalog-spec.md +0 -388
- /package/dist/{chunk-DO6WGZRS.js.map → chunk-3PIWJS6Y.js.map} +0 -0
- /package/dist/{chunk-YUE46JGV.js.map → chunk-7XBBFBYY.js.map} +0 -0
- /package/dist/{chunk-4WAE4AV5.js.map → chunk-NVXNEOUQ.js.map} +0 -0
- /package/dist/{chunk-3HOANNNR.js.map → chunk-X2LKCX6H.js.map} +0 -0
- /package/dist/{chunk-ATWQRW3K.js.map → chunk-XORPNO3Z.js.map} +0 -0
- /package/dist/{db-6AJW47NF.js.map → db-W3CP562I.js.map} +0 -0
- /package/dist/{extract-bundle-CWG35SKV.js.map → extract-bundle-K4PG3RZJ.js.map} +0 -0
- /package/dist/{location-data-repository-BGKZF7DV.js.map → location-data-repository-RLQX6SNM.js.map} +0 -0
- /package/dist/{site-extract-repository-QZKZSQLQ.js.map → site-extract-repository-2SMMFKKL.js.map} +0 -0
- /package/dist/{worker-YTIMPE57.js.map → worker-FXAGFYOE.js.map} +0 -0
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
importHostedCensusPlacesCsv,
|
|
10
10
|
importHostedZipGroupsCsv,
|
|
11
11
|
listActiveHostedLocationDatasets
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XORPNO3Z.js";
|
|
13
13
|
import {
|
|
14
14
|
classifyHarvestProblem,
|
|
15
15
|
createHarvestAttemptRecorder,
|
|
16
16
|
harvestProblemResponse,
|
|
17
17
|
serializeHarvestProblem
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NVXNEOUQ.js";
|
|
19
19
|
import {
|
|
20
20
|
BACKUP_LAST_RESORT_ATTEMPTS,
|
|
21
21
|
BrowserDriver,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
resolveBackupKernelProxyId,
|
|
30
30
|
resolveKernelProxyId,
|
|
31
31
|
serpArrivalUrl
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-7XBBFBYY.js";
|
|
33
33
|
import {
|
|
34
34
|
CreateEditorialReadingRoomInputSchema,
|
|
35
35
|
HttpMcpToolExecutor,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
sanitizeAttempts,
|
|
59
59
|
sanitizeHarvestResult,
|
|
60
60
|
transcribeMediaUrl
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-LP6E462I.js";
|
|
62
62
|
import {
|
|
63
63
|
auditImageUrls,
|
|
64
64
|
auditImages,
|
|
@@ -78,7 +78,7 @@ import {
|
|
|
78
78
|
recordKernelSession,
|
|
79
79
|
recordVendorUsage,
|
|
80
80
|
runWithCostContext
|
|
81
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-3PIWJS6Y.js";
|
|
82
82
|
import {
|
|
83
83
|
listWorkflowDefinitions,
|
|
84
84
|
runWorkflow,
|
|
@@ -105,7 +105,7 @@ import {
|
|
|
105
105
|
RawMapsOverviewSchema,
|
|
106
106
|
RawMapsReviewStatsSchema
|
|
107
107
|
} from "./chunk-CB5C3BPB.js";
|
|
108
|
-
import "./chunk-
|
|
108
|
+
import "./chunk-ZUJLSICT.js";
|
|
109
109
|
import {
|
|
110
110
|
abandonExtractSettlement,
|
|
111
111
|
countSuccessfulPages,
|
|
@@ -128,7 +128,7 @@ import {
|
|
|
128
128
|
setExtractJobTotal,
|
|
129
129
|
settleExtractJob,
|
|
130
130
|
terminalExtractJobStatus
|
|
131
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-X2LKCX6H.js";
|
|
132
132
|
import {
|
|
133
133
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
134
134
|
CONCURRENCY_PRICE_ID,
|
|
@@ -273,7 +273,7 @@ import {
|
|
|
273
273
|
softDeleteInboxMessage,
|
|
274
274
|
upsertPageSnapshot,
|
|
275
275
|
verifyPassword
|
|
276
|
-
} from "./chunk-
|
|
276
|
+
} from "./chunk-GUVKHCKE.js";
|
|
277
277
|
|
|
278
278
|
// src/blog/registry.ts
|
|
279
279
|
var posts = [
|
|
@@ -8618,7 +8618,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
8618
8618
|
});
|
|
8619
8619
|
}) : null;
|
|
8620
8620
|
const finalized = await step.run("finalize", async () => {
|
|
8621
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
8621
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-K4PG3RZJ.js");
|
|
8622
8622
|
const current = await getExtractJob(jobId2);
|
|
8623
8623
|
if (!current) throw new Error("extract job disappeared before finalization");
|
|
8624
8624
|
const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
|
|
@@ -9560,7 +9560,7 @@ async function resolveUsZipsPath(requestedPath) {
|
|
|
9560
9560
|
async function loadZipGroups(stateAbbr, requestedPath, warnings, sourceMode) {
|
|
9561
9561
|
const mode = resolvedLocationDataMode({ zipSource: sourceMode });
|
|
9562
9562
|
if (mode === "hosted") {
|
|
9563
|
-
const { getHostedZipGroups } = await import("./location-data-repository-
|
|
9563
|
+
const { getHostedZipGroups } = await import("./location-data-repository-RLQX6SNM.js");
|
|
9564
9564
|
const hosted = await getHostedZipGroups(stateAbbr);
|
|
9565
9565
|
if (!hosted.dataset) {
|
|
9566
9566
|
throw new DirectoryLocationError(
|
|
@@ -9623,7 +9623,7 @@ async function resolveDirectoryMarkets(options, config = {}) {
|
|
|
9623
9623
|
const sourceUrl = censusStateUrl(state.fips);
|
|
9624
9624
|
const warnings = [];
|
|
9625
9625
|
if (resolvedLocationDataMode(config) === "hosted") {
|
|
9626
|
-
const { queryHostedLocationMarkets } = await import("./location-data-repository-
|
|
9626
|
+
const { queryHostedLocationMarkets } = await import("./location-data-repository-RLQX6SNM.js");
|
|
9627
9627
|
const hosted = await queryHostedLocationMarkets({
|
|
9628
9628
|
state: state.abbr,
|
|
9629
9629
|
minPopulation: options.minPopulation,
|
|
@@ -26849,6 +26849,253 @@ import { Hono as Hono28 } from "hono";
|
|
|
26849
26849
|
import { Resend as Resend2 } from "resend";
|
|
26850
26850
|
import { z as z28 } from "zod";
|
|
26851
26851
|
|
|
26852
|
+
// src/email/workshop-field-notes.ts
|
|
26853
|
+
var WORKSHOP_FIELD_NOTES_URL = "https://mcp-field-notes.vercel.app/";
|
|
26854
|
+
var workshopFieldNotes = [
|
|
26855
|
+
{
|
|
26856
|
+
slug: "operating-model",
|
|
26857
|
+
category: "Start Here",
|
|
26858
|
+
title: "The Operating Model",
|
|
26859
|
+
snippet: [
|
|
26860
|
+
"MCP Scraper is an operating system for evidence work, not simply a page fetcher.",
|
|
26861
|
+
"It connects a question to the right sources, method, and durable output.",
|
|
26862
|
+
"This note gives you the mental model for everything else."
|
|
26863
|
+
]
|
|
26864
|
+
},
|
|
26865
|
+
{
|
|
26866
|
+
slug: "context-aware-vault-capture",
|
|
26867
|
+
category: "Start Here",
|
|
26868
|
+
title: "Context-Aware Vault Capture",
|
|
26869
|
+
snippet: [
|
|
26870
|
+
"This demonstrates what a governed memory write actually looks like.",
|
|
26871
|
+
"The agent inspects the vault, chooses the destination, writes with supported links and tags, then reads it back.",
|
|
26872
|
+
"The final retrieval check proves the material can be found later."
|
|
26873
|
+
]
|
|
26874
|
+
},
|
|
26875
|
+
{
|
|
26876
|
+
slug: "complete-competitive-surface-scan",
|
|
26877
|
+
category: "Start Here",
|
|
26878
|
+
title: "The Complete Competitive Surface Scan",
|
|
26879
|
+
snippet: [
|
|
26880
|
+
"A competitive scan is complete when it covers the evidence needed for a decision.",
|
|
26881
|
+
"That might combine search, sites, reviews, community language, ads, or video without scraping everything.",
|
|
26882
|
+
"This note shows how to define coverage and report gaps honestly."
|
|
26883
|
+
]
|
|
26884
|
+
},
|
|
26885
|
+
{
|
|
26886
|
+
slug: "search-serp-and-paa",
|
|
26887
|
+
category: "Research Surfaces & Systems",
|
|
26888
|
+
title: "Search, SERPs, and People Also Ask",
|
|
26889
|
+
snippet: [
|
|
26890
|
+
"Rankings, questions, local results, sources, and AI answers are different kinds of search evidence.",
|
|
26891
|
+
"A fast SERP and a deeper People Also Ask harvest answer different questions.",
|
|
26892
|
+
"This note shows how to choose the least expensive search depth that changes the decision."
|
|
26893
|
+
]
|
|
26894
|
+
},
|
|
26895
|
+
{
|
|
26896
|
+
slug: "reddit-and-community-research",
|
|
26897
|
+
category: "Research Surfaces & Systems",
|
|
26898
|
+
title: "Reddit and Community Research",
|
|
26899
|
+
snippet: [
|
|
26900
|
+
"Community research exposes language, objections, workarounds, and disagreement that polished pages often hide.",
|
|
26901
|
+
"It should preserve thread context and dissent instead of turning comments into a market-wide vote.",
|
|
26902
|
+
"This note explains discovery, deep reading, and careful synthesis."
|
|
26903
|
+
]
|
|
26904
|
+
},
|
|
26905
|
+
{
|
|
26906
|
+
slug: "browser-profiles-and-ai-fanout",
|
|
26907
|
+
category: "Research Surfaces & Systems",
|
|
26908
|
+
title: "Browser Profiles and AI Citation Fan-Out",
|
|
26909
|
+
snippet: [
|
|
26910
|
+
"A browser profile preserves delegated access to sites that require login.",
|
|
26911
|
+
"Citation fan-out studies the subqueries, browsed sources, and citations inside an AI answer.",
|
|
26912
|
+
"This note keeps the authenticated surface and the research method conceptually separate."
|
|
26913
|
+
]
|
|
26914
|
+
},
|
|
26915
|
+
{
|
|
26916
|
+
slug: "youtube-and-video-research",
|
|
26917
|
+
category: "Research Surfaces & Systems",
|
|
26918
|
+
title: "YouTube and Video Research",
|
|
26919
|
+
snippet: [
|
|
26920
|
+
"Video research can stop at discovery, continue to transcription, or go deeper into frame analysis.",
|
|
26921
|
+
"Each level costs more and answers a different question.",
|
|
26922
|
+
"This note shows how to choose the cheapest sufficient depth."
|
|
26923
|
+
]
|
|
26924
|
+
},
|
|
26925
|
+
{
|
|
26926
|
+
slug: "advertising-intelligence",
|
|
26927
|
+
category: "Research Surfaces & Systems",
|
|
26928
|
+
title: "Advertising Intelligence",
|
|
26929
|
+
snippet: [
|
|
26930
|
+
"Advertising intelligence studies the messages, offers, proof, formats, and creative patterns a company distributes.",
|
|
26931
|
+
"It does not prove spend, profitability, or performance.",
|
|
26932
|
+
"This note maps the right evidence path for Meta, Google, public libraries, and authorized account creative."
|
|
26933
|
+
]
|
|
26934
|
+
},
|
|
26935
|
+
{
|
|
26936
|
+
slug: "search-console-and-analytics-skills",
|
|
26937
|
+
category: "Connected Data & Automation",
|
|
26938
|
+
title: "Search Console and Analytics Skills",
|
|
26939
|
+
snippet: [
|
|
26940
|
+
"Search Console and Analytics supply first-party evidence, not strategy by themselves.",
|
|
26941
|
+
"A user-owned skill adds business doctrine, comparison rules, and priorities.",
|
|
26942
|
+
"This note shows how connected data, public research, Memory, and scheduling become a repeatable decision system."
|
|
26943
|
+
]
|
|
26944
|
+
},
|
|
26945
|
+
{
|
|
26946
|
+
slug: "scheduled-gmail-zoom-and-research-digestion",
|
|
26947
|
+
category: "Connected Data & Automation",
|
|
26948
|
+
title: "Scheduled Gmail, Zoom, and Research Digestion",
|
|
26949
|
+
snippet: [
|
|
26950
|
+
"Scheduled digestion turns accumulating email, meetings, and research into bounded durable updates.",
|
|
26951
|
+
"It should not silently bulk-import an account or invent people, tasks, deals, or projects.",
|
|
26952
|
+
"This note defines the source, time window, filters, classifications, and destination that make a digest safe."
|
|
26953
|
+
]
|
|
26954
|
+
},
|
|
26955
|
+
{
|
|
26956
|
+
slug: "rank-tracking-and-change-monitoring",
|
|
26957
|
+
category: "Connected Data & Automation",
|
|
26958
|
+
title: "Rank Tracking and Change Monitoring",
|
|
26959
|
+
snippet: [
|
|
26960
|
+
"A tracker compares observations, and a monitor remembers a baseline before looking again.",
|
|
26961
|
+
"Repeating the same prompt does not create either system.",
|
|
26962
|
+
"This note explains stable subjects, comparable inputs, materiality thresholds, stored history, and change receipts."
|
|
26963
|
+
]
|
|
26964
|
+
},
|
|
26965
|
+
{
|
|
26966
|
+
slug: "research-artifacts-and-reading-rooms",
|
|
26967
|
+
category: "Artifacts, Memory & Skills",
|
|
26968
|
+
title: "Research Artifacts and Reading Rooms",
|
|
26969
|
+
snippet: [
|
|
26970
|
+
"Research should end in something people can inspect, reopen, and use.",
|
|
26971
|
+
"MCP Scraper can choose among inline reports, private artifacts, tables, governed Memory, and editorial reading rooms.",
|
|
26972
|
+
"This note explains which output belongs where."
|
|
26973
|
+
]
|
|
26974
|
+
},
|
|
26975
|
+
{
|
|
26976
|
+
slug: "skills-as-user-owned-judgment",
|
|
26977
|
+
category: "Artifacts, Memory & Skills",
|
|
26978
|
+
title: "Skills as User-Owned Judgment",
|
|
26979
|
+
snippet: [
|
|
26980
|
+
"MCP Scraper supplies evidence surfaces and operating primitives.",
|
|
26981
|
+
"A skill supplies the user\u2019s doctrine, sequence, standards, rejection rules, storage choices, and verification.",
|
|
26982
|
+
"This note shows how repeated corrections become reusable judgment."
|
|
26983
|
+
]
|
|
26984
|
+
},
|
|
26985
|
+
{
|
|
26986
|
+
slug: "seo-and-content",
|
|
26987
|
+
category: "Applications & Contexts",
|
|
26988
|
+
title: "SEO & Content",
|
|
26989
|
+
snippet: [
|
|
26990
|
+
"Start with a real search decision, then inspect the live SERP, questions, and competing pages.",
|
|
26991
|
+
"Preserve the evidence before turning it into a source-backed brief.",
|
|
26992
|
+
"Memory and monitoring make the method reusable instead of disposable."
|
|
26993
|
+
]
|
|
26994
|
+
},
|
|
26995
|
+
{
|
|
26996
|
+
slug: "local-markets",
|
|
26997
|
+
category: "Applications & Contexts",
|
|
26998
|
+
title: "Local Markets",
|
|
26999
|
+
snippet: [
|
|
27000
|
+
"Local research combines businesses, services, reviews, positioning, search visibility, and geography.",
|
|
27001
|
+
"The goal is to find testable gaps without flattening every source into one score.",
|
|
27002
|
+
"This note shows how to preserve a market baseline and monitor meaningful change."
|
|
27003
|
+
]
|
|
27004
|
+
},
|
|
27005
|
+
{
|
|
27006
|
+
slug: "sales-and-client-work",
|
|
27007
|
+
category: "Applications & Contexts",
|
|
27008
|
+
title: "Sales & Client Work",
|
|
27009
|
+
snippet: [
|
|
27010
|
+
"This is a relationship graph, not a contact database.",
|
|
27011
|
+
"People, organizations, opportunities, projects, conversations, meetings, decisions, and follow-ups remain separate records connected by verified links.",
|
|
27012
|
+
"This note shows why that distinction prevents invented CRM context."
|
|
27013
|
+
]
|
|
27014
|
+
},
|
|
27015
|
+
{
|
|
27016
|
+
slug: "competitor-monitoring",
|
|
27017
|
+
category: "Applications & Contexts",
|
|
27018
|
+
title: "Competitor Monitoring",
|
|
27019
|
+
snippet: [
|
|
27020
|
+
"Monitoring begins with a dated baseline and a defined set of things worth watching.",
|
|
27021
|
+
"Each run compares trustworthy evidence and classifies only material change.",
|
|
27022
|
+
"The output reports the difference, including source failure and meaningful no-change."
|
|
27023
|
+
]
|
|
27024
|
+
},
|
|
27025
|
+
{
|
|
27026
|
+
slug: "learning-and-research",
|
|
27027
|
+
category: "Applications & Contexts",
|
|
27028
|
+
title: "Learning & Research",
|
|
27029
|
+
snippet: [
|
|
27030
|
+
"Raw articles, transcripts, pages, and datasets belong in Library with provenance intact.",
|
|
27031
|
+
"Reusable lessons belong separately in Knowledge and link back to their sources.",
|
|
27032
|
+
"This note explains recall by meaning, time, or graph without confusing relevance with truth."
|
|
27033
|
+
]
|
|
27034
|
+
},
|
|
27035
|
+
{
|
|
27036
|
+
slug: "build-your-own-tools",
|
|
27037
|
+
category: "Applications & Contexts",
|
|
27038
|
+
title: "Build Your Own Tools",
|
|
27039
|
+
snippet: [
|
|
27040
|
+
"The same acquisition, browser, connection, Memory, artifact, workflow, and scheduling primitives can sit behind something you build.",
|
|
27041
|
+
"That could be a skill, internal process, client report, monitoring service, or narrow SaaS.",
|
|
27042
|
+
"This note starts from one recurring decision and defines the product contract around it."
|
|
27043
|
+
]
|
|
27044
|
+
},
|
|
27045
|
+
{
|
|
27046
|
+
slug: "improve-originating-skill",
|
|
27047
|
+
category: "How MCP Scraper Learned",
|
|
27048
|
+
title: "Improve the Originating Skill",
|
|
27049
|
+
snippet: [
|
|
27050
|
+
"A corrected output often reveals a problem in the workflow that produced it.",
|
|
27051
|
+
"This reflection method traces the failure to the highest owning layer and improves the originating skill or validator.",
|
|
27052
|
+
"Future work then inherits the lesson instead of repeating the same repair."
|
|
27053
|
+
]
|
|
27054
|
+
},
|
|
27055
|
+
{
|
|
27056
|
+
slug: "landing-page-problem-solving",
|
|
27057
|
+
category: "How MCP Scraper Learned",
|
|
27058
|
+
title: "Problem-Solving Lessons for Use-Case Pages",
|
|
27059
|
+
snippet: [
|
|
27060
|
+
"This is the concrete receipt from applying that reflection method to MCP Scraper landing pages.",
|
|
27061
|
+
"It captures the fixes for generic copy, weak proof, wrong archetypes, visible seams, inconsistent calls to action, and missed mobile defects.",
|
|
27062
|
+
"The result is a stronger generator, not merely one repaired page."
|
|
27063
|
+
]
|
|
27064
|
+
},
|
|
27065
|
+
{
|
|
27066
|
+
slug: "workshop-playbook",
|
|
27067
|
+
category: "Workshop",
|
|
27068
|
+
title: "MCP Scraper Workshop Prompt Playbook",
|
|
27069
|
+
snippet: [
|
|
27070
|
+
"This is the practical prompt set for the live session.",
|
|
27071
|
+
"It covers research surfaces, connected data, authenticated browser work, scheduling, skills, completeness receipts, and governed Memory.",
|
|
27072
|
+
"You can replace the examples with your own business, competitors, accounts, and questions."
|
|
27073
|
+
]
|
|
27074
|
+
}
|
|
27075
|
+
];
|
|
27076
|
+
function escapeHtml3(value) {
|
|
27077
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
27078
|
+
}
|
|
27079
|
+
function workshopFieldNoteUrl(slug) {
|
|
27080
|
+
return `${WORKSHOP_FIELD_NOTES_URL}?article=${encodeURIComponent(slug)}`;
|
|
27081
|
+
}
|
|
27082
|
+
function renderWorkshopFieldNotesBody() {
|
|
27083
|
+
let activeCategory = "";
|
|
27084
|
+
return [
|
|
27085
|
+
'<p style="margin:0">The written companion to our live workshop is already available in MCP Scraper Field Notes. You do not need to read all of it beforehand\u2014browse the subjects that match the work you already do.</p>',
|
|
27086
|
+
...workshopFieldNotes.flatMap((note, index) => {
|
|
27087
|
+
const category = note.category === activeCategory ? [] : [`<h2 style="margin:${index === 0 ? 32 : 40}px 0 12px;color:#2563eb;font-size:13px;line-height:1.3;letter-spacing:.08em;text-transform:uppercase">${escapeHtml3(note.category)}</h2>`];
|
|
27088
|
+
activeCategory = note.category;
|
|
27089
|
+
const url = escapeHtml3(workshopFieldNoteUrl(note.slug));
|
|
27090
|
+
const sentences = note.snippet.map(escapeHtml3).join(" ");
|
|
27091
|
+
return [
|
|
27092
|
+
...category,
|
|
27093
|
+
`<div style="padding:20px 0;border-top:1px solid #e5e7eb"><div style="color:#6b7280;font-size:12px;font-weight:700;letter-spacing:.06em">${String(index + 1).padStart(2, "0")}</div><h3 style="margin:6px 0 8px;font-size:19px;line-height:1.3"><a href="${url}" style="color:#101114;text-decoration:none">${escapeHtml3(note.title)}</a></h3><p style="margin:0;color:#454952;font-size:15px;line-height:1.65">${sentences}</p><p style="margin:10px 0 0"><a href="${url}" style="color:#2563eb;font-size:14px;font-weight:700;text-decoration:none">Read this note \u2192</a></p></div>`
|
|
27094
|
+
];
|
|
27095
|
+
})
|
|
27096
|
+
].join("");
|
|
27097
|
+
}
|
|
27098
|
+
|
|
26852
27099
|
// src/api/workshop-registration-repository.ts
|
|
26853
27100
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
26854
27101
|
function rowToRegistration(row) {
|
|
@@ -26863,6 +27110,8 @@ function rowToRegistration(row) {
|
|
|
26863
27110
|
zoomJoinUrl: row.zoom_join_url == null ? null : String(row.zoom_join_url),
|
|
26864
27111
|
resendContactId: row.resend_contact_id == null ? null : String(row.resend_contact_id),
|
|
26865
27112
|
confirmationEmailId: row.confirmation_email_id == null ? null : String(row.confirmation_email_id),
|
|
27113
|
+
fieldNotesEmailId: row.field_notes_email_id == null ? null : String(row.field_notes_email_id),
|
|
27114
|
+
reminder48hEmailId: row.reminder_48h_email_id == null ? null : String(row.reminder_48h_email_id),
|
|
26866
27115
|
reminder24hEmailId: row.reminder_24h_email_id == null ? null : String(row.reminder_24h_email_id),
|
|
26867
27116
|
reminder1hEmailId: row.reminder_1h_email_id == null ? null : String(row.reminder_1h_email_id),
|
|
26868
27117
|
lastError: row.last_error == null ? null : String(row.last_error)
|
|
@@ -26962,6 +27211,8 @@ async function updateWorkshopRegistration(id, patch) {
|
|
|
26962
27211
|
zoom_join_url: patch.zoomJoinUrl,
|
|
26963
27212
|
resend_contact_id: patch.resendContactId,
|
|
26964
27213
|
confirmation_email_id: patch.confirmationEmailId,
|
|
27214
|
+
field_notes_email_id: patch.fieldNotesEmailId,
|
|
27215
|
+
reminder_48h_email_id: patch.reminder48hEmailId,
|
|
26965
27216
|
reminder_24h_email_id: patch.reminder24hEmailId,
|
|
26966
27217
|
reminder_1h_email_id: patch.reminder1hEmailId,
|
|
26967
27218
|
last_error: patch.lastError
|
|
@@ -27021,10 +27272,19 @@ function workshopReminderSchedule(startAt) {
|
|
|
27021
27272
|
const start = Date.parse(startAt);
|
|
27022
27273
|
if (!Number.isFinite(start)) throw new Error("Workshop start time is invalid.");
|
|
27023
27274
|
return {
|
|
27275
|
+
reminder48h: new Date(start - 48 * 60 * 60 * 1e3).toISOString(),
|
|
27024
27276
|
reminder24h: new Date(start - 24 * 60 * 60 * 1e3).toISOString(),
|
|
27025
27277
|
reminder1h: new Date(start - 60 * 60 * 1e3).toISOString()
|
|
27026
27278
|
};
|
|
27027
27279
|
}
|
|
27280
|
+
function workshopRemindersComplete(registration, schedule, now = Date.now()) {
|
|
27281
|
+
const reminders = [
|
|
27282
|
+
[registration.reminder48hEmailId, schedule.reminder48h],
|
|
27283
|
+
[registration.reminder24hEmailId, schedule.reminder24h],
|
|
27284
|
+
[registration.reminder1hEmailId, schedule.reminder1h]
|
|
27285
|
+
];
|
|
27286
|
+
return reminders.every(([emailId, scheduledAt]) => Boolean(emailId) || Date.parse(scheduledAt) <= now);
|
|
27287
|
+
}
|
|
27028
27288
|
function allowedOrigins() {
|
|
27029
27289
|
const origins = /* @__PURE__ */ new Set(["https://mcpscraper.dev", "https://www.mcpscraper.dev"]);
|
|
27030
27290
|
for (const value of (process.env.ALLOWED_ORIGINS ?? process.env.APP_ORIGIN ?? "").split(",")) {
|
|
@@ -27039,12 +27299,12 @@ function requestOriginAllowed(origin) {
|
|
|
27039
27299
|
function clientIp(headers) {
|
|
27040
27300
|
return headers.get("cf-connecting-ip") || headers.get("x-real-ip") || headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
|
27041
27301
|
}
|
|
27042
|
-
function
|
|
27302
|
+
function escapeHtml4(value) {
|
|
27043
27303
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
27044
27304
|
}
|
|
27045
|
-
function
|
|
27046
|
-
const button = input.button ? `<
|
|
27047
|
-
return `<!doctype html><html><body style="margin:0;background:#f5f7fb;color:#101114;font-family:Arial,sans-serif"><div style="display:none;max-height:0;overflow:hidden">${
|
|
27305
|
+
function renderWorkshopEmailHtml(input) {
|
|
27306
|
+
const button = input.button ? `<table role="presentation" cellspacing="0" cellpadding="0" style="margin:30px 0 0"><tr><td bgcolor="#2563eb" style="border-radius:999px"><a href="${escapeHtml4(input.button.url)}" style="display:inline-block;padding:15px 24px;border-radius:999px;background:#2563eb;color:#ffffff;text-decoration:none;font-size:16px;font-weight:700;line-height:1">${escapeHtml4(input.button.label)}</a></td></tr></table>` : "";
|
|
27307
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>@media only screen and (max-width:620px){.email-shell{padding:16px 10px!important}.email-header{padding:30px 24px!important}.email-content{padding:30px 24px!important}.email-title{font-size:34px!important}}</style></head><body style="margin:0;background:#f5f7fb;color:#101114;font-family:Arial,sans-serif"><div style="display:none;max-height:0;overflow:hidden">${escapeHtml4(input.eyebrow)}</div><table role="presentation" width="100%" cellspacing="0" cellpadding="0" bgcolor="#f5f7fb"><tr><td class="email-shell" align="center" style="padding:32px 16px"><table role="presentation" width="100%" cellspacing="0" cellpadding="0" bgcolor="#ffffff" style="max-width:620px;background:#ffffff;border-radius:24px;overflow:hidden"><tr><td class="email-header" bgcolor="#2563eb" style="padding:42px;background:#2563eb;color:#ffffff"><table role="presentation" cellspacing="0" cellpadding="0"><tr><td><img src="https://mcpscraper.dev/email/mcp-brain-white.png" width="48" height="48" alt="MCP Scraper" style="display:block;width:48px;height:48px;border:0"></td><td style="padding-left:12px;color:#ffffff;font-size:17px;font-weight:700;letter-spacing:-.01em">MCP Scraper</td></tr></table><div style="margin-top:32px;font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#dbeafe">${escapeHtml4(input.eyebrow)}</div><h1 class="email-title" style="margin:12px 0 0;color:#ffffff;font-size:42px;line-height:1.04;letter-spacing:-.04em">${escapeHtml4(input.title)}</h1></td></tr><tr><td class="email-content" style="padding:36px;color:#101114;font-size:16px;line-height:1.65">${input.body}${button}<p style="margin:32px 0 0;padding-top:24px;border-top:1px solid #e5e7eb;color:#686b73;font-size:13px;line-height:1.5">You received this because you registered for this workshop. It does not subscribe you to general marketing emails.</p></td></tr></table></td></tr></table></body></html>`;
|
|
27048
27308
|
}
|
|
27049
27309
|
function icsTimestamp(value) {
|
|
27050
27310
|
return new Date(value).toISOString().replaceAll("-", "").replaceAll(":", "").replace(/\.\d{3}Z$/, "Z");
|
|
@@ -27094,21 +27354,26 @@ async function addEventContact(resend, registration, config) {
|
|
|
27094
27354
|
email: registration.email,
|
|
27095
27355
|
segmentId: config.resendSegmentId
|
|
27096
27356
|
});
|
|
27097
|
-
if (segment.error)
|
|
27357
|
+
if (segment.error) {
|
|
27358
|
+
console.warn("[workshop-registration] event contact sync skipped:", segment.error.message);
|
|
27359
|
+
return null;
|
|
27360
|
+
}
|
|
27098
27361
|
if (config.resendTopicId) {
|
|
27099
27362
|
const topic = await resend.contacts.topics.update({
|
|
27100
27363
|
email: registration.email,
|
|
27101
27364
|
topics: [{ id: config.resendTopicId, subscription: "opt_in" }]
|
|
27102
27365
|
});
|
|
27103
|
-
if (topic.error)
|
|
27366
|
+
if (topic.error) {
|
|
27367
|
+
console.warn("[workshop-registration] event topic sync skipped:", topic.error.message);
|
|
27368
|
+
}
|
|
27104
27369
|
}
|
|
27105
27370
|
return null;
|
|
27106
27371
|
}
|
|
27107
27372
|
async function sendConfirmation(resend, registration, config) {
|
|
27108
27373
|
if (!registration.zoomJoinUrl) throw new Error("Workshop access details are not ready.");
|
|
27109
|
-
const date =
|
|
27110
|
-
const timezone =
|
|
27111
|
-
const duration =
|
|
27374
|
+
const date = escapeHtml4(config.dateLabel);
|
|
27375
|
+
const timezone = escapeHtml4(config.timezoneLabel);
|
|
27376
|
+
const duration = escapeHtml4(config.durationLabel);
|
|
27112
27377
|
const sent = await resend.emails.send({
|
|
27113
27378
|
from: "Andrew at MCP Scraper <updates@updates.mcpscraper.dev>",
|
|
27114
27379
|
replyTo: "support@updates.mcpscraper.dev",
|
|
@@ -27119,7 +27384,7 @@ async function sendConfirmation(resend, registration, config) {
|
|
|
27119
27384
|
content: workshopInvitation(registration, config),
|
|
27120
27385
|
contentType: "text/calendar; method=REQUEST; charset=UTF-8"
|
|
27121
27386
|
}],
|
|
27122
|
-
html:
|
|
27387
|
+
html: renderWorkshopEmailHtml({
|
|
27123
27388
|
eyebrow: "Your live workshop seat is saved",
|
|
27124
27389
|
title: `You\u2019re in, ${registration.firstName}.`,
|
|
27125
27390
|
body: `<p style="margin:0">I\u2019ll show you how the pieces of MCP Scraper fit together around real work: research, Memory, connected accounts, Scheduled Actions, and reusable workflows.</p><p><strong>${date}</strong><br>${timezone}<br>${duration}</p><p>Your calendar invitation is attached, and your Zoom access link is below.</p>`,
|
|
@@ -27129,21 +27394,64 @@ async function sendConfirmation(resend, registration, config) {
|
|
|
27129
27394
|
if (sent.error || !sent.data?.id) throw new Error(`Resend could not send confirmation: ${sent.error?.message ?? "unknown response"}`);
|
|
27130
27395
|
return sent.data.id;
|
|
27131
27396
|
}
|
|
27397
|
+
async function sendFieldNotesGuide(resend, registration) {
|
|
27398
|
+
const sent = await resend.emails.send({
|
|
27399
|
+
from: "Andrew at MCP Scraper <updates@updates.mcpscraper.dev>",
|
|
27400
|
+
replyTo: "support@updates.mcpscraper.dev",
|
|
27401
|
+
to: registration.email,
|
|
27402
|
+
subject: "Your MCP Scraper workshop reading guide",
|
|
27403
|
+
html: renderWorkshopEmailHtml({
|
|
27404
|
+
eyebrow: "Before the live workshop",
|
|
27405
|
+
title: "The material is already here.",
|
|
27406
|
+
body: renderWorkshopFieldNotesBody(),
|
|
27407
|
+
button: { label: "Open the Field Notes", url: WORKSHOP_FIELD_NOTES_URL }
|
|
27408
|
+
})
|
|
27409
|
+
});
|
|
27410
|
+
if (sent.error || !sent.data?.id) {
|
|
27411
|
+
throw new Error(`Resend could not send the Field Notes guide: ${sent.error?.message ?? "unknown response"}`);
|
|
27412
|
+
}
|
|
27413
|
+
return sent.data.id;
|
|
27414
|
+
}
|
|
27415
|
+
function workshopReminderMessage(input) {
|
|
27416
|
+
const isTwoDays = input.kind === "48h";
|
|
27417
|
+
const isTomorrow = input.kind === "24h";
|
|
27418
|
+
const subject = isTwoDays ? "Two days out: bring the work you want MCP Scraper to handle" : isTomorrow ? "Tomorrow: see what MCP Scraper can actually do" : "One hour: your MCP Scraper workshop link";
|
|
27419
|
+
const eyebrow = isTwoDays ? "48 hours to go" : isTomorrow ? "Your workshop is tomorrow" : "We start in one hour";
|
|
27420
|
+
const title = isTwoDays ? `Bring one real problem, ${input.firstName}.` : isTomorrow ? "Tomorrow, we build around your work." : "Grab your coffee. Bring a real use case.";
|
|
27421
|
+
const opening = isTwoDays ? "This will not be a march through every feature. I\u2019ll show you how to choose the right combination of research, Memory, connected accounts, Scheduled Actions, and skills for work you already need done." : isTomorrow ? "The point is not to use every tool. It is to recognize which pieces belong together for research, monitoring, client work, learning, or something you are building." : "We go live in one hour. Keep one real project, question, or recurring task in mind\u2014I\u2019ll show you how to map the MCP Scraper pieces around it without turning the session into a dry feature tour.";
|
|
27422
|
+
const prompt = isTwoDays ? "A stubborn workflow, research question, client account, or product idea you want to move forward." : isTomorrow ? "Choose the one job you most want your AI to remember, research, monitor, or help you build." : "Keep one project, question, or recurring task in mind. We will map the right MCP Scraper pieces around it.";
|
|
27423
|
+
const promptCard = `<div style="margin:24px 0 0;padding:18px 20px;border:1px solid #bfdbfe;border-radius:16px;background:#eff6ff"><div style="color:#1d4ed8;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase">Bring this with you</div><p style="margin:7px 0 0;color:#1f2937;font-size:15px;line-height:1.55">${escapeHtml4(prompt)}</p></div>`;
|
|
27424
|
+
const details = `<p style="margin:24px 0 0"><strong>${escapeHtml4(input.dateLabel)}</strong><br>${escapeHtml4(input.timezoneLabel)}<br>${escapeHtml4(input.durationLabel)}</p>`;
|
|
27425
|
+
return {
|
|
27426
|
+
subject,
|
|
27427
|
+
html: renderWorkshopEmailHtml({
|
|
27428
|
+
eyebrow,
|
|
27429
|
+
title,
|
|
27430
|
+
body: `<p style="margin:0">${escapeHtml4(opening)}</p>${promptCard}${details}`,
|
|
27431
|
+
button: {
|
|
27432
|
+
label: input.kind === "1h" ? "Join the live workshop" : "Open your workshop link",
|
|
27433
|
+
url: input.zoomJoinUrl
|
|
27434
|
+
}
|
|
27435
|
+
})
|
|
27436
|
+
};
|
|
27437
|
+
}
|
|
27132
27438
|
async function scheduleReminder(resend, registration, config, kind, scheduledAt) {
|
|
27133
27439
|
if (!registration.zoomJoinUrl) throw new Error("Workshop access details are not ready.");
|
|
27134
|
-
const
|
|
27440
|
+
const message = workshopReminderMessage({
|
|
27441
|
+
kind,
|
|
27442
|
+
firstName: registration.firstName,
|
|
27443
|
+
dateLabel: config.dateLabel,
|
|
27444
|
+
timezoneLabel: config.timezoneLabel,
|
|
27445
|
+
durationLabel: config.durationLabel,
|
|
27446
|
+
zoomJoinUrl: registration.zoomJoinUrl
|
|
27447
|
+
});
|
|
27135
27448
|
const sent = await resend.emails.send({
|
|
27136
27449
|
from: "Andrew at MCP Scraper <updates@updates.mcpscraper.dev>",
|
|
27137
27450
|
replyTo: "support@updates.mcpscraper.dev",
|
|
27138
27451
|
to: registration.email,
|
|
27139
|
-
subject:
|
|
27452
|
+
subject: message.subject,
|
|
27140
27453
|
scheduledAt,
|
|
27141
|
-
html:
|
|
27142
|
-
eyebrow: isTomorrow ? "Your workshop is tomorrow" : "We start in one hour",
|
|
27143
|
-
title: isTomorrow ? `See you tomorrow, ${registration.firstName}.` : `We\u2019re starting soon, ${registration.firstName}.`,
|
|
27144
|
-
body: `<p style="margin:0">${isTomorrow ? "Tomorrow I\u2019ll show you how MCP Scraper\u2019s research, Memory, connected accounts, Scheduled Actions, and reusable skills fit together around real work." : "The live MCP Scraper workshop starts in one hour. Your Zoom access link is below."}</p><p><strong>${escapeHtml3(config.dateLabel)}</strong><br>${escapeHtml3(config.timezoneLabel)}<br>${escapeHtml3(config.durationLabel)}</p>`,
|
|
27145
|
-
button: { label: "Join the live workshop", url: registration.zoomJoinUrl }
|
|
27146
|
-
})
|
|
27454
|
+
html: message.html
|
|
27147
27455
|
});
|
|
27148
27456
|
if (sent.error || !sent.data?.id) {
|
|
27149
27457
|
throw new Error(`Resend could not schedule the ${kind} reminder: ${sent.error?.message ?? "unknown response"}`);
|
|
@@ -27156,10 +27464,10 @@ async function sendWaitlistEmail(resend, registration, config) {
|
|
|
27156
27464
|
replyTo: "support@updates.mcpscraper.dev",
|
|
27157
27465
|
to: registration.email,
|
|
27158
27466
|
subject: "You\u2019re on the MCP Scraper workshop waitlist",
|
|
27159
|
-
html:
|
|
27467
|
+
html: renderWorkshopEmailHtml({
|
|
27160
27468
|
eyebrow: "MCP Scraper live workshop",
|
|
27161
27469
|
title: `You\u2019re on the list, ${registration.firstName}.`,
|
|
27162
|
-
body: `<p style="margin:0">The live room is currently full. I have saved your place on the waitlist and will email you if a seat opens.</p><p><strong>${
|
|
27470
|
+
body: `<p style="margin:0">The live room is currently full. I have saved your place on the waitlist and will email you if a seat opens.</p><p><strong>${escapeHtml4(config.dateLabel)}</strong><br>${escapeHtml4(config.timezoneLabel)}<br>${escapeHtml4(config.durationLabel)}</p>`
|
|
27163
27471
|
})
|
|
27164
27472
|
});
|
|
27165
27473
|
if (sent.error || !sent.data?.id) throw new Error(`Resend could not send waitlist confirmation: ${sent.error?.message ?? "unknown response"}`);
|
|
@@ -27171,7 +27479,7 @@ async function sendInterestEmail(resend, registration) {
|
|
|
27171
27479
|
replyTo: "support@updates.mcpscraper.dev",
|
|
27172
27480
|
to: registration.email,
|
|
27173
27481
|
subject: "You\u2019re on the early list for the MCP Scraper workshop",
|
|
27174
|
-
html:
|
|
27482
|
+
html: renderWorkshopEmailHtml({
|
|
27175
27483
|
eyebrow: "MCP Scraper live workshop",
|
|
27176
27484
|
title: `You\u2019re on the early list, ${registration.firstName}.`,
|
|
27177
27485
|
body: '<p style="margin:0">I\u2019m putting together a live August workshop about what you can actually do with MCP Scraper\u2014and how research, Memory, connected accounts, Scheduled Actions, and reusable workflows fit together around real work.</p><p>I\u2019ll send the date, time, and registration details to this email as soon as the live session is scheduled.</p>'
|
|
@@ -27222,14 +27530,17 @@ workshopRegistrationApp.post("/:event/register", async (c) => {
|
|
|
27222
27530
|
lastName: parsed.data.lastName,
|
|
27223
27531
|
capacity: config.capacity
|
|
27224
27532
|
});
|
|
27533
|
+
const reminderSchedule = config.mode === "registration" ? workshopReminderSchedule(config.startAt) : null;
|
|
27225
27534
|
if (registration.confirmationEmailId && registration.status !== "pending") {
|
|
27226
27535
|
const waitlisted = registration.status === "waitlisted";
|
|
27227
27536
|
const interested = registration.status === "interested";
|
|
27228
|
-
|
|
27229
|
-
|
|
27230
|
-
|
|
27231
|
-
|
|
27232
|
-
|
|
27537
|
+
if (interested || waitlisted || registration.fieldNotesEmailId && reminderSchedule && workshopRemindersComplete(registration, reminderSchedule)) {
|
|
27538
|
+
return c.json({
|
|
27539
|
+
ok: true,
|
|
27540
|
+
status: interested ? "interested" : waitlisted ? "waitlisted" : "confirmed",
|
|
27541
|
+
message: interested ? "You are on the early list. Check your inbox for confirmation." : waitlisted ? "The live room is full, but you are on the waitlist. Check your inbox for details." : "You are registered. Check your inbox for the confirmation and workshop access details."
|
|
27542
|
+
});
|
|
27543
|
+
}
|
|
27233
27544
|
}
|
|
27234
27545
|
const resend = new Resend2(config.resendApiKey);
|
|
27235
27546
|
try {
|
|
@@ -27278,7 +27589,17 @@ workshopRegistrationApp.post("/:event/register", async (c) => {
|
|
|
27278
27589
|
});
|
|
27279
27590
|
current = { ...current, resendContactId: contactId, confirmationEmailId: emailId };
|
|
27280
27591
|
}
|
|
27281
|
-
|
|
27592
|
+
if (!current.fieldNotesEmailId) {
|
|
27593
|
+
const fieldNotesEmailId = await sendFieldNotesGuide(resend, current);
|
|
27594
|
+
await updateWorkshopRegistration(current.id, { fieldNotesEmailId, lastError: null });
|
|
27595
|
+
current = { ...current, fieldNotesEmailId };
|
|
27596
|
+
}
|
|
27597
|
+
const schedule = reminderSchedule;
|
|
27598
|
+
if (!current.reminder48hEmailId && Date.parse(schedule.reminder48h) > Date.now()) {
|
|
27599
|
+
const reminder48hEmailId = await scheduleReminder(resend, current, config, "48h", schedule.reminder48h);
|
|
27600
|
+
await updateWorkshopRegistration(current.id, { reminder48hEmailId, lastError: null });
|
|
27601
|
+
current = { ...current, reminder48hEmailId };
|
|
27602
|
+
}
|
|
27282
27603
|
if (!current.reminder24hEmailId && Date.parse(schedule.reminder24h) > Date.now()) {
|
|
27283
27604
|
const reminder24hEmailId = await scheduleReminder(resend, current, config, "24h", schedule.reminder24h);
|
|
27284
27605
|
await updateWorkshopRegistration(current.id, { reminder24hEmailId, lastError: null });
|
|
@@ -27342,7 +27663,7 @@ function readAsset(name) {
|
|
|
27342
27663
|
assetCache.set(name, value);
|
|
27343
27664
|
return value;
|
|
27344
27665
|
}
|
|
27345
|
-
function
|
|
27666
|
+
function escapeHtml5(value) {
|
|
27346
27667
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
27347
27668
|
}
|
|
27348
27669
|
function safeUrl(value, allowImage = false) {
|
|
@@ -27358,20 +27679,20 @@ function safeUrl(value, allowImage = false) {
|
|
|
27358
27679
|
}
|
|
27359
27680
|
function markdownRenderer() {
|
|
27360
27681
|
const renderer = new Renderer();
|
|
27361
|
-
renderer.html = ({ text: text2 }) => `<pre><code>${
|
|
27682
|
+
renderer.html = ({ text: text2 }) => `<pre><code>${escapeHtml5(text2)}</code></pre>`;
|
|
27362
27683
|
renderer.link = function({ href, title, tokens }) {
|
|
27363
27684
|
const label = this.parser.parseInline(tokens);
|
|
27364
27685
|
const safe = safeUrl(href);
|
|
27365
27686
|
if (!safe) return label;
|
|
27366
|
-
const titleAttribute = title ? ` title="${
|
|
27687
|
+
const titleAttribute = title ? ` title="${escapeHtml5(title)}"` : "";
|
|
27367
27688
|
const external = safe.startsWith("http://") || safe.startsWith("https://") ? ' target="_blank" rel="noopener noreferrer"' : "";
|
|
27368
|
-
return `<a href="${
|
|
27689
|
+
return `<a href="${escapeHtml5(safe)}"${titleAttribute}${external}>${label}</a>`;
|
|
27369
27690
|
};
|
|
27370
27691
|
renderer.image = ({ href, title, text: text2 }) => {
|
|
27371
27692
|
const safe = safeUrl(href, true);
|
|
27372
|
-
if (!safe) return `<em>[Image omitted: ${
|
|
27373
|
-
const titleAttribute = title ? ` title="${
|
|
27374
|
-
return `<img src="${
|
|
27693
|
+
if (!safe) return `<em>[Image omitted: ${escapeHtml5(text2)}]</em>`;
|
|
27694
|
+
const titleAttribute = title ? ` title="${escapeHtml5(title)}"` : "";
|
|
27695
|
+
return `<img src="${escapeHtml5(safe)}" alt="${escapeHtml5(text2)}"${titleAttribute} loading="lazy">`;
|
|
27375
27696
|
};
|
|
27376
27697
|
return new Marked({ gfm: true, breaks: false, renderer });
|
|
27377
27698
|
}
|
|
@@ -27394,7 +27715,7 @@ function addHeadingIds(html) {
|
|
|
27394
27715
|
used.set(base, count + 1);
|
|
27395
27716
|
const id = count ? `${base}-${count + 1}` : base;
|
|
27396
27717
|
toc.push({ id, title, level: Number(level) });
|
|
27397
|
-
return `<h${level} id="${id}" data-jump-title="${
|
|
27718
|
+
return `<h${level} id="${id}" data-jump-title="${escapeHtml5(title)}">${inner}</h${level}>`;
|
|
27398
27719
|
});
|
|
27399
27720
|
return { rendered, toc };
|
|
27400
27721
|
}
|
|
@@ -27437,7 +27758,7 @@ function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
|
|
|
27437
27758
|
const generatedAt = now.toISOString();
|
|
27438
27759
|
const data = { ...input, generatedAt, articles };
|
|
27439
27760
|
const template = readAsset("index.html");
|
|
27440
|
-
const html = template.replace("<title>Editorial Reading Room</title>", `<title>${
|
|
27761
|
+
const html = template.replace("<title>Editorial Reading Room</title>", `<title>${escapeHtml5(input.site.title)}</title>`).replace('content="A mobile-first editorial reading room."', `content="${escapeHtml5(input.deck || input.site.heroTitle)}"`).replace("/*__READING_ROOM_STYLES__*/", readAsset("styles.css")).replace("/*__READING_ROOM_DATA__*/", escapedJsonForHtml(data)).replace("/*__READING_ROOM_SCRIPT__*/", readAsset("app.js"));
|
|
27441
27762
|
const bytes = Buffer.byteLength(html);
|
|
27442
27763
|
return {
|
|
27443
27764
|
html,
|
|
@@ -34100,7 +34421,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
34100
34421
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
34101
34422
|
return c.json({ error: "Unauthorized" }, 401);
|
|
34102
34423
|
}
|
|
34103
|
-
const { drainQueue } = await import("./worker-
|
|
34424
|
+
const { drainQueue } = await import("./worker-FXAGFYOE.js");
|
|
34104
34425
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
34105
34426
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
34106
34427
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation] = await Promise.all([
|
|
@@ -34149,8 +34470,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
34149
34470
|
finishExtractJob: finishExtractJob2,
|
|
34150
34471
|
terminalExtractJobStatus: terminalExtractJobStatus2,
|
|
34151
34472
|
extractJobLimitInfo: extractJobLimitInfo2
|
|
34152
|
-
} = await import("./site-extract-repository-
|
|
34153
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
34473
|
+
} = await import("./site-extract-repository-2SMMFKKL.js");
|
|
34474
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-K4PG3RZJ.js");
|
|
34154
34475
|
const existing = await getExtractJob2(jobId2);
|
|
34155
34476
|
if (!existing) return c.json({ error: "job not found" }, 404);
|
|
34156
34477
|
if (existing.status !== "failed") {
|
|
@@ -34177,7 +34498,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
34177
34498
|
if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
|
|
34178
34499
|
let settlement = "already_settled_or_refunded";
|
|
34179
34500
|
if (job.billedMc == null && job.userId != null) {
|
|
34180
|
-
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
34501
|
+
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-2SMMFKKL.js");
|
|
34181
34502
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
34182
34503
|
const successful = await countSuccessfulPages2(jobId2);
|
|
34183
34504
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -34350,4 +34671,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
34350
34671
|
export {
|
|
34351
34672
|
app
|
|
34352
34673
|
};
|
|
34353
|
-
//# sourceMappingURL=server-
|
|
34674
|
+
//# sourceMappingURL=server-KM3CFWCF.js.map
|