unbrowse 3.2.1 → 3.2.2-experiments.128ff09

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/server.js CHANGED
@@ -4210,7 +4210,7 @@ function extractEndpoints(requests, wsMessages, context) {
4210
4210
  return "";
4211
4211
  }
4212
4212
  })();
4213
- const isApiUrl = /\/(api|graphql)\b/i.test(urlPath) || /\.(json)(\?|$)/.test(req.url);
4213
+ const isApiUrl = /\/(api|graphql|youtubei|__ssr_data__)\b/i.test(urlPath) || /\.(json)(\?|$)/.test(req.url);
4214
4214
  let graphqlOpName;
4215
4215
  if (/graphql/i.test(req.url)) {
4216
4216
  if (req.request_body) {
@@ -4299,7 +4299,12 @@ function extractEndpoints(requests, wsMessages, context) {
4299
4299
  const sanitizedQParams = isGet ? sanitizeQueryParams(extractQueryParams(req.url)) : undefined;
4300
4300
  let pathTemplate = sanitizeUrlTemplate(normalized);
4301
4301
  const qBindings = sanitizedQParams ? buildQueryBindingMap(Object.keys(sanitizedQParams)) : {};
4302
- const qTemplateStr = sanitizedQParams && Object.keys(sanitizedQParams).length > 0 ? Object.keys(sanitizedQParams).map((k) => `${encodeURIComponent(k)}={${qBindings[k] ?? k}}`).join("&") : null;
4302
+ const existingQKeys = new Set;
4303
+ try {
4304
+ for (const k of new URL(pathTemplate).searchParams.keys())
4305
+ existingQKeys.add(k.toLowerCase());
4306
+ } catch {}
4307
+ const qTemplateStr = sanitizedQParams && Object.keys(sanitizedQParams).length > 0 ? Object.keys(sanitizedQParams).filter((k) => !existingQKeys.has(k.toLowerCase())).map((k) => `${encodeURIComponent(k)}={${qBindings[k] ?? k}}`).join("&") || null : null;
4303
4308
  const { url: templatizedPath, pathParams, pathBindingCandidates } = templatizePathSegments(pathTemplate, req.url, context);
4304
4309
  pathTemplate = templatizedPath;
4305
4310
  const parsedRequestBody = !isGet && req.request_body ? tryParseBody(req.request_body) : undefined;
@@ -4315,7 +4320,7 @@ function extractEndpoints(requests, wsMessages, context) {
4315
4320
  let endpoint = {
4316
4321
  endpoint_id: nanoid2(),
4317
4322
  method: req.method,
4318
- url_template: qTemplateStr ? `${pathTemplate}?${qTemplateStr}` : pathTemplate,
4323
+ url_template: qTemplateStr ? `${pathTemplate}${pathTemplate.includes("?") ? "&" : "?"}${qTemplateStr}` : pathTemplate,
4319
4324
  description: buildEndpointDescription(req, sampleRequest, sampleResponse),
4320
4325
  headers_template: sanitizeHeaders(req.request_headers),
4321
4326
  query: sanitizedQParams,
@@ -4946,7 +4951,7 @@ var init_reverse_engineer = __esm(() => {
4946
4951
  SKIP_HOSTS = /(cloudflare\.com|google-analytics\.com|doubleclick\.net|gstatic\.com|accounts\.google\.com|login\.microsoftonline\.com|auth0\.com|cognito-idp\.|appleid\.apple\.com|github\.com\/login|facebook\.com\/login|protechts\.net|demdex\.net|litms|platform-telemetry|datadoghq\.com|fullstory\.com|launchdarkly\.com|intercom\.io|privy\.io|mypinata\.cloud|sentry\.io|segment\.io|amplitude\.com|mixpanel\.com|hotjar\.com|clarity\.ms|googletagmanager\.com|walletconnect\.com|imagedelivery\.net|cloudflareinsights\.com)/i;
4947
4952
  SKIP_TELEMETRY_HOSTS = /(waa-pa\.|signaler-pa\.|appsgrowthpromo-pa\.|ogads-pa\.|peoplestackwebexperiments-pa\.)/i;
4948
4953
  SKIP_TELEMETRY_PATHS = /\/(log|logging|telemetry|analytics|beacon|ping|heartbeat|metrics)(\/|$)/i;
4949
- RPC_HINTS = /(\/$rpc\/|\/rpc\/|graphql|trending|search|feed|results|batchexecute|\/api\/)/i;
4954
+ RPC_HINTS = /(\/$rpc\/|\/rpc\/|graphql|trending|search|feed|results|batchexecute|\/api\/|youtubei|__ssr_data__)/i;
4950
4955
  ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
4951
4956
  STRIP_HEADERS = new Set([
4952
4957
  "cookie",
@@ -5024,7 +5029,180 @@ var init_reverse_engineer = __esm(() => {
5024
5029
  "adsize",
5025
5030
  "lineitemid"
5026
5031
  ]);
5027
- ON_DOMAIN_NOISE = /\/(recaptcha|captcha|update-recaptcha|csrf|consent|data-protection|badge|drawer|header-action|geolocation|onboarding|wana\/bids|prebid|bids\/request|ads\/|pixel|beacon|collect|impression|click-tracking|heartbeat|webConfig|config\.json|manifest\.json|service-worker|sw\.js|favicon|robots\.txt|sitemap|opensearch|partial\/[a-zA-Z]+\/mod-|logging|csp-report|gen_204|generate_204|sodar|__|devvit-|user-drawer|action-item)/i;
5032
+ ON_DOMAIN_NOISE = /\/(recaptcha|captcha|update-recaptcha|csrf|consent|data-protection|badge|drawer|header-action|geolocation|onboarding|wana\/bids|prebid|bids\/request|ads\/|pixel|beacon|collect|impression|click-tracking|heartbeat|webConfig|config\.json|manifest\.json|service-worker|sw\.js|favicon|robots\.txt|sitemap|opensearch|partial\/[a-zA-Z]+\/mod-|logging|csp-report|gen_204|generate_204|sodar|__webpack|__next|devvit-|user-drawer|action-item)/i;
5033
+ });
5034
+
5035
+ // ../../src/vault/index.ts
5036
+ var exports_vault = {};
5037
+ __export(exports_vault, {
5038
+ storeCredential: () => storeCredential,
5039
+ setKeytarClientForTests: () => setKeytarClientForTests,
5040
+ resetKeytarClientForTests: () => resetKeytarClientForTests,
5041
+ normalizeKeytarModule: () => normalizeKeytarModule,
5042
+ getCredential: () => getCredential,
5043
+ deleteCredential: () => deleteCredential
5044
+ });
5045
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
5046
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync2 } from "fs";
5047
+ import { join as join2 } from "path";
5048
+ import { homedir } from "os";
5049
+ function normalizeKeytarModule(mod) {
5050
+ let candidate = mod;
5051
+ for (let depth = 0;depth < 3; depth++) {
5052
+ if (!candidate || typeof candidate !== "object" || !("default" in candidate))
5053
+ break;
5054
+ candidate = candidate.default;
5055
+ }
5056
+ if (!candidate)
5057
+ return null;
5058
+ if (typeof candidate.setPassword === "function" && typeof candidate.getPassword === "function" && typeof candidate.deletePassword === "function") {
5059
+ return candidate;
5060
+ }
5061
+ return null;
5062
+ }
5063
+ function isKeytarBindingError(error) {
5064
+ const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
5065
+ return KEYTAR_BINDING_ERROR_RE.test(message);
5066
+ }
5067
+ function disableKeytar(error) {
5068
+ keytar = null;
5069
+ if (keytarFallbackLogged)
5070
+ return;
5071
+ const summary = error instanceof Error ? error.message : String(error);
5072
+ log("vault", `keytar runtime unavailable (${summary}); using encrypted file fallback`);
5073
+ keytarFallbackLogged = true;
5074
+ }
5075
+ async function callKeytar(op) {
5076
+ if (!keytar)
5077
+ return KEYTAR_UNAVAILABLE;
5078
+ try {
5079
+ return await op(keytar);
5080
+ } catch (error) {
5081
+ if (!isKeytarBindingError(error))
5082
+ throw error;
5083
+ disableKeytar(error);
5084
+ return KEYTAR_UNAVAILABLE;
5085
+ }
5086
+ }
5087
+ function setKeytarClientForTests(client) {
5088
+ keytar = client;
5089
+ keytarFallbackLogged = false;
5090
+ }
5091
+ function resetKeytarClientForTests() {
5092
+ keytar = importedKeytar;
5093
+ keytarFallbackLogged = false;
5094
+ }
5095
+ function getOrCreateKey() {
5096
+ if (!existsSync3(VAULT_DIR))
5097
+ mkdirSync3(VAULT_DIR, { recursive: true, mode: 448 });
5098
+ if (existsSync3(KEY_FILE))
5099
+ return readFileSync(KEY_FILE);
5100
+ const key = randomBytes(32);
5101
+ writeFileSync2(KEY_FILE, key, { mode: 384 });
5102
+ return key;
5103
+ }
5104
+ function withVaultLock(fn) {
5105
+ const prev = vaultLock;
5106
+ let release;
5107
+ vaultLock = new Promise((r) => {
5108
+ release = r;
5109
+ });
5110
+ return prev.then(fn).finally(() => release());
5111
+ }
5112
+ function readVaultFile() {
5113
+ if (!existsSync3(VAULT_FILE))
5114
+ return {};
5115
+ try {
5116
+ const key = getOrCreateKey();
5117
+ const raw = readFileSync(VAULT_FILE);
5118
+ const iv = raw.subarray(0, 16);
5119
+ const enc = raw.subarray(16);
5120
+ const decipher = createDecipheriv("aes-256-cbc", key, iv);
5121
+ const dec = Buffer.concat([decipher.update(enc), decipher.final()]);
5122
+ return JSON.parse(dec.toString("utf8"));
5123
+ } catch {
5124
+ return {};
5125
+ }
5126
+ }
5127
+ function writeVaultFile(data) {
5128
+ const key = getOrCreateKey();
5129
+ const iv = randomBytes(16);
5130
+ const cipher = createCipheriv("aes-256-cbc", key, iv);
5131
+ const enc = Buffer.concat([cipher.update(JSON.stringify(data), "utf8"), cipher.final()]);
5132
+ writeFileSync2(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
5133
+ }
5134
+ async function storeCredential(account, value, opts) {
5135
+ const wrapped = {
5136
+ value,
5137
+ stored_at: new Date().toISOString(),
5138
+ expires_at: opts?.expires_at,
5139
+ max_age_ms: opts?.max_age_ms
5140
+ };
5141
+ const serialized = JSON.stringify(wrapped);
5142
+ const keytarResult = await callKeytar((client) => client.setPassword(SERVICE, account, serialized));
5143
+ if (keytarResult !== KEYTAR_UNAVAILABLE)
5144
+ return;
5145
+ await withVaultLock(() => {
5146
+ const data = readVaultFile();
5147
+ data[account] = serialized;
5148
+ writeVaultFile(data);
5149
+ });
5150
+ }
5151
+ function isExpired(cred) {
5152
+ if (cred.expires_at) {
5153
+ return new Date(cred.expires_at).getTime() <= Date.now();
5154
+ }
5155
+ if (cred.max_age_ms) {
5156
+ return new Date(cred.stored_at).getTime() + cred.max_age_ms <= Date.now();
5157
+ }
5158
+ return false;
5159
+ }
5160
+ async function getCredential(account) {
5161
+ let raw;
5162
+ const keytarResult = await callKeytar((client) => client.getPassword(SERVICE, account));
5163
+ if (keytarResult !== KEYTAR_UNAVAILABLE) {
5164
+ raw = keytarResult;
5165
+ } else {
5166
+ const data = readVaultFile();
5167
+ raw = data[account] ?? null;
5168
+ }
5169
+ if (!raw)
5170
+ return null;
5171
+ try {
5172
+ const parsed = JSON.parse(raw);
5173
+ if (parsed.value && parsed.stored_at) {
5174
+ if (isExpired(parsed)) {
5175
+ await deleteCredential(account);
5176
+ return null;
5177
+ }
5178
+ return parsed.value;
5179
+ }
5180
+ } catch {}
5181
+ return raw;
5182
+ }
5183
+ async function deleteCredential(account) {
5184
+ const keytarResult = await callKeytar((client) => client.deletePassword(SERVICE, account));
5185
+ if (keytarResult !== KEYTAR_UNAVAILABLE)
5186
+ return;
5187
+ await withVaultLock(() => {
5188
+ const data = readVaultFile();
5189
+ delete data[account];
5190
+ writeVaultFile(data);
5191
+ });
5192
+ }
5193
+ var KEYTAR_UNAVAILABLE, KEYTAR_BINDING_ERROR_RE, keytar = null, importedKeytar, keytarFallbackLogged = false, SERVICE = "unbrowse", VAULT_DIR, VAULT_FILE, KEY_FILE, vaultLock;
5194
+ var init_vault = __esm(async () => {
5195
+ init_logger();
5196
+ KEYTAR_UNAVAILABLE = Symbol("KEYTAR_UNAVAILABLE");
5197
+ KEYTAR_BINDING_ERROR_RE = /(keytar(?:\.node)?|native bindings?|bindings file|no native build was found|could not locate the bindings file|module did not self-register|err_dlopen_failed|dlopen\(|compiled against a different node\.js version|cannot find module .*keytar|wasm is not supported on this platform|(set|get|delete)password is not a function)/i;
5198
+ try {
5199
+ keytar = normalizeKeytarModule(await import("keytar"));
5200
+ } catch {}
5201
+ importedKeytar = keytar;
5202
+ VAULT_DIR = join2(homedir(), ".unbrowse", "vault");
5203
+ VAULT_FILE = join2(VAULT_DIR, "credentials.enc");
5204
+ KEY_FILE = join2(VAULT_DIR, ".key");
5205
+ vaultLock = Promise.resolve();
5028
5206
  });
5029
5207
 
5030
5208
  // ../../src/runtime/browser-access.ts
@@ -5477,7 +5655,12 @@ function normalizeCapturedUrl(url, baseUrl) {
5477
5655
  if (!url)
5478
5656
  return url;
5479
5657
  try {
5480
- return new URL(url).toString();
5658
+ const parsed = new URL(url);
5659
+ if (baseUrl && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) {
5660
+ const pageOrigin = new URL(baseUrl).origin;
5661
+ return `${pageOrigin}${parsed.pathname}${parsed.search}`;
5662
+ }
5663
+ return parsed.toString();
5481
5664
  } catch {
5482
5665
  if (!baseUrl)
5483
5666
  return url;
@@ -5878,7 +6061,76 @@ async function captureSession(url, authHeaders, cookies, intent, options) {
5878
6061
  log("capture", "scriptInject unavailable — falling back to evaluate injection");
5879
6062
  }
5880
6063
  }
5881
- await phase("harStart", () => harStart(tabId));
6064
+ try {
6065
+ await phase("harStart", () => harStart(tabId));
6066
+ } catch {}
6067
+ const cdpNetworkEntries = [];
6068
+ const cdpRequestMap = new Map;
6069
+ const cdpResponseMeta = new Map;
6070
+ const cdpFinishedRequests = new Set;
6071
+ let cdpWs = null;
6072
+ let cdpMsgId = 10;
6073
+ const cdpPendingBodies = new Map;
6074
+ const cdpResolvedBodies = new Map;
6075
+ const API_URL_PATTERN = /\/api\/|\/graphql|voyager|youtubei|\/v\d+\//i;
6076
+ try {
6077
+ const cdpPort = getCdpPort();
6078
+ if (cdpPort) {
6079
+ const tabsResp = await fetch(`http://127.0.0.1:${cdpPort}/json`);
6080
+ const tabs = await tabsResp.json();
6081
+ const cdpTab = tabs.find((t) => t.id === tabId) ?? tabs.find((t) => t.type === "page");
6082
+ if (cdpTab?.webSocketDebuggerUrl) {
6083
+ cdpWs = new WebSocket(cdpTab.webSocketDebuggerUrl);
6084
+ await new Promise((resolve, reject) => {
6085
+ cdpWs.onopen = () => resolve();
6086
+ cdpWs.onerror = () => reject(new Error("CDP WS failed"));
6087
+ setTimeout(() => reject(new Error("CDP WS timeout")), 3000);
6088
+ });
6089
+ cdpWs.onmessage = (ev) => {
6090
+ try {
6091
+ const msg = JSON.parse(String(ev.data));
6092
+ if (msg.method === "Network.requestWillBeSent") {
6093
+ const p = msg.params;
6094
+ const reqHeaders = Object.entries(p.request?.headers ?? {}).map(([name, value]) => ({ name, value: String(value) }));
6095
+ cdpRequestMap.set(p.requestId, {
6096
+ method: p.request?.method ?? "GET",
6097
+ url: p.request?.url ?? "",
6098
+ headers: reqHeaders,
6099
+ postData: p.request?.postData
6100
+ });
6101
+ } else if (msg.method === "Network.responseReceived") {
6102
+ const p = msg.params;
6103
+ const respHeaders = Object.entries(p.response?.headers ?? {}).map(([name, value]) => ({ name, value: String(value) }));
6104
+ cdpResponseMeta.set(p.requestId, {
6105
+ status: p.response?.status ?? 0,
6106
+ headers: respHeaders,
6107
+ mimeType: p.response?.mimeType ?? ""
6108
+ });
6109
+ } else if (msg.method === "Network.loadingFinished") {
6110
+ const requestId = msg.params?.requestId;
6111
+ cdpFinishedRequests.add(requestId);
6112
+ const req = cdpRequestMap.get(requestId);
6113
+ const resp = cdpResponseMeta.get(requestId);
6114
+ if (req && resp && API_URL_PATTERN.test(req.url) && (resp.mimeType.includes("json") || resp.mimeType.includes("text"))) {
6115
+ const id = ++cdpMsgId;
6116
+ cdpPendingBodies.set(id, req.url);
6117
+ cdpWs.send(JSON.stringify({ id, method: "Network.getResponseBody", params: { requestId } }));
6118
+ }
6119
+ } else if (msg.id && cdpPendingBodies.has(msg.id)) {
6120
+ const reqUrl = cdpPendingBodies.get(msg.id);
6121
+ cdpPendingBodies.delete(msg.id);
6122
+ if (msg.result?.body) {
6123
+ cdpResolvedBodies.set(reqUrl, msg.result.body);
6124
+ }
6125
+ }
6126
+ } catch {}
6127
+ };
6128
+ cdpWs.send(JSON.stringify({ id: 1, method: "Network.enable", params: {} }));
6129
+ await new Promise((r) => setTimeout(r, 200));
6130
+ log("capture", "CDP network capture enabled (direct websocket)");
6131
+ }
6132
+ }
6133
+ } catch {}
5882
6134
  let pageDomain;
5883
6135
  try {
5884
6136
  pageDomain = getRegistrableDomain(new URL(url).hostname);
@@ -5932,11 +6184,85 @@ async function captureSession(url, authHeaders, cookies, intent, options) {
5932
6184
  const perfEntries = await phase("evaluate:perf", () => collectPerformanceResourceEntries(tabId));
5933
6185
  performanceUrls = await phase("replay-fetch", () => replayPerformanceApiResponses(tabId, perfEntries, responseBodies, url, intent));
5934
6186
  } catch {}
6187
+ if (cdpRequestMap.size > 0) {
6188
+ try {
6189
+ const cdpRawReqs = [...cdpRequestMap.values()].map((r) => ({
6190
+ url: r.url,
6191
+ method: r.method,
6192
+ request_headers: Object.fromEntries(r.headers.map((h) => [h.name.toLowerCase(), h.value])),
6193
+ response_status: 200,
6194
+ response_headers: {},
6195
+ response_body: undefined,
6196
+ timestamp: ""
6197
+ }));
6198
+ const authHeaders2 = extractAuthHeaders(cdpRawReqs);
6199
+ if (Object.keys(authHeaders2).length > 0) {
6200
+ const rawKey = `${domain}-session`;
6201
+ const { getCredential: getCredential2 } = await init_vault().then(() => exports_vault);
6202
+ let existing = {};
6203
+ try {
6204
+ const stored = await getCredential2(rawKey);
6205
+ if (stored)
6206
+ existing = JSON.parse(stored);
6207
+ } catch {}
6208
+ if (!existing.cookies || existing.cookies.length === 0) {
6209
+ try {
6210
+ const loginKey = `auth:${getRegistrableDomain(domain)}`;
6211
+ const loginStored = await getCredential2(loginKey);
6212
+ if (loginStored) {
6213
+ const loginData = JSON.parse(loginStored);
6214
+ if (loginData.cookies?.length)
6215
+ existing.cookies = loginData.cookies;
6216
+ }
6217
+ } catch {}
6218
+ }
6219
+ await storeCredential(rawKey, JSON.stringify({
6220
+ ...existing,
6221
+ headers: { ...existing.headers ?? {}, ...authHeaders2 }
6222
+ }));
6223
+ log("capture", `early auth store: ${Object.keys(authHeaders2).join(", ")} for ${domain}`);
6224
+ }
6225
+ } catch (e) {
6226
+ log("capture", `early auth store failed: ${e instanceof Error ? e.message : String(e)}`);
6227
+ }
6228
+ }
5935
6229
  let harEntries = [];
5936
6230
  try {
5937
- const harResult = await phase("harStop", () => harStop(tabId));
5938
- harEntries = harResult.entries;
6231
+ const harPromise = harStop(tabId);
6232
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve({ entries: [] }), 5000));
6233
+ const harResult = await Promise.race([harPromise, timeoutPromise]);
6234
+ harEntries = harResult.entries ?? [];
5939
6235
  } catch {}
6236
+ if (cdpPendingBodies.size > 0) {
6237
+ await new Promise((r) => setTimeout(r, 1500));
6238
+ }
6239
+ if (cdpWs) {
6240
+ try {
6241
+ cdpWs.close();
6242
+ } catch {}
6243
+ }
6244
+ const harUrls = new Set(harEntries.map((e) => e.request?.url).filter(Boolean));
6245
+ let cdpAdded = 0;
6246
+ for (const [requestId, req] of cdpRequestMap) {
6247
+ if (harUrls.has(req.url))
6248
+ continue;
6249
+ const resp = cdpResponseMeta.get(requestId);
6250
+ if (!resp)
6251
+ continue;
6252
+ const body = cdpResolvedBodies.get(req.url);
6253
+ harEntries.push({
6254
+ startedDateTime: new Date().toISOString(),
6255
+ request: { method: req.method, url: req.url, headers: req.headers, postData: req.postData ? { text: req.postData } : undefined },
6256
+ response: { status: resp.status, headers: resp.headers, content: body ? { text: body, mimeType: resp.mimeType } : {} }
6257
+ });
6258
+ if (body && body.length > 0 && !responseBodies.has(req.url)) {
6259
+ responseBodies.set(req.url, body);
6260
+ }
6261
+ cdpAdded++;
6262
+ }
6263
+ if (cdpAdded > 0) {
6264
+ log("capture", `CDP direct capture added ${cdpAdded} entries (${cdpResolvedBodies.size} with bodies, ${harEntries.length} total HAR)`);
6265
+ }
5940
6266
  const HAR_REPLAY_CT2 = /application\/json|text\/plain|\+json/i;
5941
6267
  let harReplayCount = 0;
5942
6268
  for (const entry of harEntries) {
@@ -5952,7 +6278,7 @@ async function captureSession(url, authHeaders, cookies, intent, options) {
5952
6278
  if (status < 200 || status >= 400)
5953
6279
  continue;
5954
6280
  const ct = (entry.response?.headers ?? []).find((h) => h.name.toLowerCase() === "content-type")?.value ?? "";
5955
- if (!HAR_REPLAY_CT2.test(ct) && !harUrl.includes("/api/") && !harUrl.includes("_search") && !harUrl.includes("graphql"))
6281
+ if (!HAR_REPLAY_CT2.test(ct) && !harUrl.includes("/api/") && !harUrl.includes("_search") && !harUrl.includes("graphql") && !harUrl.includes("youtubei"))
5956
6282
  continue;
5957
6283
  if (harReplayCount >= 20)
5958
6284
  break;
@@ -5960,7 +6286,7 @@ async function captureSession(url, authHeaders, cookies, intent, options) {
5960
6286
  const postData = method !== "GET" ? entry.request?.postData?.text : undefined;
5961
6287
  const replayScript = method === "GET" || !postData ? `(function(){var x=new XMLHttpRequest();x.open('GET','${harUrl.replace(/'/g, "\\'")}',false);x.send();return x.status>=200&&x.status<400?x.responseText:''})()` : `(function(){var x=new XMLHttpRequest();x.open('${method}','${harUrl.replace(/'/g, "\\'")}',false);x.setRequestHeader('Content-Type','application/json');x.send(${JSON.stringify(postData)});return x.status>=200&&x.status<400?x.responseText:''})()`;
5962
6288
  const body = await phase("har-replay", () => evaluate(tabId, replayScript));
5963
- if (typeof body === "string" && body.length > 0 && body.length < 512 * 1024) {
6289
+ if (typeof body === "string" && body.length > 0 && body.length < 524288) {
5964
6290
  responseBodies.set(harUrl, body);
5965
6291
  harReplayCount++;
5966
6292
  log("capture", `har-replay-fetched ${harUrl.substring(0, 80)} (${body.length}B)`);
@@ -5987,6 +6313,77 @@ async function captureSession(url, authHeaders, cookies, intent, options) {
5987
6313
  }
5988
6314
  html = await phase("getPageHtml", () => getPageHtml(tabId));
5989
6315
  } catch {}
6316
+ const SSR_DATA_EXTRACTORS = [
6317
+ { name: "ytmusic", script: `(function(){try{var d=ytcfg.get('YTMUSIC_INITIAL_DATA');if(!d||!d.length)return null;var out={};d.forEach(function(x){if(x.path&&x.data)out[x.path]=x.data});out.__apiKey=ytcfg.get('INNERTUBE_API_KEY')||'';out.__context=ytcfg.get('INNERTUBE_CONTEXT')||null;return JSON.stringify(out)}catch(e){return null}})()` },
6318
+ { name: "youtube", script: `(function(){try{return typeof ytInitialData!=='undefined'?JSON.stringify(ytInitialData):null}catch(e){return null}})()` },
6319
+ { name: "nextjs", script: `(function(){try{return window.__NEXT_DATA__?JSON.stringify(window.__NEXT_DATA__):null}catch(e){return null}})()` },
6320
+ { name: "nuxt", script: `(function(){try{return window.__NUXT__?JSON.stringify(window.__NUXT__):null}catch(e){return null}})()` }
6321
+ ];
6322
+ let embeddedDataCount = 0;
6323
+ for (const extractor of SSR_DATA_EXTRACTORS) {
6324
+ try {
6325
+ const raw = await phase(`ssr:${extractor.name}`, () => evaluate(tabId, extractor.script));
6326
+ if (typeof raw !== "string" || !raw || raw === "null")
6327
+ continue;
6328
+ if (extractor.name === "ytmusic") {
6329
+ const parsed = JSON.parse(raw);
6330
+ const apiKey = parsed.__apiKey || "";
6331
+ const innertubeContext = parsed.__context;
6332
+ delete parsed.__apiKey;
6333
+ delete parsed.__context;
6334
+ for (const [path4, data] of Object.entries(parsed)) {
6335
+ if (!data || typeof data !== "object")
6336
+ continue;
6337
+ const cleaned = { ...data };
6338
+ delete cleaned.responseContext;
6339
+ delete cleaned.trackingParams;
6340
+ delete cleaned.header;
6341
+ delete cleaned.background;
6342
+ let bodyStr = JSON.stringify(cleaned);
6343
+ if (bodyStr.length > 1e4) {
6344
+ bodyStr = bodyStr.substring(0, 1e4) + '"}]}';
6345
+ try {
6346
+ JSON.parse(bodyStr);
6347
+ } catch {
6348
+ bodyStr = JSON.stringify(cleaned).substring(0, 1e4);
6349
+ }
6350
+ }
6351
+ if (bodyStr.length < 100)
6352
+ continue;
6353
+ const origin = new URL(url).origin;
6354
+ const contextParams = new URL(url).searchParams;
6355
+ const keyParam = apiKey ? `key=${apiKey}&` : "";
6356
+ const queryStr = path4.includes("search") && contextParams.toString() ? `?${keyParam}${contextParams.toString()}&prettyPrint=false` : `?${keyParam}prettyPrint=false`;
6357
+ const syntheticUrl = `${origin}/youtubei/v1${path4}${queryStr}`;
6358
+ responseBodies.set(syntheticUrl, bodyStr);
6359
+ const queryValue = contextParams.get("q") ?? "";
6360
+ const postBody = path4.includes("search") && innertubeContext ? JSON.stringify({ context: innertubeContext, query: queryValue }) : path4.includes("browse") && innertubeContext ? JSON.stringify({ context: innertubeContext, browseId: "FEmusic_liked_videos" }) : JSON.stringify({ context: innertubeContext ?? {} });
6361
+ harEntries.push({
6362
+ startedDateTime: new Date().toISOString(),
6363
+ request: { method: "POST", url: syntheticUrl, headers: [{ name: "content-type", value: "application/json" }], postData: { text: postBody } },
6364
+ response: { status: 200, headers: [{ name: "content-type", value: "application/json" }], content: { text: bodyStr, mimeType: "application/json" } }
6365
+ });
6366
+ embeddedDataCount++;
6367
+ log("capture", `ssr:${extractor.name} extracted ${path4} (${bodyStr.length}B)`);
6368
+ }
6369
+ } else {
6370
+ if (raw.length < 100)
6371
+ continue;
6372
+ const syntheticUrl = `${new URL(url).origin}/__ssr_data__/${extractor.name}`;
6373
+ responseBodies.set(syntheticUrl, raw);
6374
+ harEntries.push({
6375
+ startedDateTime: new Date().toISOString(),
6376
+ request: { method: "GET", url: syntheticUrl, headers: [] },
6377
+ response: { status: 200, headers: [{ name: "content-type", value: "application/json" }], content: { text: raw, mimeType: "application/json" } }
6378
+ });
6379
+ embeddedDataCount++;
6380
+ log("capture", `ssr:${extractor.name} extracted (${raw.length}B)`);
6381
+ }
6382
+ } catch {}
6383
+ }
6384
+ if (embeddedDataCount > 0) {
6385
+ log("capture", `embedded SSR data: ${embeddedDataCount} synthetic endpoints injected`);
6386
+ }
5990
6387
  const requests = mergePassiveCaptureData(intercepted, harEntries, extensionEntries, responseBodies, performanceUrls);
5991
6388
  log("capture", `tracked ${harEntries.length} HAR, ${intercepted.length} intercepted, ${extensionEntries.length} extension, ${responseBodies.size} bodies → ${requests.length} merged`);
5992
6389
  const rawCookies = await phase("extractCookies", () => extractCookiesFromPage(tabId, url));
@@ -6385,7 +6782,8 @@ var MAX_CONCURRENT_TABS = 3, activeTabs = 0, waitQueue, activeTabRegistry, inter
6385
6782
  var isData = ct.indexOf('json') !== -1 || ct.indexOf('application/x-protobuf') !== -1 ||
6386
6783
  ct.indexOf('text/plain') !== -1 ||
6387
6784
  url.indexOf('batchexecute') !== -1 || url.indexOf('/api/') !== -1 ||
6388
- url.indexOf('graphql') !== -1 || url.indexOf('voyager') !== -1;
6785
+ url.indexOf('graphql') !== -1 || url.indexOf('voyager') !== -1 ||
6786
+ url.indexOf('youtubei') !== -1;
6389
6787
  if (!isJs && !isData) return response;
6390
6788
  if (/\\.(css|woff2?|png|jpg|svg|ico)(\\?|$)/.test(url)) return response;
6391
6789
  var clone = response.clone();
@@ -6435,7 +6833,8 @@ var MAX_CONCURRENT_TABS = 3, activeTabs = 0, waitQueue, activeTabRegistry, inter
6435
6833
  var isData = ct.indexOf('json') !== -1 || ct.indexOf('application/x-protobuf') !== -1 ||
6436
6834
  ct.indexOf('text/plain') !== -1 ||
6437
6835
  url.indexOf('batchexecute') !== -1 || url.indexOf('/api/') !== -1 ||
6438
- url.indexOf('graphql') !== -1 || url.indexOf('voyager') !== -1;
6836
+ url.indexOf('graphql') !== -1 || url.indexOf('voyager') !== -1 ||
6837
+ url.indexOf('youtubei') !== -1;
6439
6838
  if (!isJs && !isData) return;
6440
6839
  if (/\\.(css|woff2?|png|jpg|svg|ico)(\\?|$)/.test(url)) return;
6441
6840
  var respBody = xhr.responseText || '';
@@ -6457,11 +6856,13 @@ var MAX_CONCURRENT_TABS = 3, activeTabs = 0, waitQueue, activeTabRegistry, inter
6457
6856
  return origSend.apply(this, arguments);
6458
6857
  };
6459
6858
  })()`, REPLAY_SKIP, HAR_REPLAY_CT;
6460
- var init_capture = __esm(() => {
6859
+ var init_capture = __esm(async () => {
6461
6860
  init_client();
6462
6861
  init_domain();
6463
6862
  init_logger();
6863
+ init_reverse_engineer();
6464
6864
  init_browser_access();
6865
+ await init_vault();
6465
6866
  waitQueue = [];
6466
6867
  activeTabRegistry = new Set;
6467
6868
  interceptorInjectedTabs = new Set;
@@ -6478,17 +6879,17 @@ var init_capture = __esm(() => {
6478
6879
  });
6479
6880
 
6480
6881
  // ../../src/build-info.generated.ts
6481
- var BUILD_RELEASE_VERSION = "3.2.1", BUILD_GIT_SHA = "150cce0d751e", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4yLjEiLCJnaXRfc2hhIjoiMTUwY2NlMGQ3NTFlIiwiY29kZV9oYXNoIjoiMTQ4OGZjMWQ5MmI3IiwidHJhY2VfdmVyc2lvbiI6IjE0ODhmYzFkOTJiN0AxNTBjY2UwZDc1MWUiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTA2VDA3OjM0OjU3LjQ1NFoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "2iiYVQS4ow2XkpkyCm072lmrjIIGvkAPjkO1s_LI_Do", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
6882
+ var BUILD_RELEASE_VERSION = "3.2.2-experiments.128ff09", BUILD_GIT_SHA = "128ff09f51b3", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4yLjItZXhwZXJpbWVudHMuMTI4ZmYwOSIsImdpdF9zaGEiOiIxMjhmZjA5ZjUxYjMiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDEyOGZmMDlmNTFiMyIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDZUMTE6NTk6NDYuOTQyWiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "M80x5zV9aWn-teZrpEFDyzwSU53Hwfj6rf19dl9m-jM", BUILD_DEFAULT_BACKEND_URL = "https://unbrowse-backend-experiments.lewis-6d8.workers.dev";
6482
6883
 
6483
6884
  // ../../src/version.ts
6484
6885
  import { createHash } from "crypto";
6485
- import { existsSync as existsSync3, readFileSync, readdirSync } from "fs";
6486
- import { dirname, join as join2, parse } from "path";
6886
+ import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync } from "fs";
6887
+ import { dirname, join as join3, parse } from "path";
6487
6888
  import { fileURLToPath as fileURLToPath2 } from "url";
6488
6889
  function collectTsFiles(dir) {
6489
6890
  const results = [];
6490
6891
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
6491
- const full = join2(dir, entry.name);
6892
+ const full = join3(dir, entry.name);
6492
6893
  if (entry.isDirectory() && entry.name !== "node_modules") {
6493
6894
  results.push(...collectTsFiles(full));
6494
6895
  } else if (entry.name.endsWith(".ts")) {
@@ -6501,21 +6902,21 @@ function hashFiles(srcDir, files) {
6501
6902
  const hash = createHash("sha256");
6502
6903
  for (const file of files) {
6503
6904
  hash.update(file.slice(srcDir.length));
6504
- hash.update(readFileSync(file, "utf-8"));
6905
+ hash.update(readFileSync2(file, "utf-8"));
6505
6906
  }
6506
6907
  return hash.digest("hex").slice(0, 12);
6507
6908
  }
6508
6909
  function resolveCodeHashSourceDir(moduleDir) {
6509
6910
  const candidates = [
6510
6911
  moduleDir,
6511
- join2(moduleDir, "runtime-src"),
6512
- join2(moduleDir, "..", "runtime-src"),
6513
- join2(moduleDir, "src"),
6514
- join2(moduleDir, "..", "src")
6912
+ join3(moduleDir, "runtime-src"),
6913
+ join3(moduleDir, "..", "runtime-src"),
6914
+ join3(moduleDir, "src"),
6915
+ join3(moduleDir, "..", "src")
6515
6916
  ];
6516
6917
  for (const candidate of candidates) {
6517
6918
  try {
6518
- if (!existsSync3(candidate))
6919
+ if (!existsSync4(candidate))
6519
6920
  continue;
6520
6921
  const files = collectTsFiles(candidate);
6521
6922
  if (files.length > 0)
@@ -6565,7 +6966,7 @@ function getPackageVersionForModuleDir(moduleDir) {
6565
6966
  const root = parse(dir).root;
6566
6967
  while (true) {
6567
6968
  try {
6568
- const pkg = JSON.parse(readFileSync(join2(dir, "package.json"), "utf-8"));
6969
+ const pkg = JSON.parse(readFileSync2(join3(dir, "package.json"), "utf-8"));
6569
6970
  return typeof pkg.version === "string" ? pkg.version : "unknown";
6570
6971
  } catch {}
6571
6972
  if (dir === root)
@@ -6669,18 +7070,18 @@ async function ensureCascadeSplitForSkill(skill, deps = {}) {
6669
7070
  var init_cascade = () => {};
6670
7071
 
6671
7072
  // ../../src/payments/wallet.ts
6672
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
6673
- import { homedir } from "node:os";
6674
- import { join as join3 } from "node:path";
7073
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
7074
+ import { homedir as homedir2 } from "node:os";
7075
+ import { join as join4 } from "node:path";
6675
7076
  function asNonEmptyString(value) {
6676
7077
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
6677
7078
  }
6678
7079
  function getLobsterWalletFromLocalConfig() {
6679
- const agentsPath = join3(process.env.HOME || homedir(), ".lobster", "agents.json");
6680
- if (!existsSync4(agentsPath))
7080
+ const agentsPath = join4(process.env.HOME || homedir2(), ".lobster", "agents.json");
7081
+ if (!existsSync5(agentsPath))
6681
7082
  return;
6682
7083
  try {
6683
- const raw = JSON.parse(readFileSync2(agentsPath, "utf8"));
7084
+ const raw = JSON.parse(readFileSync3(agentsPath, "utf8"));
6684
7085
  const activeAgentId = asNonEmptyString(raw.activeAgentId);
6685
7086
  const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
6686
7087
  return asNonEmptyString(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString(activeAgent?.walletAddress) ?? asNonEmptyString(activeAgent?.wallet_address);
@@ -6831,9 +7232,9 @@ __export(exports_lobster_pay, {
6831
7232
  isLobsterAvailable: () => isLobsterAvailable
6832
7233
  });
6833
7234
  import { execFile, execFileSync as execFileSync2 } from "node:child_process";
6834
- import { existsSync as existsSync5 } from "node:fs";
6835
- import { homedir as homedir2 } from "node:os";
6836
- import { join as join4 } from "node:path";
7235
+ import { existsSync as existsSync6 } from "node:fs";
7236
+ import { homedir as homedir3 } from "node:os";
7237
+ import { join as join5 } from "node:path";
6837
7238
  function getLobsterCommand() {
6838
7239
  try {
6839
7240
  execFileSync2("lobstercash", ["--version"], { stdio: "ignore", timeout: 3000 });
@@ -6841,8 +7242,8 @@ function getLobsterCommand() {
6841
7242
  } catch (_e) {}
6842
7243
  try {
6843
7244
  const npmPrefix = execFileSync2("npm", ["config", "get", "prefix"], { encoding: "utf8", timeout: 5000 }).trim();
6844
- const lobsterPath = join4(npmPrefix, "bin", "lobstercash");
6845
- if (existsSync5(lobsterPath)) {
7245
+ const lobsterPath = join5(npmPrefix, "bin", "lobstercash");
7246
+ if (existsSync6(lobsterPath)) {
6846
7247
  execFileSync2(lobsterPath, ["--version"], { stdio: "ignore", timeout: 3000 });
6847
7248
  return { cmd: lobsterPath, prefix: [] };
6848
7249
  }
@@ -6855,8 +7256,8 @@ function lobsterCmd() {
6855
7256
  return cachedCommand;
6856
7257
  }
6857
7258
  function isLobsterAvailable() {
6858
- const agentsPath = join4(process.env.HOME || homedir2(), ".lobster", "agents.json");
6859
- return existsSync5(agentsPath);
7259
+ const agentsPath = join5(process.env.HOME || homedir3(), ".lobster", "agents.json");
7260
+ return existsSync6(agentsPath);
6860
7261
  }
6861
7262
  function lobsterX402Fetch(url, options) {
6862
7263
  return new Promise((resolve) => {
@@ -6988,10 +7389,10 @@ __export(exports_client2, {
6988
7389
  buildDefaultAgentName: () => buildDefaultAgentName,
6989
7390
  autoFileIssue: () => autoFileIssue
6990
7391
  });
6991
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync2 } from "fs";
6992
- import { join as join5 } from "path";
6993
- import { homedir as homedir3, hostname, release as osRelease } from "os";
6994
- import { randomBytes, createHash as createHash3 } from "crypto";
7392
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
7393
+ import { join as join6 } from "path";
7394
+ import { homedir as homedir4, hostname, release as osRelease } from "os";
7395
+ import { randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
6995
7396
  import { createInterface } from "readline";
6996
7397
  function buildReleaseAttestationHeaders(manifestBase64, signature) {
6997
7398
  const manifest = manifestBase64.trim();
@@ -7025,18 +7426,18 @@ function scopedSkillKey(skillId, scopeId) {
7025
7426
  return scopeId ? `${scopeId}:${skillId}` : skillId;
7026
7427
  }
7027
7428
  function getSkillCacheDir() {
7028
- return process.env.UNBROWSE_SKILL_CACHE_DIR || join5(getConfigDir(), "skill-cache");
7429
+ return process.env.UNBROWSE_SKILL_CACHE_DIR || join6(getConfigDir(), "skill-cache");
7029
7430
  }
7030
7431
  function getConfigDir() {
7031
7432
  if (process.env.UNBROWSE_CONFIG_DIR)
7032
7433
  return process.env.UNBROWSE_CONFIG_DIR;
7033
- return PROFILE_NAME ? join5(homedir3(), ".unbrowse", "profiles", PROFILE_NAME) : join5(homedir3(), ".unbrowse");
7434
+ return PROFILE_NAME ? join6(homedir4(), ".unbrowse", "profiles", PROFILE_NAME) : join6(homedir4(), ".unbrowse");
7034
7435
  }
7035
7436
  function getConfigPath() {
7036
- return join5(getConfigDir(), "config.json");
7437
+ return join6(getConfigDir(), "config.json");
7037
7438
  }
7038
7439
  function getInstallTelemetryPath() {
7039
- return join5(getConfigDir(), "install-state.json");
7440
+ return join6(getConfigDir(), "install-state.json");
7040
7441
  }
7041
7442
  function getLandingToken() {
7042
7443
  const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
@@ -7054,8 +7455,8 @@ function isLocalOnlyMode() {
7054
7455
  function loadConfig() {
7055
7456
  try {
7056
7457
  const configPath = getConfigPath();
7057
- if (existsSync6(configPath)) {
7058
- return JSON.parse(readFileSync3(configPath, "utf-8"));
7458
+ if (existsSync7(configPath)) {
7459
+ return JSON.parse(readFileSync4(configPath, "utf-8"));
7059
7460
  }
7060
7461
  } catch {}
7061
7462
  return null;
@@ -7063,15 +7464,15 @@ function loadConfig() {
7063
7464
  function saveConfig(config) {
7064
7465
  const configDir = getConfigDir();
7065
7466
  const configPath = getConfigPath();
7066
- if (!existsSync6(configDir))
7067
- mkdirSync3(configDir, { recursive: true });
7068
- writeFileSync2(configPath, JSON.stringify(config, null, 2), { mode: 384 });
7467
+ if (!existsSync7(configDir))
7468
+ mkdirSync4(configDir, { recursive: true });
7469
+ writeFileSync3(configPath, JSON.stringify(config, null, 2), { mode: 384 });
7069
7470
  }
7070
7471
  function loadInstallTelemetryState() {
7071
7472
  try {
7072
7473
  const statePath = getInstallTelemetryPath();
7073
- if (existsSync6(statePath)) {
7074
- return JSON.parse(readFileSync3(statePath, "utf-8"));
7474
+ if (existsSync7(statePath)) {
7475
+ return JSON.parse(readFileSync4(statePath, "utf-8"));
7075
7476
  }
7076
7477
  } catch {}
7077
7478
  return null;
@@ -7079,13 +7480,13 @@ function loadInstallTelemetryState() {
7079
7480
  function saveInstallTelemetryState(state) {
7080
7481
  const configDir = getConfigDir();
7081
7482
  const statePath = getInstallTelemetryPath();
7082
- if (!existsSync6(configDir))
7083
- mkdirSync3(configDir, { recursive: true });
7084
- writeFileSync2(statePath, JSON.stringify(state, null, 2), { mode: 384 });
7483
+ if (!existsSync7(configDir))
7484
+ mkdirSync4(configDir, { recursive: true });
7485
+ writeFileSync3(statePath, JSON.stringify(state, null, 2), { mode: 384 });
7085
7486
  }
7086
7487
  function createInstallTelemetryState() {
7087
7488
  return {
7088
- install_id: `install_${randomBytes(8).toString("hex")}`,
7489
+ install_id: `install_${randomBytes2(8).toString("hex")}`,
7089
7490
  first_seen_at: new Date().toISOString()
7090
7491
  };
7091
7492
  }
@@ -7225,7 +7626,7 @@ function isValidAgentEmail(value) {
7225
7626
  return EMAIL_RE.test(normalizeAgentEmail(value));
7226
7627
  }
7227
7628
  function buildDefaultAgentName() {
7228
- return `${hostname()}-${randomBytes(3).toString("hex")}`;
7629
+ return `${hostname()}-${randomBytes2(3).toString("hex")}`;
7229
7630
  }
7230
7631
  function resolveAgentName(preferredEmail, fallbackName) {
7231
7632
  const normalized = normalizeAgentEmail(preferredEmail ?? "");
@@ -7580,11 +7981,11 @@ async function waitForBackgroundRegistration(timeoutMs = 0) {
7580
7981
  ]);
7581
7982
  }
7582
7983
  function skillCachePath(skillId) {
7583
- return join5(getSkillCacheDir(), `${skillId}.json`);
7984
+ return join6(getSkillCacheDir(), `${skillId}.json`);
7584
7985
  }
7585
7986
  function readSkillCache(skillId) {
7586
7987
  try {
7587
- const raw = readFileSync3(skillCachePath(skillId), "utf-8");
7988
+ const raw = readFileSync4(skillCachePath(skillId), "utf-8");
7588
7989
  return JSON.parse(raw);
7589
7990
  } catch {
7590
7991
  return null;
@@ -7594,8 +7995,8 @@ function writeSkillCache(skill, scopeId) {
7594
7995
  try {
7595
7996
  recentLocalSkills.set(scopedSkillKey(skill.skill_id, scopeId), skill);
7596
7997
  const skillCacheDir = getSkillCacheDir();
7597
- if (!existsSync6(skillCacheDir))
7598
- mkdirSync3(skillCacheDir, { recursive: true });
7998
+ if (!existsSync7(skillCacheDir))
7999
+ mkdirSync4(skillCacheDir, { recursive: true });
7599
8000
  const existing = readSkillCache(skill.skill_id);
7600
8001
  if (existing) {
7601
8002
  for (const ep of skill.endpoints) {
@@ -7611,7 +8012,7 @@ function writeSkillCache(skill, scopeId) {
7611
8012
  const hasStrategy = skill.endpoints.some((e) => e.exec_strategy);
7612
8013
  if (hasStrategy)
7613
8014
  console.log(`[cache] writing skill ${skill.skill_id} with exec_strategy`);
7614
- writeFileSync2(skillCachePath(skill.skill_id), JSON.stringify(skill), "utf-8");
8015
+ writeFileSync3(skillCachePath(skill.skill_id), JSON.stringify(skill), "utf-8");
7615
8016
  } catch {}
7616
8017
  }
7617
8018
  function cachePublishedSkill(skill, scopeId) {
@@ -7646,7 +8047,7 @@ function isIntentCompatible(lhs, rhs) {
7646
8047
  function findExistingSkillForDomain(domain, intent) {
7647
8048
  try {
7648
8049
  const skillCacheDir = getSkillCacheDir();
7649
- if (!existsSync6(skillCacheDir))
8050
+ if (!existsSync7(skillCacheDir))
7650
8051
  return null;
7651
8052
  const files = readdirSync2(skillCacheDir);
7652
8053
  let compatible = null;
@@ -7655,7 +8056,7 @@ function findExistingSkillForDomain(domain, intent) {
7655
8056
  if (!f.endsWith(".json") || f === "browser-capture.json")
7656
8057
  continue;
7657
8058
  try {
7658
- const raw = readFileSync3(join5(skillCacheDir, f), "utf-8");
8059
+ const raw = readFileSync4(join6(skillCacheDir, f), "utf-8");
7659
8060
  const skill = JSON.parse(raw);
7660
8061
  if (skill.domain === domain && skill.execution_type === "http") {
7661
8062
  if (!fallback)
@@ -7705,11 +8106,11 @@ async function getSkillChunk2(skillId, opts) {
7705
8106
  async function listSkills() {
7706
8107
  if (LOCAL_ONLY) {
7707
8108
  try {
7708
- if (!existsSync6(SKILL_CACHE_DIR))
8109
+ if (!existsSync7(SKILL_CACHE_DIR))
7709
8110
  return [];
7710
8111
  return readdirSync2(SKILL_CACHE_DIR).filter((file) => file.endsWith(".json")).map((file) => {
7711
8112
  try {
7712
- return JSON.parse(readFileSync3(join5(SKILL_CACHE_DIR, file), "utf-8"));
8113
+ return JSON.parse(readFileSync4(join6(SKILL_CACHE_DIR, file), "utf-8"));
7713
8114
  } catch {
7714
8115
  return null;
7715
8116
  }
@@ -8401,10 +8802,10 @@ var init_publish_admission = __esm(() => {
8401
8802
 
8402
8803
  // ../../src/telemetry.ts
8403
8804
  import { createHash as createHash4 } from "node:crypto";
8404
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
8405
- import { join as join6 } from "node:path";
8805
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
8806
+ import { join as join7 } from "node:path";
8406
8807
  function getTraceDir() {
8407
- return process.env.UNBROWSE_TRACES_DIR ?? join6(process.env.HOME ?? "/tmp", ".unbrowse", "traces");
8808
+ return process.env.UNBROWSE_TRACES_DIR ?? join7(process.env.HOME ?? "/tmp", ".unbrowse", "traces");
8408
8809
  }
8409
8810
  function isTracingEnabled() {
8410
8811
  return process.env.UNBROWSE_DISABLE_TRACES !== "1";
@@ -8478,11 +8879,11 @@ function emitRouteTrace(params) {
8478
8879
  };
8479
8880
  try {
8480
8881
  const traceDir = getTraceDir();
8481
- if (!existsSync7(traceDir))
8482
- mkdirSync4(traceDir, { recursive: true });
8882
+ if (!existsSync8(traceDir))
8883
+ mkdirSync5(traceDir, { recursive: true });
8483
8884
  const stamp = params.started_at.replace(/[:.]/g, "-");
8484
- const file = join6(traceDir, `${stamp}-${params.outcome}-${params.trace_id}.json`);
8485
- writeFileSync3(file, JSON.stringify(artifact, null, 2), "utf-8");
8885
+ const file = join7(traceDir, `${stamp}-${params.outcome}-${params.trace_id}.json`);
8886
+ writeFileSync4(file, JSON.stringify(artifact, null, 2), "utf-8");
8486
8887
  return file;
8487
8888
  } catch {
8488
8889
  return null;
@@ -9118,161 +9519,6 @@ var init_token_resolver = __esm(() => {
9118
9519
  init_token_sources();
9119
9520
  });
9120
9521
 
9121
- // ../../src/vault/index.ts
9122
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
9123
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
9124
- import { join as join7 } from "path";
9125
- import { homedir as homedir4 } from "os";
9126
- function normalizeKeytarModule(mod) {
9127
- let candidate = mod;
9128
- for (let depth = 0;depth < 3; depth++) {
9129
- if (!candidate || typeof candidate !== "object" || !("default" in candidate))
9130
- break;
9131
- candidate = candidate.default;
9132
- }
9133
- if (!candidate)
9134
- return null;
9135
- if (typeof candidate.setPassword === "function" && typeof candidate.getPassword === "function" && typeof candidate.deletePassword === "function") {
9136
- return candidate;
9137
- }
9138
- return null;
9139
- }
9140
- function isKeytarBindingError(error) {
9141
- const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
9142
- return KEYTAR_BINDING_ERROR_RE.test(message);
9143
- }
9144
- function disableKeytar(error) {
9145
- keytar = null;
9146
- if (keytarFallbackLogged)
9147
- return;
9148
- const summary = error instanceof Error ? error.message : String(error);
9149
- log("vault", `keytar runtime unavailable (${summary}); using encrypted file fallback`);
9150
- keytarFallbackLogged = true;
9151
- }
9152
- async function callKeytar(op) {
9153
- if (!keytar)
9154
- return KEYTAR_UNAVAILABLE;
9155
- try {
9156
- return await op(keytar);
9157
- } catch (error) {
9158
- if (!isKeytarBindingError(error))
9159
- throw error;
9160
- disableKeytar(error);
9161
- return KEYTAR_UNAVAILABLE;
9162
- }
9163
- }
9164
- function getOrCreateKey() {
9165
- if (!existsSync8(VAULT_DIR))
9166
- mkdirSync5(VAULT_DIR, { recursive: true, mode: 448 });
9167
- if (existsSync8(KEY_FILE))
9168
- return readFileSync4(KEY_FILE);
9169
- const key = randomBytes2(32);
9170
- writeFileSync4(KEY_FILE, key, { mode: 384 });
9171
- return key;
9172
- }
9173
- function withVaultLock(fn) {
9174
- const prev = vaultLock;
9175
- let release;
9176
- vaultLock = new Promise((r) => {
9177
- release = r;
9178
- });
9179
- return prev.then(fn).finally(() => release());
9180
- }
9181
- function readVaultFile() {
9182
- if (!existsSync8(VAULT_FILE))
9183
- return {};
9184
- try {
9185
- const key = getOrCreateKey();
9186
- const raw = readFileSync4(VAULT_FILE);
9187
- const iv = raw.subarray(0, 16);
9188
- const enc = raw.subarray(16);
9189
- const decipher = createDecipheriv("aes-256-cbc", key, iv);
9190
- const dec = Buffer.concat([decipher.update(enc), decipher.final()]);
9191
- return JSON.parse(dec.toString("utf8"));
9192
- } catch {
9193
- return {};
9194
- }
9195
- }
9196
- function writeVaultFile(data) {
9197
- const key = getOrCreateKey();
9198
- const iv = randomBytes2(16);
9199
- const cipher = createCipheriv("aes-256-cbc", key, iv);
9200
- const enc = Buffer.concat([cipher.update(JSON.stringify(data), "utf8"), cipher.final()]);
9201
- writeFileSync4(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
9202
- }
9203
- async function storeCredential(account, value, opts) {
9204
- const wrapped = {
9205
- value,
9206
- stored_at: new Date().toISOString(),
9207
- expires_at: opts?.expires_at,
9208
- max_age_ms: opts?.max_age_ms
9209
- };
9210
- const serialized = JSON.stringify(wrapped);
9211
- const keytarResult = await callKeytar((client) => client.setPassword(SERVICE, account, serialized));
9212
- if (keytarResult !== KEYTAR_UNAVAILABLE)
9213
- return;
9214
- await withVaultLock(() => {
9215
- const data = readVaultFile();
9216
- data[account] = serialized;
9217
- writeVaultFile(data);
9218
- });
9219
- }
9220
- function isExpired(cred) {
9221
- if (cred.expires_at) {
9222
- return new Date(cred.expires_at).getTime() <= Date.now();
9223
- }
9224
- if (cred.max_age_ms) {
9225
- return new Date(cred.stored_at).getTime() + cred.max_age_ms <= Date.now();
9226
- }
9227
- return false;
9228
- }
9229
- async function getCredential(account) {
9230
- let raw;
9231
- const keytarResult = await callKeytar((client) => client.getPassword(SERVICE, account));
9232
- if (keytarResult !== KEYTAR_UNAVAILABLE) {
9233
- raw = keytarResult;
9234
- } else {
9235
- const data = readVaultFile();
9236
- raw = data[account] ?? null;
9237
- }
9238
- if (!raw)
9239
- return null;
9240
- try {
9241
- const parsed = JSON.parse(raw);
9242
- if (parsed.value && parsed.stored_at) {
9243
- if (isExpired(parsed)) {
9244
- await deleteCredential(account);
9245
- return null;
9246
- }
9247
- return parsed.value;
9248
- }
9249
- } catch {}
9250
- return raw;
9251
- }
9252
- async function deleteCredential(account) {
9253
- const keytarResult = await callKeytar((client) => client.deletePassword(SERVICE, account));
9254
- if (keytarResult !== KEYTAR_UNAVAILABLE)
9255
- return;
9256
- await withVaultLock(() => {
9257
- const data = readVaultFile();
9258
- delete data[account];
9259
- writeVaultFile(data);
9260
- });
9261
- }
9262
- var KEYTAR_UNAVAILABLE, KEYTAR_BINDING_ERROR_RE, keytar = null, keytarFallbackLogged = false, SERVICE = "unbrowse", VAULT_DIR, VAULT_FILE, KEY_FILE, vaultLock;
9263
- var init_vault = __esm(async () => {
9264
- init_logger();
9265
- KEYTAR_UNAVAILABLE = Symbol("KEYTAR_UNAVAILABLE");
9266
- KEYTAR_BINDING_ERROR_RE = /(keytar(?:\.node)?|native bindings?|bindings file|no native build was found|could not locate the bindings file|module did not self-register|err_dlopen_failed|dlopen\(|compiled against a different node\.js version|cannot find module .*keytar|wasm is not supported on this platform|(set|get|delete)password is not a function)/i;
9267
- try {
9268
- keytar = normalizeKeytarModule(await import("keytar"));
9269
- } catch {}
9270
- VAULT_DIR = join7(homedir4(), ".unbrowse", "vault");
9271
- VAULT_FILE = join7(VAULT_DIR, "credentials.enc");
9272
- KEY_FILE = join7(VAULT_DIR, ".key");
9273
- vaultLock = Promise.resolve();
9274
- });
9275
-
9276
9522
  // ../../src/runtime/supervisor.ts
9277
9523
  function getDefaultLoginConfig(headless) {
9278
9524
  return {
@@ -13944,6 +14190,10 @@ function validateWorkflowReplayParams(recipe, params) {
13944
14190
  continue;
13945
14191
  const value = valueForSpec(spec, params);
13946
14192
  if (spec.required && (value == null || value === "")) {
14193
+ if (spec.default_value != null && spec.default_value !== "")
14194
+ continue;
14195
+ if (spec.example_value != null && spec.example_value !== "")
14196
+ continue;
13947
14197
  errors.push({ name: spec.name, reason: "required" });
13948
14198
  continue;
13949
14199
  }
@@ -14238,18 +14488,27 @@ async function reloadExecutionAuthState(skill, epDomain, authHeaders, cookies) {
14238
14488
  cookies.push(...resolved);
14239
14489
  } catch {}
14240
14490
  }
14241
- if (Object.keys(authHeaders).length === 0) {
14242
- try {
14243
- const sessionKey = `${getRegistrableDomain(epDomain)}-session`;
14244
- const sessionData = await getCredential(sessionKey);
14245
- if (sessionData) {
14246
- const parsed = JSON.parse(sessionData);
14247
- if (parsed.headers)
14248
- Object.assign(authHeaders, parsed.headers);
14249
- if (parsed.cookies && cookies.length === 0)
14250
- cookies.push(...parsed.cookies);
14251
- }
14252
- } catch {}
14491
+ {
14492
+ for (const sessionKey of [`${epDomain}-session`, `${getRegistrableDomain(epDomain)}-session`]) {
14493
+ try {
14494
+ const sessionData = await getCredential(sessionKey);
14495
+ if (sessionData) {
14496
+ const parsed = JSON.parse(sessionData);
14497
+ if (parsed.headers)
14498
+ Object.assign(authHeaders, parsed.headers);
14499
+ if (parsed.cookies && cookies.length === 0)
14500
+ cookies.push(...parsed.cookies);
14501
+ if (Object.keys(authHeaders).length > 0)
14502
+ break;
14503
+ }
14504
+ } catch {}
14505
+ }
14506
+ }
14507
+ if (cookies.length > 0 && authHeaders["csrf-token"]) {
14508
+ const jsessionId = cookies.find((c) => c.name === "JSESSIONID");
14509
+ if (jsessionId) {
14510
+ authHeaders["csrf-token"] = jsessionId.value.replace(/"/g, "");
14511
+ }
14253
14512
  }
14254
14513
  }
14255
14514
  function persistWorkflowArtifactForCapture(artifactSkill, captured, capturedAuthHeaders) {
@@ -15270,10 +15529,13 @@ async function executeBrowserCapture(skill, params, options) {
15270
15529
  }));
15271
15530
  }
15272
15531
  if (!auth_profile_ref) {
15273
- const vaultKey = `auth:${targetDomain}`;
15274
- const hasStoredAuth = await getCredential(vaultKey) != null;
15275
- if (hasStoredAuth)
15276
- auth_profile_ref = vaultKey;
15532
+ for (const vaultKey of [`auth:${targetDomain}`, `${domain}-session`, `${targetDomain}-session`]) {
15533
+ const hasStoredAuth = await getCredential(vaultKey) != null;
15534
+ if (hasStoredAuth) {
15535
+ auth_profile_ref = vaultKey;
15536
+ break;
15537
+ }
15538
+ }
15277
15539
  }
15278
15540
  const authBackedCapture = usedStoredAuth || !!auth_profile_ref;
15279
15541
  if (authBackedCapture) {
@@ -15854,7 +16116,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
15854
16116
  u.searchParams.set(k, String(v));
15855
16117
  }
15856
16118
  }
15857
- urlTemplate = restoreTemplatePlaceholderEncoding(u.toString());
16119
+ urlTemplate = restoreTemplatePlaceholderEncoding(u.toString()).replace(/%28/gi, "(").replace(/%29/gi, ")").replace(/%2C/gi, ",").replace(/%3A/gi, ":");
15858
16120
  } catch {}
15859
16121
  }
15860
16122
  let url = interpolate(urlTemplate, mergedParams);
@@ -15990,7 +16252,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
15990
16252
  let last = { data: null, status: 0 };
15991
16253
  for (const replayUrl of replayUrls) {
15992
16254
  const replayHeaders = buildStructuredReplayHeaders(url, replayUrl, headers);
15993
- log("exec", `server-fetch: ${endpoint.method} ${replayUrl.substring(0, 80)} auth=${(replayHeaders["authorization"] || "none").substring(0, 50)} csrf=${replayHeaders["x-csrf-token"]?.substring(0, 10)}... cookies=${replayHeaders["cookie"]?.length ?? 0}chars`);
16255
+ log("exec", `server-fetch: ${endpoint.method} ${replayUrl.substring(0, 200)} csrf-token=${(replayHeaders["csrf-token"] || "none").substring(0, 20)}... hdrs=${Object.keys(replayHeaders).length} cookies=${replayHeaders["cookie"]?.length ?? 0}chars`);
15994
16256
  const res = await fetch(replayUrl, {
15995
16257
  method: endpoint.method,
15996
16258
  headers: replayHeaders,
@@ -16394,7 +16656,12 @@ function interpolate(template, params) {
16394
16656
  const base = template.substring(0, qIdx);
16395
16657
  const query = template.substring(qIdx + 1);
16396
16658
  const interpolatedBase = base.replace(/\{(\w+)\}/g, (_, k) => params[k] != null ? String(params[k]) : `{${k}}`);
16397
- const interpolatedQuery = query.replace(/\{(\w+)\}/g, (_, k) => params[k] != null ? encodeURIComponent(String(params[k])) : `{${k}}`);
16659
+ const interpolatedQuery = query.replace(/\{(\w+)\}/g, (_, k) => {
16660
+ if (params[k] == null)
16661
+ return `{${k}}`;
16662
+ const val = String(params[k]);
16663
+ return val.replace(/[#&=\s]/g, (ch) => encodeURIComponent(ch));
16664
+ });
16398
16665
  return `${interpolatedBase}?${interpolatedQuery}`;
16399
16666
  }
16400
16667
  function interpolateObj(obj, params) {
@@ -16709,6 +16976,17 @@ function rankEndpoints(endpoints, intent, skillDomain, contextUrl) {
16709
16976
  }
16710
16977
  score += matches * 100;
16711
16978
  }
16979
+ if (rawTokens.length > 0 && pathname) {
16980
+ const pathLower = pathname.toLowerCase();
16981
+ const pathSegs = pathLower.split("/").filter(Boolean);
16982
+ for (const token of rawTokens) {
16983
+ const stemmed = stem(token);
16984
+ if (pathSegs.some((seg) => seg === stemmed || seg === token || seg.includes(token))) {
16985
+ score += 150;
16986
+ break;
16987
+ }
16988
+ }
16989
+ }
16712
16990
  if (ep.dom_extraction)
16713
16991
  score += 25;
16714
16992
  if (descriptionMeta.needs_review && ep.dom_extraction)
@@ -16943,8 +17221,6 @@ function isSpaShell(html) {
16943
17221
  }
16944
17222
  var DEFAULT_BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", VALID_VERIFICATION_STATUSES, BM25_K1 = 1.2, BM25_B = 0.75, STOPWORDS, SYNONYMS;
16945
17223
  var init_execution = __esm(async () => {
16946
- init_capture();
16947
- init_capture();
16948
17224
  init_reverse_engineer();
16949
17225
  init_bundle_scanner();
16950
17226
  init_token_resolver();
@@ -16968,6 +17244,8 @@ var init_execution = __esm(async () => {
16968
17244
  init_compile();
16969
17245
  init_publish();
16970
17246
  await __promiseAll([
17247
+ init_capture(),
17248
+ init_capture(),
16971
17249
  init_vault(),
16972
17250
  init_auth(),
16973
17251
  init_indexer(),
@@ -18568,6 +18846,13 @@ function isCachedSkillRelevantForIntent(skill, intent, contextUrl) {
18568
18846
  const hasStructuredSearchEndpoint = candidateSkill.endpoints.some((endpoint) => endpointHasSearchBindings(endpoint) && (!!endpoint.dom_extraction || !!endpoint.response_schema) && endpointMatchesContextOrigin(endpoint, contextUrl) && endpointMatchesExplicitSearchContext(endpoint, contextUrl));
18569
18847
  if (hasStructuredSearchEndpoint)
18570
18848
  return true;
18849
+ if (top && top.score >= 0) {
18850
+ try {
18851
+ const topPath = new URL(top.endpoint.url_template).pathname.toLowerCase();
18852
+ if (/\/(search|find|query|browse|explore)\b/.test(topPath))
18853
+ return true;
18854
+ } catch {}
18855
+ }
18571
18856
  if (collectExplicitSearchContextBindingKeys(contextUrl).size > 0)
18572
18857
  return false;
18573
18858
  }
@@ -19610,7 +19895,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
19610
19895
  for (const cookie of cookies)
19611
19896
  await setCookie(handoffTabId, cookie).catch(() => {});
19612
19897
  } catch {}
19613
- await evaluate(handoffTabId, (await Promise.resolve().then(() => (init_capture(), exports_capture))).INTERCEPTOR_SCRIPT).catch(() => {});
19898
+ await evaluate(handoffTabId, (await init_capture().then(() => exports_capture)).INTERCEPTOR_SCRIPT).catch(() => {});
19614
19899
  await harStart(handoffTabId).catch(() => {});
19615
19900
  try {
19616
19901
  const routesModule = await init_routes().then(() => exports_routes);
@@ -24111,13 +24396,13 @@ function schedulePeriodicVerification() {
24111
24396
  }, VERIFICATION_INTERVAL_MS);
24112
24397
  }
24113
24398
  var VERIFICATION_INTERVAL_MS, VERIFY_ENDPOINT_BATCH_SIZE;
24114
- var init_verification = __esm(() => {
24115
- init_capture();
24399
+ var init_verification = __esm(async () => {
24116
24400
  init_marketplace();
24117
24401
  init_marketplace();
24118
24402
  init_drift();
24119
24403
  init_matrix();
24120
24404
  init_candidates();
24405
+ await init_capture();
24121
24406
  VERIFICATION_INTERVAL_MS = 6 * 60 * 60 * 1000;
24122
24407
  VERIFY_ENDPOINT_BATCH_SIZE = Math.max(1, Number(process.env.UNBROWSE_VERIFY_ENDPOINT_BATCH_SIZE ?? 3));
24123
24408
  });
@@ -24224,9 +24509,11 @@ function schedulePeriodicStaleCleanup() {
24224
24509
  var VERIFY_TIMEOUT_MS, CLEANUP_INTERVAL_MS, CLEANUP_BATCH_SIZE, CLEANUP_VERIFY_ENDPOINT_LIMIT;
24225
24510
  var init_stale_cleanup_runner = __esm(async () => {
24226
24511
  init_marketplace();
24227
- init_verification();
24228
24512
  init_candidates();
24229
- await init_orchestrator();
24513
+ await __promiseAll([
24514
+ init_verification(),
24515
+ init_orchestrator()
24516
+ ]);
24230
24517
  VERIFY_TIMEOUT_MS = Math.max(5000, Number(process.env.UNBROWSE_STALE_VERIFY_TIMEOUT_MS ?? 15000));
24231
24518
  CLEANUP_INTERVAL_MS = Math.max(30 * 60 * 1000, Number(process.env.UNBROWSE_STALE_CLEANUP_INTERVAL_MS ?? 6 * 60 * 60 * 1000));
24232
24519
  CLEANUP_BATCH_SIZE = Math.max(1, Number(process.env.UNBROWSE_STALE_CLEANUP_BATCH_SIZE ?? 25));
@@ -25268,7 +25555,7 @@ async function registerRoutes(app) {
25268
25555
  if (!skill)
25269
25556
  return reply.code(404).send({ error: "Skill not found" });
25270
25557
  try {
25271
- const { verifySkill: verifySkill2 } = await Promise.resolve().then(() => (init_verification(), exports_verification));
25558
+ const { verifySkill: verifySkill2 } = await init_verification().then(() => exports_verification);
25272
25559
  const results = await verifySkill2(skill);
25273
25560
  return reply.send({ skill_id, verification: results });
25274
25561
  } catch (err) {
@@ -25861,7 +26148,6 @@ var BETA_API_URL, TRACES_DIR, BROWSE_BROKER_MAX, BROWSE_BROKER_BASE_PORT, browse
25861
26148
  var init_routes = __esm(async () => {
25862
26149
  init_client();
25863
26150
  init_reverse_engineer();
25864
- init_capture();
25865
26151
  init_marketplace();
25866
26152
  init_graph();
25867
26153
  init_client2();
@@ -25880,6 +26166,7 @@ var init_routes = __esm(async () => {
25880
26166
  init_schema_review();
25881
26167
  init_artifact();
25882
26168
  await __promiseAll([
26169
+ init_capture(),
25883
26170
  init_indexer(),
25884
26171
  init_vault(),
25885
26172
  init_orchestrator(),
@@ -25902,12 +26189,12 @@ import { config as loadEnv } from "dotenv";
25902
26189
 
25903
26190
  // ../../src/server.ts
25904
26191
  init_ratelimit();
25905
- init_verification();
25906
26192
  init_client2();
25907
- init_capture();
25908
26193
  init_version();
25909
26194
  await __promiseAll([
25910
26195
  init_routes(),
26196
+ init_verification(),
26197
+ init_capture(),
25911
26198
  init_stale_cleanup_runner()
25912
26199
  ]);
25913
26200
  import { execSync as execSync2 } from "node:child_process";