unbrowse 2.12.0 → 2.12.2

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/cli.js CHANGED
@@ -680,6 +680,31 @@ async function waitForTabRegistration(tabId, timeoutMs = 2000) {
680
680
  await new Promise((resolve) => setTimeout(resolve, 100));
681
681
  }
682
682
  }
683
+ async function createTabViaChromeCdp(url = "about:blank") {
684
+ if (!kuriCdpPort)
685
+ return "";
686
+ try {
687
+ const res = await fetch(`http://127.0.0.1:${kuriCdpPort}/json/new?${url}`, {
688
+ method: "PUT",
689
+ signal: AbortSignal.timeout(5000)
690
+ });
691
+ const target = await res.json();
692
+ return target?.id ?? target?.targetId ?? "";
693
+ } catch (err) {
694
+ log("kuri", `Chrome tab creation failed: ${err instanceof Error ? err.message : err}`);
695
+ return "";
696
+ }
697
+ }
698
+ async function findReusableIdleTab() {
699
+ await ensureTabsDiscovered();
700
+ try {
701
+ const tabs = await kuriGet("/tabs");
702
+ const candidate = tabs.find((tab) => /^(about:blank|chrome:\/\/newtab\/?)$/i.test(tab?.url ?? ""));
703
+ return candidate?.id ?? "";
704
+ } catch {
705
+ return "";
706
+ }
707
+ }
683
708
  async function navigate(tabId, url) {
684
709
  await kuriGet("/navigate", { tab_id: tabId, url });
685
710
  }
@@ -869,8 +894,17 @@ async function newTab(url) {
869
894
  const params = {};
870
895
  if (url)
871
896
  params.url = url;
872
- const result = await kuriGet("/tab/new", params);
873
- const tabId = result?.tab_id ?? "";
897
+ let tabId = "";
898
+ try {
899
+ const result = await kuriGet("/tab/new", params);
900
+ tabId = result?.tab_id ?? result?.id ?? result?.targetId ?? "";
901
+ } catch {
902
+ tabId = "";
903
+ }
904
+ if (!tabId)
905
+ tabId = await createTabViaChromeCdp(url ?? "about:blank");
906
+ if (!tabId)
907
+ tabId = await findReusableIdleTab();
874
908
  if (tabId) {
875
909
  await waitForTabRegistration(tabId).catch(() => {});
876
910
  }
@@ -3625,12 +3659,7 @@ function templatizePathSegments(templateUrl, originalUrl, context) {
3625
3659
  try {
3626
3660
  const contextSegments = new URL(context.pageUrl).pathname.split("/");
3627
3661
  const contextSeg = contextSegments[i];
3628
- const prevSeg = tSegments[i - 1] ?? "";
3629
- const prevContextSeg = contextSegments[i - 1] ?? "";
3630
- const nextSeg = tSegments[i + 1] ?? "";
3631
- const nextContextSeg = contextSegments[i + 1] ?? "";
3632
- const hasStructuralNeighborMatch = !!prevSeg && !!prevContextSeg && prevSeg === prevContextSeg || !!nextSeg && !!nextContextSeg && nextSeg === nextContextSeg;
3633
- if (contextSeg && contextSeg !== tSeg && hasStructuralNeighborMatch && !contextSeg.includes(".") && contextSeg.length >= 2 && contextSeg.length <= 40 && !/^(api|v\d+|www|en|es|fr|de|latest|search|i)$/i.test(contextSeg)) {
3662
+ if (contextSeg && contextSeg !== tSeg && !contextSeg.includes(".") && contextSeg.length >= 2 && contextSeg.length <= 40 && !/^(api|v\d+|www|en|es|fr|de|latest|search|i)$/i.test(contextSeg)) {
3634
3663
  const paramName = inferParamName(tSegments, i, "slug", usedNames);
3635
3664
  tSegments[i] = `{${paramName}}`;
3636
3665
  pathParams[paramName] = contextSeg;
@@ -5075,11 +5104,13 @@ var init_capture = __esm(() => {
5075
5104
  // ../../src/client/index.ts
5076
5105
  var exports_client2 = {};
5077
5106
  __export(exports_client2, {
5107
+ waitForBackgroundRegistration: () => waitForBackgroundRegistration,
5078
5108
  verifyMarketplaceDiscovery: () => verifyMarketplaceDiscovery,
5079
5109
  validateManifest: () => validateManifest,
5080
5110
  updateEndpointScore: () => updateEndpointScore,
5081
5111
  updateEndpointSchema: () => updateEndpointSchema,
5082
5112
  syncAgentWallet: () => syncAgentWallet2,
5113
+ startBackgroundRegistration: () => startBackgroundRegistration,
5083
5114
  setSkillSplitConfig: () => setSkillSplitConfig,
5084
5115
  setSkillPrice: () => setSkillPrice,
5085
5116
  searchIntentResolve: () => searchIntentResolve,
@@ -5518,23 +5549,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
5518
5549
  rl.close();
5519
5550
  }
5520
5551
  }
5521
- async function checkTosStatus2() {
5552
+ async function checkTosStatus2(options) {
5553
+ const exitOnFailure = options?.exitOnFailure ?? true;
5522
5554
  const config = loadConfig2();
5523
5555
  let tosInfo;
5524
5556
  try {
5525
5557
  tosInfo = await api2("GET", "/v1/tos/current");
5526
5558
  } catch {
5527
- return;
5559
+ return true;
5528
5560
  }
5529
5561
  if (config?.tos_accepted_version === tosInfo.version) {
5530
- return;
5562
+ return true;
5531
5563
  }
5532
5564
  console.log(`
5533
5565
  The Unbrowse Terms of Service have been updated.`);
5534
5566
  const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
5535
5567
  if (!accepted) {
5536
5568
  console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
5537
- process.exit(1);
5569
+ if (exitOnFailure)
5570
+ process.exit(1);
5571
+ return false;
5538
5572
  }
5539
5573
  try {
5540
5574
  await api2("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
@@ -5547,16 +5581,20 @@ The Unbrowse Terms of Service have been updated.`);
5547
5581
  } catch (err) {
5548
5582
  console.warn(`Failed to record ToS acceptance: ${err.message}`);
5549
5583
  }
5584
+ return true;
5550
5585
  }
5551
5586
  async function ensureRegistered2(options) {
5552
5587
  if (LOCAL_ONLY2)
5553
5588
  return;
5589
+ const exitOnFailure = options?.exitOnFailure ?? true;
5554
5590
  const usableKey = await findUsableApiKey2();
5555
5591
  if (usableKey) {
5556
5592
  if (usableKey.source === "config") {
5557
5593
  console.log("[unbrowse] Restored saved registration.");
5558
5594
  }
5559
- await checkTosStatus2();
5595
+ const accepted2 = await checkTosStatus2({ exitOnFailure });
5596
+ if (!accepted2)
5597
+ return;
5560
5598
  try {
5561
5599
  const profile = await getMyProfile2();
5562
5600
  const wallet = getLocalWalletContext2();
@@ -5577,7 +5615,9 @@ async function ensureRegistered2(options) {
5577
5615
  const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
5578
5616
  if (!accepted) {
5579
5617
  console.log("You must accept the Terms of Service to use Unbrowse.");
5580
- process.exit(1);
5618
+ if (exitOnFailure)
5619
+ process.exit(1);
5620
+ return;
5581
5621
  }
5582
5622
  const fallbackName = buildDefaultAgentName2();
5583
5623
  const name = options?.promptForEmail ? await promptAgentEmail2(fallbackName) : resolveAgentName2(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
@@ -5605,8 +5645,36 @@ async function ensureRegistered2(options) {
5605
5645
  } catch (err) {
5606
5646
  console.warn(`Registration failed: ${err.message}`);
5607
5647
  console.warn("Set UNBROWSE_API_KEY manually or try again.");
5608
- process.exit(1);
5648
+ if (exitOnFailure)
5649
+ process.exit(1);
5650
+ }
5651
+ }
5652
+ function startBackgroundRegistration(options) {
5653
+ if (LOCAL_ONLY2)
5654
+ return Promise.resolve();
5655
+ if (backgroundRegistrationPromise)
5656
+ return backgroundRegistrationPromise;
5657
+ backgroundRegistrationPromise = ensureRegistered2({
5658
+ promptForEmail: options?.promptForEmail,
5659
+ exitOnFailure: false
5660
+ }).catch((err) => {
5661
+ console.warn(`[unbrowse] Background registration failed: ${err.message}`);
5662
+ }).finally(() => {
5663
+ backgroundRegistrationPromise = null;
5664
+ });
5665
+ return backgroundRegistrationPromise;
5666
+ }
5667
+ async function waitForBackgroundRegistration(timeoutMs = 0) {
5668
+ if (!backgroundRegistrationPromise)
5669
+ return;
5670
+ if (timeoutMs <= 0) {
5671
+ await backgroundRegistrationPromise;
5672
+ return;
5609
5673
  }
5674
+ await Promise.race([
5675
+ backgroundRegistrationPromise,
5676
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
5677
+ ]);
5610
5678
  }
5611
5679
  function skillCachePath(skillId) {
5612
5680
  return join3(getSkillCacheDir(), `${skillId}.json`);
@@ -5997,7 +6065,7 @@ async function setSkillPrice(skillId, priceUsd) {
5997
6065
  async function setSkillSplitConfig(skillId, splitConfig) {
5998
6066
  return api2("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
5999
6067
  }
6000
- var API_URL2, PROFILE_NAME2, recentLocalSkills2, LOCAL_ONLY2, EMAIL_RE2, API_TIMEOUT_MS2, PUBLISH_TIMEOUT_MS2;
6068
+ var API_URL2, PROFILE_NAME2, recentLocalSkills2, LOCAL_ONLY2, EMAIL_RE2, API_TIMEOUT_MS2, PUBLISH_TIMEOUT_MS2, backgroundRegistrationPromise = null;
6001
6069
  var init_client2 = __esm(() => {
6002
6070
  init_cascade();
6003
6071
  API_URL2 = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
@@ -11901,8 +11969,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
11901
11969
  } catch {}
11902
11970
  }
11903
11971
  }
11904
- const hasAuthContext = cookies.length > 0 || Object.keys(authHeaders).length > 0 || !!skill.auth_profile_ref || endpoint.semantic?.auth_required === true;
11905
- if (!options?.skip_robots_check && !hasAuthContext) {
11972
+ if (!options?.skip_robots_check) {
11906
11973
  const allowed = await isAllowedByRobots(url);
11907
11974
  if (!allowed) {
11908
11975
  const traceId = nanoid5();
@@ -15294,7 +15361,6 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15294
15361
  result: {
15295
15362
  message: `Found ${epRanked.length} endpoint(s). Pick one and call POST /v1/skills/${resolvedSkill.skill_id}/execute with params.endpoint_id.`,
15296
15363
  skill_id: resolvedSkill.skill_id,
15297
- suggested_next_operation_id: chunk.available_operation_ids[0],
15298
15364
  available_operations: chunk.operations.map((operation) => ({
15299
15365
  operation_id: operation.operation_id,
15300
15366
  endpoint_id: operation.endpoint_id,
@@ -16731,7 +16797,7 @@ var init_orchestrator = __esm(async () => {
16731
16797
  captureDomainLocks = new Map;
16732
16798
  skillRouteCache = new Map;
16733
16799
  ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
16734
- SKILL_SNAPSHOT_DIR = join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
16800
+ SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
16735
16801
  domainSkillCache = new Map;
16736
16802
  DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
16737
16803
  try {
@@ -17144,7 +17210,7 @@ async function processIndexJob(job) {
17144
17210
  }
17145
17211
  console.log(`[background-index] completed for ${domain} -> ${published.skill_id}`);
17146
17212
  }
17147
- var UNBROWSE_CONFIG_PATH, SECRET_VALUE_PATTERNS, SECRET_KEY_PATTERNS, SKILL_SNAPSHOT_DIR2, indexInFlight;
17213
+ var UNBROWSE_CONFIG_PATH, SKILL_SNAPSHOT_DIR2, SECRET_VALUE_PATTERNS, SECRET_KEY_PATTERNS, indexInFlight;
17148
17214
  var init_indexer = __esm(async () => {
17149
17215
  init_graph();
17150
17216
  init_client2();
@@ -17152,6 +17218,7 @@ var init_indexer = __esm(async () => {
17152
17218
  init_domain();
17153
17219
  await init_orchestrator();
17154
17220
  UNBROWSE_CONFIG_PATH = join10(homedir6(), ".unbrowse", "config.json");
17221
+ SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
17155
17222
  SECRET_VALUE_PATTERNS = [
17156
17223
  /^eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
17157
17224
  /^Bearer\s+\S+/i,
@@ -17165,7 +17232,6 @@ var init_indexer = __esm(async () => {
17165
17232
  /^v2\.[A-Za-z0-9_-]{20,}/
17166
17233
  ];
17167
17234
  SECRET_KEY_PATTERNS = /^(api[_-]?key|access[_-]?token|auth[_-]?token|secret[_-]?key|private[_-]?key|password|passwd|session[_-]?id|session[_-]?token|csrf[_-]?token|client[_-]?secret|bearer|refresh[_-]?token|id[_-]?token|jwt|nonce|otp|pin|ssn|credit[_-]?card)$/i;
17168
- SKILL_SNAPSHOT_DIR2 = join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
17169
17235
  indexInFlight = new Map;
17170
17236
  });
17171
17237
 
@@ -17311,113 +17377,6 @@ var init_session_logs = __esm(() => {
17311
17377
  init_domain();
17312
17378
  });
17313
17379
 
17314
- // ../../src/agent-outcome.ts
17315
- function edgePriority(kind) {
17316
- switch (kind) {
17317
- case "parent_child":
17318
- return 4;
17319
- case "pagination":
17320
- return 3;
17321
- case "dependency":
17322
- return 2;
17323
- case "hint":
17324
- return 1;
17325
- case "auth":
17326
- return 0;
17327
- default:
17328
- return -1;
17329
- }
17330
- }
17331
- function nextActionWhy(kind, bindingKey, title) {
17332
- switch (kind) {
17333
- case "parent_child":
17334
- return `Likely next detail step after this result. Exposes ${title}.`;
17335
- case "pagination":
17336
- return `Likely next page or continuation step. Carries ${bindingKey || "cursor"} forward.`;
17337
- case "dependency":
17338
- return `Unlocks the next dependent call using ${bindingKey || "known bindings"}.`;
17339
- case "auth":
17340
- return "Useful once authentication is in place.";
17341
- case "hint":
17342
- return "Common follow-up action from the current result.";
17343
- default:
17344
- return "Likely follow-up action.";
17345
- }
17346
- }
17347
- function operationTitle(operation) {
17348
- const semantic = [operation.action_kind, operation.resource_kind].filter(Boolean).join(" ").replace(/_/g, " ").trim();
17349
- return operation.description_out || semantic || operation.endpoint_id;
17350
- }
17351
- function buildAgentImpact(timing) {
17352
- if (!timing?.source)
17353
- return;
17354
- return {
17355
- source: timing.source,
17356
- cache_hit: timing.cache_hit === true,
17357
- browser_avoided: !BROWSER_SOURCES.has(timing.source),
17358
- baseline_total_ms: timing.baseline_total_ms,
17359
- actual_total_ms: timing.actual_total_ms,
17360
- time_saved_ms: timing.time_saved_ms,
17361
- time_saved_pct: timing.time_saved_pct ?? 0,
17362
- tokens_saved: timing.tokens_saved ?? 0,
17363
- tokens_saved_pct: timing.tokens_saved_pct ?? 0,
17364
- baseline_cost_uc: timing.baseline_cost_uc,
17365
- actual_cost_uc: timing.actual_cost_uc,
17366
- cost_saved_uc: timing.cost_saved_uc
17367
- };
17368
- }
17369
- function buildNextActions(skill, endpointId, maxActions = 3) {
17370
- if (!skill?.operation_graph || !endpointId)
17371
- return [];
17372
- const graph = skill.operation_graph;
17373
- const current = graph.operations.find((operation) => operation.endpoint_id === endpointId);
17374
- if (!current)
17375
- return [];
17376
- const byOperationId = new Map(graph.operations.map((operation) => [operation.operation_id, operation]));
17377
- const scored = new Map;
17378
- for (const edge of graph.edges) {
17379
- if (edge.from_operation_id !== current.operation_id)
17380
- continue;
17381
- const target = byOperationId.get(edge.to_operation_id);
17382
- if (!target)
17383
- continue;
17384
- const candidate = {
17385
- operation_id: target.operation_id,
17386
- endpoint_id: target.endpoint_id,
17387
- title: operationTitle(target),
17388
- why: nextActionWhy(edge.kind, edge.binding_key, operationTitle(target)),
17389
- score: edgePriority(edge.kind) * 10 + Math.round(edge.confidence * 10)
17390
- };
17391
- const existing = scored.get(target.operation_id);
17392
- if (!existing || candidate.score > existing.score) {
17393
- scored.set(target.operation_id, candidate);
17394
- }
17395
- }
17396
- return [...scored.values()].sort((a, b) => b.score - a.score || a.title.localeCompare(b.title)).slice(0, maxActions).map((candidate) => ({
17397
- endpoint_id: candidate.endpoint_id,
17398
- operation_id: candidate.operation_id,
17399
- title: candidate.title,
17400
- why: candidate.why,
17401
- command: `unbrowse execute --skill ${skill.skill_id} --endpoint ${candidate.endpoint_id}`
17402
- }));
17403
- }
17404
- function attachAgentOutcomeHints(payload, opts) {
17405
- const target = payload;
17406
- const impact = buildAgentImpact(opts?.timing);
17407
- if (impact) {
17408
- target.impact = impact;
17409
- }
17410
- const nextActions = buildNextActions(opts?.skill, opts?.endpointId);
17411
- if (nextActions.length > 0) {
17412
- target.next_actions = nextActions;
17413
- }
17414
- return target;
17415
- }
17416
- var BROWSER_SOURCES;
17417
- var init_agent_outcome = __esm(() => {
17418
- BROWSER_SOURCES = new Set(["live-capture", "first-pass", "browser-action"]);
17419
- });
17420
-
17421
17380
  // ../../src/api/browse-session.ts
17422
17381
  function extractBrowseFailureMessage(value) {
17423
17382
  return typeof value === "string" ? value : getKuriErrorMessage(value);
@@ -17432,6 +17391,8 @@ function isRecoverableBrowseFailure(value) {
17432
17391
  async function createBrowseSession(sessions, client, injectInterceptor2) {
17433
17392
  await client.start().catch(() => {});
17434
17393
  const tabId = await client.newTab();
17394
+ if (!tabId)
17395
+ throw new Error("Failed to create browser tab");
17435
17396
  await client.harStart(tabId).catch(() => {});
17436
17397
  await injectInterceptor2(tabId);
17437
17398
  const session = { tabId, url: "about:blank", harActive: true, domain: "" };
@@ -17454,7 +17415,7 @@ async function adoptExistingBrowseTab(sessions, client, injectInterceptor2, pref
17454
17415
  const candidate = tabs.find((tab) => {
17455
17416
  const domain = extractDomain2(tab.url);
17456
17417
  return !!domain && !!normalizedPreferred && domain === normalizedPreferred;
17457
- }) ?? tabs.find((tab) => /^https?:\/\//.test(tab.url ?? ""));
17418
+ }) ?? tabs.find((tab) => /^(about:blank|chrome:\/\/newtab\/?)$/i.test(tab.url ?? "")) ?? (!normalizedPreferred ? tabs.find((tab) => /^https?:\/\//.test(tab.url ?? "")) : undefined);
17458
17419
  if (!candidate?.id)
17459
17420
  return null;
17460
17421
  await client.harStart(candidate.id).catch(() => {});
@@ -18011,6 +17972,21 @@ function parseJsonString(value) {
18011
17972
  return null;
18012
17973
  }
18013
17974
  }
17975
+ async function settleSubmitDestination(client, tabId, url, html) {
17976
+ let settledUrl = url;
17977
+ let settledHtml = html;
17978
+ const deadline = Date.now() + SUBMIT_SETTLE_WINDOW_MS;
17979
+ while (Date.now() < deadline) {
17980
+ await sleep(Math.min(SUBMIT_POLL_INTERVAL_MS, Math.max(50, deadline - Date.now())));
17981
+ const nextUrl = await client.getCurrentUrl(tabId).catch(() => "");
17982
+ const nextHtml = await client.getPageHtml(tabId).catch(() => "");
17983
+ if (nextUrl && !nextUrl.startsWith("about:blank"))
17984
+ settledUrl = nextUrl;
17985
+ if (nextHtml)
17986
+ settledHtml = nextHtml;
17987
+ }
17988
+ return { url: settledUrl, html: settledHtml };
17989
+ }
18014
17990
  async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, options) {
18015
17991
  const timeoutMs = options.timeoutMs ?? DEFAULT_SUBMIT_TIMEOUT_MS;
18016
17992
  const deadline = Date.now() + timeoutMs;
@@ -18021,7 +17997,7 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
18021
17997
  if (waitResult?.status === "found" || waitResult?.status === "ready") {
18022
17998
  const url = await client.getCurrentUrl(tabId).catch(() => beforeUrl);
18023
17999
  const html = await client.getPageHtml(tabId).catch(() => beforeHtml);
18024
- return { ok: true, url, html };
18000
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
18025
18001
  }
18026
18002
  } catch {}
18027
18003
  }
@@ -18029,20 +18005,20 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
18029
18005
  const url = await client.getCurrentUrl(tabId).catch(() => "");
18030
18006
  const html = await client.getPageHtml(tabId).catch(() => "");
18031
18007
  if (waitFor && isUrlWaitHint(waitFor) && url.includes(waitFor)) {
18032
- return { ok: true, url, html };
18008
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
18033
18009
  }
18034
18010
  if (url && url !== beforeUrl && !url.startsWith("about:blank")) {
18035
- return { ok: true, url, html };
18011
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
18036
18012
  }
18037
18013
  if (hasMeaningfulPageChange(beforeHtml, html)) {
18038
- return { ok: true, url: url || beforeUrl, html };
18014
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url || beforeUrl, html) };
18039
18015
  }
18040
18016
  await sleep(SUBMIT_POLL_INTERVAL_MS);
18041
18017
  }
18042
18018
  return { ok: false, url: beforeUrl, html: beforeHtml };
18043
18019
  }
18044
18020
  async function submitBrowseForm(deps, options = {}) {
18045
- const { client, session, flushCapture, restartCapture, rehydratePlugins } = deps;
18021
+ const { client, session, restartCapture, rehydratePlugins } = deps;
18046
18022
  const sameOriginFetchFallback = options.sameOriginFetchFallback !== false;
18047
18023
  const beforeUrl = await client.getCurrentUrl(session.tabId).catch(() => session.url);
18048
18024
  const beforeHtml = await client.getPageHtml(session.tabId).catch(() => "");
@@ -18068,14 +18044,6 @@ async function submitBrowseForm(deps, options = {}) {
18068
18044
  const domOutcome = await waitForSubmitOutcome(client, session.tabId, beforeUrl, beforeHtml, options);
18069
18045
  if (domOutcome.ok) {
18070
18046
  session.url = domOutcome.url || beforeUrl || session.url;
18071
- let captureSync2 = null;
18072
- if (flushCapture) {
18073
- try {
18074
- captureSync2 = await flushCapture(session);
18075
- } catch {
18076
- captureSync2 = null;
18077
- }
18078
- }
18079
18047
  await restartCapture(session);
18080
18048
  return {
18081
18049
  ok: true,
@@ -18084,8 +18052,7 @@ async function submitBrowseForm(deps, options = {}) {
18084
18052
  fallback_used: false,
18085
18053
  same_origin_html_rehydrated: false,
18086
18054
  wait_for: options.waitFor,
18087
- submit_meta: submitMeta,
18088
- capture_sync: captureSync2
18055
+ submit_meta: submitMeta
18089
18056
  };
18090
18057
  }
18091
18058
  if (submitError && !isRecoverableBrowseFailure(submitError) && !sameOriginFetchFallback) {
@@ -18123,14 +18090,6 @@ async function submitBrowseForm(deps, options = {}) {
18123
18090
  if (!rehydrate) {
18124
18091
  rehydrate = await rehydratePlugins(session.tabId).catch(() => null);
18125
18092
  }
18126
- let captureSync = null;
18127
- if (flushCapture) {
18128
- try {
18129
- captureSync = await flushCapture(session);
18130
- } catch {
18131
- captureSync = null;
18132
- }
18133
- }
18134
18093
  await restartCapture(session);
18135
18094
  return {
18136
18095
  ok: true,
@@ -18141,11 +18100,10 @@ async function submitBrowseForm(deps, options = {}) {
18141
18100
  status: typeof fallbackPayload.status === "number" ? fallbackPayload.status : undefined,
18142
18101
  wait_for: options.waitFor,
18143
18102
  submit_meta: submitMeta,
18144
- capture_sync: captureSync,
18145
18103
  rehydrate
18146
18104
  };
18147
18105
  }
18148
- var DEFAULT_SUBMIT_TIMEOUT_MS = 8000, SUBMIT_POLL_INTERVAL_MS = 250;
18106
+ var DEFAULT_SUBMIT_TIMEOUT_MS = 8000, SUBMIT_POLL_INTERVAL_MS = 250, SUBMIT_SETTLE_WINDOW_MS = 1000;
18149
18107
  var init_browse_submit = __esm(() => {
18150
18108
  init_browse_session();
18151
18109
  });
@@ -18270,7 +18228,6 @@ import { join as join12 } from "path";
18270
18228
  function buildAnalyticsSessionPayload(result, opts) {
18271
18229
  const source = result.timing?.source ?? result.source;
18272
18230
  const apiCalls = result.trace.endpoint_id ? 1 : 0;
18273
- const browserMode = opts.browser_mode ?? (source === "live-capture" || source === "first-pass" || source === "browser-action" ? "default" : "replaced");
18274
18231
  const cachedSkillCalls = opts.cached_skill_calls ?? (apiCalls > 0 && source !== "live-capture" && source !== "first-pass" ? 1 : 0);
18275
18232
  const freshIndexCalls = opts.fresh_index_calls ?? (apiCalls > 0 && (source === "live-capture" || source === "first-pass") ? 1 : 0);
18276
18233
  return {
@@ -18282,14 +18239,7 @@ function buildAnalyticsSessionPayload(result, opts) {
18282
18239
  discovery_queries: opts.discovery_queries,
18283
18240
  cached_skill_calls: cachedSkillCalls,
18284
18241
  fresh_index_calls: freshIndexCalls,
18285
- browser_mode: browserMode,
18286
- success: result.trace.success ?? true,
18287
- source,
18288
- time_saved_ms: result.timing?.time_saved_ms,
18289
- time_saved_pct: result.timing?.time_saved_pct,
18290
- tokens_saved: result.trace.tokens_saved ?? result.timing?.tokens_saved,
18291
- tokens_saved_pct: result.trace.tokens_saved_pct ?? result.timing?.tokens_saved_pct,
18292
- cost_saved_uc: result.timing?.cost_saved_uc
18242
+ browser_mode: opts.browser_mode ?? "unknown"
18293
18243
  };
18294
18244
  }
18295
18245
  function passiveIndexFromRequests(requests, pageUrl) {
@@ -18470,7 +18420,11 @@ async function registerRoutes(app) {
18470
18420
  app.addHook("onRequest", async (req, reply) => {
18471
18421
  if (req.url === "/health" || req.url === "/v1/stats")
18472
18422
  return;
18473
- const key = getApiKey2();
18423
+ let key = getApiKey2();
18424
+ if (!key) {
18425
+ await waitForBackgroundRegistration(15000);
18426
+ key = getApiKey2();
18427
+ }
18474
18428
  if (!key) {
18475
18429
  return reply.code(401).send({
18476
18430
  error: "api_key_required",
@@ -18486,11 +18440,7 @@ async function registerRoutes(app) {
18486
18440
  return reply.code(400).send({ error: "intent required" });
18487
18441
  try {
18488
18442
  const result = await resolveAndExecute(intent, params ?? {}, context, projection, { confirm_unsafe, dry_run, force_capture, client_scope: clientScope });
18489
- const res = attachAgentOutcomeHints({ ...result }, {
18490
- skill: result.skill,
18491
- endpointId: result.trace.endpoint_id,
18492
- timing: result.timing
18493
- });
18443
+ const res = result;
18494
18444
  if (result.timing) {
18495
18445
  res.timing = result.timing;
18496
18446
  }
@@ -18499,9 +18449,10 @@ async function registerRoutes(app) {
18499
18449
  res.available_endpoints = innerResult.available_endpoints;
18500
18450
  }
18501
18451
  await recordAnalyticsSession(buildAnalyticsSessionPayload(result, {
18452
+ browser_mode: "replaced",
18502
18453
  discovery_queries: 1
18503
18454
  })).catch(() => {});
18504
- return reply.send(res);
18455
+ return reply.send(result);
18505
18456
  } catch (err) {
18506
18457
  return reply.code(500).send({ error: err.message });
18507
18458
  }
@@ -18711,33 +18662,26 @@ async function registerRoutes(app) {
18711
18662
  recordExecution(freshResult.trace.skill_id, freshResult.trace.endpoint_id, freshResult.trace, skill).catch(() => {});
18712
18663
  }
18713
18664
  await recordAnalyticsSession(buildAnalyticsSessionPayload(freshResult, {
18665
+ browser_mode: "manual",
18714
18666
  discovery_queries: 1
18715
18667
  })).catch(() => {});
18716
- const recovered = attachAgentOutcomeHints({
18668
+ return reply.send({
18717
18669
  ...freshResult,
18718
18670
  _recovery: {
18719
18671
  reason: "stale_endpoint_404",
18720
18672
  original_skill_id: skill_id,
18721
18673
  message: "Original endpoint returned 404. Auto-recovered with fresh capture."
18722
18674
  }
18723
- }, {
18724
- skill: freshResult.skill ?? skill,
18725
- endpointId: freshResult.trace.endpoint_id,
18726
- timing: freshResult.timing
18727
- });
18728
- return reply.send({
18729
- ...recovered
18730
18675
  });
18731
18676
  } catch {}
18732
18677
  }
18733
18678
  await recordAnalyticsSession(buildAnalyticsSessionPayload(execResult, {
18734
- discovery_queries: 0
18679
+ browser_mode: "manual",
18680
+ discovery_queries: 0,
18681
+ cached_skill_calls: execResult.trace.endpoint_id ? 1 : 0,
18682
+ fresh_index_calls: 0
18735
18683
  })).catch(() => {});
18736
- const response = attachAgentOutcomeHints({ ...execResult }, {
18737
- skill,
18738
- endpointId: execResult.trace.endpoint_id
18739
- });
18740
- return reply.send(response);
18684
+ return reply.send(execResult);
18741
18685
  } catch (err) {
18742
18686
  return reply.code(500).send({ error: err.message });
18743
18687
  }
@@ -18886,71 +18830,6 @@ async function registerRoutes(app) {
18886
18830
  session.harActive = true;
18887
18831
  await injectInterceptor(session.tabId).catch(() => {});
18888
18832
  }
18889
- async function flushBrowseCapture(session, options = {}) {
18890
- let intercepted = [];
18891
- try {
18892
- const raw = await collectInterceptedRequests(session.tabId);
18893
- intercepted = raw.map((request) => ({
18894
- url: request.url,
18895
- method: request.method,
18896
- request_headers: request.request_headers ?? {},
18897
- request_body: request.request_body,
18898
- response_status: request.response_status,
18899
- response_headers: request.response_headers ?? {},
18900
- response_body: request.response_body,
18901
- timestamp: request.timestamp
18902
- }));
18903
- } catch {}
18904
- let harEntries = [];
18905
- if (session.harActive) {
18906
- try {
18907
- const { entries } = await harStop(session.tabId);
18908
- harEntries = entries;
18909
- } catch {}
18910
- }
18911
- session.harActive = false;
18912
- const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
18913
- const syncResult = await cacheBrowseRequests({
18914
- sessionUrl: session.url,
18915
- sessionDomain: session.domain,
18916
- requests: allRequests,
18917
- getPageHtml: () => getPageHtml(session.tabId)
18918
- });
18919
- let backgroundPublishQueued = false;
18920
- if (options.queueBackgroundPublish) {
18921
- if (allRequests.length > 0) {
18922
- passiveIndexFromRequests(allRequests, session.url);
18923
- backgroundPublishQueued = true;
18924
- } else if (syncResult.skill) {
18925
- queueBackgroundIndex({
18926
- skill: { ...syncResult.skill },
18927
- domain: syncResult.domain,
18928
- intent: syncResult.skill.intent_signature || `browse ${syncResult.domain}`,
18929
- contextUrl: session.url,
18930
- cacheKey: `browse-submit:${syncResult.domain}:${Date.now()}`
18931
- });
18932
- backgroundPublishQueued = true;
18933
- }
18934
- }
18935
- return {
18936
- indexed: syncResult.indexed,
18937
- mode: syncResult.mode,
18938
- domain: syncResult.domain,
18939
- skill_id: syncResult.skill?.skill_id ?? null,
18940
- endpoint_count: syncResult.skill?.endpoints.length ?? 0,
18941
- endpoints: (syncResult.skill?.endpoints ?? []).map((endpoint) => ({
18942
- endpoint_id: endpoint.endpoint_id,
18943
- method: endpoint.method,
18944
- url_template: endpoint.url_template,
18945
- description: endpoint.description,
18946
- trigger_url: endpoint.trigger_url,
18947
- action_kind: endpoint.semantic?.action_kind,
18948
- resource_kind: endpoint.semantic?.resource_kind
18949
- })),
18950
- request_count: allRequests.length,
18951
- background_publish_queued: backgroundPublishQueued
18952
- };
18953
- }
18954
18833
  app.post("/v1/browse/go", async (req, reply) => {
18955
18834
  const { url } = req.body;
18956
18835
  if (!url)
@@ -19007,7 +18886,6 @@ async function registerRoutes(app) {
19007
18886
  const { session, result, recovered } = await withRecoveredBrowseSession(browseSessions, exports_client, injectInterceptor, async (session2) => submitBrowseForm({
19008
18887
  client: exports_client,
19009
18888
  session: session2,
19010
- flushCapture: async (session3) => await flushBrowseCapture(session3, { queueBackgroundPublish: true }),
19011
18889
  restartCapture: restartBrowseCapture,
19012
18890
  rehydratePlugins: bestEffortRehydratePlugins
19013
18891
  }, {
@@ -19020,10 +18898,8 @@ async function registerRoutes(app) {
19020
18898
  session.url = result.url || await getCurrentUrl(session.tabId).catch(() => session.url);
19021
18899
  session.domain = profileName(session.url);
19022
18900
  const statusCode = result.ok ? 200 : result.recoverable ? 502 : 400;
19023
- const nextStep = result.ok ? result.capture_sync?.background_publish_queued ? "Background publish queued for this step. Continue the flow, then run `unbrowse close` when you're done to save auth and finalize any remaining capture." : "If more UI steps remain, continue the flow. Run `unbrowse close` when you're done to save auth and finalize capture." : "Inspect the page state with `unbrowse snap --filter interactive`, then retry submit with selectors or a wait hint if needed.";
19024
18901
  return reply.code(statusCode).send({
19025
18902
  ...result,
19026
- next_step: nextStep,
19027
18903
  recovered,
19028
18904
  tab_id: session.tabId,
19029
18905
  url: session.url
@@ -19133,18 +19009,57 @@ async function registerRoutes(app) {
19133
19009
  const session = browseSessions.get("default");
19134
19010
  if (!session)
19135
19011
  return reply.send({ ok: false, error: "no active session" });
19136
- const syncResult = await flushBrowseCapture(session);
19137
- await restartBrowseCapture(session);
19012
+ let intercepted = [];
19013
+ try {
19014
+ const raw = await collectInterceptedRequests(session.tabId);
19015
+ intercepted = raw.map((request) => ({
19016
+ url: request.url,
19017
+ method: request.method,
19018
+ request_headers: request.request_headers ?? {},
19019
+ request_body: request.request_body,
19020
+ response_status: request.response_status,
19021
+ response_headers: request.response_headers ?? {},
19022
+ response_body: request.response_body,
19023
+ timestamp: request.timestamp
19024
+ }));
19025
+ } catch {}
19026
+ let harEntries = [];
19027
+ if (session.harActive) {
19028
+ try {
19029
+ const { entries } = await harStop(session.tabId);
19030
+ harEntries = entries;
19031
+ } catch {}
19032
+ }
19033
+ session.harActive = false;
19034
+ const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
19035
+ const syncResult = await cacheBrowseRequests({
19036
+ sessionUrl: session.url,
19037
+ sessionDomain: session.domain,
19038
+ requests: allRequests,
19039
+ getPageHtml: () => getPageHtml(session.tabId)
19040
+ });
19041
+ await networkEnable(session.tabId).catch(() => {});
19042
+ await harStart(session.tabId).catch(() => {});
19043
+ await scriptInject(session.tabId, INTERCEPTOR_SCRIPT).catch(() => {});
19044
+ session.harActive = true;
19045
+ await injectInterceptor(session.tabId).catch(() => {});
19138
19046
  return reply.send({
19139
19047
  ok: true,
19140
19048
  tab_id: session.tabId,
19141
19049
  indexed: syncResult.indexed,
19142
19050
  mode: syncResult.mode,
19143
19051
  domain: syncResult.domain,
19144
- skill_id: syncResult.skill_id,
19145
- endpoint_count: syncResult.endpoint_count,
19146
- endpoints: syncResult.endpoints,
19147
- request_count: syncResult.request_count
19052
+ skill_id: syncResult.skill?.skill_id ?? null,
19053
+ endpoint_count: syncResult.skill?.endpoints.length ?? 0,
19054
+ endpoints: (syncResult.skill?.endpoints ?? []).map((endpoint) => ({
19055
+ endpoint_id: endpoint.endpoint_id,
19056
+ method: endpoint.method,
19057
+ url_template: endpoint.url_template,
19058
+ description: endpoint.description,
19059
+ trigger_url: endpoint.trigger_url,
19060
+ action_kind: endpoint.semantic?.action_kind,
19061
+ resource_kind: endpoint.semantic?.resource_kind
19062
+ }))
19148
19063
  });
19149
19064
  });
19150
19065
  app.post("/v1/browse/close", async (_req, reply) => {
@@ -19154,16 +19069,42 @@ async function registerRoutes(app) {
19154
19069
  if (session.domain) {
19155
19070
  await authProfileSave(session.tabId, session.domain).catch(() => {});
19156
19071
  }
19157
- const syncResult = await flushBrowseCapture(session, { queueBackgroundPublish: true });
19072
+ let intercepted = [];
19073
+ try {
19074
+ const raw = await collectInterceptedRequests(session.tabId);
19075
+ intercepted = raw.map((r) => ({
19076
+ url: r.url,
19077
+ method: r.method,
19078
+ request_headers: r.request_headers ?? {},
19079
+ request_body: r.request_body,
19080
+ response_status: r.response_status,
19081
+ response_headers: r.response_headers ?? {},
19082
+ response_body: r.response_body,
19083
+ timestamp: r.timestamp
19084
+ }));
19085
+ } catch {}
19086
+ let harEntries = [];
19087
+ if (session.harActive) {
19088
+ try {
19089
+ const { entries } = await harStop(session.tabId);
19090
+ harEntries = entries;
19091
+ } catch {}
19092
+ }
19093
+ const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
19094
+ const syncResult = await cacheBrowseRequests({
19095
+ sessionUrl: session.url,
19096
+ sessionDomain: session.domain,
19097
+ requests: allRequests,
19098
+ getPageHtml: () => getPageHtml(session.tabId)
19099
+ });
19100
+ passiveIndexFromRequests(allRequests, session.url);
19158
19101
  await closeTab(session.tabId).catch(() => {});
19159
19102
  browseSessions.delete("default");
19160
19103
  return reply.send({
19161
19104
  ok: true,
19162
19105
  indexed: syncResult.indexed,
19163
19106
  mode: syncResult.mode,
19164
- endpoint_count: syncResult.endpoint_count,
19165
- request_count: syncResult.request_count,
19166
- background_publish_queued: syncResult.background_publish_queued,
19107
+ endpoint_count: syncResult.skill?.endpoints.length ?? 0,
19167
19108
  auth_saved: session.domain || null
19168
19109
  });
19169
19110
  });
@@ -19190,7 +19131,6 @@ var init_routes = __esm(async () => {
19190
19131
  init_ratelimit();
19191
19132
  init_graph();
19192
19133
  init_session_logs();
19193
- init_agent_outcome();
19194
19134
  init_browse_session();
19195
19135
  init_browse_submit();
19196
19136
  await __promiseAll([
@@ -19247,7 +19187,7 @@ async function startUnbrowseServer(options = {}) {
19247
19187
  try {
19248
19188
  execSync2("pkill -f chrome-headless-shell", { stdio: "ignore" });
19249
19189
  } catch {}
19250
- await ensureRegistered2();
19190
+ startBackgroundRegistration();
19251
19191
  const app = Fastify({ logger: options.logger ?? true });
19252
19192
  await app.register(cors, { origin: true });
19253
19193
  await registerRateLimiter(app);
@@ -19282,7 +19222,6 @@ var init_server = __esm(async () => {
19282
19222
 
19283
19223
  // ../../src/cli.ts
19284
19224
  import { config as loadEnv } from "dotenv";
19285
- import { spawn as spawn3 } from "child_process";
19286
19225
 
19287
19226
  // ../../src/client/index.ts
19288
19227
  init_cascade();
@@ -19664,23 +19603,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
19664
19603
  rl.close();
19665
19604
  }
19666
19605
  }
19667
- async function checkTosStatus() {
19606
+ async function checkTosStatus(options) {
19607
+ const exitOnFailure = options?.exitOnFailure ?? true;
19668
19608
  const config = loadConfig();
19669
19609
  let tosInfo;
19670
19610
  try {
19671
19611
  tosInfo = await api("GET", "/v1/tos/current");
19672
19612
  } catch {
19673
- return;
19613
+ return true;
19674
19614
  }
19675
19615
  if (config?.tos_accepted_version === tosInfo.version) {
19676
- return;
19616
+ return true;
19677
19617
  }
19678
19618
  console.log(`
19679
19619
  The Unbrowse Terms of Service have been updated.`);
19680
19620
  const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
19681
19621
  if (!accepted) {
19682
19622
  console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
19683
- process.exit(1);
19623
+ if (exitOnFailure)
19624
+ process.exit(1);
19625
+ return false;
19684
19626
  }
19685
19627
  try {
19686
19628
  await api("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
@@ -19693,16 +19635,20 @@ The Unbrowse Terms of Service have been updated.`);
19693
19635
  } catch (err) {
19694
19636
  console.warn(`Failed to record ToS acceptance: ${err.message}`);
19695
19637
  }
19638
+ return true;
19696
19639
  }
19697
19640
  async function ensureRegistered(options) {
19698
19641
  if (LOCAL_ONLY)
19699
19642
  return;
19643
+ const exitOnFailure = options?.exitOnFailure ?? true;
19700
19644
  const usableKey = await findUsableApiKey();
19701
19645
  if (usableKey) {
19702
19646
  if (usableKey.source === "config") {
19703
19647
  console.log("[unbrowse] Restored saved registration.");
19704
19648
  }
19705
- await checkTosStatus();
19649
+ const accepted2 = await checkTosStatus({ exitOnFailure });
19650
+ if (!accepted2)
19651
+ return;
19706
19652
  try {
19707
19653
  const profile = await getMyProfile();
19708
19654
  const wallet = getLocalWalletContext();
@@ -19723,7 +19669,9 @@ async function ensureRegistered(options) {
19723
19669
  const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
19724
19670
  if (!accepted) {
19725
19671
  console.log("You must accept the Terms of Service to use Unbrowse.");
19726
- process.exit(1);
19672
+ if (exitOnFailure)
19673
+ process.exit(1);
19674
+ return;
19727
19675
  }
19728
19676
  const fallbackName = buildDefaultAgentName();
19729
19677
  const name = options?.promptForEmail ? await promptAgentEmail(fallbackName) : resolveAgentName(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
@@ -19751,7 +19699,8 @@ async function ensureRegistered(options) {
19751
19699
  } catch (err) {
19752
19700
  console.warn(`Registration failed: ${err.message}`);
19753
19701
  console.warn("Set UNBROWSE_API_KEY manually or try again.");
19754
- process.exit(1);
19702
+ if (exitOnFailure)
19703
+ process.exit(1);
19755
19704
  }
19756
19705
  }
19757
19706
  async function getMyProfile() {
@@ -20149,26 +20098,7 @@ async function restartServer(baseUrl, metaUrl) {
20149
20098
  // ../../src/runtime/paths.ts
20150
20099
  import { existsSync as existsSync13, mkdirSync as mkdirSync11, realpathSync as realpathSync2 } from "node:fs";
20151
20100
  import path7 from "node:path";
20152
- import { createRequire as createRequire3 } from "node:module";
20153
20101
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
20154
- function resolveSiblingEntrypoint2(metaUrl, basename) {
20155
- const file = fileURLToPath3(metaUrl);
20156
- return path7.join(path7.dirname(file), `${basename}${path7.extname(file) || ".js"}`);
20157
- }
20158
- function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
20159
- if (path7.extname(entrypoint) !== ".ts")
20160
- return [entrypoint];
20161
- if (process.versions.bun)
20162
- return [entrypoint];
20163
- try {
20164
- const req = createRequire3(metaUrl);
20165
- const tsxPkg = req.resolve("tsx/package.json");
20166
- const tsxLoader = path7.join(path7.dirname(tsxPkg), "dist", "loader.mjs");
20167
- if (existsSync13(tsxLoader))
20168
- return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
20169
- } catch {}
20170
- return ["--import", "tsx", entrypoint];
20171
- }
20172
20102
  function isMainModule(metaUrl) {
20173
20103
  const entry = process.argv[1];
20174
20104
  if (!entry)
@@ -20190,7 +20120,7 @@ await init_orchestrator();
20190
20120
  import { join as join13 } from "node:path";
20191
20121
  import { homedir as homedir7 } from "node:os";
20192
20122
  var UNBROWSE_CONFIG_PATH2 = join13(homedir7(), ".unbrowse", "config.json");
20193
- var SKILL_SNAPSHOT_DIR3 = join13(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
20123
+ var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join13(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
20194
20124
  var indexInFlight2 = new Map;
20195
20125
  async function drainPendingIndexJobs() {
20196
20126
  const pending = [...indexInFlight2.values()];
@@ -20347,7 +20277,7 @@ async function runSetup(options) {
20347
20277
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
20348
20278
  const lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
20349
20279
  if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
20350
- console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
20280
+ console.log("[unbrowse] lobster.cash skill detected but wallet not configured — running wallet setup...");
20351
20281
  try {
20352
20282
  execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
20353
20283
  stdio: "inherit",
@@ -20358,15 +20288,15 @@ async function runSetup(options) {
20358
20288
  console.log(`[unbrowse] wallet configured (${recheck.provider})`);
20359
20289
  }
20360
20290
  } catch {
20361
- console.warn("[unbrowse] Crossmint lobster.cash setup failed or was skipped — continuing without wallet");
20291
+ console.warn("[unbrowse] lobster.cash wallet setup failed or was skipped — continuing without wallet");
20362
20292
  }
20363
20293
  }
20364
20294
  const finalWalletCheck = checkWalletConfigured();
20365
20295
  const wallet = {
20366
20296
  ...finalWalletCheck,
20367
20297
  lobster_installed: lobsterInstalled,
20368
- message: finalWalletCheck.configured ? `Wallet configured (${finalWalletCheck.provider}). This address is the contributor truth: it is synced onto your agent profile, used for contributor payouts when your routes earn, and used for paid-route spending.` : lobsterInstalled ? "Crossmint lobster.cash is installed but not paired. Pair it now so this wallet address becomes your contributor payout target and your paid-route spending wallet. Run: npx @crossmint/lobster-cli setup" : "No wallet configured. Recommended for new installs: set up Crossmint lobster.cash so contributor payouts have a destination address and paid-route spending can clear automatically. Without it you stay in free indexing mode only.",
20369
- install_hint: finalWalletCheck.configured ? undefined : lobsterInstalled ? "npx @crossmint/lobster-cli setup" : "npx @crossmint/lobster-cli setup"
20298
+ message: finalWalletCheck.configured ? `Wallet configured (${finalWalletCheck.provider}). This address is the contributor truth: it is synced onto your agent profile, used for contributor payouts when your routes earn, and used for paid-route spending.` : lobsterInstalled ? "lobster.cash installed but wallet not paired. Pair it now so this wallet address becomes your contributor payout target and your paid-route spending wallet. Run: lobstercash setup" : "No wallet configured. Install/pair a wallet so your contributor payouts have a destination address and premium-route spending can clear automatically. Without it you stay in free indexing mode only.",
20299
+ install_hint: finalWalletCheck.configured ? undefined : lobsterInstalled ? "lobstercash setup" : "npx skills add https://github.com/Crossmint/lobstercash-cli-skills --global --yes"
20370
20300
  };
20371
20301
  return {
20372
20302
  os: {
@@ -20494,73 +20424,15 @@ function slimTrace(obj) {
20494
20424
  out.result = obj.result;
20495
20425
  if (obj.available_endpoints)
20496
20426
  out.available_endpoints = obj.available_endpoints;
20497
- if (obj.impact)
20498
- out.impact = obj.impact;
20499
- if (obj.next_actions)
20500
- out.next_actions = obj.next_actions;
20501
- if (obj.next_step)
20502
- out.next_step = obj.next_step;
20503
20427
  if (obj.source)
20504
20428
  out.source = obj.source;
20505
20429
  if (obj.skill)
20506
20430
  out.skill = obj.skill;
20507
20431
  return out;
20508
20432
  }
20509
- function formatSavedDuration(ms) {
20510
- if (ms >= 60000)
20511
- return `${(ms / 60000).toFixed(1)}m`;
20512
- if (ms >= 1e4)
20513
- return `${Math.round(ms / 1000)}s`;
20514
- if (ms >= 1000)
20515
- return `${(ms / 1000).toFixed(1)}s`;
20516
- return `${ms}ms`;
20517
- }
20518
- function emitImpactSummary(result) {
20519
- const impact = result.impact;
20520
- if (!impact)
20521
- return;
20522
- const timeSavedMs = typeof impact.time_saved_ms === "number" ? impact.time_saved_ms : 0;
20523
- const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
20524
- const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
20525
- const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
20526
- const browserAvoided = impact.browser_avoided === true;
20527
- if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
20528
- return;
20529
- const parts = [];
20530
- if (timeSavedMs > 0)
20531
- parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
20532
- if (tokensSaved > 0)
20533
- parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
20534
- if (browserAvoided)
20535
- parts.push("browser avoided");
20536
- info(parts.join(" \u2022 "));
20537
- }
20538
- function emitNextActionSummary(result) {
20539
- const nextActions = Array.isArray(result.next_actions) ? result.next_actions : [];
20540
- if (nextActions.length === 0)
20541
- return;
20542
- info("Likely next actions:");
20543
- for (const action2 of nextActions.slice(0, 3)) {
20544
- const command = typeof action2.command === "string" ? action2.command : "";
20545
- const title = typeof action2.title === "string" ? action2.title : action2.endpoint_id ?? "next step";
20546
- const why = typeof action2.why === "string" ? action2.why : "";
20547
- info(` ${command || title}${why ? ` # ${why}` : ""}`);
20548
- }
20549
- }
20550
20433
  async function cmdHealth(flags) {
20551
20434
  output(await api3("GET", "/health"), !!flags.pretty);
20552
20435
  }
20553
- function telemetryDomainFromInput(domain, url) {
20554
- if (domain?.trim())
20555
- return domain.trim().replace(/^www\./, "");
20556
- if (!url?.trim())
20557
- return null;
20558
- try {
20559
- return new URL(url).hostname.replace(/^www\./, "");
20560
- } catch {
20561
- return null;
20562
- }
20563
- }
20564
20436
  async function cmdResolve(flags) {
20565
20437
  const intent = flags.intent;
20566
20438
  if (!intent)
@@ -20577,9 +20449,6 @@ async function cmdResolve(flags) {
20577
20449
  hostType,
20578
20450
  properties: {
20579
20451
  command: "resolve",
20580
- intent,
20581
- domain: telemetryDomainFromInput(flags.domain, flags.url),
20582
- url: typeof flags.url === "string" ? flags.url : null,
20583
20452
  has_url: typeof flags.url === "string",
20584
20453
  has_domain: typeof flags.domain === "string",
20585
20454
  auto_execute: !!flags.execute
@@ -20706,9 +20575,6 @@ async function cmdResolve(flags) {
20706
20575
  hostType,
20707
20576
  properties: {
20708
20577
  command: "resolve",
20709
- intent,
20710
- domain: telemetryDomainFromInput(domain, url),
20711
- url: url ?? null,
20712
20578
  source: result.source,
20713
20579
  auto_execute: autoExecute,
20714
20580
  explicit_endpoint: explicitEndpointId ?? null
@@ -20716,8 +20582,6 @@ async function cmdResolve(flags) {
20716
20582
  });
20717
20583
  }
20718
20584
  result = slimTrace(result);
20719
- emitImpactSummary(result);
20720
- emitNextActionSummary(result);
20721
20585
  const skill = result.skill;
20722
20586
  const trace = result.trace;
20723
20587
  if (skill?.skill_id && trace) {
@@ -20731,9 +20595,6 @@ async function cmdResolve(flags) {
20731
20595
  hostType,
20732
20596
  properties: {
20733
20597
  command: "resolve",
20734
- intent,
20735
- domain: telemetryDomainFromInput(flags.domain, flags.url),
20736
- url: typeof flags.url === "string" ? flags.url : null,
20737
20598
  failure_stage: "resolve",
20738
20599
  failure_reason: message
20739
20600
  }
@@ -20832,9 +20693,6 @@ async function cmdExecute(flags) {
20832
20693
  hostType,
20833
20694
  properties: {
20834
20695
  command: "execute",
20835
- intent: typeof flags.intent === "string" ? flags.intent : null,
20836
- domain: telemetryDomainFromInput(undefined, flags.url),
20837
- url: typeof flags.url === "string" ? flags.url : null,
20838
20696
  skill_id: skillId,
20839
20697
  endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
20840
20698
  }
@@ -20865,17 +20723,12 @@ async function cmdExecute(flags) {
20865
20723
  hostType,
20866
20724
  properties: {
20867
20725
  command: "execute",
20868
- intent: typeof flags.intent === "string" ? flags.intent : null,
20869
- domain: telemetryDomainFromInput(undefined, flags.url),
20870
- url: typeof flags.url === "string" ? flags.url : null,
20871
20726
  skill_id: skillId,
20872
20727
  endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
20873
20728
  }
20874
20729
  });
20875
20730
  }
20876
20731
  result = slimTrace(result);
20877
- emitImpactSummary(result);
20878
- emitNextActionSummary(result);
20879
20732
  const pathFlag = flags.path;
20880
20733
  const extractFlag = flags.extract;
20881
20734
  const limitFlag = flags.limit ? Number(flags.limit) : undefined;
@@ -20883,12 +20736,7 @@ async function cmdExecute(flags) {
20883
20736
  const rawFlag = !!flags.raw;
20884
20737
  if (schemaFlag && !rawFlag) {
20885
20738
  const data = result.result;
20886
- output({
20887
- trace: result.trace,
20888
- schema: schemaOf(data),
20889
- ...result.impact ? { impact: result.impact } : {},
20890
- ...result.next_actions ? { next_actions: result.next_actions } : {}
20891
- }, !!flags.pretty);
20739
+ output({ trace: result.trace, schema: schemaOf(data) }, !!flags.pretty);
20892
20740
  return;
20893
20741
  }
20894
20742
  if (!rawFlag && (pathFlag || extractFlag || limitFlag)) {
@@ -20897,13 +20745,7 @@ async function cmdExecute(flags) {
20897
20745
  const extracted = extractFlag ? applyExtract(items, extractFlag) : items;
20898
20746
  const limited = limitFlag ? extracted.slice(0, limitFlag) : extracted;
20899
20747
  const trace = result.trace;
20900
- const out = {
20901
- trace: result.trace,
20902
- data: limited,
20903
- count: limited.length,
20904
- ...result.impact ? { impact: result.impact } : {},
20905
- ...result.next_actions ? { next_actions: result.next_actions } : {}
20906
- };
20748
+ const out = { trace: result.trace, data: limited, count: limited.length };
20907
20749
  if (trace?.skill_id && trace?.endpoint_id && limited.length > 0) {
20908
20750
  out._review_hint = `After presenting results, improve this endpoint's description: unbrowse review --skill ${trace.skill_id} --endpoints '[{"endpoint_id":"${trace.endpoint_id}","description":"DESCRIBE WHAT THIS RETURNS","action_kind":"ACTION","resource_kind":"RESOURCE"}]'`;
20909
20751
  }
@@ -20916,8 +20758,6 @@ async function cmdExecute(flags) {
20916
20758
  const schema = schemaOf(result.result);
20917
20759
  output({
20918
20760
  trace: result.trace,
20919
- ...result.impact ? { impact: result.impact } : {},
20920
- ...result.next_actions ? { next_actions: result.next_actions } : {},
20921
20761
  extraction_hints: {
20922
20762
  message: "Response is large. Use --path/--extract/--limit to filter, or --schema to see structure, or --raw for full response.",
20923
20763
  schema_tree: schema,
@@ -20935,9 +20775,6 @@ async function cmdExecute(flags) {
20935
20775
  hostType,
20936
20776
  properties: {
20937
20777
  command: "execute",
20938
- intent: typeof flags.intent === "string" ? flags.intent : null,
20939
- domain: telemetryDomainFromInput(undefined, flags.url),
20940
- url: typeof flags.url === "string" ? flags.url : null,
20941
20778
  skill_id: skillId,
20942
20779
  failure_stage: "execute",
20943
20780
  failure_reason: message
@@ -21013,53 +20850,7 @@ async function cmdSearch(flags) {
21013
20850
  const body = { intent, k: Number(flags.k) || 5 };
21014
20851
  if (domain)
21015
20852
  body.domain = domain;
21016
- const hostType = detectTelemetryHostType();
21017
- await ensureCliInstallTracked(hostType);
21018
- await recordFunnelTelemetryEvent("cli_invoked", {
21019
- source: "cli",
21020
- hostType,
21021
- properties: { command: "search" }
21022
- });
21023
- await recordFunnelTelemetryEvent("search_started", {
21024
- source: "cli",
21025
- hostType,
21026
- properties: {
21027
- command: "search",
21028
- intent,
21029
- domain: domain ?? null,
21030
- k: body.k
21031
- }
21032
- });
21033
- try {
21034
- const result = await api3("POST", path9, body);
21035
- const results = Array.isArray(result.results) ? result.results : [];
21036
- await recordFunnelTelemetryEvent("search_completed", {
21037
- source: "cli",
21038
- hostType,
21039
- properties: {
21040
- command: "search",
21041
- intent,
21042
- domain: domain ?? null,
21043
- k: body.k,
21044
- result_count: results.length
21045
- }
21046
- });
21047
- output(result, !!flags.pretty);
21048
- } catch (error) {
21049
- const message = error instanceof Error ? error.message : String(error);
21050
- await recordFunnelTelemetryEvent("search_failed", {
21051
- source: "cli",
21052
- hostType,
21053
- properties: {
21054
- command: "search",
21055
- intent,
21056
- domain: domain ?? null,
21057
- failure_stage: "search",
21058
- failure_reason: message
21059
- }
21060
- });
21061
- throw error;
21062
- }
20853
+ output(await api3("POST", path9, body), !!flags.pretty);
21063
20854
  }
21064
20855
  async function cmdSessions(flags) {
21065
20856
  const domain = flags.domain;
@@ -21150,7 +20941,6 @@ async function cmdSetup(flags) {
21150
20941
  var CLI_REFERENCE = {
21151
20942
  commands: [
21152
20943
  { name: "health", usage: "", desc: "Server health check" },
21153
- { name: "mcp", usage: "[--no-auto-start]", desc: "Run the stdio MCP server" },
21154
20944
  { name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
21155
20945
  { name: "resolve", usage: '--intent "..." --url "..." [opts]', desc: "Resolve intent \u2192 search/capture/execute" },
21156
20946
  { name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
@@ -21163,7 +20953,7 @@ var CLI_REFERENCE = {
21163
20953
  { name: "search", usage: '--intent "..." [--domain "..."]', desc: "Search marketplace" },
21164
20954
  { name: "sessions", usage: '--domain "..." [--limit N]', desc: "Debug session logs" },
21165
20955
  { name: "go", usage: "<url>", desc: "Open a live Kuri browser tab for capture-first workflows" },
21166
- { name: "submit", usage: "[--form-selector sel] [--submit-selector sel] [--wait-for hint]", desc: "Submit current form, auto-flush current capture, and fall back to same-origin rehydrate for JS-heavy flows" },
20956
+ { name: "submit", usage: "[--form-selector sel] [--submit-selector sel] [--wait-for hint]", desc: "Submit current form with DOM-first + same-origin rehydrate fallback for JS-heavy flows" },
21167
20957
  { name: "snap", usage: "[--filter interactive]", desc: "A11y snapshot with @eN refs" },
21168
20958
  { name: "click", usage: "<ref>", desc: "Click element by ref (e.g. e5)" },
21169
20959
  { name: "fill", usage: "<ref> <value>", desc: "Fill input by ref" },
@@ -21200,7 +20990,6 @@ var CLI_REFERENCE = {
21200
20990
  ],
21201
20991
  examples: [
21202
20992
  "unbrowse setup",
21203
- "unbrowse mcp",
21204
20993
  'unbrowse resolve --intent "top stories" --url "https://news.ycombinator.com" --execute',
21205
20994
  'unbrowse resolve --intent "get timeline" --url "https://x.com"',
21206
20995
  'unbrowse go "https://www.mandai.com/en/ticketing/admission-and-rides/parks-selection.html"',
@@ -21239,7 +21028,7 @@ function printHelp() {
21239
21028
  for (const e of r.examples) {
21240
21029
  lines.push(` ${e}`);
21241
21030
  }
21242
- lines.push("", "Browser workflow:", " 1. go -> open the live tab you want to work in", " 2. snap -> inspect refs and confirm the page state", " 3. click/fill/eval -> set real page state", " 4. submit -> prefer DOM submit; auto-flush current capture; fall back to same-origin rehydrate", " 5. sync -> flush any additional captured routes after a successful step", " 6. close -> finish capture + indexing");
21031
+ lines.push("", "Browser workflow:", " 1. go -> open the live tab you want to work in", " 2. snap -> inspect refs and confirm the page state", " 3. click/fill/eval -> set real page state", " 4. submit -> prefer DOM submit; auto-falls back to same-origin rehydrate", " 5. sync -> flush captured routes after a successful step", " 6. close -> finish capture + indexing");
21243
21032
  lines.push("", "JS-heavy forms:", " Prefer real calendar/time clicks before submit.", " If the UI is flaky, inspect hidden inputs/cookies with eval, then submit the real form.");
21244
21033
  lines.push("");
21245
21034
  process.stderr.write(lines.join(`
@@ -21285,29 +21074,6 @@ async function cmdUpgrade(flags) {
21285
21074
  info(`Could not check for updates: ${err.message}`);
21286
21075
  }
21287
21076
  }
21288
- async function cmdMcp(flags) {
21289
- const entrypoint = resolveSiblingEntrypoint2(import.meta.url, "mcp");
21290
- const child = spawn3(process.execPath, [...runtimeArgsForEntrypoint2(import.meta.url, entrypoint), ...flags["no-auto-start"] ? ["--no-auto-start"] : []], {
21291
- cwd: process.cwd(),
21292
- stdio: "inherit",
21293
- env: {
21294
- ...process.env,
21295
- MCP_SERVER_MODE: "1"
21296
- }
21297
- });
21298
- const code = await new Promise((resolve, reject) => {
21299
- child.once("error", reject);
21300
- child.once("exit", (exitCode, signal) => {
21301
- if (signal) {
21302
- process.kill(process.pid, signal);
21303
- return;
21304
- }
21305
- resolve(exitCode ?? 1);
21306
- });
21307
- });
21308
- if (code !== 0)
21309
- process.exit(code);
21310
- }
21311
21077
  async function cmdSiteHelp(pack, flags) {
21312
21078
  if (flags.deps) {
21313
21079
  const graph = buildDepsGraph(pack);
@@ -21579,8 +21345,6 @@ async function main() {
21579
21345
  await cmdSetup(flags);
21580
21346
  return;
21581
21347
  }
21582
- if (command === "mcp")
21583
- return cmdMcp(flags);
21584
21348
  if (command === "status")
21585
21349
  return cmdStatus(flags);
21586
21350
  if (command === "stop") {
@@ -21595,7 +21359,6 @@ async function main() {
21595
21359
  return cmdConnectChrome();
21596
21360
  const KNOWN_COMMANDS = new Set([
21597
21361
  "health",
21598
- "mcp",
21599
21362
  "setup",
21600
21363
  "resolve",
21601
21364
  "execute",
@@ -21656,8 +21419,6 @@ async function main() {
21656
21419
  switch (command) {
21657
21420
  case "health":
21658
21421
  return cmdHealth(flags);
21659
- case "mcp":
21660
- return cmdMcp(flags);
21661
21422
  case "setup":
21662
21423
  return cmdSetup(flags);
21663
21424
  case "resolve":