mcp-scraper 0.3.28 → 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 +2459 -1137
- 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 +237 -6
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- 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-NGD4ITAO.js → chunk-QLQYNE7E.js} +238 -7
- 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-IDDX7KGT.js → server-HB7WJO3N.js} +1154 -251
- 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-7MY2NSQX.js +0 -7
- package/dist/chunk-7MY2NSQX.js.map +0 -1
- package/dist/chunk-H74L743B.js.map +0 -1
- package/dist/chunk-NGD4ITAO.js.map +0 -1
- package/dist/chunk-QSSH4RCX.js.map +0 -1
- package/dist/chunk-TRNSXR46.js.map +0 -1
- package/dist/server-IDDX7KGT.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
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
renderLinkReport,
|
|
17
17
|
sanitizeAttempts,
|
|
18
18
|
sanitizeHarvestResult
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QLQYNE7E.js";
|
|
20
20
|
import {
|
|
21
21
|
csvRecords,
|
|
22
22
|
listWorkflowDefinitions,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
workflowStepCount,
|
|
30
30
|
workflowSupportsSteps
|
|
31
31
|
} from "./chunk-NNEIXK5L.js";
|
|
32
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-I5EFBTYE.js";
|
|
33
33
|
import {
|
|
34
34
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
35
35
|
CONCURRENCY_PRICE_ID,
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
MC_PER_CREDIT,
|
|
41
41
|
MEMORY_PLANS,
|
|
42
42
|
MEMORY_PLAN_QUOTA,
|
|
43
|
+
SCHEDULING_PLANS,
|
|
43
44
|
SUBSCRIPTION_TIERS,
|
|
44
45
|
SUBSCRIPTION_TIER_BY_KEY,
|
|
45
46
|
browserActiveCostMc,
|
|
@@ -49,7 +50,7 @@ import {
|
|
|
49
50
|
harvestProblemResponse,
|
|
50
51
|
insufficientBalanceResponse,
|
|
51
52
|
serializeHarvestProblem
|
|
52
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-KJWNVQFL.js";
|
|
53
54
|
import {
|
|
54
55
|
BrowserDriver,
|
|
55
56
|
MapsSelectors,
|
|
@@ -62,7 +63,7 @@ import {
|
|
|
62
63
|
recordVendorUsage,
|
|
63
64
|
resolveKernelProxyId,
|
|
64
65
|
runWithCostContext
|
|
65
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-GMTS35L6.js";
|
|
66
67
|
import {
|
|
67
68
|
CaptchaError,
|
|
68
69
|
RECAPTCHA_INSTRUCTIONS,
|
|
@@ -158,7 +159,7 @@ import {
|
|
|
158
159
|
setSubscriptionTier,
|
|
159
160
|
setUserMemoryCreds,
|
|
160
161
|
verifyPassword
|
|
161
|
-
} from "./chunk-
|
|
162
|
+
} from "./chunk-DVRPXPYH.js";
|
|
162
163
|
|
|
163
164
|
// src/blog/registry.ts
|
|
164
165
|
var posts = [
|
|
@@ -5256,7 +5257,7 @@ async function extractSite(opts) {
|
|
|
5256
5257
|
}
|
|
5257
5258
|
|
|
5258
5259
|
// src/api/server.ts
|
|
5259
|
-
import { Hono as
|
|
5260
|
+
import { Hono as Hono18 } from "hono";
|
|
5260
5261
|
import { serve as serveInngest } from "inngest/hono";
|
|
5261
5262
|
|
|
5262
5263
|
// src/inngest/client.ts
|
|
@@ -7805,6 +7806,49 @@ var CATALOG = {
|
|
|
7805
7806
|
}
|
|
7806
7807
|
]
|
|
7807
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
|
+
},
|
|
7808
7852
|
{
|
|
7809
7853
|
key: "instagram",
|
|
7810
7854
|
label: "Instagram",
|
|
@@ -10790,9 +10834,330 @@ facebookAdApp.post("/media", createApiKeyAuth(), async (c) => {
|
|
|
10790
10834
|
});
|
|
10791
10835
|
});
|
|
10792
10836
|
|
|
10793
|
-
// src/api/
|
|
10837
|
+
// src/api/google-ads-routes.ts
|
|
10794
10838
|
import { Hono as Hono5 } from "hono";
|
|
10795
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";
|
|
10796
11161
|
import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync3, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
10797
11162
|
import { homedir as homedir3 } from "os";
|
|
10798
11163
|
import { join as join5 } from "path";
|
|
@@ -11156,33 +11521,33 @@ async function extractInstagramMediaFromPage(page, sourceUrl, capturedMediaUrls
|
|
|
11156
11521
|
}
|
|
11157
11522
|
|
|
11158
11523
|
// src/api/instagram-routes.ts
|
|
11159
|
-
var InstagramProfileContentBodySchema =
|
|
11160
|
-
handle:
|
|
11161
|
-
url:
|
|
11162
|
-
profile:
|
|
11163
|
-
saveProfileChanges:
|
|
11164
|
-
save_profile_changes:
|
|
11165
|
-
maxItems:
|
|
11166
|
-
maxScrolls:
|
|
11167
|
-
scrollDelayMs:
|
|
11168
|
-
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)
|
|
11169
11534
|
}).refine((d) => !!d.handle || !!d.url, {
|
|
11170
11535
|
message: "handle or url is required"
|
|
11171
11536
|
});
|
|
11172
|
-
var InstagramMediaTypeSchema =
|
|
11173
|
-
var InstagramMediaDownloadBodySchema =
|
|
11174
|
-
url:
|
|
11175
|
-
profile:
|
|
11176
|
-
saveProfileChanges:
|
|
11177
|
-
save_profile_changes:
|
|
11178
|
-
mediaTypes:
|
|
11179
|
-
downloadMedia:
|
|
11180
|
-
downloadAllTracks:
|
|
11181
|
-
includeTranscript:
|
|
11182
|
-
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)
|
|
11183
11548
|
});
|
|
11184
|
-
var instagramApp = new
|
|
11185
|
-
function
|
|
11549
|
+
var instagramApp = new Hono6();
|
|
11550
|
+
function invalidRequest3(message) {
|
|
11186
11551
|
return { error_code: "invalid_request", message };
|
|
11187
11552
|
}
|
|
11188
11553
|
function resolveSaveProfileChanges(body) {
|
|
@@ -11192,14 +11557,14 @@ function resolveProfileName(body) {
|
|
|
11192
11557
|
const explicit = body.profile?.trim();
|
|
11193
11558
|
return explicit || browserServiceProfileName();
|
|
11194
11559
|
}
|
|
11195
|
-
function
|
|
11560
|
+
function configuredKernelProxy3() {
|
|
11196
11561
|
return (process.env.BROWSER_SERVICE_PROXY_ID ?? process.env.KERNEL_PROXY_ID)?.trim() || void 0;
|
|
11197
11562
|
}
|
|
11198
11563
|
async function kernelLaunchOptsDirect(profileName, saveProfileChanges) {
|
|
11199
11564
|
return {
|
|
11200
11565
|
headless: true,
|
|
11201
11566
|
kernelApiKey: browserServiceApiKey(),
|
|
11202
|
-
kernelProxyId:
|
|
11567
|
+
kernelProxyId: configuredKernelProxy3(),
|
|
11203
11568
|
...profileName ? { kernelProfileName: profileName } : {},
|
|
11204
11569
|
...typeof saveProfileChanges === "boolean" ? { kernelProfileSaveChanges: saveProfileChanges } : {},
|
|
11205
11570
|
viewport: { width: 1280, height: 900 },
|
|
@@ -11285,13 +11650,13 @@ function trackFilename(track, index, shortcode) {
|
|
|
11285
11650
|
instagramApp.post("/profile-content", createApiKeyAuth(), async (c) => {
|
|
11286
11651
|
const raw = await c.req.json().catch(() => ({}));
|
|
11287
11652
|
const parsed = InstagramProfileContentBodySchema.safeParse(raw);
|
|
11288
|
-
if (!parsed.success) return c.json(
|
|
11653
|
+
if (!parsed.success) return c.json(invalidRequest3(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11289
11654
|
const body = parsed.data;
|
|
11290
11655
|
let target;
|
|
11291
11656
|
try {
|
|
11292
11657
|
target = normalizeInstagramProfileInput(body.url ?? body.handle ?? "");
|
|
11293
11658
|
} catch (err) {
|
|
11294
|
-
return c.json(
|
|
11659
|
+
return c.json(invalidRequest3(err instanceof Error ? err.message : String(err)), 400);
|
|
11295
11660
|
}
|
|
11296
11661
|
const user = c.get("user");
|
|
11297
11662
|
const gate = await acquireConcurrencyGate(user, "instagram_profile_content", {
|
|
@@ -11333,12 +11698,12 @@ instagramApp.post("/profile-content", createApiKeyAuth(), async (c) => {
|
|
|
11333
11698
|
instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
|
|
11334
11699
|
const raw = await c.req.json().catch(() => ({}));
|
|
11335
11700
|
const parsed = InstagramMediaDownloadBodySchema.safeParse(raw);
|
|
11336
|
-
if (!parsed.success) return c.json(
|
|
11701
|
+
if (!parsed.success) return c.json(invalidRequest3(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11337
11702
|
const body = parsed.data;
|
|
11338
11703
|
const urlCheck = await validatePublicHttpUrl(body.url, { field: "url", requireHttps: false });
|
|
11339
|
-
if (urlCheck.error) return c.json(
|
|
11704
|
+
if (urlCheck.error) return c.json(invalidRequest3(urlCheck.error), 400);
|
|
11340
11705
|
const sourceUrl = urlCheck.parsed;
|
|
11341
|
-
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);
|
|
11342
11707
|
const user = c.get("user");
|
|
11343
11708
|
const gate = await acquireConcurrencyGate(user, "instagram_media_download", {
|
|
11344
11709
|
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
@@ -11455,13 +11820,13 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
|
|
|
11455
11820
|
});
|
|
11456
11821
|
|
|
11457
11822
|
// src/api/reddit-routes.ts
|
|
11458
|
-
import { Hono as
|
|
11459
|
-
import { z as
|
|
11460
|
-
var RedditThreadBodySchema =
|
|
11461
|
-
url:
|
|
11462
|
-
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()
|
|
11463
11828
|
});
|
|
11464
|
-
function
|
|
11829
|
+
function invalidRequest4(message) {
|
|
11465
11830
|
return { error_code: "invalid_request", message };
|
|
11466
11831
|
}
|
|
11467
11832
|
function toOldReddit(url) {
|
|
@@ -11515,14 +11880,14 @@ var PARSE_REDDIT = `(() => {
|
|
|
11515
11880
|
});
|
|
11516
11881
|
return { title: title, author: author, score: score, postBody: postBody, blocked: blocked, numComments: comments.length, comments: comments };
|
|
11517
11882
|
})()`;
|
|
11518
|
-
var redditApp = new
|
|
11883
|
+
var redditApp = new Hono7();
|
|
11519
11884
|
redditApp.post("/thread", createApiKeyAuth(), async (c) => {
|
|
11520
11885
|
const raw = await c.req.json().catch(() => ({}));
|
|
11521
11886
|
const parsed = RedditThreadBodySchema.safeParse(raw);
|
|
11522
|
-
if (!parsed.success) return c.json(
|
|
11887
|
+
if (!parsed.success) return c.json(invalidRequest4(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11523
11888
|
const body = parsed.data;
|
|
11524
11889
|
const oldUrl = toOldReddit(body.url);
|
|
11525
|
-
if (!oldUrl) return c.json(
|
|
11890
|
+
if (!oldUrl) return c.json(invalidRequest4("url must be a reddit.com link"), 400);
|
|
11526
11891
|
const user = c.get("user");
|
|
11527
11892
|
const gate = await acquireConcurrencyGate(user, "reddit_thread", {
|
|
11528
11893
|
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
@@ -11583,7 +11948,7 @@ redditApp.post("/thread", createApiKeyAuth(), async (c) => {
|
|
|
11583
11948
|
});
|
|
11584
11949
|
|
|
11585
11950
|
// src/api/maps-routes.ts
|
|
11586
|
-
import { Hono as
|
|
11951
|
+
import { Hono as Hono8 } from "hono";
|
|
11587
11952
|
|
|
11588
11953
|
// src/extractor/MapsNavigator.ts
|
|
11589
11954
|
var MapsNavigator = class {
|
|
@@ -11758,7 +12123,7 @@ var MapsReviewCollector = class {
|
|
|
11758
12123
|
};
|
|
11759
12124
|
|
|
11760
12125
|
// src/extractor/MapsExtractor.ts
|
|
11761
|
-
import { z as
|
|
12126
|
+
import { z as z17 } from "zod";
|
|
11762
12127
|
var MapsExtractor = class {
|
|
11763
12128
|
constructor(driver) {
|
|
11764
12129
|
this.driver = driver;
|
|
@@ -11944,7 +12309,7 @@ var MapsExtractor = class {
|
|
|
11944
12309
|
});
|
|
11945
12310
|
return rows;
|
|
11946
12311
|
}, { hoursTable: MapsSelectors.hoursTable, hoursTableAlt: MapsSelectors.hoursTableAlt });
|
|
11947
|
-
const result =
|
|
12312
|
+
const result = z17.array(RawMapsHoursRowSchema).safeParse(raw);
|
|
11948
12313
|
if (!result.success) {
|
|
11949
12314
|
console.warn("[MapsExtractor] hours parse failed", result.error.flatten());
|
|
11950
12315
|
return [];
|
|
@@ -12008,7 +12373,7 @@ var MapsExtractor = class {
|
|
|
12008
12373
|
});
|
|
12009
12374
|
return results;
|
|
12010
12375
|
});
|
|
12011
|
-
const result =
|
|
12376
|
+
const result = z17.array(RawMapsAboutAttributeSchema).safeParse(raw);
|
|
12012
12377
|
if (!result.success) {
|
|
12013
12378
|
console.warn("[MapsExtractor] about parse failed", result.error.flatten());
|
|
12014
12379
|
return [];
|
|
@@ -12384,7 +12749,7 @@ function mapsErrorResponse(c, err, errorCode) {
|
|
|
12384
12749
|
attempts: rotationError?.attempts ?? void 0
|
|
12385
12750
|
}, retryable ? 503 : 500);
|
|
12386
12751
|
}
|
|
12387
|
-
var mapsApp = new
|
|
12752
|
+
var mapsApp = new Hono8();
|
|
12388
12753
|
mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
12389
12754
|
const user = c.get("user");
|
|
12390
12755
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -12522,12 +12887,12 @@ mapsApp.post("/place", createApiKeyAuth(), async (c) => {
|
|
|
12522
12887
|
});
|
|
12523
12888
|
|
|
12524
12889
|
// src/api/directory-routes.ts
|
|
12525
|
-
import { Hono as
|
|
12890
|
+
import { Hono as Hono9 } from "hono";
|
|
12526
12891
|
|
|
12527
12892
|
// src/directory/directory-workflow.ts
|
|
12528
12893
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
12529
12894
|
import { join as join6 } from "path";
|
|
12530
|
-
import { z as
|
|
12895
|
+
import { z as z18 } from "zod";
|
|
12531
12896
|
|
|
12532
12897
|
// src/directory/location-db.ts
|
|
12533
12898
|
import { access, readFile } from "fs/promises";
|
|
@@ -12688,24 +13053,24 @@ async function resolveDirectoryMarkets(options) {
|
|
|
12688
13053
|
}
|
|
12689
13054
|
|
|
12690
13055
|
// src/directory/directory-workflow.ts
|
|
12691
|
-
var DirectoryWorkflowOptionsSchema =
|
|
12692
|
-
query:
|
|
12693
|
-
state:
|
|
12694
|
-
minPopulation:
|
|
12695
|
-
populationYear:
|
|
12696
|
-
maxCities:
|
|
12697
|
-
maxResultsPerCity:
|
|
12698
|
-
concurrency:
|
|
12699
|
-
includeZipGroups:
|
|
12700
|
-
usZipsCsvPath:
|
|
12701
|
-
saveCsv:
|
|
12702
|
-
gl:
|
|
12703
|
-
hl:
|
|
12704
|
-
proxyMode:
|
|
12705
|
-
proxyZip:
|
|
12706
|
-
debug:
|
|
12707
|
-
headless:
|
|
12708
|
-
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()
|
|
12709
13074
|
});
|
|
12710
13075
|
function errorMessage(err) {
|
|
12711
13076
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -12926,7 +13291,7 @@ async function runDirectoryWorkflowFromPlan(options, plan) {
|
|
|
12926
13291
|
}
|
|
12927
13292
|
|
|
12928
13293
|
// src/api/directory-routes.ts
|
|
12929
|
-
var directoryApp = new
|
|
13294
|
+
var directoryApp = new Hono9();
|
|
12930
13295
|
directoryApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
12931
13296
|
const user = c.get("user");
|
|
12932
13297
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -12999,35 +13364,35 @@ directoryApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
|
12999
13364
|
// src/api/workflow-routes.ts
|
|
13000
13365
|
import { createHmac } from "crypto";
|
|
13001
13366
|
import { readFile as readFile2 } from "fs/promises";
|
|
13002
|
-
import { Hono as
|
|
13003
|
-
import { z as
|
|
13004
|
-
var workflowApp = new
|
|
13005
|
-
var WorkflowInputSchema =
|
|
13006
|
-
var WorkflowIdSchema =
|
|
13007
|
-
var CadenceSchema =
|
|
13008
|
-
var ScheduleStatusSchema =
|
|
13009
|
-
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({
|
|
13010
13375
|
workflowId: WorkflowIdSchema,
|
|
13011
13376
|
input: WorkflowInputSchema,
|
|
13012
|
-
webhookUrl:
|
|
13377
|
+
webhookUrl: z19.string().url().optional()
|
|
13013
13378
|
});
|
|
13014
|
-
var ScheduleCreateSchema =
|
|
13379
|
+
var ScheduleCreateSchema = z19.object({
|
|
13015
13380
|
workflowId: WorkflowIdSchema,
|
|
13016
|
-
name:
|
|
13381
|
+
name: z19.string().min(1).max(120).optional(),
|
|
13017
13382
|
input: WorkflowInputSchema,
|
|
13018
13383
|
cadence: CadenceSchema.default("weekly"),
|
|
13019
|
-
timezone:
|
|
13020
|
-
webhookUrl:
|
|
13021
|
-
nextRunAt:
|
|
13384
|
+
timezone: z19.string().min(1).max(64).default("UTC"),
|
|
13385
|
+
webhookUrl: z19.string().url().optional(),
|
|
13386
|
+
nextRunAt: z19.string().datetime().optional()
|
|
13022
13387
|
});
|
|
13023
|
-
var SchedulePatchSchema =
|
|
13024
|
-
name:
|
|
13388
|
+
var SchedulePatchSchema = z19.object({
|
|
13389
|
+
name: z19.string().min(1).max(120).optional(),
|
|
13025
13390
|
status: ScheduleStatusSchema.optional(),
|
|
13026
13391
|
input: WorkflowInputSchema.optional(),
|
|
13027
13392
|
cadence: CadenceSchema.optional(),
|
|
13028
|
-
timezone:
|
|
13029
|
-
webhookUrl:
|
|
13030
|
-
nextRunAt:
|
|
13393
|
+
timezone: z19.string().min(1).max(64).optional(),
|
|
13394
|
+
webhookUrl: z19.string().url().nullable().optional(),
|
|
13395
|
+
nextRunAt: z19.string().datetime().nullable().optional()
|
|
13031
13396
|
});
|
|
13032
13397
|
function hostedWorkflowOutputDir() {
|
|
13033
13398
|
return workflowOutputBaseDir(process.env.MCP_SCRAPER_WORKFLOW_OUTPUT_DIR?.trim() || "/tmp/mcp-scraper-workflows");
|
|
@@ -13531,7 +13896,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
|
|
|
13531
13896
|
});
|
|
13532
13897
|
|
|
13533
13898
|
// src/api/serp-intelligence-routes.ts
|
|
13534
|
-
import { Hono as
|
|
13899
|
+
import { Hono as Hono11 } from "hono";
|
|
13535
13900
|
|
|
13536
13901
|
// src/serp-intelligence/page-snapshot-extractor.ts
|
|
13537
13902
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -13843,7 +14208,7 @@ async function capturePageSnapshots(targets, options = {}) {
|
|
|
13843
14208
|
}
|
|
13844
14209
|
|
|
13845
14210
|
// src/serp-intelligence/schemas.ts
|
|
13846
|
-
import { z as
|
|
14211
|
+
import { z as z20 } from "zod";
|
|
13847
14212
|
var SerpIntelligenceDeviceValues = ["desktop", "mobile"];
|
|
13848
14213
|
var SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
|
|
13849
14214
|
var SerpIntelligenceAttemptOutcomeValues = [
|
|
@@ -13905,171 +14270,171 @@ function isPublicHttpUrl(value) {
|
|
|
13905
14270
|
return false;
|
|
13906
14271
|
}
|
|
13907
14272
|
}
|
|
13908
|
-
var SerpIntelligencePublicHttpUrlSchema =
|
|
13909
|
-
var SerpIntelligenceCaptureBodySchema =
|
|
13910
|
-
query:
|
|
13911
|
-
location:
|
|
13912
|
-
gl:
|
|
13913
|
-
hl:
|
|
13914
|
-
device:
|
|
13915
|
-
proxyMode:
|
|
13916
|
-
proxyZip:
|
|
13917
|
-
pages:
|
|
13918
|
-
debug:
|
|
13919
|
-
includePageSnapshots:
|
|
13920
|
-
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)
|
|
13921
14286
|
}).strict();
|
|
13922
|
-
var SerpIntelligencePageSnapshotRequestSchema =
|
|
14287
|
+
var SerpIntelligencePageSnapshotRequestSchema = z20.object({
|
|
13923
14288
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
13924
|
-
sourceKind:
|
|
13925
|
-
sourcePosition:
|
|
14289
|
+
sourceKind: z20.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
|
|
14290
|
+
sourcePosition: z20.number().int().min(1).optional()
|
|
13926
14291
|
}).strict();
|
|
13927
|
-
var SerpIntelligencePageSnapshotsBodySchema =
|
|
13928
|
-
urls:
|
|
13929
|
-
targets:
|
|
13930
|
-
maxConcurrency:
|
|
13931
|
-
timeoutMs:
|
|
13932
|
-
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)
|
|
13933
14298
|
}).strict();
|
|
13934
|
-
var SerpIntelligenceAICitationSchema =
|
|
13935
|
-
text:
|
|
13936
|
-
href:
|
|
14299
|
+
var SerpIntelligenceAICitationSchema = z20.object({
|
|
14300
|
+
text: z20.string(),
|
|
14301
|
+
href: z20.string()
|
|
13937
14302
|
}).strict();
|
|
13938
|
-
var SerpIntelligenceOrganicResultSchema =
|
|
13939
|
-
position:
|
|
13940
|
-
title:
|
|
13941
|
-
url:
|
|
13942
|
-
domain:
|
|
13943
|
-
cite:
|
|
13944
|
-
snippet:
|
|
13945
|
-
isRedditStyle:
|
|
13946
|
-
inlineRating:
|
|
13947
|
-
value:
|
|
13948
|
-
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()
|
|
13949
14314
|
}).strict().nullable()
|
|
13950
14315
|
}).strict();
|
|
13951
|
-
var SerpIntelligenceLocationEvidenceSchema =
|
|
13952
|
-
status:
|
|
13953
|
-
expected:
|
|
13954
|
-
city:
|
|
13955
|
-
regionCode:
|
|
13956
|
-
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()
|
|
13957
14322
|
}).strict().nullable(),
|
|
13958
|
-
candidates:
|
|
13959
|
-
city:
|
|
13960
|
-
regionCode:
|
|
13961
|
-
count:
|
|
13962
|
-
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())
|
|
13963
14328
|
}).strict())
|
|
13964
14329
|
}).strict();
|
|
13965
|
-
var SerpIntelligenceHarvestResultSchema =
|
|
13966
|
-
seed:
|
|
13967
|
-
location:
|
|
13968
|
-
extractedAt:
|
|
13969
|
-
totalQuestions:
|
|
13970
|
-
surface:
|
|
13971
|
-
aiOverview:
|
|
13972
|
-
detected:
|
|
13973
|
-
text:
|
|
13974
|
-
citations:
|
|
13975
|
-
expanded:
|
|
13976
|
-
fullyExpanded:
|
|
13977
|
-
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()
|
|
13978
14343
|
}).strict(),
|
|
13979
|
-
aiMode:
|
|
13980
|
-
detected:
|
|
13981
|
-
text:
|
|
13982
|
-
citations:
|
|
14344
|
+
aiMode: z20.object({
|
|
14345
|
+
detected: z20.boolean(),
|
|
14346
|
+
text: z20.string().nullable(),
|
|
14347
|
+
citations: z20.array(SerpIntelligenceAICitationSchema)
|
|
13983
14348
|
}).strict(),
|
|
13984
|
-
tree:
|
|
13985
|
-
flat:
|
|
13986
|
-
videos:
|
|
13987
|
-
forums:
|
|
13988
|
-
organicResults:
|
|
13989
|
-
localPack:
|
|
13990
|
-
entityIds:
|
|
13991
|
-
entities:
|
|
13992
|
-
name:
|
|
13993
|
-
kgId:
|
|
13994
|
-
cid:
|
|
13995
|
-
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()
|
|
13996
14361
|
}).strict()),
|
|
13997
|
-
kgIds:
|
|
13998
|
-
cids:
|
|
13999
|
-
gcids:
|
|
14362
|
+
kgIds: z20.array(z20.string()),
|
|
14363
|
+
cids: z20.array(z20.string()),
|
|
14364
|
+
gcids: z20.array(z20.string())
|
|
14000
14365
|
}).strict(),
|
|
14001
|
-
stats:
|
|
14002
|
-
seed:
|
|
14003
|
-
totalQuestions:
|
|
14004
|
-
maxDepthReached:
|
|
14005
|
-
durationMs:
|
|
14006
|
-
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)
|
|
14007
14372
|
}).strict(),
|
|
14008
|
-
diagnostics:
|
|
14009
|
-
completionStatus:
|
|
14010
|
-
problem:
|
|
14011
|
-
warnings:
|
|
14012
|
-
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({
|
|
14013
14378
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
|
|
14014
14379
|
}).passthrough().optional()
|
|
14015
14380
|
}).passthrough(),
|
|
14016
|
-
whatPeopleSaying:
|
|
14381
|
+
whatPeopleSaying: z20.array(z20.unknown())
|
|
14017
14382
|
}).strict();
|
|
14018
|
-
var SerpIntelligenceCaptureAttemptSchema =
|
|
14019
|
-
attemptNumber:
|
|
14020
|
-
outcome:
|
|
14021
|
-
startedAt:
|
|
14022
|
-
completedAt:
|
|
14023
|
-
durationMs:
|
|
14024
|
-
problemCode:
|
|
14025
|
-
message:
|
|
14026
|
-
kernelSessionId:
|
|
14027
|
-
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()
|
|
14028
14393
|
}).strict();
|
|
14029
|
-
var SerpPageSnapshotCaptureSchema =
|
|
14394
|
+
var SerpPageSnapshotCaptureSchema = z20.object({
|
|
14030
14395
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
14031
14396
|
requestedUrl: SerpIntelligencePublicHttpUrlSchema,
|
|
14032
14397
|
finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
14033
|
-
sourceKind:
|
|
14034
|
-
sourcePosition:
|
|
14035
|
-
status:
|
|
14036
|
-
fetchedVia:
|
|
14037
|
-
httpStatus:
|
|
14038
|
-
contentType:
|
|
14039
|
-
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(),
|
|
14040
14405
|
canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
14041
|
-
metaDescription:
|
|
14042
|
-
headings:
|
|
14043
|
-
level:
|
|
14044
|
-
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()
|
|
14045
14410
|
}).strict()).default([]),
|
|
14046
|
-
artifact:
|
|
14047
|
-
htmlBlobUrl:
|
|
14048
|
-
textBlobUrl:
|
|
14049
|
-
markdownBlobUrl:
|
|
14050
|
-
screenshotBlobUrl:
|
|
14051
|
-
contentSha256:
|
|
14052
|
-
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()
|
|
14053
14418
|
}).strict(),
|
|
14054
|
-
error:
|
|
14055
|
-
code:
|
|
14056
|
-
message:
|
|
14419
|
+
error: z20.object({
|
|
14420
|
+
code: z20.string(),
|
|
14421
|
+
message: z20.string()
|
|
14057
14422
|
}).strict().nullable()
|
|
14058
14423
|
}).strict();
|
|
14059
|
-
var SerpIntelligenceCaptureResponseSchema =
|
|
14424
|
+
var SerpIntelligenceCaptureResponseSchema = z20.object({
|
|
14060
14425
|
harvestResult: SerpIntelligenceHarvestResultSchema,
|
|
14061
|
-
attempts:
|
|
14426
|
+
attempts: z20.array(SerpIntelligenceCaptureAttemptSchema),
|
|
14062
14427
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
|
|
14063
|
-
pageSnapshotArtifacts:
|
|
14064
|
-
billing:
|
|
14065
|
-
creditsUsed:
|
|
14066
|
-
requestId:
|
|
14067
|
-
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()
|
|
14068
14433
|
}).strict().optional()
|
|
14069
14434
|
}).strict();
|
|
14070
|
-
var SerpIntelligencePageSnapshotsResponseSchema =
|
|
14071
|
-
pageSnapshotArtifacts:
|
|
14072
|
-
attempts:
|
|
14435
|
+
var SerpIntelligencePageSnapshotsResponseSchema = z20.object({
|
|
14436
|
+
pageSnapshotArtifacts: z20.array(SerpPageSnapshotCaptureSchema),
|
|
14437
|
+
attempts: z20.array(SerpIntelligenceCaptureAttemptSchema).default([])
|
|
14073
14438
|
}).strict();
|
|
14074
14439
|
|
|
14075
14440
|
// src/serp-intelligence/serp-capture-service.ts
|
|
@@ -14241,7 +14606,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
|
|
|
14241
14606
|
var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
|
|
14242
14607
|
var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
|
|
14243
14608
|
var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
|
|
14244
|
-
var serpIntelligenceApp = new
|
|
14609
|
+
var serpIntelligenceApp = new Hono11();
|
|
14245
14610
|
serpIntelligenceApp.use("*", createApiKeyAuth());
|
|
14246
14611
|
function structuredError(input) {
|
|
14247
14612
|
return {
|
|
@@ -14410,7 +14775,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
|
|
|
14410
14775
|
});
|
|
14411
14776
|
|
|
14412
14777
|
// src/mcp/mcp-routes.ts
|
|
14413
|
-
import { Hono as
|
|
14778
|
+
import { Hono as Hono12 } from "hono";
|
|
14414
14779
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
14415
14780
|
|
|
14416
14781
|
// src/mcp/mcp-oauth.ts
|
|
@@ -14494,7 +14859,7 @@ async function requireMcpCallerKey(c) {
|
|
|
14494
14859
|
if (!user) return mcpAuthError();
|
|
14495
14860
|
return callerKey;
|
|
14496
14861
|
}
|
|
14497
|
-
var mcpApp = new
|
|
14862
|
+
var mcpApp = new Hono12();
|
|
14498
14863
|
mcpApp.all("/", async (c) => {
|
|
14499
14864
|
try {
|
|
14500
14865
|
const keyOrError = await requireMcpCallerKey(c);
|
|
@@ -14518,7 +14883,7 @@ mcpApp.all("/", async (c) => {
|
|
|
14518
14883
|
});
|
|
14519
14884
|
|
|
14520
14885
|
// src/api/browser-agent-routes.ts
|
|
14521
|
-
import { Hono as
|
|
14886
|
+
import { Hono as Hono13 } from "hono";
|
|
14522
14887
|
|
|
14523
14888
|
// src/api/browser-agent-db.ts
|
|
14524
14889
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -15952,7 +16317,7 @@ async function loadOpenSession(id, userId) {
|
|
|
15952
16317
|
return row;
|
|
15953
16318
|
}
|
|
15954
16319
|
function buildBrowserAgentRoutes() {
|
|
15955
|
-
const app2 = new
|
|
16320
|
+
const app2 = new Hono13();
|
|
15956
16321
|
app2.use("*", async (c, next) => {
|
|
15957
16322
|
await migrateBrowserAgent();
|
|
15958
16323
|
return next();
|
|
@@ -16658,7 +17023,7 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
|
|
|
16658
17023
|
|
|
16659
17024
|
// src/api/stripe-routes.ts
|
|
16660
17025
|
import Stripe from "stripe";
|
|
16661
|
-
import { Hono as
|
|
17026
|
+
import { Hono as Hono14 } from "hono";
|
|
16662
17027
|
|
|
16663
17028
|
// src/api/memory.ts
|
|
16664
17029
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
@@ -16797,10 +17162,22 @@ async function syncMemoryKeyPlan(user, plan) {
|
|
|
16797
17162
|
const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
|
|
16798
17163
|
return { ok: res.ok, error: res.error };
|
|
16799
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
|
+
}
|
|
16800
17177
|
|
|
16801
17178
|
// src/api/stripe-routes.ts
|
|
16802
17179
|
var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
16803
|
-
var stripeApp = new
|
|
17180
|
+
var stripeApp = new Hono14();
|
|
16804
17181
|
stripeApp.post("/webhooks", async (c) => {
|
|
16805
17182
|
const sig = c.req.header("stripe-signature");
|
|
16806
17183
|
const body = await c.req.text();
|
|
@@ -16839,6 +17216,15 @@ stripeApp.post("/webhooks", async (c) => {
|
|
|
16839
17216
|
}
|
|
16840
17217
|
return c.json({ received: true });
|
|
16841
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
|
+
}
|
|
16842
17228
|
const user = await getUserByStripeCustomerId(sub.customer);
|
|
16843
17229
|
if (!user) return c.json({ received: true });
|
|
16844
17230
|
if (priceId && priceId in SUBSCRIPTION_TIERS) {
|
|
@@ -16861,6 +17247,11 @@ stripeApp.post("/webhooks", async (c) => {
|
|
|
16861
17247
|
}
|
|
16862
17248
|
return c.json({ received: true });
|
|
16863
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
|
+
}
|
|
16864
17255
|
const user = await getUserByStripeCustomerId(sub.customer);
|
|
16865
17256
|
if (!user) return c.json({ received: true });
|
|
16866
17257
|
if (priceId && priceId in SUBSCRIPTION_TIERS) {
|
|
@@ -16886,7 +17277,7 @@ async function resolveUser(customerId, emailFallback) {
|
|
|
16886
17277
|
}
|
|
16887
17278
|
|
|
16888
17279
|
// src/api/oauth-routes.ts
|
|
16889
|
-
import { Hono as
|
|
17280
|
+
import { Hono as Hono15 } from "hono";
|
|
16890
17281
|
import { getCookie, setCookie } from "hono/cookie";
|
|
16891
17282
|
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
16892
17283
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
@@ -16938,6 +17329,9 @@ function isAllowedRedirect(uri) {
|
|
|
16938
17329
|
const h = u.hostname.toLowerCase();
|
|
16939
17330
|
if (["claude.ai", "www.claude.ai", "claude.com", "www.claude.com"].includes(h)) return true;
|
|
16940
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;
|
|
16941
17335
|
const extra = (process.env.OAUTH_EXTRA_REDIRECT_HOSTS ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
16942
17336
|
return extra.includes(h);
|
|
16943
17337
|
}
|
|
@@ -17050,7 +17444,7 @@ function redirectWithError(redirectUri, state, error) {
|
|
|
17050
17444
|
if (state) u.searchParams.set("state", state);
|
|
17051
17445
|
return new Response(null, { status: 302, headers: { Location: u.toString() } });
|
|
17052
17446
|
}
|
|
17053
|
-
var oauthApp = new
|
|
17447
|
+
var oauthApp = new Hono15();
|
|
17054
17448
|
oauthApp.use("*", async (c, next) => {
|
|
17055
17449
|
c.header("Access-Control-Allow-Origin", "*");
|
|
17056
17450
|
c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -17058,42 +17452,51 @@ oauthApp.use("*", async (c, next) => {
|
|
|
17058
17452
|
if (c.req.method === "OPTIONS") return c.body(null, 204);
|
|
17059
17453
|
return next();
|
|
17060
17454
|
});
|
|
17061
|
-
function authServerMetadata() {
|
|
17455
|
+
function authServerMetadata(scopes = SUPPORTED_SCOPES) {
|
|
17062
17456
|
return {
|
|
17063
17457
|
issuer: ISSUER,
|
|
17064
17458
|
authorization_endpoint: `${ISSUER}/oauth/authorize`,
|
|
17065
17459
|
token_endpoint: `${ISSUER}/oauth/token`,
|
|
17066
17460
|
registration_endpoint: `${ISSUER}/oauth/register`,
|
|
17067
17461
|
jwks_uri: `${ISSUER}/.well-known/jwks.json`,
|
|
17068
|
-
scopes_supported: [...
|
|
17462
|
+
scopes_supported: [...scopes],
|
|
17069
17463
|
response_types_supported: ["code"],
|
|
17070
17464
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
17071
17465
|
code_challenge_methods_supported: ["S256"],
|
|
17072
17466
|
token_endpoint_auth_methods_supported: ["none"]
|
|
17073
17467
|
};
|
|
17074
17468
|
}
|
|
17075
|
-
function openidConfiguration() {
|
|
17469
|
+
function openidConfiguration(scopes = SUPPORTED_SCOPES) {
|
|
17076
17470
|
return {
|
|
17077
|
-
...authServerMetadata(),
|
|
17471
|
+
...authServerMetadata(scopes),
|
|
17078
17472
|
subject_types_supported: ["public"],
|
|
17079
17473
|
id_token_signing_alg_values_supported: ["RS256"]
|
|
17080
17474
|
};
|
|
17081
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
|
+
}
|
|
17082
17485
|
oauthApp.get("/.well-known/oauth-authorization-server", (c) => {
|
|
17083
17486
|
console.log("[oauth-dcr] GET /.well-known/oauth-authorization-server ua=%s", c.req.header("user-agent") ?? "");
|
|
17084
|
-
return c.json(authServerMetadata());
|
|
17487
|
+
return c.json(authServerMetadata(scopesForRequest(c)));
|
|
17085
17488
|
});
|
|
17086
17489
|
oauthApp.get("/.well-known/oauth-authorization-server/*", (c) => {
|
|
17087
17490
|
console.log("[oauth-dcr] GET %s (path-aware AS) ua=%s", c.req.path, c.req.header("user-agent") ?? "");
|
|
17088
|
-
return c.json(authServerMetadata());
|
|
17491
|
+
return c.json(authServerMetadata(scopesForRequest(c)));
|
|
17089
17492
|
});
|
|
17090
17493
|
oauthApp.get("/.well-known/openid-configuration", (c) => {
|
|
17091
17494
|
console.log("[oauth-dcr] GET /.well-known/openid-configuration ua=%s", c.req.header("user-agent") ?? "");
|
|
17092
|
-
return c.json(openidConfiguration());
|
|
17495
|
+
return c.json(openidConfiguration(scopesForRequest(c)));
|
|
17093
17496
|
});
|
|
17094
17497
|
oauthApp.get("/.well-known/openid-configuration/*", (c) => {
|
|
17095
17498
|
console.log("[oauth-dcr] GET %s (path-aware OIDC) ua=%s", c.req.path, c.req.header("user-agent") ?? "");
|
|
17096
|
-
return c.json(openidConfiguration());
|
|
17499
|
+
return c.json(openidConfiguration(scopesForRequest(c)));
|
|
17097
17500
|
});
|
|
17098
17501
|
oauthApp.get("/.well-known/jwks.json", async (c) => {
|
|
17099
17502
|
const { publicJwk } = await getKeys();
|
|
@@ -17297,8 +17700,8 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
17297
17700
|
});
|
|
17298
17701
|
|
|
17299
17702
|
// src/api/chat-routes.ts
|
|
17300
|
-
import { Hono as
|
|
17301
|
-
var chatApp = new
|
|
17703
|
+
import { Hono as Hono16 } from "hono";
|
|
17704
|
+
var chatApp = new Hono16();
|
|
17302
17705
|
function tokenOk(token) {
|
|
17303
17706
|
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
17304
17707
|
}
|
|
@@ -17967,6 +18370,331 @@ loadChannels();
|
|
|
17967
18370
|
</html>`;
|
|
17968
18371
|
}
|
|
17969
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
|
+
|
|
17970
18698
|
// src/api/site-audit-worker.ts
|
|
17971
18699
|
var MAX_CONCURRENT_SITE_AUDIT = 1;
|
|
17972
18700
|
async function drainSiteAuditQueue(budget) {
|
|
@@ -18172,13 +18900,14 @@ function toGb(bytes) {
|
|
|
18172
18900
|
}
|
|
18173
18901
|
async function entitledVaultRows(identity) {
|
|
18174
18902
|
const rows = await sql().query(
|
|
18175
|
-
`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`,
|
|
18176
18904
|
[identity]
|
|
18177
18905
|
);
|
|
18178
18906
|
const seen = /* @__PURE__ */ new Map();
|
|
18179
18907
|
for (const r of rows) {
|
|
18180
18908
|
if (seen.has(r.vault)) continue;
|
|
18181
|
-
|
|
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 });
|
|
18182
18911
|
}
|
|
18183
18912
|
return [...seen.values()];
|
|
18184
18913
|
}
|
|
@@ -18204,7 +18933,8 @@ async function dbListVaults(identity) {
|
|
|
18204
18933
|
role: isOwner ? "owner" : "shared",
|
|
18205
18934
|
...isOwner ? {} : { sharedBy: e.owner },
|
|
18206
18935
|
notes: st?.notes ?? 0,
|
|
18207
|
-
bytes: st?.bytes ?? 0
|
|
18936
|
+
bytes: st?.bytes ?? 0,
|
|
18937
|
+
kind: e.kind
|
|
18208
18938
|
};
|
|
18209
18939
|
});
|
|
18210
18940
|
}
|
|
@@ -18381,7 +19111,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
18381
19111
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
18382
19112
|
return next();
|
|
18383
19113
|
});
|
|
18384
|
-
var app = new
|
|
19114
|
+
var app = new Hono18();
|
|
18385
19115
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
18386
19116
|
function requireStripeSecret() {
|
|
18387
19117
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -18572,6 +19302,7 @@ app.get("/me", async (c) => {
|
|
|
18572
19302
|
const stats = await getUserStats(user.id);
|
|
18573
19303
|
return c.json({
|
|
18574
19304
|
authenticated: true,
|
|
19305
|
+
id: user.id,
|
|
18575
19306
|
email: user.email,
|
|
18576
19307
|
api_key: user.key_active ? user.api_key : null,
|
|
18577
19308
|
created_at: user.created_at,
|
|
@@ -18646,6 +19377,28 @@ app.get("/memory/vaults", auth2, async (c) => {
|
|
|
18646
19377
|
return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load vaults" }, 502);
|
|
18647
19378
|
}
|
|
18648
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
|
+
});
|
|
18649
19402
|
app.get("/memory/connect", auth2, async (c) => {
|
|
18650
19403
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
18651
19404
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
@@ -18809,6 +19562,154 @@ app.post("/memory/portal", auth2, async (c) => {
|
|
|
18809
19562
|
return c.json({ error: message }, 500);
|
|
18810
19563
|
}
|
|
18811
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
|
+
});
|
|
18812
19713
|
app.get("/memory/usage", auth2, async (c) => {
|
|
18813
19714
|
const user = c.get("user");
|
|
18814
19715
|
try {
|
|
@@ -19522,7 +20423,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
19522
20423
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
19523
20424
|
return c.json({ error: "Unauthorized" }, 401);
|
|
19524
20425
|
}
|
|
19525
|
-
const { drainQueue } = await import("./worker-
|
|
20426
|
+
const { drainQueue } = await import("./worker-TL4J65DE.js");
|
|
19526
20427
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
19527
20428
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
19528
20429
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -19539,6 +20440,7 @@ app.route("/api/internal/site-architecture-auditor", siteAuditApp);
|
|
|
19539
20440
|
app.route("/youtube", youtubeApp);
|
|
19540
20441
|
app.route("/screenshot", screenshotApp);
|
|
19541
20442
|
app.route("/facebook", facebookAdApp);
|
|
20443
|
+
app.route("/google-ads", googleAdsApp);
|
|
19542
20444
|
app.route("/instagram", instagramApp);
|
|
19543
20445
|
app.route("/reddit", redditApp);
|
|
19544
20446
|
app.route("/maps", mapsApp);
|
|
@@ -19548,6 +20450,7 @@ app.route("/serp-intelligence", serpIntelligenceApp);
|
|
|
19548
20450
|
app.route("/mcp", mcpApp);
|
|
19549
20451
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
19550
20452
|
app.route("/chat", chatApp);
|
|
20453
|
+
app.route("/schedule", scheduleApp);
|
|
19551
20454
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
19552
20455
|
app.get("/console/auth/:id", async (c) => {
|
|
19553
20456
|
const id = c.req.param("id");
|
|
@@ -19671,4 +20574,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
19671
20574
|
export {
|
|
19672
20575
|
app
|
|
19673
20576
|
};
|
|
19674
|
-
//# sourceMappingURL=server-
|
|
20577
|
+
//# sourceMappingURL=server-HB7WJO3N.js.map
|