mcp-scraper 0.3.28 → 0.3.42
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 +2464 -1134
- 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-NGD4ITAO.js → chunk-KPF64WST.js} +238 -7
- package/dist/chunk-KPF64WST.js.map +1 -0
- package/dist/chunk-OUZZNW3I.js +7 -0
- package/dist/chunk-OUZZNW3I.js.map +1 -0
- package/dist/{chunk-H74L743B.js → chunk-RXJHXBGG.js} +40 -3
- package/dist/chunk-RXJHXBGG.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-CZT7EULY.js} +1157 -246
- package/dist/server-CZT7EULY.js.map +1 -0
- package/dist/{worker-NV4GTIRG.js → worker-FNSBPP7Y.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-FNSBPP7Y.js.map} +0 -0
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
renderLinkReport,
|
|
17
17
|
sanitizeAttempts,
|
|
18
18
|
sanitizeHarvestResult
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-KPF64WST.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-OUZZNW3I.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-RXJHXBGG.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 = [
|
|
@@ -3547,7 +3548,7 @@ async function fetchWithKernel(url) {
|
|
|
3547
3548
|
});
|
|
3548
3549
|
await client2.browsers.deleteByID(kb.session_id).catch(() => {
|
|
3549
3550
|
});
|
|
3550
|
-
void recordKernelSession({ kernelSessionId: kb.session_id, source: "kernel_fetch", stealth: typeof kb.stealth === "boolean" ? kb.stealth : null, headlessSent: useHeadless
|
|
3551
|
+
void recordKernelSession({ kernelSessionId: kb.session_id, source: "kernel_fetch", stealth: typeof kb.stealth === "boolean" ? kb.stealth : null, headlessSent: useHeadless, proxyUsed: typeof kb.proxy_id === "string" ? Boolean(kb.proxy_id) : null, fallback: true, openedAtMs, closedAtMs: Date.now() });
|
|
3551
3552
|
}
|
|
3552
3553
|
}
|
|
3553
3554
|
|
|
@@ -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";
|
|
@@ -16934,10 +17325,14 @@ function isAllowedRedirect(uri) {
|
|
|
16934
17325
|
} catch {
|
|
16935
17326
|
return false;
|
|
16936
17327
|
}
|
|
16937
|
-
if (u.protocol !== "https:") return false;
|
|
16938
17328
|
const h = u.hostname.toLowerCase();
|
|
17329
|
+
if (u.protocol === "http:" && (h === "localhost" || h === "127.0.0.1" || h === "::1")) return true;
|
|
17330
|
+
if (u.protocol !== "https:") return false;
|
|
16939
17331
|
if (["claude.ai", "www.claude.ai", "claude.com", "www.claude.com"].includes(h)) return true;
|
|
16940
17332
|
if (h.endsWith(".claude.ai") || h.endsWith(".claude.com")) return true;
|
|
17333
|
+
if (["chatgpt.com", "www.chatgpt.com", "chat.openai.com", "www.chat.openai.com"].includes(h)) return true;
|
|
17334
|
+
if (h.endsWith(".chatgpt.com") || h.endsWith(".openai.com")) return true;
|
|
17335
|
+
if (h === "viktor.com" || h.endsWith(".viktor.com")) return true;
|
|
16941
17336
|
const extra = (process.env.OAUTH_EXTRA_REDIRECT_HOSTS ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
16942
17337
|
return extra.includes(h);
|
|
16943
17338
|
}
|
|
@@ -17050,7 +17445,7 @@ function redirectWithError(redirectUri, state, error) {
|
|
|
17050
17445
|
if (state) u.searchParams.set("state", state);
|
|
17051
17446
|
return new Response(null, { status: 302, headers: { Location: u.toString() } });
|
|
17052
17447
|
}
|
|
17053
|
-
var oauthApp = new
|
|
17448
|
+
var oauthApp = new Hono15();
|
|
17054
17449
|
oauthApp.use("*", async (c, next) => {
|
|
17055
17450
|
c.header("Access-Control-Allow-Origin", "*");
|
|
17056
17451
|
c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -17297,8 +17692,8 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
17297
17692
|
});
|
|
17298
17693
|
|
|
17299
17694
|
// src/api/chat-routes.ts
|
|
17300
|
-
import { Hono as
|
|
17301
|
-
var chatApp = new
|
|
17695
|
+
import { Hono as Hono16 } from "hono";
|
|
17696
|
+
var chatApp = new Hono16();
|
|
17302
17697
|
function tokenOk(token) {
|
|
17303
17698
|
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
17304
17699
|
}
|
|
@@ -17967,6 +18362,331 @@ loadChannels();
|
|
|
17967
18362
|
</html>`;
|
|
17968
18363
|
}
|
|
17969
18364
|
|
|
18365
|
+
// src/api/schedule-routes.ts
|
|
18366
|
+
import { Hono as Hono17 } from "hono";
|
|
18367
|
+
var scheduleApp = new Hono17();
|
|
18368
|
+
function tokenOk2(token) {
|
|
18369
|
+
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
18370
|
+
}
|
|
18371
|
+
function jsonError2(message, status = 400) {
|
|
18372
|
+
return new Response(JSON.stringify({ ok: false, error: message }), { status, headers: { "content-type": "application/json" } });
|
|
18373
|
+
}
|
|
18374
|
+
scheduleApp.get("/api/status", async (c) => {
|
|
18375
|
+
const token = c.req.query("token");
|
|
18376
|
+
if (!tokenOk2(token)) return jsonError2("missing or malformed token", 401);
|
|
18377
|
+
const r = await memoryCall("getScheduleStatusTool", {}, token);
|
|
18378
|
+
return c.json(r);
|
|
18379
|
+
});
|
|
18380
|
+
scheduleApp.get("/api/vaults", async (c) => {
|
|
18381
|
+
const token = c.req.query("token");
|
|
18382
|
+
if (!tokenOk2(token)) return jsonError2("missing or malformed token", 401);
|
|
18383
|
+
const r = await memoryCall("listVaultsTool", {}, token);
|
|
18384
|
+
if (!r.ok) return c.json(r);
|
|
18385
|
+
const vaults = (r.vaults ?? []).filter((v) => v.kind !== "channel");
|
|
18386
|
+
return c.json({ ok: true, vaults });
|
|
18387
|
+
});
|
|
18388
|
+
scheduleApp.get("/api/actions", 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("listScheduledActionsTool", {}, token);
|
|
18392
|
+
return c.json(r);
|
|
18393
|
+
});
|
|
18394
|
+
scheduleApp.post("/api/propose", async (c) => {
|
|
18395
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18396
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18397
|
+
if (!body.request) return jsonError2("request is required");
|
|
18398
|
+
const r = await memoryCall("proposeScheduledActionTool", { request: body.request }, body.token);
|
|
18399
|
+
return c.json(r);
|
|
18400
|
+
});
|
|
18401
|
+
scheduleApp.post("/api/actions", async (c) => {
|
|
18402
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18403
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18404
|
+
if (!body.description || !body.vault || !body.cadence) return jsonError2("description, vault and cadence are required");
|
|
18405
|
+
const r = await memoryCall("createScheduledActionTool", { description: body.description, vault: body.vault, cadence: body.cadence }, body.token);
|
|
18406
|
+
return c.json(r);
|
|
18407
|
+
});
|
|
18408
|
+
scheduleApp.post("/api/actions/:id/pause", async (c) => {
|
|
18409
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18410
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18411
|
+
const r = await memoryCall("pauseScheduledActionTool", { id: c.req.param("id") }, body.token);
|
|
18412
|
+
return c.json(r);
|
|
18413
|
+
});
|
|
18414
|
+
scheduleApp.post("/api/actions/:id/resume", async (c) => {
|
|
18415
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18416
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18417
|
+
const r = await memoryCall("resumeScheduledActionTool", { id: c.req.param("id") }, body.token);
|
|
18418
|
+
return c.json(r);
|
|
18419
|
+
});
|
|
18420
|
+
scheduleApp.post("/api/actions/:id/delete", async (c) => {
|
|
18421
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18422
|
+
if (!tokenOk2(body.token)) return jsonError2("missing or malformed token", 401);
|
|
18423
|
+
const r = await memoryCall("deleteScheduledActionTool", { id: c.req.param("id") }, body.token);
|
|
18424
|
+
return c.json(r);
|
|
18425
|
+
});
|
|
18426
|
+
scheduleApp.get("/:token", (c) => {
|
|
18427
|
+
const token = c.req.param("token");
|
|
18428
|
+
if (!tokenOk2(token)) {
|
|
18429
|
+
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);
|
|
18430
|
+
}
|
|
18431
|
+
return c.html(renderSchedulePage(token));
|
|
18432
|
+
});
|
|
18433
|
+
function renderSchedulePage(token) {
|
|
18434
|
+
return `<!doctype html>
|
|
18435
|
+
<html>
|
|
18436
|
+
<head>
|
|
18437
|
+
<meta charset="utf-8">
|
|
18438
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
|
18439
|
+
<title>Scheduled Actions</title>
|
|
18440
|
+
<style>
|
|
18441
|
+
: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; }
|
|
18442
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; color: var(--ink); background: var(--bg); padding-bottom: 90px; }
|
|
18443
|
+
header { position: sticky; top: 0; background: var(--bg); padding: 18px 16px 12px; border-bottom: 1px solid var(--line); z-index: 10; }
|
|
18444
|
+
h1 { font-size: 21px; font-weight: 800; margin: 0 0 4px; letter-spacing: -.01em; }
|
|
18445
|
+
.quota { font-size: 13px; color: var(--muted); transition: color .2s; }
|
|
18446
|
+
.quota.warn { color: var(--warn); font-weight: 600; }
|
|
18447
|
+
.quota-bar { height: 6px; border-radius: 999px; background: var(--line); margin-top: 6px; overflow: hidden; }
|
|
18448
|
+
.quota-bar-fill { height: 100%; background: var(--accent); transition: width .3s ease, background .2s; }
|
|
18449
|
+
.quota-bar-fill.warn { background: var(--warn); }
|
|
18450
|
+
main { padding: 14px; max-width: 640px; margin: 0 auto; }
|
|
18451
|
+
.card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px; margin-bottom: 12px; animation: rise .25s ease both; }
|
|
18452
|
+
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
18453
|
+
.empty { text-align: center; color: var(--muted); padding: 56px 20px; font-size: 14px; }
|
|
18454
|
+
.empty-icon { font-size: 32px; margin-bottom: 10px; opacity: .7; }
|
|
18455
|
+
.action-desc { font-size: 15px; line-height: 1.45; margin-bottom: 6px; }
|
|
18456
|
+
.action-meta { font-size: 12.5px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 6px 10px; }
|
|
18457
|
+
.pill { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 600; }
|
|
18458
|
+
.pill-active { background: #e3f3ea; color: #1a7a4c; }
|
|
18459
|
+
.pill-paused { background: #f3ecd8; color: #93701a; }
|
|
18460
|
+
.action-row { display: flex; gap: 8px; margin-top: 12px; }
|
|
18461
|
+
button { font: inherit; }
|
|
18462
|
+
.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; }
|
|
18463
|
+
.btn:active { transform: scale(.96); }
|
|
18464
|
+
.btn-accent { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
18465
|
+
.btn-danger { color: #b3261e; }
|
|
18466
|
+
.btn-danger.confirming { background: var(--warn); border-color: var(--warn); color: #fff; }
|
|
18467
|
+
.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; }
|
|
18468
|
+
.fab:active { transform: scale(.98); box-shadow: 0 4px 14px rgba(208,96,69,.35); }
|
|
18469
|
+
.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; }
|
|
18470
|
+
.overlay.open { opacity: 1; pointer-events: auto; }
|
|
18471
|
+
.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); }
|
|
18472
|
+
.overlay.open .sheet { transform: translateY(0); }
|
|
18473
|
+
.sheet h2 { font-size: 17px; margin: 0 0 14px; }
|
|
18474
|
+
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; }
|
|
18475
|
+
textarea:focus, input:focus, select:focus { border-color: var(--accent); }
|
|
18476
|
+
textarea.autogrow { resize: none; overflow: hidden; line-height: 1.5; min-height: 90px; transition: height .12s ease; }
|
|
18477
|
+
label { font-size: 12.5px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; display: block; margin-top: 14px; }
|
|
18478
|
+
.rationale { font-size: 13px; color: var(--muted); background: #f5f0ea; border-radius: 9px; padding: 10px 12px; margin-top: 10px; }
|
|
18479
|
+
.sheet-actions { display: flex; gap: 8px; margin-top: 18px; }
|
|
18480
|
+
.err { color: #b3261e; font-size: 13px; margin-top: 8px; display: none; }
|
|
18481
|
+
.spin { opacity: .55; pointer-events: none; }
|
|
18482
|
+
.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; }
|
|
18483
|
+
.toast.show { opacity: 1; transform: translate(-50%, 0); }
|
|
18484
|
+
</style>
|
|
18485
|
+
</head>
|
|
18486
|
+
<body>
|
|
18487
|
+
<header>
|
|
18488
|
+
<h1>Scheduled Actions</h1>
|
|
18489
|
+
<div class="quota" id="quota-text">Loading\u2026</div>
|
|
18490
|
+
<div class="quota-bar"><div class="quota-bar-fill" id="quota-fill" style="width:0%"></div></div>
|
|
18491
|
+
</header>
|
|
18492
|
+
<main id="list"></main>
|
|
18493
|
+
<button class="fab" id="new-btn">+ New scheduled action</button>
|
|
18494
|
+
<div class="toast" id="toast"></div>
|
|
18495
|
+
|
|
18496
|
+
<div class="overlay" id="propose-overlay">
|
|
18497
|
+
<div class="sheet">
|
|
18498
|
+
<h2>What should this do?</h2>
|
|
18499
|
+
<textarea id="propose-input" class="autogrow" rows="3" placeholder="e.g. check my competitor's pricing page every Monday and summarize what changed"></textarea>
|
|
18500
|
+
<div class="err" id="propose-err"></div>
|
|
18501
|
+
<div class="sheet-actions">
|
|
18502
|
+
<button class="btn" id="propose-cancel">Cancel</button>
|
|
18503
|
+
<button class="btn btn-accent" id="propose-submit">Propose</button>
|
|
18504
|
+
</div>
|
|
18505
|
+
</div>
|
|
18506
|
+
</div>
|
|
18507
|
+
|
|
18508
|
+
<div class="overlay" id="confirm-overlay">
|
|
18509
|
+
<div class="sheet">
|
|
18510
|
+
<h2>Review before confirming</h2>
|
|
18511
|
+
<label>Description</label>
|
|
18512
|
+
<textarea id="confirm-description" class="autogrow" rows="8"></textarea>
|
|
18513
|
+
<label>Vault</label>
|
|
18514
|
+
<select id="confirm-vault"></select>
|
|
18515
|
+
<label>Cadence</label>
|
|
18516
|
+
<select id="confirm-cadence">
|
|
18517
|
+
<option value="daily">Daily</option>
|
|
18518
|
+
<option value="weekly">Weekly</option>
|
|
18519
|
+
<option value="monthly">Monthly</option>
|
|
18520
|
+
</select>
|
|
18521
|
+
<div class="rationale" id="confirm-rationale"></div>
|
|
18522
|
+
<div class="err" id="confirm-err"></div>
|
|
18523
|
+
<div class="sheet-actions">
|
|
18524
|
+
<button class="btn" id="confirm-cancel">Cancel</button>
|
|
18525
|
+
<button class="btn btn-accent" id="confirm-submit">Confirm & create</button>
|
|
18526
|
+
</div>
|
|
18527
|
+
</div>
|
|
18528
|
+
</div>
|
|
18529
|
+
|
|
18530
|
+
<script>
|
|
18531
|
+
const TOKEN = ${JSON.stringify(token)};
|
|
18532
|
+
const state = { actions: [], vaults: [] };
|
|
18533
|
+
|
|
18534
|
+
function api(path, opts) { return fetch('/schedule' + path, opts).then(r => r.json()); }
|
|
18535
|
+
function get(path, params) { const qs = new URLSearchParams({ token: TOKEN, ...(params||{}) }).toString(); return api(path + '?' + qs); }
|
|
18536
|
+
function post(path, body) { return api(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ token: TOKEN, ...(body||{}) }) }); }
|
|
18537
|
+
function esc(s) { return (s || '').replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c])); }
|
|
18538
|
+
function fmt(iso) { try { return new Date(iso).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return iso || ''; } }
|
|
18539
|
+
|
|
18540
|
+
function autogrow(el) { el.style.height = 'auto'; el.style.height = el.scrollHeight + 'px'; }
|
|
18541
|
+
document.querySelectorAll('.autogrow').forEach(el => {
|
|
18542
|
+
el.addEventListener('input', () => autogrow(el));
|
|
18543
|
+
});
|
|
18544
|
+
|
|
18545
|
+
let toastTimer = null;
|
|
18546
|
+
function toast(message) {
|
|
18547
|
+
const el = document.getElementById('toast');
|
|
18548
|
+
el.textContent = message;
|
|
18549
|
+
el.classList.add('show');
|
|
18550
|
+
if (toastTimer) clearTimeout(toastTimer);
|
|
18551
|
+
toastTimer = setTimeout(() => el.classList.remove('show'), 2200);
|
|
18552
|
+
}
|
|
18553
|
+
|
|
18554
|
+
async function loadStatus() {
|
|
18555
|
+
const r = await get('/api/status');
|
|
18556
|
+
const text = document.getElementById('quota-text');
|
|
18557
|
+
const fill = document.getElementById('quota-fill');
|
|
18558
|
+
if (!r.ok || !r.enabled) {
|
|
18559
|
+
text.textContent = 'No active Scheduled Actions subscription.';
|
|
18560
|
+
text.classList.remove('warn');
|
|
18561
|
+
fill.style.width = '0%';
|
|
18562
|
+
fill.classList.remove('warn');
|
|
18563
|
+
return;
|
|
18564
|
+
}
|
|
18565
|
+
const pct = Math.min(100, Math.round((r.usedThisPeriod / Math.max(1, r.quotaPerPeriod)) * 100));
|
|
18566
|
+
const nearLimit = pct >= 90;
|
|
18567
|
+
text.textContent = r.usedThisPeriod + ' / ' + r.quotaPerPeriod + ' actions used this period' + (nearLimit ? ' \u2014 almost at your limit' : '');
|
|
18568
|
+
text.classList.toggle('warn', nearLimit);
|
|
18569
|
+
fill.style.width = pct + '%';
|
|
18570
|
+
fill.classList.toggle('warn', nearLimit);
|
|
18571
|
+
}
|
|
18572
|
+
|
|
18573
|
+
async function loadVaults() {
|
|
18574
|
+
const r = await get('/api/vaults');
|
|
18575
|
+
state.vaults = r.ok ? (r.vaults || []) : [];
|
|
18576
|
+
const sel = document.getElementById('confirm-vault');
|
|
18577
|
+
sel.innerHTML = state.vaults.map(v => '<option value="' + esc(v.handle) + '">' + esc(v.handle) + '</option>').join('');
|
|
18578
|
+
}
|
|
18579
|
+
|
|
18580
|
+
async function loadActions() {
|
|
18581
|
+
const r = await get('/api/actions');
|
|
18582
|
+
const box = document.getElementById('list');
|
|
18583
|
+
if (!r.ok) { box.innerHTML = '<div class="empty">' + esc(r.error || 'Could not load') + '</div>'; return; }
|
|
18584
|
+
state.actions = r.actions || [];
|
|
18585
|
+
if (!state.actions.length) {
|
|
18586
|
+
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>';
|
|
18587
|
+
return;
|
|
18588
|
+
}
|
|
18589
|
+
box.innerHTML = state.actions.map(renderAction).join('');
|
|
18590
|
+
state.actions.forEach(a => {
|
|
18591
|
+
const pauseBtn = document.getElementById('pause-' + a.id);
|
|
18592
|
+
const resumeBtn = document.getElementById('resume-' + a.id);
|
|
18593
|
+
const delBtn = document.getElementById('delete-' + a.id);
|
|
18594
|
+
if (pauseBtn) pauseBtn.onclick = () => act('pause', a.id, 'Paused');
|
|
18595
|
+
if (resumeBtn) resumeBtn.onclick = () => act('resume', a.id, 'Resumed');
|
|
18596
|
+
if (delBtn) delBtn.onclick = () => confirmDelete(delBtn, a.id);
|
|
18597
|
+
});
|
|
18598
|
+
}
|
|
18599
|
+
|
|
18600
|
+
function confirmDelete(btn, id) {
|
|
18601
|
+
if (!btn.classList.contains('confirming')) {
|
|
18602
|
+
btn.classList.add('confirming');
|
|
18603
|
+
btn.textContent = 'Tap again to delete';
|
|
18604
|
+
btn.dataset.confirmTimer = setTimeout(() => {
|
|
18605
|
+
btn.classList.remove('confirming');
|
|
18606
|
+
btn.textContent = 'Delete';
|
|
18607
|
+
}, 3000);
|
|
18608
|
+
return;
|
|
18609
|
+
}
|
|
18610
|
+
clearTimeout(btn.dataset.confirmTimer);
|
|
18611
|
+
act('delete', id, 'Deleted');
|
|
18612
|
+
}
|
|
18613
|
+
|
|
18614
|
+
function renderAction(a) {
|
|
18615
|
+
const pill = a.status === 'active' ? '<span class="pill pill-active">active</span>' : '<span class="pill pill-paused">' + esc(a.status) + '</span>';
|
|
18616
|
+
return '<div class="card">' +
|
|
18617
|
+
'<div class="action-desc">' + esc(a.description) + '</div>' +
|
|
18618
|
+
'<div class="action-meta">' + pill + '<span>' + esc(a.cadence) + '</span><span>next ' + fmt(a.nextRunAt) + '</span>' +
|
|
18619
|
+
(a.lastRunStatus ? '<span>last: ' + esc(a.lastRunStatus) + '</span>' : '') + '</div>' +
|
|
18620
|
+
'<div class="action-row">' +
|
|
18621
|
+
(a.status === 'active'
|
|
18622
|
+
? '<button class="btn" id="pause-' + a.id + '">Pause</button>'
|
|
18623
|
+
: '<button class="btn" id="resume-' + a.id + '">Resume</button>') +
|
|
18624
|
+
'<button class="btn btn-danger" id="delete-' + a.id + '">Delete</button>' +
|
|
18625
|
+
'</div></div>';
|
|
18626
|
+
}
|
|
18627
|
+
|
|
18628
|
+
async function act(kind, id, toastMessage) {
|
|
18629
|
+
await post('/api/actions/' + id + '/' + kind, {});
|
|
18630
|
+
await loadActions();
|
|
18631
|
+
await loadStatus();
|
|
18632
|
+
if (toastMessage) toast(toastMessage);
|
|
18633
|
+
}
|
|
18634
|
+
|
|
18635
|
+
document.getElementById('new-btn').onclick = () => {
|
|
18636
|
+
document.getElementById('propose-input').value = '';
|
|
18637
|
+
document.getElementById('propose-err').style.display = 'none';
|
|
18638
|
+
document.getElementById('propose-overlay').classList.add('open');
|
|
18639
|
+
};
|
|
18640
|
+
document.getElementById('propose-cancel').onclick = () => document.getElementById('propose-overlay').classList.remove('open');
|
|
18641
|
+
|
|
18642
|
+
document.getElementById('propose-submit').onclick = async () => {
|
|
18643
|
+
const request = document.getElementById('propose-input').value.trim();
|
|
18644
|
+
const errEl = document.getElementById('propose-err');
|
|
18645
|
+
errEl.style.display = 'none';
|
|
18646
|
+
if (!request) return;
|
|
18647
|
+
const btn = document.getElementById('propose-submit');
|
|
18648
|
+
btn.classList.add('spin'); btn.textContent = 'Thinking\u2026';
|
|
18649
|
+
const r = await post('/api/propose', { request });
|
|
18650
|
+
btn.classList.remove('spin'); btn.textContent = 'Propose';
|
|
18651
|
+
if (!r.ok) { errEl.textContent = r.error || 'Could not interpret that request'; errEl.style.display = 'block'; return; }
|
|
18652
|
+
document.getElementById('propose-overlay').classList.remove('open');
|
|
18653
|
+
document.getElementById('confirm-description').value = r.description || '';
|
|
18654
|
+
document.getElementById('confirm-vault').value = r.vault || '';
|
|
18655
|
+
document.getElementById('confirm-cadence').value = r.cadence || 'daily';
|
|
18656
|
+
document.getElementById('confirm-rationale').textContent = r.rationale || '';
|
|
18657
|
+
document.getElementById('confirm-err').style.display = 'none';
|
|
18658
|
+
document.getElementById('confirm-overlay').classList.add('open');
|
|
18659
|
+
autogrow(document.getElementById('confirm-description'));
|
|
18660
|
+
};
|
|
18661
|
+
|
|
18662
|
+
document.getElementById('confirm-cancel').onclick = () => document.getElementById('confirm-overlay').classList.remove('open');
|
|
18663
|
+
|
|
18664
|
+
document.getElementById('confirm-submit').onclick = async () => {
|
|
18665
|
+
const description = document.getElementById('confirm-description').value.trim();
|
|
18666
|
+
const vault = document.getElementById('confirm-vault').value;
|
|
18667
|
+
const cadence = document.getElementById('confirm-cadence').value;
|
|
18668
|
+
const errEl = document.getElementById('confirm-err');
|
|
18669
|
+
errEl.style.display = 'none';
|
|
18670
|
+
if (!description || !vault) { errEl.textContent = 'Description and vault are required'; errEl.style.display = 'block'; return; }
|
|
18671
|
+
const btn = document.getElementById('confirm-submit');
|
|
18672
|
+
btn.classList.add('spin'); btn.textContent = 'Creating\u2026';
|
|
18673
|
+
const r = await post('/api/actions', { description, vault, cadence });
|
|
18674
|
+
btn.classList.remove('spin'); btn.textContent = 'Confirm & create';
|
|
18675
|
+
if (!r.ok) { errEl.textContent = r.error || 'Could not create scheduled action'; errEl.style.display = 'block'; return; }
|
|
18676
|
+
document.getElementById('confirm-overlay').classList.remove('open');
|
|
18677
|
+
await loadActions();
|
|
18678
|
+
await loadStatus();
|
|
18679
|
+
toast('Scheduled action created');
|
|
18680
|
+
};
|
|
18681
|
+
|
|
18682
|
+
loadStatus();
|
|
18683
|
+
loadVaults();
|
|
18684
|
+
loadActions();
|
|
18685
|
+
</script>
|
|
18686
|
+
</body>
|
|
18687
|
+
</html>`;
|
|
18688
|
+
}
|
|
18689
|
+
|
|
17970
18690
|
// src/api/site-audit-worker.ts
|
|
17971
18691
|
var MAX_CONCURRENT_SITE_AUDIT = 1;
|
|
17972
18692
|
async function drainSiteAuditQueue(budget) {
|
|
@@ -18172,16 +18892,32 @@ function toGb(bytes) {
|
|
|
18172
18892
|
}
|
|
18173
18893
|
async function entitledVaultRows(identity) {
|
|
18174
18894
|
const rows = await sql().query(
|
|
18175
|
-
`SELECT vault, owner_identity FROM mem_vault_registry WHERE grantee = $1 ORDER BY created_at ASC`,
|
|
18895
|
+
`SELECT vault, owner_identity, contract FROM mem_vault_registry WHERE grantee = $1 ORDER BY created_at ASC`,
|
|
18176
18896
|
[identity]
|
|
18177
18897
|
);
|
|
18178
18898
|
const seen = /* @__PURE__ */ new Map();
|
|
18179
18899
|
for (const r of rows) {
|
|
18180
18900
|
if (seen.has(r.vault)) continue;
|
|
18181
|
-
|
|
18901
|
+
const kind = r.contract?.kind === "channel" ? "channel" : "notes";
|
|
18902
|
+
seen.set(r.vault, { vault: r.vault, owner: r.owner_identity, physical: physicalVault(r.owner_identity, r.vault), kind });
|
|
18182
18903
|
}
|
|
18183
18904
|
return [...seen.values()];
|
|
18184
18905
|
}
|
|
18906
|
+
var EMBED_BYTES_PER_CHUNK = Number(process.env.JINA_EMBED_DIM ?? 1024) * 4;
|
|
18907
|
+
async function embedBytesByPhysical(physicals) {
|
|
18908
|
+
const m = /* @__PURE__ */ new Map();
|
|
18909
|
+
try {
|
|
18910
|
+
const rows = await sql().query(
|
|
18911
|
+
`SELECT metadata->>'vault' AS vault, COUNT(*)::bigint AS chunks
|
|
18912
|
+
FROM "memory" WHERE metadata->>'vault' = ANY($1) GROUP BY 1`,
|
|
18913
|
+
[physicals]
|
|
18914
|
+
);
|
|
18915
|
+
for (const r of rows) if (r.vault) m.set(r.vault, Number(r.chunks) * EMBED_BYTES_PER_CHUNK);
|
|
18916
|
+
} catch {
|
|
18917
|
+
return m;
|
|
18918
|
+
}
|
|
18919
|
+
return m;
|
|
18920
|
+
}
|
|
18185
18921
|
async function storageByPhysical(physicals) {
|
|
18186
18922
|
if (physicals.length === 0) return /* @__PURE__ */ new Map();
|
|
18187
18923
|
const rows = await sql().query(
|
|
@@ -18189,8 +18925,9 @@ async function storageByPhysical(physicals) {
|
|
|
18189
18925
|
FROM mem_notes WHERE vault = ANY($1) GROUP BY vault`,
|
|
18190
18926
|
[physicals]
|
|
18191
18927
|
);
|
|
18928
|
+
const embed = await embedBytesByPhysical(physicals);
|
|
18192
18929
|
const m = /* @__PURE__ */ new Map();
|
|
18193
|
-
for (const r of rows) m.set(r.vault, { notes: Number(r.notes), bytes: Number(r.bytes) });
|
|
18930
|
+
for (const r of rows) m.set(r.vault, { notes: Number(r.notes), bytes: Number(r.bytes) + (embed.get(r.vault) ?? 0) });
|
|
18194
18931
|
return m;
|
|
18195
18932
|
}
|
|
18196
18933
|
async function dbListVaults(identity) {
|
|
@@ -18204,7 +18941,8 @@ async function dbListVaults(identity) {
|
|
|
18204
18941
|
role: isOwner ? "owner" : "shared",
|
|
18205
18942
|
...isOwner ? {} : { sharedBy: e.owner },
|
|
18206
18943
|
notes: st?.notes ?? 0,
|
|
18207
|
-
bytes: st?.bytes ?? 0
|
|
18944
|
+
bytes: st?.bytes ?? 0,
|
|
18945
|
+
kind: e.kind
|
|
18208
18946
|
};
|
|
18209
18947
|
});
|
|
18210
18948
|
}
|
|
@@ -18381,7 +19119,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
18381
19119
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
18382
19120
|
return next();
|
|
18383
19121
|
});
|
|
18384
|
-
var app = new
|
|
19122
|
+
var app = new Hono18();
|
|
18385
19123
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
18386
19124
|
function requireStripeSecret() {
|
|
18387
19125
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -18572,6 +19310,7 @@ app.get("/me", async (c) => {
|
|
|
18572
19310
|
const stats = await getUserStats(user.id);
|
|
18573
19311
|
return c.json({
|
|
18574
19312
|
authenticated: true,
|
|
19313
|
+
id: user.id,
|
|
18575
19314
|
email: user.email,
|
|
18576
19315
|
api_key: user.key_active ? user.api_key : null,
|
|
18577
19316
|
created_at: user.created_at,
|
|
@@ -18646,6 +19385,28 @@ app.get("/memory/vaults", auth2, async (c) => {
|
|
|
18646
19385
|
return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load vaults" }, 502);
|
|
18647
19386
|
}
|
|
18648
19387
|
});
|
|
19388
|
+
app.get("/memory/approved-senders", auth2, async (c) => {
|
|
19389
|
+
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
19390
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
|
|
19391
|
+
const r = await memoryCall("listApprovedSendersTool", {}, key);
|
|
19392
|
+
return c.json(r);
|
|
19393
|
+
});
|
|
19394
|
+
app.post("/memory/approved-senders", auth2, async (c) => {
|
|
19395
|
+
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
19396
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
|
|
19397
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19398
|
+
const senderIdentity = body.senderIdentity?.trim();
|
|
19399
|
+
if (!senderIdentity) return c.json({ ok: false, error: "senderIdentity is required" }, 400);
|
|
19400
|
+
const r = await memoryCall("approveSenderTool", { senderIdentity }, key);
|
|
19401
|
+
return c.json(r);
|
|
19402
|
+
});
|
|
19403
|
+
app.delete("/memory/approved-senders/:identity", auth2, async (c) => {
|
|
19404
|
+
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
19405
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
|
|
19406
|
+
const senderIdentity = c.req.param("identity");
|
|
19407
|
+
const r = await memoryCall("removeApprovedSenderTool", { senderIdentity }, key);
|
|
19408
|
+
return c.json(r);
|
|
19409
|
+
});
|
|
18649
19410
|
app.get("/memory/connect", auth2, async (c) => {
|
|
18650
19411
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
18651
19412
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
@@ -18809,6 +19570,154 @@ app.post("/memory/portal", auth2, async (c) => {
|
|
|
18809
19570
|
return c.json({ error: message }, 500);
|
|
18810
19571
|
}
|
|
18811
19572
|
});
|
|
19573
|
+
app.post("/schedule-checkout", auth2, async (c) => {
|
|
19574
|
+
try {
|
|
19575
|
+
const user = c.get("user");
|
|
19576
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19577
|
+
const priceId = body.priceId?.trim();
|
|
19578
|
+
if (!priceId || !SCHEDULING_PLANS[priceId]) return c.json({ error: "Invalid priceId \u2014 must be a scheduling plan price." }, 400);
|
|
19579
|
+
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
19580
|
+
if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
|
|
19581
|
+
const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
19582
|
+
let customerId = user.stripe_customer_id;
|
|
19583
|
+
if (!customerId) {
|
|
19584
|
+
const customer = await stripeClient.customers.create({ email: user.email });
|
|
19585
|
+
customerId = customer.id;
|
|
19586
|
+
await setStripeCustomerId(user.id, customerId);
|
|
19587
|
+
}
|
|
19588
|
+
const session = await stripeClient.checkout.sessions.create({
|
|
19589
|
+
mode: "subscription",
|
|
19590
|
+
customer: customerId,
|
|
19591
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
19592
|
+
client_reference_id: String(user.id),
|
|
19593
|
+
metadata: { product: "mcp-scheduling", userId: String(user.id) },
|
|
19594
|
+
subscription_data: { metadata: { product: "mcp-scheduling", userId: String(user.id) } },
|
|
19595
|
+
automatic_tax: { enabled: true },
|
|
19596
|
+
billing_address_collection: "required",
|
|
19597
|
+
customer_update: { address: "auto", name: "auto" },
|
|
19598
|
+
tax_id_collection: { enabled: true },
|
|
19599
|
+
success_url: `${appOrigin()}/memory?billing=success`,
|
|
19600
|
+
cancel_url: `${appOrigin()}/memory?billing=cancel`
|
|
19601
|
+
});
|
|
19602
|
+
if (!session.url) return c.json({ error: "Stripe did not return a checkout URL." }, 502);
|
|
19603
|
+
return c.json({ url: session.url });
|
|
19604
|
+
} catch (err) {
|
|
19605
|
+
const message = err instanceof Error ? err.message : "Unable to start scheduling checkout.";
|
|
19606
|
+
console.error("[schedule-checkout]", message);
|
|
19607
|
+
return c.json({ error: message }, 500);
|
|
19608
|
+
}
|
|
19609
|
+
});
|
|
19610
|
+
app.get("/schedule-status", auth2, async (c) => {
|
|
19611
|
+
try {
|
|
19612
|
+
const user = c.get("user");
|
|
19613
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19614
|
+
if (!key) return c.json({ error: error ?? "memory unavailable" }, 503);
|
|
19615
|
+
const r = await memoryCall("getScheduleStatusTool", {}, key);
|
|
19616
|
+
return c.json(r);
|
|
19617
|
+
} catch (err) {
|
|
19618
|
+
const message = err instanceof Error ? err.message : "Unable to get schedule status.";
|
|
19619
|
+
console.error("[schedule-status]", message);
|
|
19620
|
+
return c.json({ error: message }, 500);
|
|
19621
|
+
}
|
|
19622
|
+
});
|
|
19623
|
+
app.post("/schedule-link", auth2, async (c) => {
|
|
19624
|
+
try {
|
|
19625
|
+
const user = c.get("user");
|
|
19626
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19627
|
+
if (!key) return c.json({ error: error ?? "memory unavailable" }, 503);
|
|
19628
|
+
const r = await memoryCall("getScheduleLinkTool", {}, key);
|
|
19629
|
+
return c.json(r);
|
|
19630
|
+
} catch (err) {
|
|
19631
|
+
const message = err instanceof Error ? err.message : "Unable to get schedule link.";
|
|
19632
|
+
console.error("[schedule-link]", message);
|
|
19633
|
+
return c.json({ error: message }, 500);
|
|
19634
|
+
}
|
|
19635
|
+
});
|
|
19636
|
+
app.get("/schedule-actions", auth2, async (c) => {
|
|
19637
|
+
const user = c.get("user");
|
|
19638
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19639
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19640
|
+
const r = await memoryCall("listScheduledActionsTool", {}, key);
|
|
19641
|
+
return c.json(r);
|
|
19642
|
+
});
|
|
19643
|
+
app.post("/schedule-actions", 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("createScheduledActionTool", body, key);
|
|
19649
|
+
return c.json(r);
|
|
19650
|
+
});
|
|
19651
|
+
app.post("/schedule-actions/propose", 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 body = await c.req.json().catch(() => ({}));
|
|
19656
|
+
const r = await memoryCall("proposeScheduledActionTool", body, key);
|
|
19657
|
+
return c.json(r);
|
|
19658
|
+
});
|
|
19659
|
+
app.post("/schedule-actions/:id/pause", auth2, async (c) => {
|
|
19660
|
+
const user = c.get("user");
|
|
19661
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19662
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19663
|
+
const r = await memoryCall("pauseScheduledActionTool", { id: c.req.param("id") }, key);
|
|
19664
|
+
return c.json(r);
|
|
19665
|
+
});
|
|
19666
|
+
app.post("/schedule-actions/:id/resume", auth2, async (c) => {
|
|
19667
|
+
const user = c.get("user");
|
|
19668
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19669
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19670
|
+
const r = await memoryCall("resumeScheduledActionTool", { id: c.req.param("id") }, key);
|
|
19671
|
+
return c.json(r);
|
|
19672
|
+
});
|
|
19673
|
+
app.delete("/schedule-actions/:id", auth2, async (c) => {
|
|
19674
|
+
const user = c.get("user");
|
|
19675
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19676
|
+
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
19677
|
+
const r = await memoryCall("deleteScheduledActionTool", { id: c.req.param("id") }, key);
|
|
19678
|
+
return c.json(r);
|
|
19679
|
+
});
|
|
19680
|
+
app.post("/schedule-link/revoke", auth2, async (c) => {
|
|
19681
|
+
try {
|
|
19682
|
+
const user = c.get("user");
|
|
19683
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
19684
|
+
if (!key) return c.json({ error: error ?? "memory unavailable" }, 503);
|
|
19685
|
+
const r = await memoryCall("revokeScheduleLinkTool", {}, key);
|
|
19686
|
+
return c.json(r);
|
|
19687
|
+
} catch (err) {
|
|
19688
|
+
const message = err instanceof Error ? err.message : "Unable to revoke schedule link.";
|
|
19689
|
+
console.error("[schedule-link/revoke]", message);
|
|
19690
|
+
return c.json({ error: message }, 500);
|
|
19691
|
+
}
|
|
19692
|
+
});
|
|
19693
|
+
app.post("/support/submit", auth2, async (c) => {
|
|
19694
|
+
try {
|
|
19695
|
+
const user = c.get("user");
|
|
19696
|
+
const body = await c.req.json().catch(() => ({}));
|
|
19697
|
+
const message = body.message?.trim();
|
|
19698
|
+
if (!message) return c.json({ error: "message is required" }, 400);
|
|
19699
|
+
if (message.length > 8e3) return c.json({ error: "message is too long (max 8000 characters)" }, 400);
|
|
19700
|
+
const inboxKey = process.env.SUPPORT_INBOX_MEMORY_KEY?.trim();
|
|
19701
|
+
if (!inboxKey) return c.json({ error: "support inbox is not configured" }, 503);
|
|
19702
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
19703
|
+
const path5 = `support/${stamp.replace(/[:.]/g, "-")}-user-${user.id}`;
|
|
19704
|
+
const title = `Support request from ${user.email} (user #${user.id})`;
|
|
19705
|
+
const content = [
|
|
19706
|
+
`**From:** ${user.email} (user #${user.id})`,
|
|
19707
|
+
`**Submitted:** ${stamp}`,
|
|
19708
|
+
"",
|
|
19709
|
+
"**Message:**",
|
|
19710
|
+
message
|
|
19711
|
+
].join("\n");
|
|
19712
|
+
const r = await memoryCall("putTool", { vault: "Issues", path: path5, title, content }, inboxKey);
|
|
19713
|
+
if (!r.ok) return c.json({ error: r.error ?? "failed to submit support request" }, 502);
|
|
19714
|
+
return c.json({ ok: true });
|
|
19715
|
+
} catch (err) {
|
|
19716
|
+
const message = err instanceof Error ? err.message : "Unable to submit support request.";
|
|
19717
|
+
console.error("[support/submit]", message);
|
|
19718
|
+
return c.json({ error: message }, 500);
|
|
19719
|
+
}
|
|
19720
|
+
});
|
|
18812
19721
|
app.get("/memory/usage", auth2, async (c) => {
|
|
18813
19722
|
const user = c.get("user");
|
|
18814
19723
|
try {
|
|
@@ -19522,7 +20431,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
19522
20431
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
19523
20432
|
return c.json({ error: "Unauthorized" }, 401);
|
|
19524
20433
|
}
|
|
19525
|
-
const { drainQueue } = await import("./worker-
|
|
20434
|
+
const { drainQueue } = await import("./worker-FNSBPP7Y.js");
|
|
19526
20435
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
19527
20436
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
19528
20437
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -19539,6 +20448,7 @@ app.route("/api/internal/site-architecture-auditor", siteAuditApp);
|
|
|
19539
20448
|
app.route("/youtube", youtubeApp);
|
|
19540
20449
|
app.route("/screenshot", screenshotApp);
|
|
19541
20450
|
app.route("/facebook", facebookAdApp);
|
|
20451
|
+
app.route("/google-ads", googleAdsApp);
|
|
19542
20452
|
app.route("/instagram", instagramApp);
|
|
19543
20453
|
app.route("/reddit", redditApp);
|
|
19544
20454
|
app.route("/maps", mapsApp);
|
|
@@ -19548,6 +20458,7 @@ app.route("/serp-intelligence", serpIntelligenceApp);
|
|
|
19548
20458
|
app.route("/mcp", mcpApp);
|
|
19549
20459
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
19550
20460
|
app.route("/chat", chatApp);
|
|
20461
|
+
app.route("/schedule", scheduleApp);
|
|
19551
20462
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
19552
20463
|
app.get("/console/auth/:id", async (c) => {
|
|
19553
20464
|
const id = c.req.param("id");
|
|
@@ -19671,4 +20582,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
19671
20582
|
export {
|
|
19672
20583
|
app
|
|
19673
20584
|
};
|
|
19674
|
-
//# sourceMappingURL=server-
|
|
20585
|
+
//# sourceMappingURL=server-CZT7EULY.js.map
|