mcp-scraper 0.3.27 → 0.3.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/api-server.cjs +2475 -1154
- 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 +346 -7
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +7 -4
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs +109 -5
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-TRNSXR46.js → chunk-DVRPXPYH.js} +22 -3
- package/dist/chunk-DVRPXPYH.js.map +1 -0
- package/dist/{chunk-QSSH4RCX.js → chunk-GMTS35L6.js} +111 -7
- package/dist/chunk-GMTS35L6.js.map +1 -0
- package/dist/chunk-I5EFBTYE.js +7 -0
- package/dist/chunk-I5EFBTYE.js.map +1 -0
- package/dist/{chunk-H74L743B.js → chunk-KJWNVQFL.js} +39 -2
- package/dist/chunk-KJWNVQFL.js.map +1 -0
- package/dist/{chunk-6KHGYJIT.js → chunk-QLQYNE7E.js} +255 -14
- package/dist/chunk-QLQYNE7E.js.map +1 -0
- package/dist/{db-FP2ABUU4.js → db-LYQENFPW.js} +2 -2
- package/dist/index.cjs +109 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2 -2
- package/dist/{server-KUNOK47B.js → server-HB7WJO3N.js} +1155 -274
- package/dist/server-HB7WJO3N.js.map +1 -0
- package/dist/{worker-NV4GTIRG.js → worker-TL4J65DE.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-6KHGYJIT.js.map +0 -1
- package/dist/chunk-DYV72F6A.js +0 -7
- package/dist/chunk-DYV72F6A.js.map +0 -1
- package/dist/chunk-H74L743B.js.map +0 -1
- package/dist/chunk-QSSH4RCX.js.map +0 -1
- package/dist/chunk-TRNSXR46.js.map +0 -1
- package/dist/server-KUNOK47B.js.map +0 -1
- /package/dist/{db-FP2ABUU4.js.map → db-LYQENFPW.js.map} +0 -0
- /package/dist/{worker-NV4GTIRG.js.map → worker-TL4J65DE.js.map} +0 -0
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CaptureSerpPageSnapshotsInputSchema,
|
|
3
|
-
CaptureSerpPageSnapshotsOutputSchema,
|
|
4
|
-
CaptureSerpSnapshotInputSchema,
|
|
5
|
-
CaptureSerpSnapshotOutputSchema,
|
|
6
2
|
HttpMcpToolExecutor,
|
|
7
3
|
auditImages,
|
|
8
4
|
buildLinkGraph,
|
|
@@ -11,18 +7,16 @@ import {
|
|
|
11
7
|
computeIssues,
|
|
12
8
|
configureReportSaving,
|
|
13
9
|
exportFanout,
|
|
14
|
-
formatCaptureSerpPageSnapshots,
|
|
15
|
-
formatCaptureSerpSnapshot,
|
|
16
10
|
harvestTimeoutBudget,
|
|
17
|
-
liveWebToolAnnotations,
|
|
18
11
|
outputBaseDir,
|
|
19
12
|
registerBrowserAgentMcpTools,
|
|
13
|
+
registerSerpIntelligenceCaptureTools,
|
|
20
14
|
renderImageSection,
|
|
21
15
|
renderIssueReport,
|
|
22
16
|
renderLinkReport,
|
|
23
17
|
sanitizeAttempts,
|
|
24
18
|
sanitizeHarvestResult
|
|
25
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QLQYNE7E.js";
|
|
26
20
|
import {
|
|
27
21
|
csvRecords,
|
|
28
22
|
listWorkflowDefinitions,
|
|
@@ -35,7 +29,7 @@ import {
|
|
|
35
29
|
workflowStepCount,
|
|
36
30
|
workflowSupportsSteps
|
|
37
31
|
} from "./chunk-NNEIXK5L.js";
|
|
38
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-I5EFBTYE.js";
|
|
39
33
|
import {
|
|
40
34
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
41
35
|
CONCURRENCY_PRICE_ID,
|
|
@@ -46,6 +40,7 @@ import {
|
|
|
46
40
|
MC_PER_CREDIT,
|
|
47
41
|
MEMORY_PLANS,
|
|
48
42
|
MEMORY_PLAN_QUOTA,
|
|
43
|
+
SCHEDULING_PLANS,
|
|
49
44
|
SUBSCRIPTION_TIERS,
|
|
50
45
|
SUBSCRIPTION_TIER_BY_KEY,
|
|
51
46
|
browserActiveCostMc,
|
|
@@ -55,7 +50,7 @@ import {
|
|
|
55
50
|
harvestProblemResponse,
|
|
56
51
|
insufficientBalanceResponse,
|
|
57
52
|
serializeHarvestProblem
|
|
58
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-KJWNVQFL.js";
|
|
59
54
|
import {
|
|
60
55
|
BrowserDriver,
|
|
61
56
|
MapsSelectors,
|
|
@@ -68,7 +63,7 @@ import {
|
|
|
68
63
|
recordVendorUsage,
|
|
69
64
|
resolveKernelProxyId,
|
|
70
65
|
runWithCostContext
|
|
71
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-GMTS35L6.js";
|
|
72
67
|
import {
|
|
73
68
|
CaptchaError,
|
|
74
69
|
RECAPTCHA_INSTRUCTIONS,
|
|
@@ -164,7 +159,7 @@ import {
|
|
|
164
159
|
setSubscriptionTier,
|
|
165
160
|
setUserMemoryCreds,
|
|
166
161
|
verifyPassword
|
|
167
|
-
} from "./chunk-
|
|
162
|
+
} from "./chunk-DVRPXPYH.js";
|
|
168
163
|
|
|
169
164
|
// src/blog/registry.ts
|
|
170
165
|
var posts = [
|
|
@@ -5262,7 +5257,7 @@ async function extractSite(opts) {
|
|
|
5262
5257
|
}
|
|
5263
5258
|
|
|
5264
5259
|
// src/api/server.ts
|
|
5265
|
-
import { Hono as
|
|
5260
|
+
import { Hono as Hono18 } from "hono";
|
|
5266
5261
|
import { serve as serveInngest } from "inngest/hono";
|
|
5267
5262
|
|
|
5268
5263
|
// src/inngest/client.ts
|
|
@@ -7811,6 +7806,49 @@ var CATALOG = {
|
|
|
7811
7806
|
}
|
|
7812
7807
|
]
|
|
7813
7808
|
},
|
|
7809
|
+
{
|
|
7810
|
+
key: "google_ads",
|
|
7811
|
+
label: "Google Ads",
|
|
7812
|
+
icon: "megaphone",
|
|
7813
|
+
tools: [
|
|
7814
|
+
{
|
|
7815
|
+
key: "google_ads_search",
|
|
7816
|
+
label: "Ad Search",
|
|
7817
|
+
kind: "simple",
|
|
7818
|
+
endpoint: "/google-ads/search",
|
|
7819
|
+
mcpName: "google_ads_search",
|
|
7820
|
+
tagline: "Find advertisers in Ads Transparency",
|
|
7821
|
+
params: [
|
|
7822
|
+
{ key: "query", label: "Domain or advertiser", type: "string", required: true, placeholder: "getviktor.com" },
|
|
7823
|
+
{ key: "region", label: "Region", type: "string", default: "US" },
|
|
7824
|
+
{ key: "maxResults", label: "Max results", type: "number", min: 1, max: 20, default: 10, advanced: true }
|
|
7825
|
+
],
|
|
7826
|
+
examples: ["Find Google ads pointing to this domain"]
|
|
7827
|
+
},
|
|
7828
|
+
{
|
|
7829
|
+
key: "google_ads_page_intel",
|
|
7830
|
+
label: "Advertiser Intel",
|
|
7831
|
+
kind: "simple",
|
|
7832
|
+
endpoint: "/google-ads/page-intel",
|
|
7833
|
+
mcpName: "google_ads_page_intel",
|
|
7834
|
+
tagline: "An advertiser's ad creatives + media",
|
|
7835
|
+
params: [
|
|
7836
|
+
{ key: "domain", label: "Domain or advertiser ID", type: "string", required: true, placeholder: "getviktor.com" },
|
|
7837
|
+
{ key: "region", label: "Region", type: "string", default: "US" },
|
|
7838
|
+
{ key: "maxAds", label: "Max ads", type: "number", min: 1, max: 200, default: 50, advanced: true }
|
|
7839
|
+
]
|
|
7840
|
+
},
|
|
7841
|
+
{
|
|
7842
|
+
key: "google_ads_transcribe",
|
|
7843
|
+
label: "Ad Transcribe",
|
|
7844
|
+
kind: "simple",
|
|
7845
|
+
endpoint: "/google-ads/transcribe",
|
|
7846
|
+
mcpName: "google_ads_transcribe",
|
|
7847
|
+
tagline: "Transcribe a video ad",
|
|
7848
|
+
params: [{ ...URL_PARAM, label: "Ad video URL" }]
|
|
7849
|
+
}
|
|
7850
|
+
]
|
|
7851
|
+
},
|
|
7814
7852
|
{
|
|
7815
7853
|
key: "instagram",
|
|
7816
7854
|
label: "Instagram",
|
|
@@ -10796,9 +10834,330 @@ facebookAdApp.post("/media", createApiKeyAuth(), async (c) => {
|
|
|
10796
10834
|
});
|
|
10797
10835
|
});
|
|
10798
10836
|
|
|
10799
|
-
// src/api/
|
|
10837
|
+
// src/api/google-ads-routes.ts
|
|
10800
10838
|
import { Hono as Hono5 } from "hono";
|
|
10801
10839
|
import { z as z14 } from "zod";
|
|
10840
|
+
|
|
10841
|
+
// src/extractor/GoogleAdsExtractor.ts
|
|
10842
|
+
var ORIGIN = "https://adstransparency.google.com";
|
|
10843
|
+
function looksLikeDomain(q) {
|
|
10844
|
+
return /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(q.trim()) && !q.includes(" ");
|
|
10845
|
+
}
|
|
10846
|
+
var GoogleAdsExtractor = class {
|
|
10847
|
+
constructor(driver) {
|
|
10848
|
+
this.driver = driver;
|
|
10849
|
+
}
|
|
10850
|
+
driver;
|
|
10851
|
+
page() {
|
|
10852
|
+
return this.driver.getPage();
|
|
10853
|
+
}
|
|
10854
|
+
async search(query, region, maxResults) {
|
|
10855
|
+
const q = query.trim();
|
|
10856
|
+
const url = looksLikeDomain(q) ? `${ORIGIN}/?region=${encodeURIComponent(region)}&domain=${encodeURIComponent(q)}` : `${ORIGIN}/?region=${encodeURIComponent(region)}&query=${encodeURIComponent(q)}`;
|
|
10857
|
+
await this.driver.navigateTo(url);
|
|
10858
|
+
const page = this.page();
|
|
10859
|
+
await page.waitForTimeout(3500);
|
|
10860
|
+
await this.autoScroll(page, 6);
|
|
10861
|
+
const advertisers = await page.evaluate((max) => {
|
|
10862
|
+
const map = /* @__PURE__ */ new Map();
|
|
10863
|
+
const links = Array.from(document.querySelectorAll('a[href*="/advertiser/"]'));
|
|
10864
|
+
for (const a of links) {
|
|
10865
|
+
const m = a.getAttribute("href")?.match(/\/advertiser\/(AR[0-9]+)/);
|
|
10866
|
+
if (!m) continue;
|
|
10867
|
+
const id = m[1];
|
|
10868
|
+
const card = a.closest("creative-preview, advertiser-preview") || a.parentElement;
|
|
10869
|
+
const nameEl = card?.querySelector('[class*="advertiser"], [class*="name"]');
|
|
10870
|
+
const name = (nameEl?.innerText || "").trim() || null;
|
|
10871
|
+
const cur = map.get(id);
|
|
10872
|
+
if (cur) cur.count++;
|
|
10873
|
+
else map.set(id, { advertiserId: id, name, count: 1 });
|
|
10874
|
+
}
|
|
10875
|
+
return Array.from(map.values()).slice(0, max).map((v) => ({
|
|
10876
|
+
advertiserId: v.advertiserId,
|
|
10877
|
+
name: v.name,
|
|
10878
|
+
approxAdCount: v.count
|
|
10879
|
+
}));
|
|
10880
|
+
}, maxResults);
|
|
10881
|
+
return {
|
|
10882
|
+
query: q,
|
|
10883
|
+
region,
|
|
10884
|
+
advertiserCount: advertisers.length,
|
|
10885
|
+
advertisers: advertisers.map((a) => ({
|
|
10886
|
+
advertiserId: a.advertiserId,
|
|
10887
|
+
name: a.name,
|
|
10888
|
+
domain: looksLikeDomain(q) ? q : null,
|
|
10889
|
+
approxAdCount: a.approxAdCount,
|
|
10890
|
+
detailUrl: `${ORIGIN}/advertiser/${a.advertiserId}?region=${region}`
|
|
10891
|
+
}))
|
|
10892
|
+
};
|
|
10893
|
+
}
|
|
10894
|
+
async pageIntel(opts) {
|
|
10895
|
+
const { region, maxAds } = opts;
|
|
10896
|
+
let advertiserId = opts.advertiserId?.trim() || null;
|
|
10897
|
+
if (!advertiserId && opts.domain) {
|
|
10898
|
+
const s = await this.search(opts.domain, region, 1);
|
|
10899
|
+
advertiserId = s.advertisers[0]?.advertiserId ?? null;
|
|
10900
|
+
}
|
|
10901
|
+
if (!advertiserId) {
|
|
10902
|
+
return { advertiserId: null, advertiserName: null, domain: opts.domain ?? null, region, totalCreatives: 0, videoCount: 0, imageCount: 0, textCount: 0, ads: [] };
|
|
10903
|
+
}
|
|
10904
|
+
const listUrl = `${ORIGIN}/advertiser/${advertiserId}?region=${region}`;
|
|
10905
|
+
await this.driver.navigateTo(listUrl);
|
|
10906
|
+
const page = this.page();
|
|
10907
|
+
await page.waitForTimeout(3500);
|
|
10908
|
+
await this.autoScroll(page, 10);
|
|
10909
|
+
const listing = await page.evaluate(() => {
|
|
10910
|
+
const nameEl = document.querySelector('h1, [class*="advertiser-name"]');
|
|
10911
|
+
const advertiserName = (nameEl?.innerText || "").trim() || null;
|
|
10912
|
+
const ids = [];
|
|
10913
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10914
|
+
for (const a of Array.from(document.querySelectorAll('a[href*="/creative/"]'))) {
|
|
10915
|
+
const m = a.getAttribute("href")?.match(/\/creative\/(CR[0-9]+)/);
|
|
10916
|
+
if (m && !seen.has(m[1])) {
|
|
10917
|
+
seen.add(m[1]);
|
|
10918
|
+
ids.push(m[1]);
|
|
10919
|
+
}
|
|
10920
|
+
}
|
|
10921
|
+
return { advertiserName, creativeIds: ids };
|
|
10922
|
+
});
|
|
10923
|
+
const creativeIds = listing.creativeIds.slice(0, maxAds);
|
|
10924
|
+
const ads = [];
|
|
10925
|
+
for (const creativeId of creativeIds) {
|
|
10926
|
+
try {
|
|
10927
|
+
const hydrated = await this.hydrateCreative(page, advertiserId, creativeId);
|
|
10928
|
+
ads.push({
|
|
10929
|
+
creativeId,
|
|
10930
|
+
advertiserId,
|
|
10931
|
+
detailUrl: `${ORIGIN}/advertiser/${advertiserId}/creative/${creativeId}?region=${region}`,
|
|
10932
|
+
...hydrated
|
|
10933
|
+
});
|
|
10934
|
+
} catch {
|
|
10935
|
+
ads.push({
|
|
10936
|
+
creativeId,
|
|
10937
|
+
advertiserId,
|
|
10938
|
+
detailUrl: `${ORIGIN}/advertiser/${advertiserId}/creative/${creativeId}?region=${region}`,
|
|
10939
|
+
format: null,
|
|
10940
|
+
lastShown: null,
|
|
10941
|
+
landingDomain: null,
|
|
10942
|
+
imageUrls: [],
|
|
10943
|
+
youtubeVideoId: null,
|
|
10944
|
+
videoUrl: null,
|
|
10945
|
+
variations: null
|
|
10946
|
+
});
|
|
10947
|
+
}
|
|
10948
|
+
}
|
|
10949
|
+
const videoCount = ads.filter((a) => a.format === "video").length;
|
|
10950
|
+
const imageCount = ads.filter((a) => a.format === "image").length;
|
|
10951
|
+
const textCount = ads.filter((a) => a.format === "text").length;
|
|
10952
|
+
return {
|
|
10953
|
+
advertiserId,
|
|
10954
|
+
advertiserName: listing.advertiserName,
|
|
10955
|
+
domain: opts.domain ?? null,
|
|
10956
|
+
region,
|
|
10957
|
+
totalCreatives: ads.length,
|
|
10958
|
+
videoCount,
|
|
10959
|
+
imageCount,
|
|
10960
|
+
textCount,
|
|
10961
|
+
ads
|
|
10962
|
+
};
|
|
10963
|
+
}
|
|
10964
|
+
async hydrateCreative(page, advertiserId, creativeId) {
|
|
10965
|
+
return page.evaluate(async ({ advertiserId: advertiserId2, creativeId: creativeId2 }) => {
|
|
10966
|
+
const empty = { format: null, lastShown: null, landingDomain: null, imageUrls: [], youtubeVideoId: null, videoUrl: null, variations: null };
|
|
10967
|
+
const rpcUrl = "https://adstransparency.google.com/anji/_/rpc/LookupService/GetCreativeById?authuser=0";
|
|
10968
|
+
const payload = { "1": advertiserId2, "2": creativeId2, "5": { "1": 1, "2": 1 } };
|
|
10969
|
+
const resp = await fetch(rpcUrl, {
|
|
10970
|
+
method: "POST",
|
|
10971
|
+
headers: { "content-type": "application/x-www-form-urlencoded", "x-same-domain": "1" },
|
|
10972
|
+
body: "f.req=" + encodeURIComponent(JSON.stringify(payload)),
|
|
10973
|
+
credentials: "include"
|
|
10974
|
+
}).then((r) => r.text()).catch(() => "");
|
|
10975
|
+
if (!resp) return empty;
|
|
10976
|
+
const decode = (s) => s.replace(/\\u002f/gi, "/").replace(/\\u([0-9a-fA-F]{4})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
10977
|
+
const previewUrls = Array.from(resp.matchAll(/https:(?:\\u002f\\u002f|\/\/)[^"\\]*content\.js(?:[^"\\]|\\u[0-9a-fA-F]{4})*/g)).map((m) => decode(m[0]));
|
|
10978
|
+
const variations = previewUrls.length || null;
|
|
10979
|
+
let imageUrls = [];
|
|
10980
|
+
let youtubeVideoId = null;
|
|
10981
|
+
let videoUrl = null;
|
|
10982
|
+
const previewUrl = previewUrls[0];
|
|
10983
|
+
if (previewUrl) {
|
|
10984
|
+
const rendered = await fetch(previewUrl, { credentials: "omit" }).then((r) => r.text()).catch(() => "");
|
|
10985
|
+
const yt = rendered.match(/i\.ytimg\.com\/vi\/([A-Za-z0-9_-]{11})/);
|
|
10986
|
+
if (yt) youtubeVideoId = yt[1];
|
|
10987
|
+
const gv = rendered.match(/https:\/\/[a-z0-9-]+\.googlevideo\.com\/videoplayback[^"'\\ ]+/);
|
|
10988
|
+
if (gv) videoUrl = gv[0].replace(/\\u0026/g, "&").replace(/\\u003d/g, "=");
|
|
10989
|
+
const simgads = Array.from(rendered.matchAll(/(?:tpc\.googlesyndication\.com\/(?:archive\/)?simgad\/|\/simgad\/)([0-9]+)/g)).map((m) => "https://tpc.googlesyndication.com/simgad/" + m[1]);
|
|
10990
|
+
imageUrls = Array.from(new Set(simgads)).slice(0, 8);
|
|
10991
|
+
}
|
|
10992
|
+
const format = youtubeVideoId || videoUrl ? "video" : imageUrls.length ? "image" : "text";
|
|
10993
|
+
return { format, lastShown: null, landingDomain: null, imageUrls, youtubeVideoId, videoUrl, variations };
|
|
10994
|
+
}, { advertiserId, creativeId });
|
|
10995
|
+
}
|
|
10996
|
+
async autoScroll(page, rounds) {
|
|
10997
|
+
for (let i = 0; i < rounds; i++) {
|
|
10998
|
+
await page.evaluate(() => {
|
|
10999
|
+
if (document.scrollingElement) window.scrollTo(0, document.scrollingElement.scrollHeight);
|
|
11000
|
+
}).catch(() => {
|
|
11001
|
+
});
|
|
11002
|
+
await page.waitForTimeout(700);
|
|
11003
|
+
}
|
|
11004
|
+
}
|
|
11005
|
+
};
|
|
11006
|
+
|
|
11007
|
+
// src/api/google-ads-routes.ts
|
|
11008
|
+
import { fal as fal4 } from "@fal-ai/client";
|
|
11009
|
+
var GoogleAdsSearchBodySchema = z14.object({
|
|
11010
|
+
query: z14.string().trim().min(1, "query is required"),
|
|
11011
|
+
region: z14.string().trim().toUpperCase().length(2).optional(),
|
|
11012
|
+
maxResults: z14.number().int().min(1).max(20).optional()
|
|
11013
|
+
});
|
|
11014
|
+
var GoogleAdsPageIntelBodySchema = z14.object({
|
|
11015
|
+
advertiserId: z14.string().trim().optional(),
|
|
11016
|
+
domain: z14.string().trim().optional(),
|
|
11017
|
+
region: z14.string().trim().toUpperCase().length(2).optional(),
|
|
11018
|
+
maxAds: z14.number().int().min(1).max(200).optional()
|
|
11019
|
+
}).refine((d) => !!d.advertiserId || !!d.domain, { message: "advertiserId or domain is required" });
|
|
11020
|
+
var GoogleAdsTranscribeBodySchema = z14.object({
|
|
11021
|
+
videoUrl: z14.string().trim().min(1, "videoUrl is required")
|
|
11022
|
+
});
|
|
11023
|
+
function invalidRequest2(message) {
|
|
11024
|
+
return { error_code: "invalid_request", message };
|
|
11025
|
+
}
|
|
11026
|
+
function configuredKernelProxy2() {
|
|
11027
|
+
return (process.env.BROWSER_SERVICE_PROXY_ID ?? process.env.KERNEL_PROXY_ID)?.trim() || void 0;
|
|
11028
|
+
}
|
|
11029
|
+
async function kernelLaunchOptsResidential2() {
|
|
11030
|
+
let proxyId = configuredKernelProxy2();
|
|
11031
|
+
try {
|
|
11032
|
+
const resolution = await resolveKernelProxyId({
|
|
11033
|
+
kernelApiKey: browserServiceApiKey(),
|
|
11034
|
+
proxyMode: DEFAULT_PROXY_MODE,
|
|
11035
|
+
configuredKernelProxyId: configuredKernelProxy2(),
|
|
11036
|
+
gl: "us"
|
|
11037
|
+
});
|
|
11038
|
+
if (resolution.kernelProxyId) proxyId = resolution.kernelProxyId;
|
|
11039
|
+
} catch {
|
|
11040
|
+
proxyId = configuredKernelProxy2();
|
|
11041
|
+
}
|
|
11042
|
+
return { headless: true, kernelApiKey: browserServiceApiKey(), kernelProxyId: proxyId, viewport: { width: 1280, height: 900 }, locale: "en-US" };
|
|
11043
|
+
}
|
|
11044
|
+
function isBlockedMessage(msg) {
|
|
11045
|
+
return msg.toLowerCase().includes("blocked") || msg.toLowerCase().includes("captcha");
|
|
11046
|
+
}
|
|
11047
|
+
var googleAdsApp = new Hono5();
|
|
11048
|
+
googleAdsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
11049
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
11050
|
+
const parsed = GoogleAdsSearchBodySchema.safeParse(raw);
|
|
11051
|
+
if (!parsed.success) return c.json(invalidRequest2(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11052
|
+
const body = parsed.data;
|
|
11053
|
+
const region = body.region ?? "US";
|
|
11054
|
+
const maxResults = Math.min(20, Math.max(1, body.maxResults ?? 10));
|
|
11055
|
+
const user = c.get("user");
|
|
11056
|
+
const gate = await acquireConcurrencyGate(user, "google_ads_search", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { query: body.query.trim(), region } });
|
|
11057
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
11058
|
+
const driver = new BrowserDriver();
|
|
11059
|
+
let debited = false, refunded = false;
|
|
11060
|
+
try {
|
|
11061
|
+
const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.google_ads_search, LedgerOperation.GOOGLE_ADS_SEARCH, body.query.trim());
|
|
11062
|
+
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.google_ads_search), 402);
|
|
11063
|
+
debited = true;
|
|
11064
|
+
await driver.launch(await kernelLaunchOptsResidential2());
|
|
11065
|
+
const extractor = new GoogleAdsExtractor(driver);
|
|
11066
|
+
const result = await extractor.search(body.query.trim(), region, maxResults);
|
|
11067
|
+
if (result.advertisers.length === 0) {
|
|
11068
|
+
await creditMc(user.id, MC_COSTS.google_ads_search, LedgerOperation.GOOGLE_ADS_SEARCH_REFUND, "empty result");
|
|
11069
|
+
refunded = true;
|
|
11070
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_search", status: "failed", query: body.query.trim(), error: "empty result refunded" });
|
|
11071
|
+
return c.json({ error: "no advertisers found (refunded)" }, 503);
|
|
11072
|
+
}
|
|
11073
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_search", status: "done", query: body.query.trim(), resultCount: result.advertisers.length, result });
|
|
11074
|
+
return c.json(result);
|
|
11075
|
+
} catch (err) {
|
|
11076
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11077
|
+
if (debited && !refunded) await creditMc(user.id, MC_COSTS.google_ads_search, LedgerOperation.GOOGLE_ADS_SEARCH_REFUND, "failed call");
|
|
11078
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_search", status: "failed", query: body.query.trim(), error: msg });
|
|
11079
|
+
return c.json({ error: msg }, isBlockedMessage(msg) ? 503 : 500);
|
|
11080
|
+
} finally {
|
|
11081
|
+
await driver.close();
|
|
11082
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
11083
|
+
}
|
|
11084
|
+
});
|
|
11085
|
+
googleAdsApp.post("/page-intel", createApiKeyAuth(), async (c) => {
|
|
11086
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
11087
|
+
const parsed = GoogleAdsPageIntelBodySchema.safeParse(raw);
|
|
11088
|
+
if (!parsed.success) return c.json(invalidRequest2(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11089
|
+
const body = parsed.data;
|
|
11090
|
+
const region = body.region ?? "US";
|
|
11091
|
+
const maxAds = Math.min(200, Math.max(1, body.maxAds ?? 50));
|
|
11092
|
+
const queryLabel = body.advertiserId ?? body.domain ?? "";
|
|
11093
|
+
const user = c.get("user");
|
|
11094
|
+
const gate = await acquireConcurrencyGate(user, "google_ads_page_intel", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { advertiserId: body.advertiserId, domain: body.domain, region } });
|
|
11095
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
11096
|
+
const driver = new BrowserDriver();
|
|
11097
|
+
let debited = false, refunded = false;
|
|
11098
|
+
try {
|
|
11099
|
+
const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.google_ads_intel, LedgerOperation.GOOGLE_ADS_INTEL, queryLabel);
|
|
11100
|
+
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.google_ads_intel), 402);
|
|
11101
|
+
debited = true;
|
|
11102
|
+
await driver.launch(await kernelLaunchOptsResidential2());
|
|
11103
|
+
const extractor = new GoogleAdsExtractor(driver);
|
|
11104
|
+
const result = await extractor.pageIntel({ advertiserId: body.advertiserId, domain: body.domain, region, maxAds });
|
|
11105
|
+
if (result.ads.length === 0) {
|
|
11106
|
+
await creditMc(user.id, MC_COSTS.google_ads_intel, LedgerOperation.GOOGLE_ADS_INTEL_REFUND, "empty result");
|
|
11107
|
+
refunded = true;
|
|
11108
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_page_intel", status: "failed", query: queryLabel, error: "empty result refunded" });
|
|
11109
|
+
return c.json({ error: "no ads found for this advertiser/domain (refunded)" }, 503);
|
|
11110
|
+
}
|
|
11111
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_page_intel", status: "done", query: queryLabel, resultCount: result.ads.length, result });
|
|
11112
|
+
return c.json(result);
|
|
11113
|
+
} catch (err) {
|
|
11114
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11115
|
+
if (debited && !refunded) await creditMc(user.id, MC_COSTS.google_ads_intel, LedgerOperation.GOOGLE_ADS_INTEL_REFUND, "failed call");
|
|
11116
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_page_intel", status: "failed", query: queryLabel, error: msg });
|
|
11117
|
+
return c.json({ error: msg }, isBlockedMessage(msg) ? 503 : 500);
|
|
11118
|
+
} finally {
|
|
11119
|
+
await driver.close();
|
|
11120
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
11121
|
+
}
|
|
11122
|
+
});
|
|
11123
|
+
googleAdsApp.post("/transcribe", createApiKeyAuth(), async (c) => {
|
|
11124
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
11125
|
+
const parsed = GoogleAdsTranscribeBodySchema.safeParse(raw);
|
|
11126
|
+
if (!parsed.success) return c.json(invalidRequest2(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11127
|
+
const body = parsed.data;
|
|
11128
|
+
const urlCheck = await validatePublicHttpUrl(body.videoUrl, { field: "videoUrl", requireHttps: false });
|
|
11129
|
+
if (urlCheck.error) return c.json(invalidRequest2(urlCheck.error), 400);
|
|
11130
|
+
const videoUrl = urlCheck.parsed.href;
|
|
11131
|
+
const user = c.get("user");
|
|
11132
|
+
const gate = await acquireConcurrencyGate(user, "google_ads_transcribe", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { videoUrl } });
|
|
11133
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
11134
|
+
fal4.config({ credentials: process.env.FAL_KEY });
|
|
11135
|
+
const holdMc = MC_COSTS.google_ads_transcribe * 5;
|
|
11136
|
+
let debited = false;
|
|
11137
|
+
try {
|
|
11138
|
+
const { ok, balance_mc } = await debitMc(user.id, holdMc, LedgerOperation.GOOGLE_ADS_TRANSCRIBE, videoUrl);
|
|
11139
|
+
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, holdMc), 402);
|
|
11140
|
+
debited = true;
|
|
11141
|
+
const transcript = await transcribeMediaUrl(videoUrl, "# Google Ad Transcript");
|
|
11142
|
+
const actualMins = Math.max(1, Math.ceil((Number.isFinite(transcript.durationMs) ? transcript.durationMs / 1e3 : 60) / 60));
|
|
11143
|
+
const diff = holdMc - MC_COSTS.google_ads_transcribe * actualMins;
|
|
11144
|
+
if (diff > 0) await creditMc(user.id, diff, LedgerOperation.GOOGLE_ADS_TRANSCRIBE_REFUND, "overestimate refund");
|
|
11145
|
+
else if (diff < 0) await debitMc(user.id, -diff, LedgerOperation.GOOGLE_ADS_TRANSCRIBE, videoUrl);
|
|
11146
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_transcribe", status: "done", query: videoUrl, resultCount: transcript.chunks.length, result: { text: transcript.text, chunks: transcript.chunks, durationMs: transcript.durationMs } });
|
|
11147
|
+
return c.json(transcript);
|
|
11148
|
+
} catch (err) {
|
|
11149
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11150
|
+
if (debited) await creditMc(user.id, holdMc, LedgerOperation.GOOGLE_ADS_TRANSCRIBE_REFUND, "failed call");
|
|
11151
|
+
await logRequestEvent({ userId: user.id, source: "google_ads_transcribe", status: "failed", query: videoUrl, error: msg });
|
|
11152
|
+
return c.json({ error: msg }, 500);
|
|
11153
|
+
} finally {
|
|
11154
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
11155
|
+
}
|
|
11156
|
+
});
|
|
11157
|
+
|
|
11158
|
+
// src/api/instagram-routes.ts
|
|
11159
|
+
import { Hono as Hono6 } from "hono";
|
|
11160
|
+
import { z as z15 } from "zod";
|
|
10802
11161
|
import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync3, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
10803
11162
|
import { homedir as homedir3 } from "os";
|
|
10804
11163
|
import { join as join5 } from "path";
|
|
@@ -11162,33 +11521,33 @@ async function extractInstagramMediaFromPage(page, sourceUrl, capturedMediaUrls
|
|
|
11162
11521
|
}
|
|
11163
11522
|
|
|
11164
11523
|
// src/api/instagram-routes.ts
|
|
11165
|
-
var InstagramProfileContentBodySchema =
|
|
11166
|
-
handle:
|
|
11167
|
-
url:
|
|
11168
|
-
profile:
|
|
11169
|
-
saveProfileChanges:
|
|
11170
|
-
save_profile_changes:
|
|
11171
|
-
maxItems:
|
|
11172
|
-
maxScrolls:
|
|
11173
|
-
scrollDelayMs:
|
|
11174
|
-
stableScrollLimit:
|
|
11524
|
+
var InstagramProfileContentBodySchema = z15.object({
|
|
11525
|
+
handle: z15.string().trim().optional(),
|
|
11526
|
+
url: z15.string().trim().optional(),
|
|
11527
|
+
profile: z15.string().trim().min(1).optional(),
|
|
11528
|
+
saveProfileChanges: z15.boolean().optional(),
|
|
11529
|
+
save_profile_changes: z15.boolean().optional(),
|
|
11530
|
+
maxItems: z15.number().int().min(1).max(2e3).default(50),
|
|
11531
|
+
maxScrolls: z15.number().int().min(0).max(250).default(10),
|
|
11532
|
+
scrollDelayMs: z15.number().int().min(250).max(5e3).default(1200),
|
|
11533
|
+
stableScrollLimit: z15.number().int().min(1).max(10).default(4)
|
|
11175
11534
|
}).refine((d) => !!d.handle || !!d.url, {
|
|
11176
11535
|
message: "handle or url is required"
|
|
11177
11536
|
});
|
|
11178
|
-
var InstagramMediaTypeSchema =
|
|
11179
|
-
var InstagramMediaDownloadBodySchema =
|
|
11180
|
-
url:
|
|
11181
|
-
profile:
|
|
11182
|
-
saveProfileChanges:
|
|
11183
|
-
save_profile_changes:
|
|
11184
|
-
mediaTypes:
|
|
11185
|
-
downloadMedia:
|
|
11186
|
-
downloadAllTracks:
|
|
11187
|
-
includeTranscript:
|
|
11188
|
-
mux:
|
|
11537
|
+
var InstagramMediaTypeSchema = z15.enum(["image", "video", "audio"]);
|
|
11538
|
+
var InstagramMediaDownloadBodySchema = z15.object({
|
|
11539
|
+
url: z15.string().trim().min(1, "url is required"),
|
|
11540
|
+
profile: z15.string().trim().min(1).optional(),
|
|
11541
|
+
saveProfileChanges: z15.boolean().optional(),
|
|
11542
|
+
save_profile_changes: z15.boolean().optional(),
|
|
11543
|
+
mediaTypes: z15.array(InstagramMediaTypeSchema).default(["image", "video", "audio"]),
|
|
11544
|
+
downloadMedia: z15.boolean().default(true),
|
|
11545
|
+
downloadAllTracks: z15.boolean().default(false),
|
|
11546
|
+
includeTranscript: z15.boolean().default(false),
|
|
11547
|
+
mux: z15.boolean().default(true)
|
|
11189
11548
|
});
|
|
11190
|
-
var instagramApp = new
|
|
11191
|
-
function
|
|
11549
|
+
var instagramApp = new Hono6();
|
|
11550
|
+
function invalidRequest3(message) {
|
|
11192
11551
|
return { error_code: "invalid_request", message };
|
|
11193
11552
|
}
|
|
11194
11553
|
function resolveSaveProfileChanges(body) {
|
|
@@ -11198,14 +11557,14 @@ function resolveProfileName(body) {
|
|
|
11198
11557
|
const explicit = body.profile?.trim();
|
|
11199
11558
|
return explicit || browserServiceProfileName();
|
|
11200
11559
|
}
|
|
11201
|
-
function
|
|
11560
|
+
function configuredKernelProxy3() {
|
|
11202
11561
|
return (process.env.BROWSER_SERVICE_PROXY_ID ?? process.env.KERNEL_PROXY_ID)?.trim() || void 0;
|
|
11203
11562
|
}
|
|
11204
11563
|
async function kernelLaunchOptsDirect(profileName, saveProfileChanges) {
|
|
11205
11564
|
return {
|
|
11206
11565
|
headless: true,
|
|
11207
11566
|
kernelApiKey: browserServiceApiKey(),
|
|
11208
|
-
kernelProxyId:
|
|
11567
|
+
kernelProxyId: configuredKernelProxy3(),
|
|
11209
11568
|
...profileName ? { kernelProfileName: profileName } : {},
|
|
11210
11569
|
...typeof saveProfileChanges === "boolean" ? { kernelProfileSaveChanges: saveProfileChanges } : {},
|
|
11211
11570
|
viewport: { width: 1280, height: 900 },
|
|
@@ -11291,13 +11650,13 @@ function trackFilename(track, index, shortcode) {
|
|
|
11291
11650
|
instagramApp.post("/profile-content", createApiKeyAuth(), async (c) => {
|
|
11292
11651
|
const raw = await c.req.json().catch(() => ({}));
|
|
11293
11652
|
const parsed = InstagramProfileContentBodySchema.safeParse(raw);
|
|
11294
|
-
if (!parsed.success) return c.json(
|
|
11653
|
+
if (!parsed.success) return c.json(invalidRequest3(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11295
11654
|
const body = parsed.data;
|
|
11296
11655
|
let target;
|
|
11297
11656
|
try {
|
|
11298
11657
|
target = normalizeInstagramProfileInput(body.url ?? body.handle ?? "");
|
|
11299
11658
|
} catch (err) {
|
|
11300
|
-
return c.json(
|
|
11659
|
+
return c.json(invalidRequest3(err instanceof Error ? err.message : String(err)), 400);
|
|
11301
11660
|
}
|
|
11302
11661
|
const user = c.get("user");
|
|
11303
11662
|
const gate = await acquireConcurrencyGate(user, "instagram_profile_content", {
|
|
@@ -11339,12 +11698,12 @@ instagramApp.post("/profile-content", createApiKeyAuth(), async (c) => {
|
|
|
11339
11698
|
instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
|
|
11340
11699
|
const raw = await c.req.json().catch(() => ({}));
|
|
11341
11700
|
const parsed = InstagramMediaDownloadBodySchema.safeParse(raw);
|
|
11342
|
-
if (!parsed.success) return c.json(
|
|
11701
|
+
if (!parsed.success) return c.json(invalidRequest3(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11343
11702
|
const body = parsed.data;
|
|
11344
11703
|
const urlCheck = await validatePublicHttpUrl(body.url, { field: "url", requireHttps: false });
|
|
11345
|
-
if (urlCheck.error) return c.json(
|
|
11704
|
+
if (urlCheck.error) return c.json(invalidRequest3(urlCheck.error), 400);
|
|
11346
11705
|
const sourceUrl = urlCheck.parsed;
|
|
11347
|
-
if (!isAllowedInstagramPageUrl(sourceUrl)) return c.json(
|
|
11706
|
+
if (!isAllowedInstagramPageUrl(sourceUrl)) return c.json(invalidRequest3("url must be an Instagram post, reel, or tv URL"), 400);
|
|
11348
11707
|
const user = c.get("user");
|
|
11349
11708
|
const gate = await acquireConcurrencyGate(user, "instagram_media_download", {
|
|
11350
11709
|
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
@@ -11461,13 +11820,13 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
|
|
|
11461
11820
|
});
|
|
11462
11821
|
|
|
11463
11822
|
// src/api/reddit-routes.ts
|
|
11464
|
-
import { Hono as
|
|
11465
|
-
import { z as
|
|
11466
|
-
var RedditThreadBodySchema =
|
|
11467
|
-
url:
|
|
11468
|
-
maxComments:
|
|
11823
|
+
import { Hono as Hono7 } from "hono";
|
|
11824
|
+
import { z as z16 } from "zod";
|
|
11825
|
+
var RedditThreadBodySchema = z16.object({
|
|
11826
|
+
url: z16.string().trim().min(1, "url is required"),
|
|
11827
|
+
maxComments: z16.number().int().min(1).max(2e3).optional()
|
|
11469
11828
|
});
|
|
11470
|
-
function
|
|
11829
|
+
function invalidRequest4(message) {
|
|
11471
11830
|
return { error_code: "invalid_request", message };
|
|
11472
11831
|
}
|
|
11473
11832
|
function toOldReddit(url) {
|
|
@@ -11521,14 +11880,14 @@ var PARSE_REDDIT = `(() => {
|
|
|
11521
11880
|
});
|
|
11522
11881
|
return { title: title, author: author, score: score, postBody: postBody, blocked: blocked, numComments: comments.length, comments: comments };
|
|
11523
11882
|
})()`;
|
|
11524
|
-
var redditApp = new
|
|
11883
|
+
var redditApp = new Hono7();
|
|
11525
11884
|
redditApp.post("/thread", createApiKeyAuth(), async (c) => {
|
|
11526
11885
|
const raw = await c.req.json().catch(() => ({}));
|
|
11527
11886
|
const parsed = RedditThreadBodySchema.safeParse(raw);
|
|
11528
|
-
if (!parsed.success) return c.json(
|
|
11887
|
+
if (!parsed.success) return c.json(invalidRequest4(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11529
11888
|
const body = parsed.data;
|
|
11530
11889
|
const oldUrl = toOldReddit(body.url);
|
|
11531
|
-
if (!oldUrl) return c.json(
|
|
11890
|
+
if (!oldUrl) return c.json(invalidRequest4("url must be a reddit.com link"), 400);
|
|
11532
11891
|
const user = c.get("user");
|
|
11533
11892
|
const gate = await acquireConcurrencyGate(user, "reddit_thread", {
|
|
11534
11893
|
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
@@ -11589,7 +11948,7 @@ redditApp.post("/thread", createApiKeyAuth(), async (c) => {
|
|
|
11589
11948
|
});
|
|
11590
11949
|
|
|
11591
11950
|
// src/api/maps-routes.ts
|
|
11592
|
-
import { Hono as
|
|
11951
|
+
import { Hono as Hono8 } from "hono";
|
|
11593
11952
|
|
|
11594
11953
|
// src/extractor/MapsNavigator.ts
|
|
11595
11954
|
var MapsNavigator = class {
|
|
@@ -11764,7 +12123,7 @@ var MapsReviewCollector = class {
|
|
|
11764
12123
|
};
|
|
11765
12124
|
|
|
11766
12125
|
// src/extractor/MapsExtractor.ts
|
|
11767
|
-
import { z as
|
|
12126
|
+
import { z as z17 } from "zod";
|
|
11768
12127
|
var MapsExtractor = class {
|
|
11769
12128
|
constructor(driver) {
|
|
11770
12129
|
this.driver = driver;
|
|
@@ -11950,7 +12309,7 @@ var MapsExtractor = class {
|
|
|
11950
12309
|
});
|
|
11951
12310
|
return rows;
|
|
11952
12311
|
}, { hoursTable: MapsSelectors.hoursTable, hoursTableAlt: MapsSelectors.hoursTableAlt });
|
|
11953
|
-
const result =
|
|
12312
|
+
const result = z17.array(RawMapsHoursRowSchema).safeParse(raw);
|
|
11954
12313
|
if (!result.success) {
|
|
11955
12314
|
console.warn("[MapsExtractor] hours parse failed", result.error.flatten());
|
|
11956
12315
|
return [];
|
|
@@ -12014,7 +12373,7 @@ var MapsExtractor = class {
|
|
|
12014
12373
|
});
|
|
12015
12374
|
return results;
|
|
12016
12375
|
});
|
|
12017
|
-
const result =
|
|
12376
|
+
const result = z17.array(RawMapsAboutAttributeSchema).safeParse(raw);
|
|
12018
12377
|
if (!result.success) {
|
|
12019
12378
|
console.warn("[MapsExtractor] about parse failed", result.error.flatten());
|
|
12020
12379
|
return [];
|
|
@@ -12390,7 +12749,7 @@ function mapsErrorResponse(c, err, errorCode) {
|
|
|
12390
12749
|
attempts: rotationError?.attempts ?? void 0
|
|
12391
12750
|
}, retryable ? 503 : 500);
|
|
12392
12751
|
}
|
|
12393
|
-
var mapsApp = new
|
|
12752
|
+
var mapsApp = new Hono8();
|
|
12394
12753
|
mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
12395
12754
|
const user = c.get("user");
|
|
12396
12755
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -12528,12 +12887,12 @@ mapsApp.post("/place", createApiKeyAuth(), async (c) => {
|
|
|
12528
12887
|
});
|
|
12529
12888
|
|
|
12530
12889
|
// src/api/directory-routes.ts
|
|
12531
|
-
import { Hono as
|
|
12890
|
+
import { Hono as Hono9 } from "hono";
|
|
12532
12891
|
|
|
12533
12892
|
// src/directory/directory-workflow.ts
|
|
12534
12893
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
12535
12894
|
import { join as join6 } from "path";
|
|
12536
|
-
import { z as
|
|
12895
|
+
import { z as z18 } from "zod";
|
|
12537
12896
|
|
|
12538
12897
|
// src/directory/location-db.ts
|
|
12539
12898
|
import { access, readFile } from "fs/promises";
|
|
@@ -12694,24 +13053,24 @@ async function resolveDirectoryMarkets(options) {
|
|
|
12694
13053
|
}
|
|
12695
13054
|
|
|
12696
13055
|
// src/directory/directory-workflow.ts
|
|
12697
|
-
var DirectoryWorkflowOptionsSchema =
|
|
12698
|
-
query:
|
|
12699
|
-
state:
|
|
12700
|
-
minPopulation:
|
|
12701
|
-
populationYear:
|
|
12702
|
-
maxCities:
|
|
12703
|
-
maxResultsPerCity:
|
|
12704
|
-
concurrency:
|
|
12705
|
-
includeZipGroups:
|
|
12706
|
-
usZipsCsvPath:
|
|
12707
|
-
saveCsv:
|
|
12708
|
-
gl:
|
|
12709
|
-
hl:
|
|
12710
|
-
proxyMode:
|
|
12711
|
-
proxyZip:
|
|
12712
|
-
debug:
|
|
12713
|
-
headless:
|
|
12714
|
-
kernelApiKey:
|
|
13056
|
+
var DirectoryWorkflowOptionsSchema = z18.object({
|
|
13057
|
+
query: z18.string().min(1),
|
|
13058
|
+
state: z18.string().min(2).default("TN"),
|
|
13059
|
+
minPopulation: z18.number().int().min(0).default(1e5),
|
|
13060
|
+
populationYear: z18.union(POPULATION_YEARS.map((year) => z18.literal(year))).default(2025),
|
|
13061
|
+
maxCities: z18.number().int().min(1).max(100).default(25),
|
|
13062
|
+
maxResultsPerCity: z18.number().int().min(1).max(50).default(50),
|
|
13063
|
+
concurrency: z18.number().int().min(1).max(5).default(5),
|
|
13064
|
+
includeZipGroups: z18.boolean().default(true),
|
|
13065
|
+
usZipsCsvPath: z18.string().optional(),
|
|
13066
|
+
saveCsv: z18.boolean().default(true),
|
|
13067
|
+
gl: z18.string().length(2).default("us"),
|
|
13068
|
+
hl: z18.string().length(2).default("en"),
|
|
13069
|
+
proxyMode: z18.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
13070
|
+
proxyZip: z18.string().regex(/^\d{5}$/).optional(),
|
|
13071
|
+
debug: z18.boolean().default(false),
|
|
13072
|
+
headless: z18.boolean().default(true),
|
|
13073
|
+
kernelApiKey: z18.string().optional()
|
|
12715
13074
|
});
|
|
12716
13075
|
function errorMessage(err) {
|
|
12717
13076
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -12932,7 +13291,7 @@ async function runDirectoryWorkflowFromPlan(options, plan) {
|
|
|
12932
13291
|
}
|
|
12933
13292
|
|
|
12934
13293
|
// src/api/directory-routes.ts
|
|
12935
|
-
var directoryApp = new
|
|
13294
|
+
var directoryApp = new Hono9();
|
|
12936
13295
|
directoryApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
12937
13296
|
const user = c.get("user");
|
|
12938
13297
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -13005,35 +13364,35 @@ directoryApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
|
13005
13364
|
// src/api/workflow-routes.ts
|
|
13006
13365
|
import { createHmac } from "crypto";
|
|
13007
13366
|
import { readFile as readFile2 } from "fs/promises";
|
|
13008
|
-
import { Hono as
|
|
13009
|
-
import { z as
|
|
13010
|
-
var workflowApp = new
|
|
13011
|
-
var WorkflowInputSchema =
|
|
13012
|
-
var WorkflowIdSchema =
|
|
13013
|
-
var CadenceSchema =
|
|
13014
|
-
var ScheduleStatusSchema =
|
|
13015
|
-
var RunBodySchema =
|
|
13367
|
+
import { Hono as Hono10 } from "hono";
|
|
13368
|
+
import { z as z19 } from "zod";
|
|
13369
|
+
var workflowApp = new Hono10();
|
|
13370
|
+
var WorkflowInputSchema = z19.record(z19.unknown()).default({});
|
|
13371
|
+
var WorkflowIdSchema = z19.string().min(1);
|
|
13372
|
+
var CadenceSchema = z19.enum(["daily", "weekly", "monthly"]);
|
|
13373
|
+
var ScheduleStatusSchema = z19.enum(["active", "paused"]);
|
|
13374
|
+
var RunBodySchema = z19.object({
|
|
13016
13375
|
workflowId: WorkflowIdSchema,
|
|
13017
13376
|
input: WorkflowInputSchema,
|
|
13018
|
-
webhookUrl:
|
|
13377
|
+
webhookUrl: z19.string().url().optional()
|
|
13019
13378
|
});
|
|
13020
|
-
var ScheduleCreateSchema =
|
|
13379
|
+
var ScheduleCreateSchema = z19.object({
|
|
13021
13380
|
workflowId: WorkflowIdSchema,
|
|
13022
|
-
name:
|
|
13381
|
+
name: z19.string().min(1).max(120).optional(),
|
|
13023
13382
|
input: WorkflowInputSchema,
|
|
13024
13383
|
cadence: CadenceSchema.default("weekly"),
|
|
13025
|
-
timezone:
|
|
13026
|
-
webhookUrl:
|
|
13027
|
-
nextRunAt:
|
|
13384
|
+
timezone: z19.string().min(1).max(64).default("UTC"),
|
|
13385
|
+
webhookUrl: z19.string().url().optional(),
|
|
13386
|
+
nextRunAt: z19.string().datetime().optional()
|
|
13028
13387
|
});
|
|
13029
|
-
var SchedulePatchSchema =
|
|
13030
|
-
name:
|
|
13388
|
+
var SchedulePatchSchema = z19.object({
|
|
13389
|
+
name: z19.string().min(1).max(120).optional(),
|
|
13031
13390
|
status: ScheduleStatusSchema.optional(),
|
|
13032
13391
|
input: WorkflowInputSchema.optional(),
|
|
13033
13392
|
cadence: CadenceSchema.optional(),
|
|
13034
|
-
timezone:
|
|
13035
|
-
webhookUrl:
|
|
13036
|
-
nextRunAt:
|
|
13393
|
+
timezone: z19.string().min(1).max(64).optional(),
|
|
13394
|
+
webhookUrl: z19.string().url().nullable().optional(),
|
|
13395
|
+
nextRunAt: z19.string().datetime().nullable().optional()
|
|
13037
13396
|
});
|
|
13038
13397
|
function hostedWorkflowOutputDir() {
|
|
13039
13398
|
return workflowOutputBaseDir(process.env.MCP_SCRAPER_WORKFLOW_OUTPUT_DIR?.trim() || "/tmp/mcp-scraper-workflows");
|
|
@@ -13537,7 +13896,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
|
|
|
13537
13896
|
});
|
|
13538
13897
|
|
|
13539
13898
|
// src/api/serp-intelligence-routes.ts
|
|
13540
|
-
import { Hono as
|
|
13899
|
+
import { Hono as Hono11 } from "hono";
|
|
13541
13900
|
|
|
13542
13901
|
// src/serp-intelligence/page-snapshot-extractor.ts
|
|
13543
13902
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -13849,7 +14208,7 @@ async function capturePageSnapshots(targets, options = {}) {
|
|
|
13849
14208
|
}
|
|
13850
14209
|
|
|
13851
14210
|
// src/serp-intelligence/schemas.ts
|
|
13852
|
-
import { z as
|
|
14211
|
+
import { z as z20 } from "zod";
|
|
13853
14212
|
var SerpIntelligenceDeviceValues = ["desktop", "mobile"];
|
|
13854
14213
|
var SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
|
|
13855
14214
|
var SerpIntelligenceAttemptOutcomeValues = [
|
|
@@ -13911,171 +14270,171 @@ function isPublicHttpUrl(value) {
|
|
|
13911
14270
|
return false;
|
|
13912
14271
|
}
|
|
13913
14272
|
}
|
|
13914
|
-
var SerpIntelligencePublicHttpUrlSchema =
|
|
13915
|
-
var SerpIntelligenceCaptureBodySchema =
|
|
13916
|
-
query:
|
|
13917
|
-
location:
|
|
13918
|
-
gl:
|
|
13919
|
-
hl:
|
|
13920
|
-
device:
|
|
13921
|
-
proxyMode:
|
|
13922
|
-
proxyZip:
|
|
13923
|
-
pages:
|
|
13924
|
-
debug:
|
|
13925
|
-
includePageSnapshots:
|
|
13926
|
-
pageSnapshotLimit:
|
|
14273
|
+
var SerpIntelligencePublicHttpUrlSchema = z20.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
|
|
14274
|
+
var SerpIntelligenceCaptureBodySchema = z20.object({
|
|
14275
|
+
query: z20.string().trim().min(1, "query is required"),
|
|
14276
|
+
location: z20.string().trim().min(1).optional(),
|
|
14277
|
+
gl: z20.string().trim().length(2).default("us"),
|
|
14278
|
+
hl: z20.string().trim().length(2).default("en"),
|
|
14279
|
+
device: z20.enum(SerpIntelligenceDeviceValues).default("desktop"),
|
|
14280
|
+
proxyMode: z20.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
|
|
14281
|
+
proxyZip: z20.string().regex(/^\d{5}$/).optional(),
|
|
14282
|
+
pages: z20.number().int().min(1).max(2).default(1),
|
|
14283
|
+
debug: z20.boolean().default(false),
|
|
14284
|
+
includePageSnapshots: z20.boolean().default(false),
|
|
14285
|
+
pageSnapshotLimit: z20.number().int().min(0).max(10).default(0)
|
|
13927
14286
|
}).strict();
|
|
13928
|
-
var SerpIntelligencePageSnapshotRequestSchema =
|
|
14287
|
+
var SerpIntelligencePageSnapshotRequestSchema = z20.object({
|
|
13929
14288
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
13930
|
-
sourceKind:
|
|
13931
|
-
sourcePosition:
|
|
14289
|
+
sourceKind: z20.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
|
|
14290
|
+
sourcePosition: z20.number().int().min(1).optional()
|
|
13932
14291
|
}).strict();
|
|
13933
|
-
var SerpIntelligencePageSnapshotsBodySchema =
|
|
13934
|
-
urls:
|
|
13935
|
-
targets:
|
|
13936
|
-
maxConcurrency:
|
|
13937
|
-
timeoutMs:
|
|
13938
|
-
debug:
|
|
14292
|
+
var SerpIntelligencePageSnapshotsBodySchema = z20.object({
|
|
14293
|
+
urls: z20.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
|
|
14294
|
+
targets: z20.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
|
|
14295
|
+
maxConcurrency: z20.number().int().min(1).max(5).default(2),
|
|
14296
|
+
timeoutMs: z20.number().int().min(1e3).max(6e4).default(15e3),
|
|
14297
|
+
debug: z20.boolean().default(false)
|
|
13939
14298
|
}).strict();
|
|
13940
|
-
var SerpIntelligenceAICitationSchema =
|
|
13941
|
-
text:
|
|
13942
|
-
href:
|
|
14299
|
+
var SerpIntelligenceAICitationSchema = z20.object({
|
|
14300
|
+
text: z20.string(),
|
|
14301
|
+
href: z20.string()
|
|
13943
14302
|
}).strict();
|
|
13944
|
-
var SerpIntelligenceOrganicResultSchema =
|
|
13945
|
-
position:
|
|
13946
|
-
title:
|
|
13947
|
-
url:
|
|
13948
|
-
domain:
|
|
13949
|
-
cite:
|
|
13950
|
-
snippet:
|
|
13951
|
-
isRedditStyle:
|
|
13952
|
-
inlineRating:
|
|
13953
|
-
value:
|
|
13954
|
-
count:
|
|
14303
|
+
var SerpIntelligenceOrganicResultSchema = z20.object({
|
|
14304
|
+
position: z20.number().int().min(1),
|
|
14305
|
+
title: z20.string(),
|
|
14306
|
+
url: z20.string(),
|
|
14307
|
+
domain: z20.string(),
|
|
14308
|
+
cite: z20.string().nullable(),
|
|
14309
|
+
snippet: z20.string().nullable(),
|
|
14310
|
+
isRedditStyle: z20.boolean(),
|
|
14311
|
+
inlineRating: z20.object({
|
|
14312
|
+
value: z20.string(),
|
|
14313
|
+
count: z20.string()
|
|
13955
14314
|
}).strict().nullable()
|
|
13956
14315
|
}).strict();
|
|
13957
|
-
var SerpIntelligenceLocationEvidenceSchema =
|
|
13958
|
-
status:
|
|
13959
|
-
expected:
|
|
13960
|
-
city:
|
|
13961
|
-
regionCode:
|
|
13962
|
-
canonicalLocation:
|
|
14316
|
+
var SerpIntelligenceLocationEvidenceSchema = z20.object({
|
|
14317
|
+
status: z20.enum(SerpIntelligenceLocalizationStatusValues),
|
|
14318
|
+
expected: z20.object({
|
|
14319
|
+
city: z20.string(),
|
|
14320
|
+
regionCode: z20.string().nullable(),
|
|
14321
|
+
canonicalLocation: z20.string()
|
|
13963
14322
|
}).strict().nullable(),
|
|
13964
|
-
candidates:
|
|
13965
|
-
city:
|
|
13966
|
-
regionCode:
|
|
13967
|
-
count:
|
|
13968
|
-
examples:
|
|
14323
|
+
candidates: z20.array(z20.object({
|
|
14324
|
+
city: z20.string(),
|
|
14325
|
+
regionCode: z20.string(),
|
|
14326
|
+
count: z20.number().int().min(0),
|
|
14327
|
+
examples: z20.array(z20.string())
|
|
13969
14328
|
}).strict())
|
|
13970
14329
|
}).strict();
|
|
13971
|
-
var SerpIntelligenceHarvestResultSchema =
|
|
13972
|
-
seed:
|
|
13973
|
-
location:
|
|
13974
|
-
extractedAt:
|
|
13975
|
-
totalQuestions:
|
|
13976
|
-
surface:
|
|
13977
|
-
aiOverview:
|
|
13978
|
-
detected:
|
|
13979
|
-
text:
|
|
13980
|
-
citations:
|
|
13981
|
-
expanded:
|
|
13982
|
-
fullyExpanded:
|
|
13983
|
-
sections:
|
|
14330
|
+
var SerpIntelligenceHarvestResultSchema = z20.object({
|
|
14331
|
+
seed: z20.string(),
|
|
14332
|
+
location: z20.string().nullable(),
|
|
14333
|
+
extractedAt: z20.string(),
|
|
14334
|
+
totalQuestions: z20.number().int().min(0),
|
|
14335
|
+
surface: z20.enum(["web", "aim", "unknown"]),
|
|
14336
|
+
aiOverview: z20.object({
|
|
14337
|
+
detected: z20.boolean(),
|
|
14338
|
+
text: z20.string().nullable(),
|
|
14339
|
+
citations: z20.array(SerpIntelligenceAICitationSchema),
|
|
14340
|
+
expanded: z20.boolean().optional(),
|
|
14341
|
+
fullyExpanded: z20.boolean().optional(),
|
|
14342
|
+
sections: z20.array(z20.string()).optional()
|
|
13984
14343
|
}).strict(),
|
|
13985
|
-
aiMode:
|
|
13986
|
-
detected:
|
|
13987
|
-
text:
|
|
13988
|
-
citations:
|
|
14344
|
+
aiMode: z20.object({
|
|
14345
|
+
detected: z20.boolean(),
|
|
14346
|
+
text: z20.string().nullable(),
|
|
14347
|
+
citations: z20.array(SerpIntelligenceAICitationSchema)
|
|
13989
14348
|
}).strict(),
|
|
13990
|
-
tree:
|
|
13991
|
-
flat:
|
|
13992
|
-
videos:
|
|
13993
|
-
forums:
|
|
13994
|
-
organicResults:
|
|
13995
|
-
localPack:
|
|
13996
|
-
entityIds:
|
|
13997
|
-
entities:
|
|
13998
|
-
name:
|
|
13999
|
-
kgId:
|
|
14000
|
-
cid:
|
|
14001
|
-
gcid:
|
|
14349
|
+
tree: z20.array(z20.unknown()),
|
|
14350
|
+
flat: z20.array(z20.unknown()),
|
|
14351
|
+
videos: z20.array(z20.unknown()),
|
|
14352
|
+
forums: z20.array(z20.unknown()),
|
|
14353
|
+
organicResults: z20.array(SerpIntelligenceOrganicResultSchema),
|
|
14354
|
+
localPack: z20.array(z20.unknown()),
|
|
14355
|
+
entityIds: z20.object({
|
|
14356
|
+
entities: z20.array(z20.object({
|
|
14357
|
+
name: z20.string(),
|
|
14358
|
+
kgId: z20.string().nullable(),
|
|
14359
|
+
cid: z20.string().nullable(),
|
|
14360
|
+
gcid: z20.string().nullable()
|
|
14002
14361
|
}).strict()),
|
|
14003
|
-
kgIds:
|
|
14004
|
-
cids:
|
|
14005
|
-
gcids:
|
|
14362
|
+
kgIds: z20.array(z20.string()),
|
|
14363
|
+
cids: z20.array(z20.string()),
|
|
14364
|
+
gcids: z20.array(z20.string())
|
|
14006
14365
|
}).strict(),
|
|
14007
|
-
stats:
|
|
14008
|
-
seed:
|
|
14009
|
-
totalQuestions:
|
|
14010
|
-
maxDepthReached:
|
|
14011
|
-
durationMs:
|
|
14012
|
-
errorCount:
|
|
14366
|
+
stats: z20.object({
|
|
14367
|
+
seed: z20.string(),
|
|
14368
|
+
totalQuestions: z20.number().int().min(0),
|
|
14369
|
+
maxDepthReached: z20.number().int().min(0),
|
|
14370
|
+
durationMs: z20.number().min(0),
|
|
14371
|
+
errorCount: z20.number().int().min(0)
|
|
14013
14372
|
}).strict(),
|
|
14014
|
-
diagnostics:
|
|
14015
|
-
completionStatus:
|
|
14016
|
-
problem:
|
|
14017
|
-
warnings:
|
|
14018
|
-
debug:
|
|
14373
|
+
diagnostics: z20.object({
|
|
14374
|
+
completionStatus: z20.enum(["paa_found", "no_paa", "serp_only"]),
|
|
14375
|
+
problem: z20.null(),
|
|
14376
|
+
warnings: z20.array(z20.unknown()).optional(),
|
|
14377
|
+
debug: z20.object({
|
|
14019
14378
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
|
|
14020
14379
|
}).passthrough().optional()
|
|
14021
14380
|
}).passthrough(),
|
|
14022
|
-
whatPeopleSaying:
|
|
14381
|
+
whatPeopleSaying: z20.array(z20.unknown())
|
|
14023
14382
|
}).strict();
|
|
14024
|
-
var SerpIntelligenceCaptureAttemptSchema =
|
|
14025
|
-
attemptNumber:
|
|
14026
|
-
outcome:
|
|
14027
|
-
startedAt:
|
|
14028
|
-
completedAt:
|
|
14029
|
-
durationMs:
|
|
14030
|
-
problemCode:
|
|
14031
|
-
message:
|
|
14032
|
-
kernelSessionId:
|
|
14033
|
-
cleanupSucceeded:
|
|
14383
|
+
var SerpIntelligenceCaptureAttemptSchema = z20.object({
|
|
14384
|
+
attemptNumber: z20.number().int().min(1),
|
|
14385
|
+
outcome: z20.enum(SerpIntelligenceAttemptOutcomeValues),
|
|
14386
|
+
startedAt: z20.string().optional(),
|
|
14387
|
+
completedAt: z20.string().optional(),
|
|
14388
|
+
durationMs: z20.number().min(0).optional(),
|
|
14389
|
+
problemCode: z20.string().optional(),
|
|
14390
|
+
message: z20.string().optional(),
|
|
14391
|
+
kernelSessionId: z20.string().nullable().optional(),
|
|
14392
|
+
cleanupSucceeded: z20.boolean().nullable().optional()
|
|
14034
14393
|
}).strict();
|
|
14035
|
-
var SerpPageSnapshotCaptureSchema =
|
|
14394
|
+
var SerpPageSnapshotCaptureSchema = z20.object({
|
|
14036
14395
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
14037
14396
|
requestedUrl: SerpIntelligencePublicHttpUrlSchema,
|
|
14038
14397
|
finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
14039
|
-
sourceKind:
|
|
14040
|
-
sourcePosition:
|
|
14041
|
-
status:
|
|
14042
|
-
fetchedVia:
|
|
14043
|
-
httpStatus:
|
|
14044
|
-
contentType:
|
|
14045
|
-
title:
|
|
14398
|
+
sourceKind: z20.enum(SerpPageSnapshotSourceKindValues),
|
|
14399
|
+
sourcePosition: z20.number().int().min(1).nullable(),
|
|
14400
|
+
status: z20.enum(SerpPageFetchStatusValues),
|
|
14401
|
+
fetchedVia: z20.enum(SerpPageFetchedViaValues).nullable(),
|
|
14402
|
+
httpStatus: z20.number().int().min(100).max(599).nullable(),
|
|
14403
|
+
contentType: z20.string().nullable(),
|
|
14404
|
+
title: z20.string().nullable(),
|
|
14046
14405
|
canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
14047
|
-
metaDescription:
|
|
14048
|
-
headings:
|
|
14049
|
-
level:
|
|
14050
|
-
text:
|
|
14406
|
+
metaDescription: z20.string().nullable(),
|
|
14407
|
+
headings: z20.array(z20.object({
|
|
14408
|
+
level: z20.number().int().min(1).max(6),
|
|
14409
|
+
text: z20.string()
|
|
14051
14410
|
}).strict()).default([]),
|
|
14052
|
-
artifact:
|
|
14053
|
-
htmlBlobUrl:
|
|
14054
|
-
textBlobUrl:
|
|
14055
|
-
markdownBlobUrl:
|
|
14056
|
-
screenshotBlobUrl:
|
|
14057
|
-
contentSha256:
|
|
14058
|
-
capturedAt:
|
|
14411
|
+
artifact: z20.object({
|
|
14412
|
+
htmlBlobUrl: z20.string().url().nullable(),
|
|
14413
|
+
textBlobUrl: z20.string().url().nullable(),
|
|
14414
|
+
markdownBlobUrl: z20.string().url().nullable(),
|
|
14415
|
+
screenshotBlobUrl: z20.string().url().nullable(),
|
|
14416
|
+
contentSha256: z20.string().nullable(),
|
|
14417
|
+
capturedAt: z20.string().nullable()
|
|
14059
14418
|
}).strict(),
|
|
14060
|
-
error:
|
|
14061
|
-
code:
|
|
14062
|
-
message:
|
|
14419
|
+
error: z20.object({
|
|
14420
|
+
code: z20.string(),
|
|
14421
|
+
message: z20.string()
|
|
14063
14422
|
}).strict().nullable()
|
|
14064
14423
|
}).strict();
|
|
14065
|
-
var SerpIntelligenceCaptureResponseSchema =
|
|
14424
|
+
var SerpIntelligenceCaptureResponseSchema = z20.object({
|
|
14066
14425
|
harvestResult: SerpIntelligenceHarvestResultSchema,
|
|
14067
|
-
attempts:
|
|
14426
|
+
attempts: z20.array(SerpIntelligenceCaptureAttemptSchema),
|
|
14068
14427
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
|
|
14069
|
-
pageSnapshotArtifacts:
|
|
14070
|
-
billing:
|
|
14071
|
-
creditsUsed:
|
|
14072
|
-
requestId:
|
|
14073
|
-
jobId:
|
|
14428
|
+
pageSnapshotArtifacts: z20.array(SerpPageSnapshotCaptureSchema),
|
|
14429
|
+
billing: z20.object({
|
|
14430
|
+
creditsUsed: z20.number().min(0).optional(),
|
|
14431
|
+
requestId: z20.string().optional(),
|
|
14432
|
+
jobId: z20.string().optional()
|
|
14074
14433
|
}).strict().optional()
|
|
14075
14434
|
}).strict();
|
|
14076
|
-
var SerpIntelligencePageSnapshotsResponseSchema =
|
|
14077
|
-
pageSnapshotArtifacts:
|
|
14078
|
-
attempts:
|
|
14435
|
+
var SerpIntelligencePageSnapshotsResponseSchema = z20.object({
|
|
14436
|
+
pageSnapshotArtifacts: z20.array(SerpPageSnapshotCaptureSchema),
|
|
14437
|
+
attempts: z20.array(SerpIntelligenceCaptureAttemptSchema).default([])
|
|
14079
14438
|
}).strict();
|
|
14080
14439
|
|
|
14081
14440
|
// src/serp-intelligence/serp-capture-service.ts
|
|
@@ -14247,7 +14606,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
|
|
|
14247
14606
|
var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
|
|
14248
14607
|
var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
|
|
14249
14608
|
var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
|
|
14250
|
-
var serpIntelligenceApp = new
|
|
14609
|
+
var serpIntelligenceApp = new Hono11();
|
|
14251
14610
|
serpIntelligenceApp.use("*", createApiKeyAuth());
|
|
14252
14611
|
function structuredError(input) {
|
|
14253
14612
|
return {
|
|
@@ -14416,7 +14775,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
|
|
|
14416
14775
|
});
|
|
14417
14776
|
|
|
14418
14777
|
// src/mcp/mcp-routes.ts
|
|
14419
|
-
import { Hono as
|
|
14778
|
+
import { Hono as Hono12 } from "hono";
|
|
14420
14779
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
14421
14780
|
|
|
14422
14781
|
// src/mcp/mcp-oauth.ts
|
|
@@ -14500,23 +14859,7 @@ async function requireMcpCallerKey(c) {
|
|
|
14500
14859
|
if (!user) return mcpAuthError();
|
|
14501
14860
|
return callerKey;
|
|
14502
14861
|
}
|
|
14503
|
-
var mcpApp = new
|
|
14504
|
-
function registerSerpIntelligenceCaptureTools(server, executor) {
|
|
14505
|
-
server.registerTool("capture_serp_snapshot", {
|
|
14506
|
-
title: "SERP Intelligence Snapshot",
|
|
14507
|
-
description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location and infer gl/hl. Routing is automatic \u2014 leave proxyMode unset. Set debug true when investigating location evidence, proxy behavior, CAPTCHA, or capture reliability.",
|
|
14508
|
-
inputSchema: CaptureSerpSnapshotInputSchema,
|
|
14509
|
-
outputSchema: CaptureSerpSnapshotOutputSchema,
|
|
14510
|
-
annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
|
|
14511
|
-
}, async (input) => formatCaptureSerpSnapshot(await executor.captureSerpSnapshot(input), input));
|
|
14512
|
-
server.registerTool("capture_serp_page_snapshots", {
|
|
14513
|
-
title: "SERP Intelligence Page Snapshots",
|
|
14514
|
-
description: "Capture public ranking-page evidence through POST /serp-intelligence/page-snapshots, the same product page snapshot path used by Phoenix. Provide urls for simple captures or targets when preserving organic, AI citation, local-pack, configured target, or site-subject source metadata. Private IPs, localhost, file URLs, and internal URLs are rejected by the service. Use timeoutMs for slow pages and debug true for sanitized proxy/browser diagnostics.",
|
|
14515
|
-
inputSchema: CaptureSerpPageSnapshotsInputSchema,
|
|
14516
|
-
outputSchema: CaptureSerpPageSnapshotsOutputSchema,
|
|
14517
|
-
annotations: liveWebToolAnnotations("SERP Intelligence Page Snapshots")
|
|
14518
|
-
}, async (input) => formatCaptureSerpPageSnapshots(await executor.captureSerpPageSnapshots(input), input));
|
|
14519
|
-
}
|
|
14862
|
+
var mcpApp = new Hono12();
|
|
14520
14863
|
mcpApp.all("/", async (c) => {
|
|
14521
14864
|
try {
|
|
14522
14865
|
const keyOrError = await requireMcpCallerKey(c);
|
|
@@ -14540,7 +14883,7 @@ mcpApp.all("/", async (c) => {
|
|
|
14540
14883
|
});
|
|
14541
14884
|
|
|
14542
14885
|
// src/api/browser-agent-routes.ts
|
|
14543
|
-
import { Hono as
|
|
14886
|
+
import { Hono as Hono13 } from "hono";
|
|
14544
14887
|
|
|
14545
14888
|
// src/api/browser-agent-db.ts
|
|
14546
14889
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -15974,7 +16317,7 @@ async function loadOpenSession(id, userId) {
|
|
|
15974
16317
|
return row;
|
|
15975
16318
|
}
|
|
15976
16319
|
function buildBrowserAgentRoutes() {
|
|
15977
|
-
const app2 = new
|
|
16320
|
+
const app2 = new Hono13();
|
|
15978
16321
|
app2.use("*", async (c, next) => {
|
|
15979
16322
|
await migrateBrowserAgent();
|
|
15980
16323
|
return next();
|
|
@@ -16680,7 +17023,7 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
|
|
|
16680
17023
|
|
|
16681
17024
|
// src/api/stripe-routes.ts
|
|
16682
17025
|
import Stripe from "stripe";
|
|
16683
|
-
import { Hono as
|
|
17026
|
+
import { Hono as Hono14 } from "hono";
|
|
16684
17027
|
|
|
16685
17028
|
// src/api/memory.ts
|
|
16686
17029
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
@@ -16819,10 +17162,22 @@ async function syncMemoryKeyPlan(user, plan) {
|
|
|
16819
17162
|
const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
|
|
16820
17163
|
return { ok: res.ok, error: res.error };
|
|
16821
17164
|
}
|
|
17165
|
+
async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
|
|
17166
|
+
const admin = ADMIN_KEY();
|
|
17167
|
+
if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
|
|
17168
|
+
const identity = resolveMemoryIdentity(user);
|
|
17169
|
+
const res = await memoryCall("setScheduleEntitlementTool", {
|
|
17170
|
+
granteeIdentity: identity,
|
|
17171
|
+
enabled,
|
|
17172
|
+
quotaPerPeriod,
|
|
17173
|
+
mcpScraperApiKey: user.api_key
|
|
17174
|
+
}, admin);
|
|
17175
|
+
return { ok: res.ok, error: res.error };
|
|
17176
|
+
}
|
|
16822
17177
|
|
|
16823
17178
|
// src/api/stripe-routes.ts
|
|
16824
17179
|
var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
16825
|
-
var stripeApp = new
|
|
17180
|
+
var stripeApp = new Hono14();
|
|
16826
17181
|
stripeApp.post("/webhooks", async (c) => {
|
|
16827
17182
|
const sig = c.req.header("stripe-signature");
|
|
16828
17183
|
const body = await c.req.text();
|
|
@@ -16861,6 +17216,15 @@ stripeApp.post("/webhooks", async (c) => {
|
|
|
16861
17216
|
}
|
|
16862
17217
|
return c.json({ received: true });
|
|
16863
17218
|
}
|
|
17219
|
+
if (priceId && priceId in SCHEDULING_PLANS) {
|
|
17220
|
+
const sched = SCHEDULING_PLANS[priceId];
|
|
17221
|
+
const user2 = await resolveUser(sub.customer, sub.customer_email);
|
|
17222
|
+
if (user2) {
|
|
17223
|
+
const live = sub.status === "active" || sub.status === "trialing";
|
|
17224
|
+
await syncScheduleEntitlement(user2, live, sched.quota_per_period);
|
|
17225
|
+
}
|
|
17226
|
+
return c.json({ received: true });
|
|
17227
|
+
}
|
|
16864
17228
|
const user = await getUserByStripeCustomerId(sub.customer);
|
|
16865
17229
|
if (!user) return c.json({ received: true });
|
|
16866
17230
|
if (priceId && priceId in SUBSCRIPTION_TIERS) {
|
|
@@ -16883,6 +17247,11 @@ stripeApp.post("/webhooks", async (c) => {
|
|
|
16883
17247
|
}
|
|
16884
17248
|
return c.json({ received: true });
|
|
16885
17249
|
}
|
|
17250
|
+
if (priceId && priceId in SCHEDULING_PLANS) {
|
|
17251
|
+
const user2 = await getUserByStripeCustomerId(sub.customer);
|
|
17252
|
+
if (user2) await syncScheduleEntitlement(user2, false);
|
|
17253
|
+
return c.json({ received: true });
|
|
17254
|
+
}
|
|
16886
17255
|
const user = await getUserByStripeCustomerId(sub.customer);
|
|
16887
17256
|
if (!user) return c.json({ received: true });
|
|
16888
17257
|
if (priceId && priceId in SUBSCRIPTION_TIERS) {
|
|
@@ -16908,7 +17277,7 @@ async function resolveUser(customerId, emailFallback) {
|
|
|
16908
17277
|
}
|
|
16909
17278
|
|
|
16910
17279
|
// src/api/oauth-routes.ts
|
|
16911
|
-
import { Hono as
|
|
17280
|
+
import { Hono as Hono15 } from "hono";
|
|
16912
17281
|
import { getCookie, setCookie } from "hono/cookie";
|
|
16913
17282
|
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
16914
17283
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
@@ -16960,6 +17329,9 @@ function isAllowedRedirect(uri) {
|
|
|
16960
17329
|
const h = u.hostname.toLowerCase();
|
|
16961
17330
|
if (["claude.ai", "www.claude.ai", "claude.com", "www.claude.com"].includes(h)) return true;
|
|
16962
17331
|
if (h.endsWith(".claude.ai") || h.endsWith(".claude.com")) return true;
|
|
17332
|
+
if (["chatgpt.com", "www.chatgpt.com", "chat.openai.com", "www.chat.openai.com"].includes(h)) return true;
|
|
17333
|
+
if (h.endsWith(".chatgpt.com") || h.endsWith(".openai.com")) return true;
|
|
17334
|
+
if (h === "viktor.com" || h.endsWith(".viktor.com")) return true;
|
|
16963
17335
|
const extra = (process.env.OAUTH_EXTRA_REDIRECT_HOSTS ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
16964
17336
|
return extra.includes(h);
|
|
16965
17337
|
}
|
|
@@ -17072,7 +17444,7 @@ function redirectWithError(redirectUri, state, error) {
|
|
|
17072
17444
|
if (state) u.searchParams.set("state", state);
|
|
17073
17445
|
return new Response(null, { status: 302, headers: { Location: u.toString() } });
|
|
17074
17446
|
}
|
|
17075
|
-
var oauthApp = new
|
|
17447
|
+
var oauthApp = new Hono15();
|
|
17076
17448
|
oauthApp.use("*", async (c, next) => {
|
|
17077
17449
|
c.header("Access-Control-Allow-Origin", "*");
|
|
17078
17450
|
c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -17080,42 +17452,51 @@ oauthApp.use("*", async (c, next) => {
|
|
|
17080
17452
|
if (c.req.method === "OPTIONS") return c.body(null, 204);
|
|
17081
17453
|
return next();
|
|
17082
17454
|
});
|
|
17083
|
-
function authServerMetadata() {
|
|
17455
|
+
function authServerMetadata(scopes = SUPPORTED_SCOPES) {
|
|
17084
17456
|
return {
|
|
17085
17457
|
issuer: ISSUER,
|
|
17086
17458
|
authorization_endpoint: `${ISSUER}/oauth/authorize`,
|
|
17087
17459
|
token_endpoint: `${ISSUER}/oauth/token`,
|
|
17088
17460
|
registration_endpoint: `${ISSUER}/oauth/register`,
|
|
17089
17461
|
jwks_uri: `${ISSUER}/.well-known/jwks.json`,
|
|
17090
|
-
scopes_supported: [...
|
|
17462
|
+
scopes_supported: [...scopes],
|
|
17091
17463
|
response_types_supported: ["code"],
|
|
17092
17464
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
17093
17465
|
code_challenge_methods_supported: ["S256"],
|
|
17094
17466
|
token_endpoint_auth_methods_supported: ["none"]
|
|
17095
17467
|
};
|
|
17096
17468
|
}
|
|
17097
|
-
function openidConfiguration() {
|
|
17469
|
+
function openidConfiguration(scopes = SUPPORTED_SCOPES) {
|
|
17098
17470
|
return {
|
|
17099
|
-
...authServerMetadata(),
|
|
17471
|
+
...authServerMetadata(scopes),
|
|
17100
17472
|
subject_types_supported: ["public"],
|
|
17101
17473
|
id_token_signing_alg_values_supported: ["RS256"]
|
|
17102
17474
|
};
|
|
17103
17475
|
}
|
|
17476
|
+
function scopesForRequest(c) {
|
|
17477
|
+
const host = (c.req.header("host") ?? "").toLowerCase();
|
|
17478
|
+
if (!host) return SUPPORTED_SCOPES;
|
|
17479
|
+
const suffix2 = c.req.path.replace(/^\/\.well-known\/(oauth-authorization-server|openid-configuration)/, "");
|
|
17480
|
+
const candidate = `https://${host}${suffix2}`.replace(/\/$/, "");
|
|
17481
|
+
if (candidate === SCRAPER_RESOURCE()) return SCRAPER_SCOPES;
|
|
17482
|
+
if (candidate === RESOURCE().replace(/\/$/, "")) return MEMORY_SCOPES;
|
|
17483
|
+
return SUPPORTED_SCOPES;
|
|
17484
|
+
}
|
|
17104
17485
|
oauthApp.get("/.well-known/oauth-authorization-server", (c) => {
|
|
17105
17486
|
console.log("[oauth-dcr] GET /.well-known/oauth-authorization-server ua=%s", c.req.header("user-agent") ?? "");
|
|
17106
|
-
return c.json(authServerMetadata());
|
|
17487
|
+
return c.json(authServerMetadata(scopesForRequest(c)));
|
|
17107
17488
|
});
|
|
17108
17489
|
oauthApp.get("/.well-known/oauth-authorization-server/*", (c) => {
|
|
17109
17490
|
console.log("[oauth-dcr] GET %s (path-aware AS) ua=%s", c.req.path, c.req.header("user-agent") ?? "");
|
|
17110
|
-
return c.json(authServerMetadata());
|
|
17491
|
+
return c.json(authServerMetadata(scopesForRequest(c)));
|
|
17111
17492
|
});
|
|
17112
17493
|
oauthApp.get("/.well-known/openid-configuration", (c) => {
|
|
17113
17494
|
console.log("[oauth-dcr] GET /.well-known/openid-configuration ua=%s", c.req.header("user-agent") ?? "");
|
|
17114
|
-
return c.json(openidConfiguration());
|
|
17495
|
+
return c.json(openidConfiguration(scopesForRequest(c)));
|
|
17115
17496
|
});
|
|
17116
17497
|
oauthApp.get("/.well-known/openid-configuration/*", (c) => {
|
|
17117
17498
|
console.log("[oauth-dcr] GET %s (path-aware OIDC) ua=%s", c.req.path, c.req.header("user-agent") ?? "");
|
|
17118
|
-
return c.json(openidConfiguration());
|
|
17499
|
+
return c.json(openidConfiguration(scopesForRequest(c)));
|
|
17119
17500
|
});
|
|
17120
17501
|
oauthApp.get("/.well-known/jwks.json", async (c) => {
|
|
17121
17502
|
const { publicJwk } = await getKeys();
|
|
@@ -17319,8 +17700,8 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
17319
17700
|
});
|
|
17320
17701
|
|
|
17321
17702
|
// src/api/chat-routes.ts
|
|
17322
|
-
import { Hono as
|
|
17323
|
-
var chatApp = new
|
|
17703
|
+
import { Hono as Hono16 } from "hono";
|
|
17704
|
+
var chatApp = new Hono16();
|
|
17324
17705
|
function tokenOk(token) {
|
|
17325
17706
|
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
17326
17707
|
}
|
|
@@ -17989,6 +18370,331 @@ loadChannels();
|
|
|
17989
18370
|
</html>`;
|
|
17990
18371
|
}
|
|
17991
18372
|
|
|
18373
|
+
// src/api/schedule-routes.ts
|
|
18374
|
+
import { Hono as Hono17 } from "hono";
|
|
18375
|
+
var scheduleApp = new Hono17();
|
|
18376
|
+
function tokenOk2(token) {
|
|
18377
|
+
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
18378
|
+
}
|
|
18379
|
+
function jsonError2(message, status = 400) {
|
|
18380
|
+
return new Response(JSON.stringify({ ok: false, error: message }), { status, headers: { "content-type": "application/json" } });
|
|
18381
|
+
}
|
|
18382
|
+
scheduleApp.get("/api/status", async (c) => {
|
|
18383
|
+
const token = c.req.query("token");
|
|
18384
|
+
if (!tokenOk2(token)) return jsonError2("missing or malformed token", 401);
|
|
18385
|
+
const r = await memoryCall("getScheduleStatusTool", {}, token);
|
|
18386
|
+
return c.json(r);
|
|
18387
|
+
});
|
|
18388
|
+
scheduleApp.get("/api/vaults", async (c) => {
|
|
18389
|
+
const token = c.req.query("token");
|
|
18390
|
+
if (!tokenOk2(token)) return jsonError2("missing or malformed token", 401);
|
|
18391
|
+
const r = await memoryCall("listVaultsTool", {}, token);
|
|
18392
|
+
if (!r.ok) return c.json(r);
|
|
18393
|
+
const vaults = (r.vaults ?? []).filter((v) => v.kind !== "channel");
|
|
18394
|
+
return c.json({ ok: true, vaults });
|
|
18395
|
+
});
|
|
18396
|
+
scheduleApp.get("/api/actions", async (c) => {
|
|
18397
|
+
const token = c.req.query("token");
|
|
18398
|
+
if (!tokenOk2(token)) return jsonError2("missing or malformed token", 401);
|
|
18399
|
+
const r = await memoryCall("listScheduledActionsTool", {}, token);
|
|
18400
|
+
return c.json(r);
|
|
18401
|
+
});
|
|
18402
|
+
scheduleApp.post("/api/propose", async (c) => {
|
|
18403
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18404
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18405
|
+
if (!body.request) return jsonError2("request is required");
|
|
18406
|
+
const r = await memoryCall("proposeScheduledActionTool", { request: body.request }, body.token);
|
|
18407
|
+
return c.json(r);
|
|
18408
|
+
});
|
|
18409
|
+
scheduleApp.post("/api/actions", async (c) => {
|
|
18410
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18411
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18412
|
+
if (!body.description || !body.vault || !body.cadence) return jsonError2("description, vault and cadence are required");
|
|
18413
|
+
const r = await memoryCall("createScheduledActionTool", { description: body.description, vault: body.vault, cadence: body.cadence }, body.token);
|
|
18414
|
+
return c.json(r);
|
|
18415
|
+
});
|
|
18416
|
+
scheduleApp.post("/api/actions/:id/pause", async (c) => {
|
|
18417
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18418
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18419
|
+
const r = await memoryCall("pauseScheduledActionTool", { id: c.req.param("id") }, body.token);
|
|
18420
|
+
return c.json(r);
|
|
18421
|
+
});
|
|
18422
|
+
scheduleApp.post("/api/actions/:id/resume", async (c) => {
|
|
18423
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18424
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18425
|
+
const r = await memoryCall("resumeScheduledActionTool", { id: c.req.param("id") }, body.token);
|
|
18426
|
+
return c.json(r);
|
|
18427
|
+
});
|
|
18428
|
+
scheduleApp.post("/api/actions/:id/delete", async (c) => {
|
|
18429
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18430
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18431
|
+
const r = await memoryCall("deleteScheduledActionTool", { id: c.req.param("id") }, body.token);
|
|
18432
|
+
return c.json(r);
|
|
18433
|
+
});
|
|
18434
|
+
scheduleApp.get("/:token", (c) => {
|
|
18435
|
+
const token = c.req.param("token");
|
|
18436
|
+
if (!tokenOk2(token)) {
|
|
18437
|
+
return c.html(`<!doctype html><html><body style="font-family:system-ui;padding:40px;color:#3D3548"><h2>This automations link isn't valid.</h2><p>Ask the AI to run get-schedule-link again, or revoke-schedule-link then get-schedule-link if you think it was compromised.</p></body></html>`, 401);
|
|
18438
|
+
}
|
|
18439
|
+
return c.html(renderSchedulePage(token));
|
|
18440
|
+
});
|
|
18441
|
+
function renderSchedulePage(token) {
|
|
18442
|
+
return `<!doctype html>
|
|
18443
|
+
<html>
|
|
18444
|
+
<head>
|
|
18445
|
+
<meta charset="utf-8">
|
|
18446
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
|
18447
|
+
<title>Scheduled Actions</title>
|
|
18448
|
+
<style>
|
|
18449
|
+
:root { color-scheme: light; --accent: #d06045; --warn: #b3261e; --bg: #faf8f6; --card: #fff; --line: #e8e1db; --ink: #211a14; --muted: #7a6f64; } html, body, div, span { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
|
18450
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; color: var(--ink); background: var(--bg); padding-bottom: 90px; }
|
|
18451
|
+
header { position: sticky; top: 0; background: var(--bg); padding: 18px 16px 12px; border-bottom: 1px solid var(--line); z-index: 10; }
|
|
18452
|
+
h1 { font-size: 21px; font-weight: 800; margin: 0 0 4px; letter-spacing: -.01em; }
|
|
18453
|
+
.quota { font-size: 13px; color: var(--muted); transition: color .2s; }
|
|
18454
|
+
.quota.warn { color: var(--warn); font-weight: 600; }
|
|
18455
|
+
.quota-bar { height: 6px; border-radius: 999px; background: var(--line); margin-top: 6px; overflow: hidden; }
|
|
18456
|
+
.quota-bar-fill { height: 100%; background: var(--accent); transition: width .3s ease, background .2s; }
|
|
18457
|
+
.quota-bar-fill.warn { background: var(--warn); }
|
|
18458
|
+
main { padding: 14px; max-width: 640px; margin: 0 auto; }
|
|
18459
|
+
.card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px; margin-bottom: 12px; animation: rise .25s ease both; }
|
|
18460
|
+
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
18461
|
+
.empty { text-align: center; color: var(--muted); padding: 56px 20px; font-size: 14px; }
|
|
18462
|
+
.empty-icon { font-size: 32px; margin-bottom: 10px; opacity: .7; }
|
|
18463
|
+
.action-desc { font-size: 15px; line-height: 1.45; margin-bottom: 6px; }
|
|
18464
|
+
.action-meta { font-size: 12.5px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 6px 10px; }
|
|
18465
|
+
.pill { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 600; }
|
|
18466
|
+
.pill-active { background: #e3f3ea; color: #1a7a4c; }
|
|
18467
|
+
.pill-paused { background: #f3ecd8; color: #93701a; }
|
|
18468
|
+
.action-row { display: flex; gap: 8px; margin-top: 12px; }
|
|
18469
|
+
button { font: inherit; }
|
|
18470
|
+
.btn { flex: 1; padding: 10px 12px; border-radius: 9px; border: 1px solid var(--line); background: #fff; color: var(--ink); font-size: 13.5px; font-weight: 600; cursor: pointer; transition: transform .08s ease, background .15s, color .15s, border-color .15s; }
|
|
18471
|
+
.btn:active { transform: scale(.96); }
|
|
18472
|
+
.btn-accent { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
18473
|
+
.btn-danger { color: #b3261e; }
|
|
18474
|
+
.btn-danger.confirming { background: var(--warn); border-color: var(--warn); color: #fff; }
|
|
18475
|
+
.fab { position: fixed; bottom: 20px; left: 16px; right: 16px; max-width: 608px; margin: 0 auto; background: var(--accent); color: #fff; border: none; border-radius: 14px; padding: 16px; font-size: 15.5px; font-weight: 700; cursor: pointer; box-shadow: 0 8px 24px rgba(208,96,69,.35); transition: transform .1s ease, box-shadow .15s; }
|
|
18476
|
+
.fab:active { transform: scale(.98); box-shadow: 0 4px 14px rgba(208,96,69,.35); }
|
|
18477
|
+
.overlay { position: fixed; inset: 0; background: rgba(33,26,20,.45); display: flex; align-items: flex-end; justify-content: center; z-index: 50; opacity: 0; pointer-events: none; transition: opacity .2s ease; }
|
|
18478
|
+
.overlay.open { opacity: 1; pointer-events: auto; }
|
|
18479
|
+
.sheet { width: 100%; max-width: 640px; background: #fff; border-radius: 18px 18px 0 0; max-height: 88vh; overflow: auto; padding: 18px 16px 24px; transform: translateY(28px); transition: transform .28s cubic-bezier(.16,1,.3,1); }
|
|
18480
|
+
.overlay.open .sheet { transform: translateY(0); }
|
|
18481
|
+
.sheet h2 { font-size: 17px; margin: 0 0 14px; }
|
|
18482
|
+
textarea, input, select { width: 100%; box-sizing: border-box; border: 1px solid var(--line); border-radius: 10px; padding: 12px 13px; font-size: 15px; outline: none; margin-top: 6px; font-family: inherit; transition: border-color .15s; }
|
|
18483
|
+
textarea:focus, input:focus, select:focus { border-color: var(--accent); }
|
|
18484
|
+
textarea.autogrow { resize: none; overflow: hidden; line-height: 1.5; min-height: 90px; transition: height .12s ease; }
|
|
18485
|
+
label { font-size: 12.5px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; display: block; margin-top: 14px; }
|
|
18486
|
+
.rationale { font-size: 13px; color: var(--muted); background: #f5f0ea; border-radius: 9px; padding: 10px 12px; margin-top: 10px; }
|
|
18487
|
+
.sheet-actions { display: flex; gap: 8px; margin-top: 18px; }
|
|
18488
|
+
.err { color: #b3261e; font-size: 13px; margin-top: 8px; display: none; }
|
|
18489
|
+
.spin { opacity: .55; pointer-events: none; }
|
|
18490
|
+
.toast { position: fixed; bottom: 96px; left: 50%; background: var(--ink); color: #fff; padding: 10px 18px; border-radius: 999px; font-size: 13.5px; font-weight: 600; z-index: 60; opacity: 0; pointer-events: none; transform: translate(-50%, 8px); transition: opacity .2s ease, transform .2s ease; }
|
|
18491
|
+
.toast.show { opacity: 1; transform: translate(-50%, 0); }
|
|
18492
|
+
</style>
|
|
18493
|
+
</head>
|
|
18494
|
+
<body>
|
|
18495
|
+
<header>
|
|
18496
|
+
<h1>Scheduled Actions</h1>
|
|
18497
|
+
<div class="quota" id="quota-text">Loading\u2026</div>
|
|
18498
|
+
<div class="quota-bar"><div class="quota-bar-fill" id="quota-fill" style="width:0%"></div></div>
|
|
18499
|
+
</header>
|
|
18500
|
+
<main id="list"></main>
|
|
18501
|
+
<button class="fab" id="new-btn">+ New scheduled action</button>
|
|
18502
|
+
<div class="toast" id="toast"></div>
|
|
18503
|
+
|
|
18504
|
+
<div class="overlay" id="propose-overlay">
|
|
18505
|
+
<div class="sheet">
|
|
18506
|
+
<h2>What should this do?</h2>
|
|
18507
|
+
<textarea id="propose-input" class="autogrow" rows="3" placeholder="e.g. check my competitor's pricing page every Monday and summarize what changed"></textarea>
|
|
18508
|
+
<div class="err" id="propose-err"></div>
|
|
18509
|
+
<div class="sheet-actions">
|
|
18510
|
+
<button class="btn" id="propose-cancel">Cancel</button>
|
|
18511
|
+
<button class="btn btn-accent" id="propose-submit">Propose</button>
|
|
18512
|
+
</div>
|
|
18513
|
+
</div>
|
|
18514
|
+
</div>
|
|
18515
|
+
|
|
18516
|
+
<div class="overlay" id="confirm-overlay">
|
|
18517
|
+
<div class="sheet">
|
|
18518
|
+
<h2>Review before confirming</h2>
|
|
18519
|
+
<label>Description</label>
|
|
18520
|
+
<textarea id="confirm-description" class="autogrow" rows="8"></textarea>
|
|
18521
|
+
<label>Vault</label>
|
|
18522
|
+
<select id="confirm-vault"></select>
|
|
18523
|
+
<label>Cadence</label>
|
|
18524
|
+
<select id="confirm-cadence">
|
|
18525
|
+
<option value="daily">Daily</option>
|
|
18526
|
+
<option value="weekly">Weekly</option>
|
|
18527
|
+
<option value="monthly">Monthly</option>
|
|
18528
|
+
</select>
|
|
18529
|
+
<div class="rationale" id="confirm-rationale"></div>
|
|
18530
|
+
<div class="err" id="confirm-err"></div>
|
|
18531
|
+
<div class="sheet-actions">
|
|
18532
|
+
<button class="btn" id="confirm-cancel">Cancel</button>
|
|
18533
|
+
<button class="btn btn-accent" id="confirm-submit">Confirm & create</button>
|
|
18534
|
+
</div>
|
|
18535
|
+
</div>
|
|
18536
|
+
</div>
|
|
18537
|
+
|
|
18538
|
+
<script>
|
|
18539
|
+
const TOKEN = ${JSON.stringify(token)};
|
|
18540
|
+
const state = { actions: [], vaults: [] };
|
|
18541
|
+
|
|
18542
|
+
function api(path, opts) { return fetch('/schedule' + path, opts).then(r => r.json()); }
|
|
18543
|
+
function get(path, params) { const qs = new URLSearchParams({ token: TOKEN, ...(params||{}) }).toString(); return api(path + '?' + qs); }
|
|
18544
|
+
function post(path, body) { return api(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ token: TOKEN, ...(body||{}) }) }); }
|
|
18545
|
+
function esc(s) { return (s || '').replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c])); }
|
|
18546
|
+
function fmt(iso) { try { return new Date(iso).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return iso || ''; } }
|
|
18547
|
+
|
|
18548
|
+
function autogrow(el) { el.style.height = 'auto'; el.style.height = el.scrollHeight + 'px'; }
|
|
18549
|
+
document.querySelectorAll('.autogrow').forEach(el => {
|
|
18550
|
+
el.addEventListener('input', () => autogrow(el));
|
|
18551
|
+
});
|
|
18552
|
+
|
|
18553
|
+
let toastTimer = null;
|
|
18554
|
+
function toast(message) {
|
|
18555
|
+
const el = document.getElementById('toast');
|
|
18556
|
+
el.textContent = message;
|
|
18557
|
+
el.classList.add('show');
|
|
18558
|
+
if (toastTimer) clearTimeout(toastTimer);
|
|
18559
|
+
toastTimer = setTimeout(() => el.classList.remove('show'), 2200);
|
|
18560
|
+
}
|
|
18561
|
+
|
|
18562
|
+
async function loadStatus() {
|
|
18563
|
+
const r = await get('/api/status');
|
|
18564
|
+
const text = document.getElementById('quota-text');
|
|
18565
|
+
const fill = document.getElementById('quota-fill');
|
|
18566
|
+
if (!r.ok || !r.enabled) {
|
|
18567
|
+
text.textContent = 'No active Scheduled Actions subscription.';
|
|
18568
|
+
text.classList.remove('warn');
|
|
18569
|
+
fill.style.width = '0%';
|
|
18570
|
+
fill.classList.remove('warn');
|
|
18571
|
+
return;
|
|
18572
|
+
}
|
|
18573
|
+
const pct = Math.min(100, Math.round((r.usedThisPeriod / Math.max(1, r.quotaPerPeriod)) * 100));
|
|
18574
|
+
const nearLimit = pct >= 90;
|
|
18575
|
+
text.textContent = r.usedThisPeriod + ' / ' + r.quotaPerPeriod + ' actions used this period' + (nearLimit ? ' \u2014 almost at your limit' : '');
|
|
18576
|
+
text.classList.toggle('warn', nearLimit);
|
|
18577
|
+
fill.style.width = pct + '%';
|
|
18578
|
+
fill.classList.toggle('warn', nearLimit);
|
|
18579
|
+
}
|
|
18580
|
+
|
|
18581
|
+
async function loadVaults() {
|
|
18582
|
+
const r = await get('/api/vaults');
|
|
18583
|
+
state.vaults = r.ok ? (r.vaults || []) : [];
|
|
18584
|
+
const sel = document.getElementById('confirm-vault');
|
|
18585
|
+
sel.innerHTML = state.vaults.map(v => '<option value="' + esc(v.handle) + '">' + esc(v.handle) + '</option>').join('');
|
|
18586
|
+
}
|
|
18587
|
+
|
|
18588
|
+
async function loadActions() {
|
|
18589
|
+
const r = await get('/api/actions');
|
|
18590
|
+
const box = document.getElementById('list');
|
|
18591
|
+
if (!r.ok) { box.innerHTML = '<div class="empty">' + esc(r.error || 'Could not load') + '</div>'; return; }
|
|
18592
|
+
state.actions = r.actions || [];
|
|
18593
|
+
if (!state.actions.length) {
|
|
18594
|
+
box.innerHTML = '<div class="empty"><div class="empty-icon">\u23F1\uFE0F</div>No scheduled actions yet.<br>Tap "New scheduled action" to create your first one.</div>';
|
|
18595
|
+
return;
|
|
18596
|
+
}
|
|
18597
|
+
box.innerHTML = state.actions.map(renderAction).join('');
|
|
18598
|
+
state.actions.forEach(a => {
|
|
18599
|
+
const pauseBtn = document.getElementById('pause-' + a.id);
|
|
18600
|
+
const resumeBtn = document.getElementById('resume-' + a.id);
|
|
18601
|
+
const delBtn = document.getElementById('delete-' + a.id);
|
|
18602
|
+
if (pauseBtn) pauseBtn.onclick = () => act('pause', a.id, 'Paused');
|
|
18603
|
+
if (resumeBtn) resumeBtn.onclick = () => act('resume', a.id, 'Resumed');
|
|
18604
|
+
if (delBtn) delBtn.onclick = () => confirmDelete(delBtn, a.id);
|
|
18605
|
+
});
|
|
18606
|
+
}
|
|
18607
|
+
|
|
18608
|
+
function confirmDelete(btn, id) {
|
|
18609
|
+
if (!btn.classList.contains('confirming')) {
|
|
18610
|
+
btn.classList.add('confirming');
|
|
18611
|
+
btn.textContent = 'Tap again to delete';
|
|
18612
|
+
btn.dataset.confirmTimer = setTimeout(() => {
|
|
18613
|
+
btn.classList.remove('confirming');
|
|
18614
|
+
btn.textContent = 'Delete';
|
|
18615
|
+
}, 3000);
|
|
18616
|
+
return;
|
|
18617
|
+
}
|
|
18618
|
+
clearTimeout(btn.dataset.confirmTimer);
|
|
18619
|
+
act('delete', id, 'Deleted');
|
|
18620
|
+
}
|
|
18621
|
+
|
|
18622
|
+
function renderAction(a) {
|
|
18623
|
+
const pill = a.status === 'active' ? '<span class="pill pill-active">active</span>' : '<span class="pill pill-paused">' + esc(a.status) + '</span>';
|
|
18624
|
+
return '<div class="card">' +
|
|
18625
|
+
'<div class="action-desc">' + esc(a.description) + '</div>' +
|
|
18626
|
+
'<div class="action-meta">' + pill + '<span>' + esc(a.cadence) + '</span><span>next ' + fmt(a.nextRunAt) + '</span>' +
|
|
18627
|
+
(a.lastRunStatus ? '<span>last: ' + esc(a.lastRunStatus) + '</span>' : '') + '</div>' +
|
|
18628
|
+
'<div class="action-row">' +
|
|
18629
|
+
(a.status === 'active'
|
|
18630
|
+
? '<button class="btn" id="pause-' + a.id + '">Pause</button>'
|
|
18631
|
+
: '<button class="btn" id="resume-' + a.id + '">Resume</button>') +
|
|
18632
|
+
'<button class="btn btn-danger" id="delete-' + a.id + '">Delete</button>' +
|
|
18633
|
+
'</div></div>';
|
|
18634
|
+
}
|
|
18635
|
+
|
|
18636
|
+
async function act(kind, id, toastMessage) {
|
|
18637
|
+
await post('/api/actions/' + id + '/' + kind, {});
|
|
18638
|
+
await loadActions();
|
|
18639
|
+
await loadStatus();
|
|
18640
|
+
if (toastMessage) toast(toastMessage);
|
|
18641
|
+
}
|
|
18642
|
+
|
|
18643
|
+
document.getElementById('new-btn').onclick = () => {
|
|
18644
|
+
document.getElementById('propose-input').value = '';
|
|
18645
|
+
document.getElementById('propose-err').style.display = 'none';
|
|
18646
|
+
document.getElementById('propose-overlay').classList.add('open');
|
|
18647
|
+
};
|
|
18648
|
+
document.getElementById('propose-cancel').onclick = () => document.getElementById('propose-overlay').classList.remove('open');
|
|
18649
|
+
|
|
18650
|
+
document.getElementById('propose-submit').onclick = async () => {
|
|
18651
|
+
const request = document.getElementById('propose-input').value.trim();
|
|
18652
|
+
const errEl = document.getElementById('propose-err');
|
|
18653
|
+
errEl.style.display = 'none';
|
|
18654
|
+
if (!request) return;
|
|
18655
|
+
const btn = document.getElementById('propose-submit');
|
|
18656
|
+
btn.classList.add('spin'); btn.textContent = 'Thinking\u2026';
|
|
18657
|
+
const r = await post('/api/propose', { request });
|
|
18658
|
+
btn.classList.remove('spin'); btn.textContent = 'Propose';
|
|
18659
|
+
if (!r.ok) { errEl.textContent = r.error || 'Could not interpret that request'; errEl.style.display = 'block'; return; }
|
|
18660
|
+
document.getElementById('propose-overlay').classList.remove('open');
|
|
18661
|
+
document.getElementById('confirm-description').value = r.description || '';
|
|
18662
|
+
document.getElementById('confirm-vault').value = r.vault || '';
|
|
18663
|
+
document.getElementById('confirm-cadence').value = r.cadence || 'daily';
|
|
18664
|
+
document.getElementById('confirm-rationale').textContent = r.rationale || '';
|
|
18665
|
+
document.getElementById('confirm-err').style.display = 'none';
|
|
18666
|
+
document.getElementById('confirm-overlay').classList.add('open');
|
|
18667
|
+
autogrow(document.getElementById('confirm-description'));
|
|
18668
|
+
};
|
|
18669
|
+
|
|
18670
|
+
document.getElementById('confirm-cancel').onclick = () => document.getElementById('confirm-overlay').classList.remove('open');
|
|
18671
|
+
|
|
18672
|
+
document.getElementById('confirm-submit').onclick = async () => {
|
|
18673
|
+
const description = document.getElementById('confirm-description').value.trim();
|
|
18674
|
+
const vault = document.getElementById('confirm-vault').value;
|
|
18675
|
+
const cadence = document.getElementById('confirm-cadence').value;
|
|
18676
|
+
const errEl = document.getElementById('confirm-err');
|
|
18677
|
+
errEl.style.display = 'none';
|
|
18678
|
+
if (!description || !vault) { errEl.textContent = 'Description and vault are required'; errEl.style.display = 'block'; return; }
|
|
18679
|
+
const btn = document.getElementById('confirm-submit');
|
|
18680
|
+
btn.classList.add('spin'); btn.textContent = 'Creating\u2026';
|
|
18681
|
+
const r = await post('/api/actions', { description, vault, cadence });
|
|
18682
|
+
btn.classList.remove('spin'); btn.textContent = 'Confirm & create';
|
|
18683
|
+
if (!r.ok) { errEl.textContent = r.error || 'Could not create scheduled action'; errEl.style.display = 'block'; return; }
|
|
18684
|
+
document.getElementById('confirm-overlay').classList.remove('open');
|
|
18685
|
+
await loadActions();
|
|
18686
|
+
await loadStatus();
|
|
18687
|
+
toast('Scheduled action created');
|
|
18688
|
+
};
|
|
18689
|
+
|
|
18690
|
+
loadStatus();
|
|
18691
|
+
loadVaults();
|
|
18692
|
+
loadActions();
|
|
18693
|
+
</script>
|
|
18694
|
+
</body>
|
|
18695
|
+
</html>`;
|
|
18696
|
+
}
|
|
18697
|
+
|
|
17992
18698
|
// src/api/site-audit-worker.ts
|
|
17993
18699
|
var MAX_CONCURRENT_SITE_AUDIT = 1;
|
|
17994
18700
|
async function drainSiteAuditQueue(budget) {
|
|
@@ -18194,13 +18900,14 @@ function toGb(bytes) {
|
|
|
18194
18900
|
}
|
|
18195
18901
|
async function entitledVaultRows(identity) {
|
|
18196
18902
|
const rows = await sql().query(
|
|
18197
|
-
`SELECT vault, owner_identity FROM mem_vault_registry WHERE grantee = $1 ORDER BY created_at ASC`,
|
|
18903
|
+
`SELECT vault, owner_identity, contract FROM mem_vault_registry WHERE grantee = $1 ORDER BY created_at ASC`,
|
|
18198
18904
|
[identity]
|
|
18199
18905
|
);
|
|
18200
18906
|
const seen = /* @__PURE__ */ new Map();
|
|
18201
18907
|
for (const r of rows) {
|
|
18202
18908
|
if (seen.has(r.vault)) continue;
|
|
18203
|
-
|
|
18909
|
+
const kind = r.contract?.kind === "channel" ? "channel" : "notes";
|
|
18910
|
+
seen.set(r.vault, { vault: r.vault, owner: r.owner_identity, physical: physicalVault(r.owner_identity, r.vault), kind });
|
|
18204
18911
|
}
|
|
18205
18912
|
return [...seen.values()];
|
|
18206
18913
|
}
|
|
@@ -18226,7 +18933,8 @@ async function dbListVaults(identity) {
|
|
|
18226
18933
|
role: isOwner ? "owner" : "shared",
|
|
18227
18934
|
...isOwner ? {} : { sharedBy: e.owner },
|
|
18228
18935
|
notes: st?.notes ?? 0,
|
|
18229
|
-
bytes: st?.bytes ?? 0
|
|
18936
|
+
bytes: st?.bytes ?? 0,
|
|
18937
|
+
kind: e.kind
|
|
18230
18938
|
};
|
|
18231
18939
|
});
|
|
18232
18940
|
}
|
|
@@ -18403,7 +19111,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
18403
19111
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
18404
19112
|
return next();
|
|
18405
19113
|
});
|
|
18406
|
-
var app = new
|
|
19114
|
+
var app = new Hono18();
|
|
18407
19115
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
18408
19116
|
function requireStripeSecret() {
|
|
18409
19117
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -18594,6 +19302,7 @@ app.get("/me", async (c) => {
|
|
|
18594
19302
|
const stats = await getUserStats(user.id);
|
|
18595
19303
|
return c.json({
|
|
18596
19304
|
authenticated: true,
|
|
19305
|
+
id: user.id,
|
|
18597
19306
|
email: user.email,
|
|
18598
19307
|
api_key: user.key_active ? user.api_key : null,
|
|
18599
19308
|
created_at: user.created_at,
|
|
@@ -18668,6 +19377,28 @@ app.get("/memory/vaults", auth2, async (c) => {
|
|
|
18668
19377
|
return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load vaults" }, 502);
|
|
18669
19378
|
}
|
|
18670
19379
|
});
|
|
19380
|
+
app.get("/memory/approved-senders", auth2, async (c) => {
|
|
19381
|
+
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
19382
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
|
|
19383
|
+
const r = await memoryCall("listApprovedSendersTool", {}, key);
|
|
19384
|
+
return c.json(r);
|
|
19385
|
+
});
|
|
19386
|
+
app.post("/memory/approved-senders", auth2, async (c) => {
|
|
19387
|
+
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
19388
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
|
|
19389
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19390
|
+
const senderIdentity = body.senderIdentity?.trim();
|
|
19391
|
+
if (!senderIdentity) return c.json({ ok: false, error: "senderIdentity is required" }, 400);
|
|
19392
|
+
const r = await memoryCall("approveSenderTool", { senderIdentity }, key);
|
|
19393
|
+
return c.json(r);
|
|
19394
|
+
});
|
|
19395
|
+
app.delete("/memory/approved-senders/:identity", auth2, async (c) => {
|
|
19396
|
+
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
19397
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
|
|
19398
|
+
const senderIdentity = c.req.param("identity");
|
|
19399
|
+
const r = await memoryCall("removeApprovedSenderTool", { senderIdentity }, key);
|
|
19400
|
+
return c.json(r);
|
|
19401
|
+
});
|
|
18671
19402
|
app.get("/memory/connect", auth2, async (c) => {
|
|
18672
19403
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
18673
19404
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
@@ -18831,6 +19562,154 @@ app.post("/memory/portal", auth2, async (c) => {
|
|
|
18831
19562
|
return c.json({ error: message }, 500);
|
|
18832
19563
|
}
|
|
18833
19564
|
});
|
|
19565
|
+
app.post("/schedule-checkout", auth2, async (c) => {
|
|
19566
|
+
try {
|
|
19567
|
+
const user = c.get("user");
|
|
19568
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19569
|
+
const priceId = body.priceId?.trim();
|
|
19570
|
+
if (!priceId || !SCHEDULING_PLANS[priceId]) return c.json({ error: "Invalid priceId \u2014 must be a scheduling plan price." }, 400);
|
|
19571
|
+
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
19572
|
+
if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
|
|
19573
|
+
const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
19574
|
+
let customerId = user.stripe_customer_id;
|
|
19575
|
+
if (!customerId) {
|
|
19576
|
+
const customer = await stripeClient.customers.create({ email: user.email });
|
|
19577
|
+
customerId = customer.id;
|
|
19578
|
+
await setStripeCustomerId(user.id, customerId);
|
|
19579
|
+
}
|
|
19580
|
+
const session = await stripeClient.checkout.sessions.create({
|
|
19581
|
+
mode: "subscription",
|
|
19582
|
+
customer: customerId,
|
|
19583
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
19584
|
+
client_reference_id: String(user.id),
|
|
19585
|
+
metadata: { product: "mcp-scheduling", userId: String(user.id) },
|
|
19586
|
+
subscription_data: { metadata: { product: "mcp-scheduling", userId: String(user.id) } },
|
|
19587
|
+
automatic_tax: { enabled: true },
|
|
19588
|
+
billing_address_collection: "required",
|
|
19589
|
+
customer_update: { address: "auto", name: "auto" },
|
|
19590
|
+
tax_id_collection: { enabled: true },
|
|
19591
|
+
success_url: `${appOrigin()}/memory?billing=success`,
|
|
19592
|
+
cancel_url: `${appOrigin()}/memory?billing=cancel`
|
|
19593
|
+
});
|
|
19594
|
+
if (!session.url) return c.json({ error: "Stripe did not return a checkout URL." }, 502);
|
|
19595
|
+
return c.json({ url: session.url });
|
|
19596
|
+
} catch (err) {
|
|
19597
|
+
const message = err instanceof Error ? err.message : "Unable to start scheduling checkout.";
|
|
19598
|
+
console.error("[schedule-checkout]", message);
|
|
19599
|
+
return c.json({ error: message }, 500);
|
|
19600
|
+
}
|
|
19601
|
+
});
|
|
19602
|
+
app.get("/schedule-status", auth2, async (c) => {
|
|
19603
|
+
try {
|
|
19604
|
+
const user = c.get("user");
|
|
19605
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19606
|
+
if (!key) return c.json({ error: error ?? "memory unavailable" }, 503);
|
|
19607
|
+
const r = await memoryCall("getScheduleStatusTool", {}, key);
|
|
19608
|
+
return c.json(r);
|
|
19609
|
+
} catch (err) {
|
|
19610
|
+
const message = err instanceof Error ? err.message : "Unable to get schedule status.";
|
|
19611
|
+
console.error("[schedule-status]", message);
|
|
19612
|
+
return c.json({ error: message }, 500);
|
|
19613
|
+
}
|
|
19614
|
+
});
|
|
19615
|
+
app.post("/schedule-link", auth2, async (c) => {
|
|
19616
|
+
try {
|
|
19617
|
+
const user = c.get("user");
|
|
19618
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19619
|
+
if (!key) return c.json({ error: error ?? "memory unavailable" }, 503);
|
|
19620
|
+
const r = await memoryCall("getScheduleLinkTool", {}, key);
|
|
19621
|
+
return c.json(r);
|
|
19622
|
+
} catch (err) {
|
|
19623
|
+
const message = err instanceof Error ? err.message : "Unable to get schedule link.";
|
|
19624
|
+
console.error("[schedule-link]", message);
|
|
19625
|
+
return c.json({ error: message }, 500);
|
|
19626
|
+
}
|
|
19627
|
+
});
|
|
19628
|
+
app.get("/schedule-actions", auth2, async (c) => {
|
|
19629
|
+
const user = c.get("user");
|
|
19630
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19631
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19632
|
+
const r = await memoryCall("listScheduledActionsTool", {}, key);
|
|
19633
|
+
return c.json(r);
|
|
19634
|
+
});
|
|
19635
|
+
app.post("/schedule-actions", auth2, async (c) => {
|
|
19636
|
+
const user = c.get("user");
|
|
19637
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19638
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19639
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19640
|
+
const r = await memoryCall("createScheduledActionTool", body, key);
|
|
19641
|
+
return c.json(r);
|
|
19642
|
+
});
|
|
19643
|
+
app.post("/schedule-actions/propose", auth2, async (c) => {
|
|
19644
|
+
const user = c.get("user");
|
|
19645
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19646
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19647
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19648
|
+
const r = await memoryCall("proposeScheduledActionTool", body, key);
|
|
19649
|
+
return c.json(r);
|
|
19650
|
+
});
|
|
19651
|
+
app.post("/schedule-actions/:id/pause", auth2, async (c) => {
|
|
19652
|
+
const user = c.get("user");
|
|
19653
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19654
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19655
|
+
const r = await memoryCall("pauseScheduledActionTool", { id: c.req.param("id") }, key);
|
|
19656
|
+
return c.json(r);
|
|
19657
|
+
});
|
|
19658
|
+
app.post("/schedule-actions/:id/resume", auth2, async (c) => {
|
|
19659
|
+
const user = c.get("user");
|
|
19660
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19661
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19662
|
+
const r = await memoryCall("resumeScheduledActionTool", { id: c.req.param("id") }, key);
|
|
19663
|
+
return c.json(r);
|
|
19664
|
+
});
|
|
19665
|
+
app.delete("/schedule-actions/:id", auth2, async (c) => {
|
|
19666
|
+
const user = c.get("user");
|
|
19667
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19668
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19669
|
+
const r = await memoryCall("deleteScheduledActionTool", { id: c.req.param("id") }, key);
|
|
19670
|
+
return c.json(r);
|
|
19671
|
+
});
|
|
19672
|
+
app.post("/schedule-link/revoke", auth2, async (c) => {
|
|
19673
|
+
try {
|
|
19674
|
+
const user = c.get("user");
|
|
19675
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19676
|
+
if (!key) return c.json({ error: error ?? "memory unavailable" }, 503);
|
|
19677
|
+
const r = await memoryCall("revokeScheduleLinkTool", {}, key);
|
|
19678
|
+
return c.json(r);
|
|
19679
|
+
} catch (err) {
|
|
19680
|
+
const message = err instanceof Error ? err.message : "Unable to revoke schedule link.";
|
|
19681
|
+
console.error("[schedule-link/revoke]", message);
|
|
19682
|
+
return c.json({ error: message }, 500);
|
|
19683
|
+
}
|
|
19684
|
+
});
|
|
19685
|
+
app.post("/support/submit", auth2, async (c) => {
|
|
19686
|
+
try {
|
|
19687
|
+
const user = c.get("user");
|
|
19688
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19689
|
+
const message = body.message?.trim();
|
|
19690
|
+
if (!message) return c.json({ error: "message is required" }, 400);
|
|
19691
|
+
if (message.length > 8e3) return c.json({ error: "message is too long (max 8000 characters)" }, 400);
|
|
19692
|
+
const inboxKey = process.env.SUPPORT_INBOX_MEMORY_KEY?.trim();
|
|
19693
|
+
if (!inboxKey) return c.json({ error: "support inbox is not configured" }, 503);
|
|
19694
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
19695
|
+
const path5 = `support/${stamp.replace(/[:.]/g, "-")}-user-${user.id}`;
|
|
19696
|
+
const title = `Support request from ${user.email} (user #${user.id})`;
|
|
19697
|
+
const content = [
|
|
19698
|
+
`**From:** ${user.email} (user #${user.id})`,
|
|
19699
|
+
`**Submitted:** ${stamp}`,
|
|
19700
|
+
"",
|
|
19701
|
+
"**Message:**",
|
|
19702
|
+
message
|
|
19703
|
+
].join("\n");
|
|
19704
|
+
const r = await memoryCall("putTool", { vault: "Issues", path: path5, title, content }, inboxKey);
|
|
19705
|
+
if (!r.ok) return c.json({ error: r.error ?? "failed to submit support request" }, 502);
|
|
19706
|
+
return c.json({ ok: true });
|
|
19707
|
+
} catch (err) {
|
|
19708
|
+
const message = err instanceof Error ? err.message : "Unable to submit support request.";
|
|
19709
|
+
console.error("[support/submit]", message);
|
|
19710
|
+
return c.json({ error: message }, 500);
|
|
19711
|
+
}
|
|
19712
|
+
});
|
|
18834
19713
|
app.get("/memory/usage", auth2, async (c) => {
|
|
18835
19714
|
const user = c.get("user");
|
|
18836
19715
|
try {
|
|
@@ -19544,7 +20423,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
19544
20423
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
19545
20424
|
return c.json({ error: "Unauthorized" }, 401);
|
|
19546
20425
|
}
|
|
19547
|
-
const { drainQueue } = await import("./worker-
|
|
20426
|
+
const { drainQueue } = await import("./worker-TL4J65DE.js");
|
|
19548
20427
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
19549
20428
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
19550
20429
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -19561,6 +20440,7 @@ app.route("/api/internal/site-architecture-auditor", siteAuditApp);
|
|
|
19561
20440
|
app.route("/youtube", youtubeApp);
|
|
19562
20441
|
app.route("/screenshot", screenshotApp);
|
|
19563
20442
|
app.route("/facebook", facebookAdApp);
|
|
20443
|
+
app.route("/google-ads", googleAdsApp);
|
|
19564
20444
|
app.route("/instagram", instagramApp);
|
|
19565
20445
|
app.route("/reddit", redditApp);
|
|
19566
20446
|
app.route("/maps", mapsApp);
|
|
@@ -19570,6 +20450,7 @@ app.route("/serp-intelligence", serpIntelligenceApp);
|
|
|
19570
20450
|
app.route("/mcp", mcpApp);
|
|
19571
20451
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
19572
20452
|
app.route("/chat", chatApp);
|
|
20453
|
+
app.route("/schedule", scheduleApp);
|
|
19573
20454
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
19574
20455
|
app.get("/console/auth/:id", async (c) => {
|
|
19575
20456
|
const id = c.req.param("id");
|
|
@@ -19693,4 +20574,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
19693
20574
|
export {
|
|
19694
20575
|
app
|
|
19695
20576
|
};
|
|
19696
|
-
//# sourceMappingURL=server-
|
|
20577
|
+
//# sourceMappingURL=server-HB7WJO3N.js.map
|