mcp-scraper 0.1.9 → 0.2.0
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 +845 -40
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/browser-agent-stdio-server.cjs +314 -0
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -0
- package/dist/bin/browser-agent-stdio-server.d.cts +1 -0
- package/dist/bin/browser-agent-stdio-server.d.ts +1 -0
- package/dist/bin/browser-agent-stdio-server.js +313 -0
- package/dist/bin/browser-agent-stdio-server.js.map +1 -0
- package/dist/bin/mcp-stdio-server.cjs +1 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -1
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/chunk-2BS7BUEE.js +7 -0
- package/dist/chunk-2BS7BUEE.js.map +1 -0
- package/dist/{chunk-JNC32DMS.js → chunk-BMVQB3WN.js} +4 -4
- package/dist/chunk-BMVQB3WN.js.map +1 -0
- package/dist/{chunk-ZK456YXN.js → chunk-GXBT5CDU.js} +20 -3
- package/dist/chunk-GXBT5CDU.js.map +1 -0
- package/dist/{server-MTXAJG5J.js → server-ASCMKUQ5.js} +790 -28
- package/dist/server-ASCMKUQ5.js.map +1 -0
- package/dist/{worker-AUCXFHEL.js → worker-KJ4A7WIR.js} +2 -2
- package/docs/specs/api-forge-spec.md +234 -0
- package/docs/specs/deferred-work-spec.md +74 -0
- package/docs/specs/oauth-mcp-spec.md +213 -0
- package/package.json +3 -2
- package/dist/chunk-JNC32DMS.js.map +0 -1
- package/dist/chunk-ZK456YXN.js.map +0 -1
- package/dist/server-MTXAJG5J.js.map +0 -1
- /package/dist/{worker-AUCXFHEL.js.map → worker-KJ4A7WIR.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -3530,8 +3530,8 @@ var init_url_utils = __esm({
|
|
|
3530
3530
|
async function fetchWithKernel(url) {
|
|
3531
3531
|
const apiKey = browserServiceApiKey();
|
|
3532
3532
|
if (!apiKey) throw new Error("Browser backend API key not set");
|
|
3533
|
-
const
|
|
3534
|
-
const kb = await
|
|
3533
|
+
const client2 = new import_sdk.default({ apiKey });
|
|
3534
|
+
const kb = await client2.browsers.create({ stealth: true, timeout_seconds: 60 });
|
|
3535
3535
|
const browser = await import_playwright.chromium.connectOverCDP(kb.cdp_ws_url);
|
|
3536
3536
|
try {
|
|
3537
3537
|
const context = browser.contexts()[0] ?? await browser.newContext({
|
|
@@ -3544,7 +3544,7 @@ async function fetchWithKernel(url) {
|
|
|
3544
3544
|
} finally {
|
|
3545
3545
|
await browser.close().catch(() => {
|
|
3546
3546
|
});
|
|
3547
|
-
await
|
|
3547
|
+
await client2.browsers.deleteByID(kb.session_id).catch(() => {
|
|
3548
3548
|
});
|
|
3549
3549
|
}
|
|
3550
3550
|
}
|
|
@@ -8292,6 +8292,9 @@ var init_site_audit_routes = __esm({
|
|
|
8292
8292
|
});
|
|
8293
8293
|
|
|
8294
8294
|
// src/api/rates.ts
|
|
8295
|
+
function browserActiveCostMc(activeMs) {
|
|
8296
|
+
return Math.round(activeMs * MC_PER_BROWSER_MS);
|
|
8297
|
+
}
|
|
8295
8298
|
function mcToCredits(mc) {
|
|
8296
8299
|
return mc / MC_PER_CREDIT;
|
|
8297
8300
|
}
|
|
@@ -8308,7 +8311,7 @@ function insufficientBalanceResponse(balanceMc, requiredMc) {
|
|
|
8308
8311
|
topup_url: topupUrl
|
|
8309
8312
|
};
|
|
8310
8313
|
}
|
|
8311
|
-
var MC_COSTS, MC_PER_CREDIT, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, FREE_SIGNUP_MC, FREE_MONTHLY_REFRESH_MC, BALANCE_PRICE_IDS, BALANCE_PACK_LABELS, LedgerOperation;
|
|
8314
|
+
var MC_COSTS, MC_PER_BROWSER_MS, BROWSER_OPEN_MIN_BALANCE_MC, MC_PER_CREDIT, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, FREE_SIGNUP_MC, FREE_MONTHLY_REFRESH_MC, BALANCE_PRICE_IDS, BALANCE_PACK_LABELS, LedgerOperation;
|
|
8312
8315
|
var init_rates = __esm({
|
|
8313
8316
|
"src/api/rates.ts"() {
|
|
8314
8317
|
"use strict";
|
|
@@ -8324,8 +8327,11 @@ var init_rates = __esm({
|
|
|
8324
8327
|
maps_place: 2e3,
|
|
8325
8328
|
maps_review: 50,
|
|
8326
8329
|
fb_search: 50,
|
|
8327
|
-
fb_transcribe: 50
|
|
8330
|
+
fb_transcribe: 50,
|
|
8331
|
+
browser_minute: 4e3
|
|
8328
8332
|
};
|
|
8333
|
+
MC_PER_BROWSER_MS = MC_COSTS.browser_minute / 6e4;
|
|
8334
|
+
BROWSER_OPEN_MIN_BALANCE_MC = 1e3;
|
|
8329
8335
|
MC_PER_CREDIT = 1e3;
|
|
8330
8336
|
CREDIT_COST_CATALOG = [
|
|
8331
8337
|
{
|
|
@@ -8421,6 +8427,14 @@ var init_rates = __esm({
|
|
|
8421
8427
|
credits: mcToCredits(MC_COSTS.fb_transcribe),
|
|
8422
8428
|
unit: "per call",
|
|
8423
8429
|
notes: "Whisper transcription of Facebook ad video via fal.ai."
|
|
8430
|
+
},
|
|
8431
|
+
{
|
|
8432
|
+
key: "browser_minute",
|
|
8433
|
+
label: "Interactive browser session",
|
|
8434
|
+
aliases: ["browser_open", "browser agent", "browser_agent", "live browser", "browse", "browser control", "interactive browser"],
|
|
8435
|
+
credits: mcToCredits(MC_COSTS.browser_minute),
|
|
8436
|
+
unit: "per minute of active time",
|
|
8437
|
+
notes: "Metered per second of active browser work (navigation, clicks, typing, screenshots). Idle and standby time are free. Billed against your balance as you act; close the session to stop the meter."
|
|
8424
8438
|
}
|
|
8425
8439
|
];
|
|
8426
8440
|
CONCURRENCY_PRICE_ID = "price_1Ta1NRS8aAcsk3TGwsRnYbix";
|
|
@@ -8466,7 +8480,8 @@ var init_rates = __esm({
|
|
|
8466
8480
|
FB_SEARCH: "fb_search",
|
|
8467
8481
|
FB_TRANSCRIBE: "fb_transcribe",
|
|
8468
8482
|
FB_SEARCH_REFUND: "fb_search_refund",
|
|
8469
|
-
FB_TRANSCRIBE_REFUND: "fb_transcribe_refund"
|
|
8483
|
+
FB_TRANSCRIBE_REFUND: "fb_transcribe_refund",
|
|
8484
|
+
BROWSER_SESSION: "browser_session"
|
|
8470
8485
|
};
|
|
8471
8486
|
}
|
|
8472
8487
|
});
|
|
@@ -9032,19 +9047,19 @@ var init_BrowserDriver = __esm({
|
|
|
9032
9047
|
if (this.browser) {
|
|
9033
9048
|
const b = this.browser;
|
|
9034
9049
|
const sessionId = this.kernelSessionId;
|
|
9035
|
-
const
|
|
9050
|
+
const client2 = this.kernelClient;
|
|
9036
9051
|
this.browser = null;
|
|
9037
9052
|
this.context = null;
|
|
9038
9053
|
this.page = null;
|
|
9039
9054
|
this.kernelSessionId = null;
|
|
9040
9055
|
this.kernelClient = null;
|
|
9041
|
-
if (
|
|
9056
|
+
if (client2 && sessionId) {
|
|
9042
9057
|
console.info(JSON.stringify({
|
|
9043
9058
|
event: "kernel_browser_delete_started",
|
|
9044
9059
|
kernel_session_id: sessionId
|
|
9045
9060
|
}));
|
|
9046
9061
|
const deleteSession = withTimeout(
|
|
9047
|
-
|
|
9062
|
+
client2.browsers.deleteByID(sessionId),
|
|
9048
9063
|
KERNEL_SESSION_DELETE_TIMEOUT_MS,
|
|
9049
9064
|
`Kernel session ${sessionId} delete`
|
|
9050
9065
|
);
|
|
@@ -10513,13 +10528,13 @@ var init_FacebookAdExtractor = __esm({
|
|
|
10513
10528
|
}
|
|
10514
10529
|
await page.waitForTimeout(1500);
|
|
10515
10530
|
let prevCount = 0;
|
|
10516
|
-
for (let
|
|
10531
|
+
for (let scroll2 = 0; scroll2 < 20; scroll2++) {
|
|
10517
10532
|
const count = await page.evaluate(() => {
|
|
10518
10533
|
const bt = document.body ? document.body.innerText ?? "" : "";
|
|
10519
10534
|
return [...bt.matchAll(/Library ID/g)].length;
|
|
10520
10535
|
});
|
|
10521
10536
|
if (count >= maxAds) break;
|
|
10522
|
-
if (count === prevCount &&
|
|
10537
|
+
if (count === prevCount && scroll2 > 0) break;
|
|
10523
10538
|
prevCount = count;
|
|
10524
10539
|
await page.evaluate(() => {
|
|
10525
10540
|
if (document.body) window.scrollTo(0, document.body.scrollHeight);
|
|
@@ -11663,7 +11678,7 @@ var init_facebook_ad_routes = __esm({
|
|
|
11663
11678
|
return c.json(searchResult2);
|
|
11664
11679
|
}
|
|
11665
11680
|
await page.waitForTimeout(1500);
|
|
11666
|
-
for (let
|
|
11681
|
+
for (let scroll2 = 0; scroll2 < 3; scroll2++) {
|
|
11667
11682
|
await page.evaluate(() => {
|
|
11668
11683
|
if (document.body) window.scrollTo(0, document.body.scrollHeight);
|
|
11669
11684
|
});
|
|
@@ -15276,7 +15291,7 @@ var PACKAGE_VERSION;
|
|
|
15276
15291
|
var init_version = __esm({
|
|
15277
15292
|
"src/version.ts"() {
|
|
15278
15293
|
"use strict";
|
|
15279
|
-
PACKAGE_VERSION = "0.
|
|
15294
|
+
PACKAGE_VERSION = "0.2.0";
|
|
15280
15295
|
}
|
|
15281
15296
|
});
|
|
15282
15297
|
|
|
@@ -16809,17 +16824,802 @@ var init_mcp_routes = __esm({
|
|
|
16809
16824
|
}
|
|
16810
16825
|
});
|
|
16811
16826
|
|
|
16827
|
+
// src/api/browser-agent-db.ts
|
|
16828
|
+
async function migrateBrowserAgent() {
|
|
16829
|
+
if (_ready) return;
|
|
16830
|
+
const db = getDb();
|
|
16831
|
+
await db.execute(`
|
|
16832
|
+
CREATE TABLE IF NOT EXISTS browser_agent_sessions (
|
|
16833
|
+
id TEXT PRIMARY KEY,
|
|
16834
|
+
runtime_session_id TEXT NOT NULL,
|
|
16835
|
+
live_view_url TEXT,
|
|
16836
|
+
cdp_ws_url TEXT NOT NULL,
|
|
16837
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
16838
|
+
label TEXT,
|
|
16839
|
+
user_id INTEGER,
|
|
16840
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
16841
|
+
closed_at TEXT,
|
|
16842
|
+
last_action_at TEXT,
|
|
16843
|
+
active_ms INTEGER NOT NULL DEFAULT 0,
|
|
16844
|
+
billed_mc INTEGER NOT NULL DEFAULT 0
|
|
16845
|
+
)
|
|
16846
|
+
`);
|
|
16847
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_sessions_status ON browser_agent_sessions(status)`);
|
|
16848
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_sessions_user ON browser_agent_sessions(user_id)`);
|
|
16849
|
+
await db.execute(`
|
|
16850
|
+
CREATE TABLE IF NOT EXISTS browser_agent_actions (
|
|
16851
|
+
id TEXT PRIMARY KEY,
|
|
16852
|
+
session_id TEXT NOT NULL,
|
|
16853
|
+
type TEXT NOT NULL,
|
|
16854
|
+
params_json TEXT,
|
|
16855
|
+
ok INTEGER NOT NULL DEFAULT 1,
|
|
16856
|
+
error TEXT,
|
|
16857
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
16858
|
+
)
|
|
16859
|
+
`);
|
|
16860
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_actions_session ON browser_agent_actions(session_id)`);
|
|
16861
|
+
await db.execute(`
|
|
16862
|
+
CREATE TABLE IF NOT EXISTS browser_agent_replays (
|
|
16863
|
+
replay_id TEXT PRIMARY KEY,
|
|
16864
|
+
session_id TEXT NOT NULL,
|
|
16865
|
+
view_url TEXT,
|
|
16866
|
+
label TEXT,
|
|
16867
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
16868
|
+
stopped_at TEXT
|
|
16869
|
+
)
|
|
16870
|
+
`);
|
|
16871
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_replays_session ON browser_agent_replays(session_id)`);
|
|
16872
|
+
_ready = true;
|
|
16873
|
+
}
|
|
16874
|
+
async function createSessionRow(input) {
|
|
16875
|
+
const db = getDb();
|
|
16876
|
+
const id = `bas_${(0, import_node_crypto3.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
|
|
16877
|
+
await db.execute({
|
|
16878
|
+
sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, last_action_at)
|
|
16879
|
+
VALUES (?, ?, ?, ?, 'open', ?, ?, datetime('now'))`,
|
|
16880
|
+
args: [id, input.runtimeSessionId, input.liveViewUrl, input.cdpWsUrl, input.label, input.userId]
|
|
16881
|
+
});
|
|
16882
|
+
const row = await getSessionRow(id);
|
|
16883
|
+
if (!row) throw new Error("session insert failed");
|
|
16884
|
+
return row;
|
|
16885
|
+
}
|
|
16886
|
+
async function getSessionRow(id) {
|
|
16887
|
+
const db = getDb();
|
|
16888
|
+
const res = await db.execute({ sql: `SELECT * FROM browser_agent_sessions WHERE id = ?`, args: [id] });
|
|
16889
|
+
return res.rows[0] ?? null;
|
|
16890
|
+
}
|
|
16891
|
+
async function listSessionRows(userId, includeClosed = false) {
|
|
16892
|
+
const db = getDb();
|
|
16893
|
+
const clauses = [];
|
|
16894
|
+
const args = [];
|
|
16895
|
+
if (userId != null) {
|
|
16896
|
+
clauses.push("(user_id = ? OR user_id IS NULL)");
|
|
16897
|
+
args.push(userId);
|
|
16898
|
+
}
|
|
16899
|
+
if (!includeClosed) clauses.push(`status = 'open'`);
|
|
16900
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
16901
|
+
const res = await db.execute({
|
|
16902
|
+
sql: `SELECT * FROM browser_agent_sessions ${where} ORDER BY created_at DESC LIMIT 100`,
|
|
16903
|
+
args
|
|
16904
|
+
});
|
|
16905
|
+
return res.rows;
|
|
16906
|
+
}
|
|
16907
|
+
async function addActiveMs(id, deltaMs) {
|
|
16908
|
+
const db = getDb();
|
|
16909
|
+
await db.execute({
|
|
16910
|
+
sql: `UPDATE browser_agent_sessions SET active_ms = active_ms + ?, last_action_at = datetime('now') WHERE id = ?`,
|
|
16911
|
+
args: [Math.max(0, Math.round(deltaMs)), id]
|
|
16912
|
+
});
|
|
16913
|
+
const res = await db.execute({ sql: `SELECT active_ms, billed_mc FROM browser_agent_sessions WHERE id = ?`, args: [id] });
|
|
16914
|
+
const row = res.rows[0];
|
|
16915
|
+
return { active_ms: Number(row?.active_ms ?? 0), billed_mc: Number(row?.billed_mc ?? 0) };
|
|
16916
|
+
}
|
|
16917
|
+
async function setBilledMc(id, billedMc) {
|
|
16918
|
+
const db = getDb();
|
|
16919
|
+
await db.execute({
|
|
16920
|
+
sql: `UPDATE browser_agent_sessions SET billed_mc = ? WHERE id = ?`,
|
|
16921
|
+
args: [Math.round(billedMc), id]
|
|
16922
|
+
});
|
|
16923
|
+
}
|
|
16924
|
+
async function markSessionClosed(id) {
|
|
16925
|
+
const db = getDb();
|
|
16926
|
+
await db.execute({
|
|
16927
|
+
sql: `UPDATE browser_agent_sessions SET status = 'closed', closed_at = datetime('now') WHERE id = ?`,
|
|
16928
|
+
args: [id]
|
|
16929
|
+
});
|
|
16930
|
+
}
|
|
16931
|
+
async function recordAction(input) {
|
|
16932
|
+
const db = getDb();
|
|
16933
|
+
await db.execute({
|
|
16934
|
+
sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
|
|
16935
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
16936
|
+
args: [
|
|
16937
|
+
`baa_${(0, import_node_crypto3.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
|
|
16938
|
+
input.sessionId,
|
|
16939
|
+
input.type,
|
|
16940
|
+
input.params == null ? null : JSON.stringify(input.params),
|
|
16941
|
+
input.ok ? 1 : 0,
|
|
16942
|
+
input.error ?? null
|
|
16943
|
+
]
|
|
16944
|
+
});
|
|
16945
|
+
}
|
|
16946
|
+
async function recordReplayStart(input) {
|
|
16947
|
+
const db = getDb();
|
|
16948
|
+
await db.execute({
|
|
16949
|
+
sql: `INSERT INTO browser_agent_replays (replay_id, session_id, view_url, label)
|
|
16950
|
+
VALUES (?, ?, ?, ?)`,
|
|
16951
|
+
args: [input.replayId, input.sessionId, input.viewUrl, input.label]
|
|
16952
|
+
});
|
|
16953
|
+
}
|
|
16954
|
+
async function recordReplayStop(replayId, viewUrl) {
|
|
16955
|
+
const db = getDb();
|
|
16956
|
+
await db.execute({
|
|
16957
|
+
sql: `UPDATE browser_agent_replays SET stopped_at = datetime('now'), view_url = COALESCE(?, view_url) WHERE replay_id = ?`,
|
|
16958
|
+
args: [viewUrl, replayId]
|
|
16959
|
+
});
|
|
16960
|
+
}
|
|
16961
|
+
async function listReplayRows(sessionId) {
|
|
16962
|
+
const db = getDb();
|
|
16963
|
+
const res = await db.execute({
|
|
16964
|
+
sql: `SELECT * FROM browser_agent_replays WHERE session_id = ? ORDER BY started_at DESC`,
|
|
16965
|
+
args: [sessionId]
|
|
16966
|
+
});
|
|
16967
|
+
return res.rows;
|
|
16968
|
+
}
|
|
16969
|
+
var import_node_crypto3, _ready;
|
|
16970
|
+
var init_browser_agent_db = __esm({
|
|
16971
|
+
"src/api/browser-agent-db.ts"() {
|
|
16972
|
+
"use strict";
|
|
16973
|
+
import_node_crypto3 = require("crypto");
|
|
16974
|
+
init_db();
|
|
16975
|
+
_ready = false;
|
|
16976
|
+
}
|
|
16977
|
+
});
|
|
16978
|
+
|
|
16979
|
+
// src/services/browser-agent/browser-agent-service.ts
|
|
16980
|
+
function client() {
|
|
16981
|
+
const apiKey = browserServiceApiKey();
|
|
16982
|
+
if (!apiKey) throw new Error("Browser backend API key is required");
|
|
16983
|
+
return new import_sdk6.default({ apiKey });
|
|
16984
|
+
}
|
|
16985
|
+
async function createSession(opts = {}) {
|
|
16986
|
+
const k = client();
|
|
16987
|
+
const resolvedProxyId = opts.proxyId ?? browserServiceProxyId();
|
|
16988
|
+
const browser = await k.browsers.create({
|
|
16989
|
+
stealth: opts.stealth ?? true,
|
|
16990
|
+
timeout_seconds: opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS,
|
|
16991
|
+
...resolvedProxyId ? { proxy_id: resolvedProxyId } : {},
|
|
16992
|
+
...opts.profileName ? { profile: { name: opts.profileName } } : {}
|
|
16993
|
+
});
|
|
16994
|
+
const runtimeSessionId = browser.session_id;
|
|
16995
|
+
if (opts.disableDefaultProxy) {
|
|
16996
|
+
try {
|
|
16997
|
+
await k.browsers.update(runtimeSessionId, { disable_default_proxy: true });
|
|
16998
|
+
} catch {
|
|
16999
|
+
}
|
|
17000
|
+
}
|
|
17001
|
+
if (opts.viewport) {
|
|
17002
|
+
try {
|
|
17003
|
+
await k.browsers.update(runtimeSessionId, { viewport: opts.viewport });
|
|
17004
|
+
} catch {
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
return {
|
|
17008
|
+
runtimeSessionId,
|
|
17009
|
+
liveViewUrl: browser.browser_live_view_url ?? null,
|
|
17010
|
+
cdpWsUrl: browser.cdp_ws_url
|
|
17011
|
+
};
|
|
17012
|
+
}
|
|
17013
|
+
async function closeSession(runtimeSessionId) {
|
|
17014
|
+
const k = client();
|
|
17015
|
+
await k.browsers.deleteByID(runtimeSessionId);
|
|
17016
|
+
}
|
|
17017
|
+
async function screenshot(runtimeSessionId) {
|
|
17018
|
+
const k = client();
|
|
17019
|
+
const res = await k.browsers.computer.captureScreenshot(runtimeSessionId);
|
|
17020
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
17021
|
+
return { base64: buf.toString("base64"), mimeType: "image/png" };
|
|
17022
|
+
}
|
|
17023
|
+
async function click(runtimeSessionId, x, y, opts = {}) {
|
|
17024
|
+
const k = client();
|
|
17025
|
+
await k.browsers.computer.clickMouse(runtimeSessionId, {
|
|
17026
|
+
x,
|
|
17027
|
+
y,
|
|
17028
|
+
button: opts.button ?? "left",
|
|
17029
|
+
...opts.numClicks ? { num_clicks: opts.numClicks } : {}
|
|
17030
|
+
});
|
|
17031
|
+
}
|
|
17032
|
+
async function typeText(runtimeSessionId, text, delayMs) {
|
|
17033
|
+
const k = client();
|
|
17034
|
+
await k.browsers.computer.typeText(runtimeSessionId, {
|
|
17035
|
+
text,
|
|
17036
|
+
...typeof delayMs === "number" ? { delay: delayMs } : {}
|
|
17037
|
+
});
|
|
17038
|
+
}
|
|
17039
|
+
async function scroll(runtimeSessionId, x, y, deltaX, deltaY) {
|
|
17040
|
+
const k = client();
|
|
17041
|
+
await k.browsers.computer.scroll(runtimeSessionId, { x, y, delta_x: deltaX, delta_y: deltaY });
|
|
17042
|
+
}
|
|
17043
|
+
async function pressKeys(runtimeSessionId, keys) {
|
|
17044
|
+
const k = client();
|
|
17045
|
+
await k.browsers.computer.pressKey(runtimeSessionId, { keys });
|
|
17046
|
+
}
|
|
17047
|
+
async function goto(cdpWsUrl, url) {
|
|
17048
|
+
const browser = await import_playwright4.chromium.connectOverCDP(cdpWsUrl);
|
|
17049
|
+
try {
|
|
17050
|
+
const context = browser.contexts()[0] ?? await browser.newContext();
|
|
17051
|
+
const page = context.pages()[0] ?? await context.newPage();
|
|
17052
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
17053
|
+
return { url: page.url(), title: await page.title() };
|
|
17054
|
+
} finally {
|
|
17055
|
+
await browser.close().catch(() => {
|
|
17056
|
+
});
|
|
17057
|
+
}
|
|
17058
|
+
}
|
|
17059
|
+
async function readPage(cdpWsUrl) {
|
|
17060
|
+
const browser = await import_playwright4.chromium.connectOverCDP(cdpWsUrl);
|
|
17061
|
+
try {
|
|
17062
|
+
const context = browser.contexts()[0] ?? await browser.newContext();
|
|
17063
|
+
const page = context.pages()[0] ?? await context.newPage();
|
|
17064
|
+
const url = page.url();
|
|
17065
|
+
const title = await page.title().catch(() => "");
|
|
17066
|
+
const data = await page.evaluate(() => {
|
|
17067
|
+
const SEL = 'a[href], button, input, textarea, select, [role="button"], [role="link"], [role="tab"], [onclick]';
|
|
17068
|
+
const out = [];
|
|
17069
|
+
const nodes = Array.from(document.querySelectorAll(SEL)).slice(0, 120);
|
|
17070
|
+
for (const el2 of nodes) {
|
|
17071
|
+
const r = el2.getBoundingClientRect();
|
|
17072
|
+
if (r.width < 1 || r.height < 1) continue;
|
|
17073
|
+
if (r.bottom < 0 || r.top > window.innerHeight) continue;
|
|
17074
|
+
const e = el2;
|
|
17075
|
+
const name = (e.getAttribute("aria-label") || e.placeholder || e.innerText || e.getAttribute("value") || e.getAttribute("title") || "").trim().replace(/\s+/g, " ").slice(0, 80);
|
|
17076
|
+
out.push({
|
|
17077
|
+
role: el2.getAttribute("role") || el2.tagName.toLowerCase(),
|
|
17078
|
+
name,
|
|
17079
|
+
x: Math.round(r.left + r.width / 2),
|
|
17080
|
+
y: Math.round(r.top + r.height / 2)
|
|
17081
|
+
});
|
|
17082
|
+
}
|
|
17083
|
+
const text = (document.body?.innerText || "").replace(/\n{3,}/g, "\n\n").trim().slice(0, 6e3);
|
|
17084
|
+
return { text, els: out };
|
|
17085
|
+
});
|
|
17086
|
+
return {
|
|
17087
|
+
url,
|
|
17088
|
+
title,
|
|
17089
|
+
text: data.text,
|
|
17090
|
+
elements: data.els.map((e, i) => ({ ref: i + 1, role: e.role, name: e.name, x: e.x, y: e.y }))
|
|
17091
|
+
};
|
|
17092
|
+
} finally {
|
|
17093
|
+
await browser.close().catch(() => {
|
|
17094
|
+
});
|
|
17095
|
+
}
|
|
17096
|
+
}
|
|
17097
|
+
async function replayStart(runtimeSessionId) {
|
|
17098
|
+
const k = client();
|
|
17099
|
+
const res = await k.browsers.replays.start(runtimeSessionId);
|
|
17100
|
+
return { replayId: res.replay_id, viewUrl: res.replay_view_url ?? null };
|
|
17101
|
+
}
|
|
17102
|
+
async function replayStop(runtimeSessionId, replayId) {
|
|
17103
|
+
const k = client();
|
|
17104
|
+
await k.browsers.replays.stop(replayId, { id: runtimeSessionId });
|
|
17105
|
+
}
|
|
17106
|
+
async function replayList(runtimeSessionId) {
|
|
17107
|
+
const k = client();
|
|
17108
|
+
const res = await k.browsers.replays.list(runtimeSessionId);
|
|
17109
|
+
return res.map((r) => ({
|
|
17110
|
+
replayId: r.replay_id,
|
|
17111
|
+
viewUrl: r.replay_view_url ?? null,
|
|
17112
|
+
startedAt: r.started_at ?? null,
|
|
17113
|
+
finishedAt: r.finished_at ?? null
|
|
17114
|
+
}));
|
|
17115
|
+
}
|
|
17116
|
+
var import_sdk6, import_playwright4, DEFAULT_TIMEOUT_SECONDS;
|
|
17117
|
+
var init_browser_agent_service = __esm({
|
|
17118
|
+
"src/services/browser-agent/browser-agent-service.ts"() {
|
|
17119
|
+
"use strict";
|
|
17120
|
+
import_sdk6 = __toESM(require("@onkernel/sdk"), 1);
|
|
17121
|
+
import_playwright4 = require("playwright");
|
|
17122
|
+
init_browser_service_env();
|
|
17123
|
+
DEFAULT_TIMEOUT_SECONDS = 600;
|
|
17124
|
+
}
|
|
17125
|
+
});
|
|
17126
|
+
|
|
17127
|
+
// src/api/browser-agent-routes.ts
|
|
17128
|
+
async function charge(sessionId, userId, startedAtMs) {
|
|
17129
|
+
const elapsedMs = Date.now() - startedAtMs;
|
|
17130
|
+
const { active_ms, billed_mc } = await addActiveMs(sessionId, elapsedMs);
|
|
17131
|
+
const owed = browserActiveCostMc(active_ms);
|
|
17132
|
+
const delta = owed - billed_mc;
|
|
17133
|
+
if (delta > 0) {
|
|
17134
|
+
const res = await debitMc(userId, delta, LedgerOperation.BROWSER_SESSION, sessionId);
|
|
17135
|
+
if (res.ok) await setBilledMc(sessionId, owed);
|
|
17136
|
+
}
|
|
17137
|
+
}
|
|
17138
|
+
function publicSession(row) {
|
|
17139
|
+
return {
|
|
17140
|
+
session_id: row.id,
|
|
17141
|
+
status: row.status,
|
|
17142
|
+
label: row.label,
|
|
17143
|
+
created_at: row.created_at,
|
|
17144
|
+
last_action_at: row.last_action_at,
|
|
17145
|
+
closed_at: row.closed_at,
|
|
17146
|
+
active_seconds: Math.round((row.active_ms ?? 0) / 1e3),
|
|
17147
|
+
credits_used: Math.round((row.billed_mc ?? 0) / 10) / 100
|
|
17148
|
+
};
|
|
17149
|
+
}
|
|
17150
|
+
function failure(err) {
|
|
17151
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17152
|
+
return { error: sanitizeVendorName(msg) };
|
|
17153
|
+
}
|
|
17154
|
+
async function loadOpenSession(id, userId) {
|
|
17155
|
+
const row = await getSessionRow(id);
|
|
17156
|
+
if (!row) return null;
|
|
17157
|
+
if (row.user_id != null && row.user_id !== userId) return null;
|
|
17158
|
+
return row;
|
|
17159
|
+
}
|
|
17160
|
+
function buildBrowserAgentRoutes() {
|
|
17161
|
+
const app2 = new import_hono8.Hono();
|
|
17162
|
+
app2.use("*", async (c, next) => {
|
|
17163
|
+
await migrateBrowserAgent();
|
|
17164
|
+
return next();
|
|
17165
|
+
});
|
|
17166
|
+
app2.use("*", auth);
|
|
17167
|
+
app2.post("/sessions", async (c) => {
|
|
17168
|
+
const user = c.get("user");
|
|
17169
|
+
if (Number(user.balance_mc ?? 0) < BROWSER_OPEN_MIN_BALANCE_MC) {
|
|
17170
|
+
return c.json(insufficientBalanceResponse(Number(user.balance_mc ?? 0), BROWSER_OPEN_MIN_BALANCE_MC), 402);
|
|
17171
|
+
}
|
|
17172
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17173
|
+
try {
|
|
17174
|
+
const created = await createSession({
|
|
17175
|
+
timeoutSeconds: typeof body.timeout_seconds === "number" ? body.timeout_seconds : void 0,
|
|
17176
|
+
proxyId: typeof body.proxy_id === "string" ? body.proxy_id : void 0,
|
|
17177
|
+
profileName: typeof body.profile === "string" ? body.profile : void 0,
|
|
17178
|
+
disableDefaultProxy: body.disable_default_proxy === true,
|
|
17179
|
+
viewport: body.viewport && typeof body.viewport === "object" ? body.viewport : void 0
|
|
17180
|
+
});
|
|
17181
|
+
const row = await createSessionRow({
|
|
17182
|
+
runtimeSessionId: created.runtimeSessionId,
|
|
17183
|
+
liveViewUrl: created.liveViewUrl,
|
|
17184
|
+
cdpWsUrl: created.cdpWsUrl,
|
|
17185
|
+
label: typeof body.label === "string" ? body.label : null,
|
|
17186
|
+
userId: user.id
|
|
17187
|
+
});
|
|
17188
|
+
return c.json({ ...publicSession(row), watch_url: `/console/${row.id}` });
|
|
17189
|
+
} catch (err) {
|
|
17190
|
+
return c.json(failure(err), 502);
|
|
17191
|
+
}
|
|
17192
|
+
});
|
|
17193
|
+
app2.get("/sessions", async (c) => {
|
|
17194
|
+
const user = c.get("user");
|
|
17195
|
+
const includeClosed = c.req.query("all") === "1";
|
|
17196
|
+
const rows = await listSessionRows(user.id, includeClosed);
|
|
17197
|
+
return c.json({ sessions: rows.map(publicSession) });
|
|
17198
|
+
});
|
|
17199
|
+
app2.get("/sessions/:id", async (c) => {
|
|
17200
|
+
const user = c.get("user");
|
|
17201
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17202
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17203
|
+
return c.json({ ...publicSession(row), watch_url: `/console/${row.id}` });
|
|
17204
|
+
});
|
|
17205
|
+
app2.get("/sessions/:id/live-view", async (c) => {
|
|
17206
|
+
const user = c.get("user");
|
|
17207
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17208
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17209
|
+
return c.json({ live_view_url: row.live_view_url });
|
|
17210
|
+
});
|
|
17211
|
+
app2.delete("/sessions/:id", async (c) => {
|
|
17212
|
+
const user = c.get("user");
|
|
17213
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17214
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17215
|
+
try {
|
|
17216
|
+
await closeSession(row.runtime_session_id);
|
|
17217
|
+
} catch {
|
|
17218
|
+
}
|
|
17219
|
+
await markSessionClosed(row.id);
|
|
17220
|
+
return c.json({ ok: true });
|
|
17221
|
+
});
|
|
17222
|
+
app2.post("/sessions/:id/goto", async (c) => {
|
|
17223
|
+
const user = c.get("user");
|
|
17224
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17225
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17226
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17227
|
+
const url = typeof body.url === "string" ? body.url : "";
|
|
17228
|
+
if (!url) return c.json({ error: "url is required" }, 400);
|
|
17229
|
+
const t0 = Date.now();
|
|
17230
|
+
try {
|
|
17231
|
+
const result = await goto(row.cdp_ws_url, url);
|
|
17232
|
+
await charge(row.id, user.id, t0);
|
|
17233
|
+
await recordAction({ sessionId: row.id, type: "goto", params: { url }, ok: true });
|
|
17234
|
+
return c.json(result);
|
|
17235
|
+
} catch (err) {
|
|
17236
|
+
await charge(row.id, user.id, t0);
|
|
17237
|
+
await recordAction({ sessionId: row.id, type: "goto", params: { url }, ok: false, error: String(err) });
|
|
17238
|
+
return c.json(failure(err), 502);
|
|
17239
|
+
}
|
|
17240
|
+
});
|
|
17241
|
+
app2.post("/sessions/:id/screenshot", async (c) => {
|
|
17242
|
+
const user = c.get("user");
|
|
17243
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17244
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17245
|
+
const t0 = Date.now();
|
|
17246
|
+
try {
|
|
17247
|
+
const shot = await screenshot(row.runtime_session_id);
|
|
17248
|
+
let page = null;
|
|
17249
|
+
try {
|
|
17250
|
+
page = await readPage(row.cdp_ws_url);
|
|
17251
|
+
} catch {
|
|
17252
|
+
page = null;
|
|
17253
|
+
}
|
|
17254
|
+
await charge(row.id, user.id, t0);
|
|
17255
|
+
await recordAction({ sessionId: row.id, type: "screenshot", params: null, ok: true });
|
|
17256
|
+
return c.json({
|
|
17257
|
+
image_base64: shot.base64,
|
|
17258
|
+
mime_type: shot.mimeType,
|
|
17259
|
+
url: page?.url ?? null,
|
|
17260
|
+
title: page?.title ?? null,
|
|
17261
|
+
elements: page?.elements ?? [],
|
|
17262
|
+
text: page?.text ?? null
|
|
17263
|
+
});
|
|
17264
|
+
} catch (err) {
|
|
17265
|
+
await charge(row.id, user.id, t0);
|
|
17266
|
+
await recordAction({ sessionId: row.id, type: "screenshot", params: null, ok: false, error: String(err) });
|
|
17267
|
+
return c.json(failure(err), 502);
|
|
17268
|
+
}
|
|
17269
|
+
});
|
|
17270
|
+
app2.post("/sessions/:id/read", async (c) => {
|
|
17271
|
+
const user = c.get("user");
|
|
17272
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17273
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17274
|
+
const t0 = Date.now();
|
|
17275
|
+
try {
|
|
17276
|
+
const page = await readPage(row.cdp_ws_url);
|
|
17277
|
+
await charge(row.id, user.id, t0);
|
|
17278
|
+
await recordAction({ sessionId: row.id, type: "read", params: null, ok: true });
|
|
17279
|
+
return c.json(page);
|
|
17280
|
+
} catch (err) {
|
|
17281
|
+
await charge(row.id, user.id, t0);
|
|
17282
|
+
return c.json(failure(err), 502);
|
|
17283
|
+
}
|
|
17284
|
+
});
|
|
17285
|
+
app2.post("/sessions/:id/click", async (c) => {
|
|
17286
|
+
const user = c.get("user");
|
|
17287
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17288
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17289
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17290
|
+
const x = Number(body.x);
|
|
17291
|
+
const y = Number(body.y);
|
|
17292
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return c.json({ error: "x and y are required" }, 400);
|
|
17293
|
+
const t0 = Date.now();
|
|
17294
|
+
try {
|
|
17295
|
+
await click(row.runtime_session_id, x, y, {
|
|
17296
|
+
button: body.button === "right" || body.button === "middle" ? body.button : "left",
|
|
17297
|
+
numClicks: typeof body.num_clicks === "number" ? body.num_clicks : void 0
|
|
17298
|
+
});
|
|
17299
|
+
await charge(row.id, user.id, t0);
|
|
17300
|
+
await recordAction({ sessionId: row.id, type: "click", params: { x, y }, ok: true });
|
|
17301
|
+
return c.json({ ok: true });
|
|
17302
|
+
} catch (err) {
|
|
17303
|
+
await charge(row.id, user.id, t0);
|
|
17304
|
+
await recordAction({ sessionId: row.id, type: "click", params: { x, y }, ok: false, error: String(err) });
|
|
17305
|
+
return c.json(failure(err), 502);
|
|
17306
|
+
}
|
|
17307
|
+
});
|
|
17308
|
+
app2.post("/sessions/:id/type", async (c) => {
|
|
17309
|
+
const user = c.get("user");
|
|
17310
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17311
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17312
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17313
|
+
const text = typeof body.text === "string" ? body.text : "";
|
|
17314
|
+
if (!text) return c.json({ error: "text is required" }, 400);
|
|
17315
|
+
const t0 = Date.now();
|
|
17316
|
+
try {
|
|
17317
|
+
await typeText(row.runtime_session_id, text, typeof body.delay === "number" ? body.delay : void 0);
|
|
17318
|
+
await charge(row.id, user.id, t0);
|
|
17319
|
+
await recordAction({ sessionId: row.id, type: "type", params: { length: text.length }, ok: true });
|
|
17320
|
+
return c.json({ ok: true });
|
|
17321
|
+
} catch (err) {
|
|
17322
|
+
await charge(row.id, user.id, t0);
|
|
17323
|
+
return c.json(failure(err), 502);
|
|
17324
|
+
}
|
|
17325
|
+
});
|
|
17326
|
+
app2.post("/sessions/:id/scroll", async (c) => {
|
|
17327
|
+
const user = c.get("user");
|
|
17328
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17329
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17330
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17331
|
+
const x = typeof body.x === "number" ? body.x : 640;
|
|
17332
|
+
const y = typeof body.y === "number" ? body.y : 400;
|
|
17333
|
+
const deltaX = typeof body.delta_x === "number" ? body.delta_x : 0;
|
|
17334
|
+
const deltaY = typeof body.delta_y === "number" ? body.delta_y : 5;
|
|
17335
|
+
const t0 = Date.now();
|
|
17336
|
+
try {
|
|
17337
|
+
await scroll(row.runtime_session_id, x, y, deltaX, deltaY);
|
|
17338
|
+
await charge(row.id, user.id, t0);
|
|
17339
|
+
await recordAction({ sessionId: row.id, type: "scroll", params: { deltaX, deltaY }, ok: true });
|
|
17340
|
+
return c.json({ ok: true });
|
|
17341
|
+
} catch (err) {
|
|
17342
|
+
await charge(row.id, user.id, t0);
|
|
17343
|
+
return c.json(failure(err), 502);
|
|
17344
|
+
}
|
|
17345
|
+
});
|
|
17346
|
+
app2.post("/sessions/:id/press", async (c) => {
|
|
17347
|
+
const user = c.get("user");
|
|
17348
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17349
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17350
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17351
|
+
const keys = Array.isArray(body.keys) ? body.keys.map(String) : [];
|
|
17352
|
+
if (!keys.length) return c.json({ error: "keys is required" }, 400);
|
|
17353
|
+
const t0 = Date.now();
|
|
17354
|
+
try {
|
|
17355
|
+
await pressKeys(row.runtime_session_id, keys);
|
|
17356
|
+
await charge(row.id, user.id, t0);
|
|
17357
|
+
await recordAction({ sessionId: row.id, type: "press", params: { keys }, ok: true });
|
|
17358
|
+
return c.json({ ok: true });
|
|
17359
|
+
} catch (err) {
|
|
17360
|
+
await charge(row.id, user.id, t0);
|
|
17361
|
+
return c.json(failure(err), 502);
|
|
17362
|
+
}
|
|
17363
|
+
});
|
|
17364
|
+
app2.post("/sessions/:id/replay/start", async (c) => {
|
|
17365
|
+
const user = c.get("user");
|
|
17366
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17367
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17368
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17369
|
+
try {
|
|
17370
|
+
const started = await replayStart(row.runtime_session_id);
|
|
17371
|
+
await recordReplayStart({
|
|
17372
|
+
sessionId: row.id,
|
|
17373
|
+
replayId: started.replayId,
|
|
17374
|
+
viewUrl: started.viewUrl,
|
|
17375
|
+
label: typeof body.label === "string" ? body.label : null
|
|
17376
|
+
});
|
|
17377
|
+
return c.json({ replay_id: started.replayId });
|
|
17378
|
+
} catch (err) {
|
|
17379
|
+
return c.json(failure(err), 502);
|
|
17380
|
+
}
|
|
17381
|
+
});
|
|
17382
|
+
app2.post("/sessions/:id/replay/stop", async (c) => {
|
|
17383
|
+
const user = c.get("user");
|
|
17384
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17385
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17386
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17387
|
+
const replayId = typeof body.replay_id === "string" ? body.replay_id : "";
|
|
17388
|
+
if (!replayId) return c.json({ error: "replay_id is required" }, 400);
|
|
17389
|
+
try {
|
|
17390
|
+
await replayStop(row.runtime_session_id, replayId);
|
|
17391
|
+
let viewUrl = null;
|
|
17392
|
+
try {
|
|
17393
|
+
const all = await replayList(row.runtime_session_id);
|
|
17394
|
+
viewUrl = all.find((r) => r.replayId === replayId)?.viewUrl ?? null;
|
|
17395
|
+
} catch {
|
|
17396
|
+
viewUrl = null;
|
|
17397
|
+
}
|
|
17398
|
+
await recordReplayStop(replayId, viewUrl);
|
|
17399
|
+
return c.json({ ok: true });
|
|
17400
|
+
} catch (err) {
|
|
17401
|
+
return c.json(failure(err), 502);
|
|
17402
|
+
}
|
|
17403
|
+
});
|
|
17404
|
+
app2.get("/sessions/:id/replays", async (c) => {
|
|
17405
|
+
const user = c.get("user");
|
|
17406
|
+
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
17407
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17408
|
+
const rows = await listReplayRows(row.id);
|
|
17409
|
+
return c.json({
|
|
17410
|
+
replays: rows.map((r) => ({
|
|
17411
|
+
replay_id: r.replay_id,
|
|
17412
|
+
view_url: r.view_url,
|
|
17413
|
+
label: r.label,
|
|
17414
|
+
started_at: r.started_at,
|
|
17415
|
+
stopped_at: r.stopped_at
|
|
17416
|
+
}))
|
|
17417
|
+
});
|
|
17418
|
+
});
|
|
17419
|
+
return app2;
|
|
17420
|
+
}
|
|
17421
|
+
var import_hono8, auth;
|
|
17422
|
+
var init_browser_agent_routes = __esm({
|
|
17423
|
+
"src/api/browser-agent-routes.ts"() {
|
|
17424
|
+
"use strict";
|
|
17425
|
+
import_hono8 = require("hono");
|
|
17426
|
+
init_api_auth();
|
|
17427
|
+
init_errors();
|
|
17428
|
+
init_db();
|
|
17429
|
+
init_rates();
|
|
17430
|
+
init_browser_agent_db();
|
|
17431
|
+
init_browser_agent_service();
|
|
17432
|
+
auth = createApiKeyAuth();
|
|
17433
|
+
}
|
|
17434
|
+
});
|
|
17435
|
+
|
|
17436
|
+
// src/api/browser-agent-console.ts
|
|
17437
|
+
function renderConsoleHtml(initialSessionId) {
|
|
17438
|
+
const initial = JSON.stringify(initialSessionId ?? "");
|
|
17439
|
+
return `<!DOCTYPE html>
|
|
17440
|
+
<html lang="en">
|
|
17441
|
+
<head>
|
|
17442
|
+
<meta charset="UTF-8" />
|
|
17443
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
17444
|
+
<title>Browser Agent Console</title>
|
|
17445
|
+
<style>
|
|
17446
|
+
:root { color-scheme: dark; }
|
|
17447
|
+
:where(*) { box-sizing: border-box; }
|
|
17448
|
+
body { margin: 0; font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif; background: #0b0e14; color: #d7dce5; }
|
|
17449
|
+
header { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid #1c2230; background: #0f131c; }
|
|
17450
|
+
header h1 { font-size: 15px; margin: 0; font-weight: 600; color: #fff; letter-spacing: .2px; }
|
|
17451
|
+
header .spacer { flex: 1; }
|
|
17452
|
+
input, button, select { font: inherit; }
|
|
17453
|
+
input[type=text], input[type=password], input[type=url] { background: #141925; border: 1px solid #232b3a; color: #e6eaf2; border-radius: 7px; padding: 7px 10px; }
|
|
17454
|
+
button { background: #2b6cff; border: 0; color: #fff; border-radius: 7px; padding: 7px 12px; cursor: pointer; font-weight: 500; }
|
|
17455
|
+
button.ghost { background: #1a2030; color: #cdd5e4; border: 1px solid #28303f; }
|
|
17456
|
+
button:disabled { opacity: .5; cursor: default; }
|
|
17457
|
+
.layout { display: grid; grid-template-columns: 280px 1fr; height: calc(100vh - 53px); }
|
|
17458
|
+
aside { border-right: 1px solid #1c2230; overflow-y: auto; padding: 12px; }
|
|
17459
|
+
aside h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: #6b7689; margin: 4px 4px 10px; }
|
|
17460
|
+
.sess { padding: 9px 10px; border-radius: 8px; border: 1px solid #1c2230; margin-bottom: 8px; cursor: pointer; }
|
|
17461
|
+
.sess:hover { border-color: #2b6cff; }
|
|
17462
|
+
.sess.active { border-color: #2b6cff; background: #131b2e; }
|
|
17463
|
+
.sess .id { font-family: ui-monospace, monospace; font-size: 12px; color: #aeb8cc; }
|
|
17464
|
+
.sess .meta { font-size: 11px; color: #6b7689; margin-top: 3px; }
|
|
17465
|
+
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px; }
|
|
17466
|
+
.dot.open { background: #36d399; } .dot.closed { background: #5a6677; }
|
|
17467
|
+
main { display: flex; flex-direction: column; overflow: hidden; }
|
|
17468
|
+
.toolbar { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-bottom: 1px solid #1c2230; }
|
|
17469
|
+
.toolbar label { font-size: 13px; color: #aeb8cc; display: flex; align-items: center; gap: 6px; }
|
|
17470
|
+
.stage { flex: 1; position: relative; background: #05070b; overflow: auto; }
|
|
17471
|
+
.stage iframe { width: 100%; height: 100%; border: 0; display: block; }
|
|
17472
|
+
.empty { display: flex; align-items: center; justify-content: center; height: 100%; color: #5a6677; flex-direction: column; gap: 10px; }
|
|
17473
|
+
.replays { border-top: 1px solid #1c2230; padding: 10px 16px; max-height: 200px; overflow-y: auto; }
|
|
17474
|
+
.replays h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: #6b7689; margin: 0 0 8px; }
|
|
17475
|
+
.replay { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 13px; }
|
|
17476
|
+
.replay a { color: #7aa2ff; }
|
|
17477
|
+
.gate { max-width: 380px; margin: 80px auto; padding: 24px; border: 1px solid #1c2230; border-radius: 12px; background: #0f131c; }
|
|
17478
|
+
.gate h2 { margin: 0 0 6px; font-size: 16px; color: #fff; }
|
|
17479
|
+
.gate p { color: #8893a7; margin: 0 0 16px; }
|
|
17480
|
+
.gate input { width: 100%; margin-bottom: 12px; }
|
|
17481
|
+
.gate button { width: 100%; }
|
|
17482
|
+
.muted { color: #6b7689; font-size: 12px; }
|
|
17483
|
+
</style>
|
|
17484
|
+
</head>
|
|
17485
|
+
<body>
|
|
17486
|
+
<div id="app"></div>
|
|
17487
|
+
<script>
|
|
17488
|
+
const INITIAL_SESSION = ${initial};
|
|
17489
|
+
const KEY_STORE = 'browser_agent_api_key';
|
|
17490
|
+
let state = { key: localStorage.getItem(KEY_STORE) || '', sessions: [], current: INITIAL_SESSION || null, readOnly: true, liveUrl: null, replays: [] };
|
|
17491
|
+
|
|
17492
|
+
function api(method, path, body) {
|
|
17493
|
+
return fetch('/agent' + path, {
|
|
17494
|
+
method,
|
|
17495
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': state.key },
|
|
17496
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
17497
|
+
}).then(async r => ({ ok: r.ok, data: await r.json().catch(() => ({})) }));
|
|
17498
|
+
}
|
|
17499
|
+
|
|
17500
|
+
async function refreshSessions() {
|
|
17501
|
+
const r = await api('GET', '/sessions?all=1');
|
|
17502
|
+
if (r.ok) { state.sessions = r.data.sessions || []; render(); }
|
|
17503
|
+
}
|
|
17504
|
+
|
|
17505
|
+
async function selectSession(id) {
|
|
17506
|
+
state.current = id; state.liveUrl = null; state.replays = [];
|
|
17507
|
+
history.replaceState(null, '', '/console/' + id);
|
|
17508
|
+
render();
|
|
17509
|
+
const live = await api('GET', '/sessions/' + id + '/live-view');
|
|
17510
|
+
state.liveUrl = live.ok ? live.data.live_view_url : null;
|
|
17511
|
+
const reps = await api('GET', '/sessions/' + id + '/replays');
|
|
17512
|
+
state.replays = reps.ok ? (reps.data.replays || []) : [];
|
|
17513
|
+
render();
|
|
17514
|
+
}
|
|
17515
|
+
|
|
17516
|
+
async function openSession() {
|
|
17517
|
+
const r = await api('POST', '/sessions', { label: 'console' });
|
|
17518
|
+
if (r.ok) { await refreshSessions(); selectSession(r.data.session_id); }
|
|
17519
|
+
else alert('Open failed: ' + JSON.stringify(r.data));
|
|
17520
|
+
}
|
|
17521
|
+
|
|
17522
|
+
async function closeCurrent() {
|
|
17523
|
+
if (!state.current) return;
|
|
17524
|
+
await api('DELETE', '/sessions/' + state.current);
|
|
17525
|
+
await refreshSessions();
|
|
17526
|
+
}
|
|
17527
|
+
|
|
17528
|
+
function frameSrc() {
|
|
17529
|
+
if (!state.liveUrl) return null;
|
|
17530
|
+
const sep = state.liveUrl.includes('?') ? '&' : '?';
|
|
17531
|
+
return state.readOnly ? state.liveUrl + sep + 'readOnly=true' : state.liveUrl;
|
|
17532
|
+
}
|
|
17533
|
+
|
|
17534
|
+
function saveKey(v) { state.key = v.trim(); localStorage.setItem(KEY_STORE, state.key); render(); if (state.key) { refreshSessions(); if (state.current) selectSession(state.current); } }
|
|
17535
|
+
|
|
17536
|
+
function h(html) { const t = document.createElement('template'); t.innerHTML = html.trim(); return t.content.firstChild; }
|
|
17537
|
+
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); }
|
|
17538
|
+
|
|
17539
|
+
function render() {
|
|
17540
|
+
const app = document.getElementById('app');
|
|
17541
|
+
app.innerHTML = '';
|
|
17542
|
+
if (!state.key) {
|
|
17543
|
+
app.appendChild(h('<div class="gate"><h2>Browser Agent Console</h2><p>Paste your API key to watch and control browser sessions.</p><input id="k" type="password" placeholder="API key" /><button id="kb">Continue</button></div>'));
|
|
17544
|
+
document.getElementById('kb').onclick = () => saveKey(document.getElementById('k').value);
|
|
17545
|
+
document.getElementById('k').onkeydown = e => { if (e.key === 'Enter') saveKey(e.target.value); };
|
|
17546
|
+
return;
|
|
17547
|
+
}
|
|
17548
|
+
const header = h('<header><h1>Browser Agent</h1><span class="muted">live control + replays</span><span class="spacer"></span><button id="open">+ New Session</button><button class="ghost" id="logout">Forget key</button></header>');
|
|
17549
|
+
app.appendChild(header);
|
|
17550
|
+
document.getElementById('open').onclick = openSession;
|
|
17551
|
+
document.getElementById('logout').onclick = () => saveKey('');
|
|
17552
|
+
|
|
17553
|
+
const layout = h('<div class="layout"></div>');
|
|
17554
|
+
const aside = h('<aside><h2>Sessions</h2></aside>');
|
|
17555
|
+
if (!state.sessions.length) aside.appendChild(h('<div class="muted" style="padding:4px">No sessions yet.</div>'));
|
|
17556
|
+
for (const s of state.sessions) {
|
|
17557
|
+
const el = h('<div class="sess ' + (s.session_id === state.current ? 'active' : '') + '"><div class="id">' + esc(s.session_id) + '</div><div class="meta"><span class="dot ' + esc(s.status) + '"></span>' + esc(s.status) + (s.label ? ' \xB7 ' + esc(s.label) : '') + '</div></div>');
|
|
17558
|
+
el.onclick = () => selectSession(s.session_id);
|
|
17559
|
+
aside.appendChild(el);
|
|
17560
|
+
}
|
|
17561
|
+
layout.appendChild(aside);
|
|
17562
|
+
|
|
17563
|
+
const main = h('<main></main>');
|
|
17564
|
+
if (!state.current) {
|
|
17565
|
+
main.appendChild(h('<div class="empty"><div>Select or open a session to watch.</div></div>'));
|
|
17566
|
+
} else {
|
|
17567
|
+
const tb = h('<div class="toolbar"><label><input type="checkbox" id="ro" ' + (state.readOnly ? 'checked' : '') + ' /> Read-only (uncheck to take control)</label><span class="spacer"></span><button class="ghost" id="reload">Reload view</button><button class="ghost" id="close">Close session</button></div>');
|
|
17568
|
+
main.appendChild(tb);
|
|
17569
|
+
const stage = h('<div class="stage"></div>');
|
|
17570
|
+
const src = frameSrc();
|
|
17571
|
+
if (src) {
|
|
17572
|
+
const f = h('<iframe allow="autoplay; clipboard-read; clipboard-write" src="' + esc(src) + '"></iframe>');
|
|
17573
|
+
stage.appendChild(f);
|
|
17574
|
+
} else {
|
|
17575
|
+
stage.appendChild(h('<div class="empty"><div>Live view unavailable for this session.</div><div class="muted">It may be closed or still starting.</div></div>'));
|
|
17576
|
+
}
|
|
17577
|
+
main.appendChild(stage);
|
|
17578
|
+
|
|
17579
|
+
const rep = h('<div class="replays"><h3>Replays</h3></div>');
|
|
17580
|
+
if (!state.replays.length) rep.appendChild(h('<div class="muted">No replays recorded.</div>'));
|
|
17581
|
+
for (const r of state.replays) {
|
|
17582
|
+
const status = r.stopped_at ? 'ready' : 'recording\u2026';
|
|
17583
|
+
const link = r.view_url ? '<a href="' + esc(r.view_url) + '" target="_blank" rel="noopener">view mp4</a>' : '<span class="muted">' + status + '</span>';
|
|
17584
|
+
rep.appendChild(h('<div class="replay"><span class="muted">' + esc(r.started_at || '') + '</span><span class="spacer"></span>' + link + '</div>'));
|
|
17585
|
+
}
|
|
17586
|
+
main.appendChild(rep);
|
|
17587
|
+
|
|
17588
|
+
layout.appendChild(main);
|
|
17589
|
+
}
|
|
17590
|
+
app.appendChild(layout);
|
|
17591
|
+
|
|
17592
|
+
const ro = document.getElementById('ro');
|
|
17593
|
+
if (ro) ro.onchange = e => { state.readOnly = e.target.checked; render(); };
|
|
17594
|
+
const reload = document.getElementById('reload');
|
|
17595
|
+
if (reload) reload.onclick = () => selectSession(state.current);
|
|
17596
|
+
const close = document.getElementById('close');
|
|
17597
|
+
if (close) close.onclick = closeCurrent;
|
|
17598
|
+
}
|
|
17599
|
+
|
|
17600
|
+
render();
|
|
17601
|
+
if (state.key) { refreshSessions(); if (state.current) selectSession(state.current); }
|
|
17602
|
+
</script>
|
|
17603
|
+
</body>
|
|
17604
|
+
</html>`;
|
|
17605
|
+
}
|
|
17606
|
+
var init_browser_agent_console = __esm({
|
|
17607
|
+
"src/api/browser-agent-console.ts"() {
|
|
17608
|
+
"use strict";
|
|
17609
|
+
}
|
|
17610
|
+
});
|
|
17611
|
+
|
|
16812
17612
|
// src/api/stripe-routes.ts
|
|
16813
|
-
var import_stripe,
|
|
17613
|
+
var import_stripe, import_hono9, stripe, stripeApp;
|
|
16814
17614
|
var init_stripe_routes = __esm({
|
|
16815
17615
|
"src/api/stripe-routes.ts"() {
|
|
16816
17616
|
"use strict";
|
|
16817
17617
|
import_stripe = __toESM(require("stripe"), 1);
|
|
16818
|
-
|
|
17618
|
+
import_hono9 = require("hono");
|
|
16819
17619
|
init_db();
|
|
16820
17620
|
init_rates();
|
|
16821
17621
|
stripe = new import_stripe.default(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
16822
|
-
stripeApp = new
|
|
17622
|
+
stripeApp = new import_hono9.Hono();
|
|
16823
17623
|
stripeApp.post("/webhooks", async (c) => {
|
|
16824
17624
|
const sig = c.req.header("stripe-signature");
|
|
16825
17625
|
const body = await c.req.text();
|
|
@@ -17106,14 +17906,14 @@ function getSessionSecret() {
|
|
|
17106
17906
|
function safeEqualHex(a, b) {
|
|
17107
17907
|
if (a.length !== b.length) return false;
|
|
17108
17908
|
try {
|
|
17109
|
-
return (0,
|
|
17909
|
+
return (0, import_node_crypto4.timingSafeEqual)(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
17110
17910
|
} catch {
|
|
17111
17911
|
return false;
|
|
17112
17912
|
}
|
|
17113
17913
|
}
|
|
17114
17914
|
function signSession(userId) {
|
|
17115
17915
|
const payload = String(userId);
|
|
17116
|
-
const sig = (0,
|
|
17916
|
+
const sig = (0, import_node_crypto4.createHmac)("sha256", secret()).update(payload).digest("hex");
|
|
17117
17917
|
return `${payload}.${sig}`;
|
|
17118
17918
|
}
|
|
17119
17919
|
function verifySession(token) {
|
|
@@ -17121,16 +17921,16 @@ function verifySession(token) {
|
|
|
17121
17921
|
if (dot === -1) return null;
|
|
17122
17922
|
const payload = token.slice(0, dot);
|
|
17123
17923
|
const sig = token.slice(dot + 1);
|
|
17124
|
-
const expected = (0,
|
|
17924
|
+
const expected = (0, import_node_crypto4.createHmac)("sha256", secret()).update(payload).digest("hex");
|
|
17125
17925
|
if (!safeEqualHex(sig, expected)) return null;
|
|
17126
17926
|
const id = parseInt(payload);
|
|
17127
17927
|
return isNaN(id) ? null : id;
|
|
17128
17928
|
}
|
|
17129
|
-
var
|
|
17929
|
+
var import_node_crypto4, isProduction, secret;
|
|
17130
17930
|
var init_session = __esm({
|
|
17131
17931
|
"src/api/session.ts"() {
|
|
17132
17932
|
"use strict";
|
|
17133
|
-
|
|
17933
|
+
import_node_crypto4 = require("crypto");
|
|
17134
17934
|
isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
17135
17935
|
secret = () => getSessionSecret();
|
|
17136
17936
|
}
|
|
@@ -17346,7 +18146,7 @@ async function checkHarvestLimits(userId, email, extraSlots = 0) {
|
|
|
17346
18146
|
if (active >= limit) return { error: `You have ${active} job${active !== 1 ? "s" : ""} running. Your account allows ${limit} concurrent job${limit !== 1 ? "s" : ""}. Wait for one to finish or add a concurrency slot at mcpscraper.dev/billing.` };
|
|
17347
18147
|
return null;
|
|
17348
18148
|
}
|
|
17349
|
-
var import_resend,
|
|
18149
|
+
var import_resend, import_hono10, import_hono11, import_factory6, import_cookie, import_stripe2, secureCookies, isProduction2, sessionCookieOptions, requireAllowedOrigin, auth2, adminAuth, sessionAuth, app, STRIPE_API_VERSION, BYPASS_EMAILS, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
|
|
17350
18150
|
var init_server = __esm({
|
|
17351
18151
|
"src/api/server.ts"() {
|
|
17352
18152
|
"use strict";
|
|
@@ -17363,8 +18163,8 @@ var init_server = __esm({
|
|
|
17363
18163
|
init_media_extractor();
|
|
17364
18164
|
init_site_mapper();
|
|
17365
18165
|
init_site_extractor();
|
|
17366
|
-
|
|
17367
|
-
|
|
18166
|
+
import_hono10 = require("hono");
|
|
18167
|
+
import_hono11 = require("inngest/hono");
|
|
17368
18168
|
init_client();
|
|
17369
18169
|
init_site_audit();
|
|
17370
18170
|
init_site_audit_routes();
|
|
@@ -17374,6 +18174,8 @@ var init_server = __esm({
|
|
|
17374
18174
|
init_maps_routes();
|
|
17375
18175
|
init_serp_intelligence_routes();
|
|
17376
18176
|
init_mcp_routes();
|
|
18177
|
+
init_browser_agent_routes();
|
|
18178
|
+
init_browser_agent_console();
|
|
17377
18179
|
init_stripe_routes();
|
|
17378
18180
|
init_site_audit_worker();
|
|
17379
18181
|
import_factory6 = require("hono/factory");
|
|
@@ -17403,7 +18205,7 @@ var init_server = __esm({
|
|
|
17403
18205
|
if (!configuredOrigins().has(origin)) return c.json({ error: "Origin not allowed" }, 403);
|
|
17404
18206
|
return next();
|
|
17405
18207
|
});
|
|
17406
|
-
|
|
18208
|
+
auth2 = (0, import_factory6.createMiddleware)(async (c, next) => {
|
|
17407
18209
|
const key = c.req.header("x-api-key");
|
|
17408
18210
|
if (!key) return c.json({ error: "Missing API key" }, 401);
|
|
17409
18211
|
const user = await getUserByApiKey(key);
|
|
@@ -17432,7 +18234,7 @@ var init_server = __esm({
|
|
|
17432
18234
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
17433
18235
|
return next();
|
|
17434
18236
|
});
|
|
17435
|
-
app = new
|
|
18237
|
+
app = new import_hono10.Hono();
|
|
17436
18238
|
STRIPE_API_VERSION = "2026-02-25.clover";
|
|
17437
18239
|
app.use("*", async (c, next) => {
|
|
17438
18240
|
await next();
|
|
@@ -17592,7 +18394,7 @@ var init_server = __esm({
|
|
|
17592
18394
|
const parsed = raw === void 0 ? NaN : Number(raw);
|
|
17593
18395
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
17594
18396
|
})();
|
|
17595
|
-
app.post("/harvest",
|
|
18397
|
+
app.post("/harvest", auth2, async (c) => {
|
|
17596
18398
|
const user = c.get("user");
|
|
17597
18399
|
const raw = await c.req.json().catch(() => ({}));
|
|
17598
18400
|
const bodyResult = HarvestBodySchema.safeParse(raw);
|
|
@@ -17634,7 +18436,7 @@ var init_server = __esm({
|
|
|
17634
18436
|
}
|
|
17635
18437
|
return c.json({ job_id: jobId, status: "pending" }, 202);
|
|
17636
18438
|
});
|
|
17637
|
-
app.post("/harvest/sync",
|
|
18439
|
+
app.post("/harvest/sync", auth2, async (c) => {
|
|
17638
18440
|
const user = c.get("user");
|
|
17639
18441
|
const raw = await c.req.json().catch(() => ({}));
|
|
17640
18442
|
const bodyResult = HarvestBodySchema.safeParse(raw);
|
|
@@ -17699,17 +18501,17 @@ var init_server = __esm({
|
|
|
17699
18501
|
return c.json({ job_id: jobId, status: "failed", ...response, attempts: sanitizeAttempts(attempts) }, problem.httpStatus);
|
|
17700
18502
|
}
|
|
17701
18503
|
});
|
|
17702
|
-
app.get("/jobs/:id",
|
|
18504
|
+
app.get("/jobs/:id", auth2, async (c) => {
|
|
17703
18505
|
const job = await getJob(c.req.param("id"), c.get("user").id);
|
|
17704
18506
|
if (!job) return c.json({ error: "Job not found" }, 404);
|
|
17705
18507
|
const attempts = await listHarvestAttempts(job.id, c.get("user").id);
|
|
17706
18508
|
const safeResult = job.result && typeof job.result === "object" ? sanitizeHarvestResult(job.result) : job.result;
|
|
17707
18509
|
return c.json({ ...job, result: safeResult, attempts: sanitizeAttempts(attempts) });
|
|
17708
18510
|
});
|
|
17709
|
-
app.get("/jobs",
|
|
18511
|
+
app.get("/jobs", auth2, async (c) => {
|
|
17710
18512
|
return c.json(await listJobs(c.get("user").id));
|
|
17711
18513
|
});
|
|
17712
|
-
app.get("/history",
|
|
18514
|
+
app.get("/history", auth2, async (c) => {
|
|
17713
18515
|
const userId = c.get("user").id;
|
|
17714
18516
|
const [jobs, events] = await Promise.all([
|
|
17715
18517
|
listJobs(userId),
|
|
@@ -17739,7 +18541,7 @@ var init_server = __esm({
|
|
|
17739
18541
|
const rows = [...jobRows, ...eventRows].sort((a, b) => String(b.ts).localeCompare(String(a.ts)));
|
|
17740
18542
|
return c.json(rows.slice(0, 100));
|
|
17741
18543
|
});
|
|
17742
|
-
app.get("/ledger",
|
|
18544
|
+
app.get("/ledger", auth2, async (c) => {
|
|
17743
18545
|
return c.json(await getLedger(c.get("user").id, 100));
|
|
17744
18546
|
});
|
|
17745
18547
|
app.post("/admin/users", adminAuth, async (c) => {
|
|
@@ -17777,11 +18579,11 @@ var init_server = __esm({
|
|
|
17777
18579
|
}
|
|
17778
18580
|
return c.json({ processed, credited, skipped, users_credited });
|
|
17779
18581
|
});
|
|
17780
|
-
app.post("/extract-url",
|
|
18582
|
+
app.post("/extract-url", auth2, async (c) => {
|
|
17781
18583
|
const raw = await c.req.json().catch(() => ({}));
|
|
17782
18584
|
const bodyResult = ExtractUrlBodySchema.safeParse(raw);
|
|
17783
18585
|
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
17784
|
-
const { url, screenshot, screenshotDevice, extractBranding, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
|
|
18586
|
+
const { url, screenshot: screenshot2, screenshotDevice, extractBranding, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
|
|
17785
18587
|
if (!allowLocal) {
|
|
17786
18588
|
const checked = await validatePublicHttpUrl(url, { field: "URL" });
|
|
17787
18589
|
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
@@ -17807,7 +18609,7 @@ var init_server = __esm({
|
|
|
17807
18609
|
const device = screenshotDevice === "mobile" ? "mobile" : "desktop";
|
|
17808
18610
|
const [result, pageData] = await Promise.all([
|
|
17809
18611
|
extractKpo({ url: canonicalUrl, kernelApiKey }),
|
|
17810
|
-
|
|
18612
|
+
screenshot2 || extractBranding ? capturePageData(canonicalUrl, { kernelApiKey, device, screenshot: !!screenshot2, branding: !!extractBranding }).catch(() => null) : null
|
|
17811
18613
|
]);
|
|
17812
18614
|
const screenshotBuf = pageData?.screenshot ?? null;
|
|
17813
18615
|
const brandingData = pageData?.branding ?? null;
|
|
@@ -17825,7 +18627,7 @@ var init_server = __esm({
|
|
|
17825
18627
|
return c.json({ error: msg }, 500);
|
|
17826
18628
|
}
|
|
17827
18629
|
});
|
|
17828
|
-
app.post("/map-urls",
|
|
18630
|
+
app.post("/map-urls", auth2, async (c) => {
|
|
17829
18631
|
const raw = await c.req.json().catch(() => ({}));
|
|
17830
18632
|
const bodyResult = MapUrlsBodySchema.safeParse(raw);
|
|
17831
18633
|
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
@@ -17865,7 +18667,7 @@ var init_server = __esm({
|
|
|
17865
18667
|
return c.json({ error: msg }, 500);
|
|
17866
18668
|
}
|
|
17867
18669
|
});
|
|
17868
|
-
app.post("/extract-site",
|
|
18670
|
+
app.post("/extract-site", auth2, async (c) => {
|
|
17869
18671
|
const raw = await c.req.json().catch(() => ({}));
|
|
17870
18672
|
const bodyResult = ExtractSiteBodySchema.safeParse(raw);
|
|
17871
18673
|
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
@@ -17987,7 +18789,7 @@ var init_server = __esm({
|
|
|
17987
18789
|
await setConcurrencySubId(user.id, null);
|
|
17988
18790
|
return c.json({ ok: true, concurrency_limit: user.extra_concurrency_slots });
|
|
17989
18791
|
});
|
|
17990
|
-
app.get("/billing/balance",
|
|
18792
|
+
app.get("/billing/balance", auth2, async (c) => {
|
|
17991
18793
|
const user = c.get("user");
|
|
17992
18794
|
const balanceMc = await reconcileBalanceMc(user.id);
|
|
17993
18795
|
const ledger = await getLedger(user.id, 20);
|
|
@@ -17999,7 +18801,7 @@ var init_server = __esm({
|
|
|
17999
18801
|
ledger
|
|
18000
18802
|
});
|
|
18001
18803
|
});
|
|
18002
|
-
app.post("/billing/credits",
|
|
18804
|
+
app.post("/billing/credits", auth2, async (c) => {
|
|
18003
18805
|
const user = c.get("user");
|
|
18004
18806
|
const balanceMc = await reconcileBalanceMc(user.id);
|
|
18005
18807
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -18036,7 +18838,7 @@ var init_server = __esm({
|
|
|
18036
18838
|
]);
|
|
18037
18839
|
return c.json({ drained: results.length, results, sweepResult });
|
|
18038
18840
|
});
|
|
18039
|
-
app.on(["GET", "POST", "PUT"], "/api/inngest", (0,
|
|
18841
|
+
app.on(["GET", "POST", "PUT"], "/api/inngest", (0, import_hono11.serve)({ client: inngest, functions: [siteAuditFn] }));
|
|
18040
18842
|
app.route("/api/internal/site-architecture-auditor", siteAuditApp);
|
|
18041
18843
|
app.route("/youtube", youtubeApp);
|
|
18042
18844
|
app.route("/screenshot", screenshotApp);
|
|
@@ -18044,6 +18846,9 @@ var init_server = __esm({
|
|
|
18044
18846
|
app.route("/maps", mapsApp);
|
|
18045
18847
|
app.route("/serp-intelligence", serpIntelligenceApp);
|
|
18046
18848
|
app.route("/mcp", mcpApp);
|
|
18849
|
+
app.route("/agent", buildBrowserAgentRoutes());
|
|
18850
|
+
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
18851
|
+
app.get("/console/:id", (c) => c.html(renderConsoleHtml(c.req.param("id"))));
|
|
18047
18852
|
app.route("/stripe", stripeApp);
|
|
18048
18853
|
if (!process.env.INNGEST_EVENT_KEY) {
|
|
18049
18854
|
startSiteAuditWorker();
|