unbrowse 2.12.1 → 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;
@@ -4603,12 +4632,10 @@ async function captureSession(url, authHeaders, cookies, intent, options) {
4603
4632
  log("capture", `captured ${jsBundleBodies.size} JS bundles for route scanning`);
4604
4633
  const responseBodyCount = responseBodies.size;
4605
4634
  if (isBlockedAppShell(html) && responseBodyCount < 10 && !hasUsefulCapturedResponses(responseBodies.keys(), url, intent)) {
4606
- if (options?.forceEphemeral) {
4607
- const cloudflareBlocked = !!html && /Cloudflare|cf\.errors\.css|cf-error-details/i.test(html);
4608
- throw Object.assign(new Error(cloudflareBlocked ? "cloudflare_waf_block" : "blocked_app_shell"), {
4635
+ if (options?.forceEphemeral && html && /Cloudflare|cf\.errors\.css|cf-error-details/i.test(html)) {
4636
+ throw Object.assign(new Error("cloudflare_waf_block"), {
4609
4637
  code: "auth_required",
4610
- login_url: final_url || url,
4611
- ...cloudflareBlocked ? { reason: "cloudflare_waf" } : { reason: "blocked_app_shell" }
4638
+ login_url: url
4612
4639
  });
4613
4640
  }
4614
4641
  retryFreshTab = true;
@@ -5077,11 +5104,13 @@ var init_capture = __esm(() => {
5077
5104
  // ../../src/client/index.ts
5078
5105
  var exports_client2 = {};
5079
5106
  __export(exports_client2, {
5107
+ waitForBackgroundRegistration: () => waitForBackgroundRegistration,
5080
5108
  verifyMarketplaceDiscovery: () => verifyMarketplaceDiscovery,
5081
5109
  validateManifest: () => validateManifest,
5082
5110
  updateEndpointScore: () => updateEndpointScore,
5083
5111
  updateEndpointSchema: () => updateEndpointSchema,
5084
5112
  syncAgentWallet: () => syncAgentWallet2,
5113
+ startBackgroundRegistration: () => startBackgroundRegistration,
5085
5114
  setSkillSplitConfig: () => setSkillSplitConfig,
5086
5115
  setSkillPrice: () => setSkillPrice,
5087
5116
  searchIntentResolve: () => searchIntentResolve,
@@ -5217,18 +5246,11 @@ function createInstallTelemetryState2() {
5217
5246
  }
5218
5247
  function getOrCreateInstallTelemetryState2() {
5219
5248
  const existing = loadInstallTelemetryState2();
5220
- const state = existing?.install_id ? existing : createInstallTelemetryState2();
5221
- const landingToken = process.env.UNBROWSE_LANDING_TOKEN?.trim();
5222
- if (landingToken && state.landing_token !== landingToken) {
5223
- state.landing_token = landingToken;
5224
- state.landing_token_seen_at = new Date().toISOString();
5225
- saveInstallTelemetryState2(state);
5226
- return state;
5227
- }
5228
- if (!existing?.install_id) {
5229
- saveInstallTelemetryState2(state);
5230
- }
5231
- return state;
5249
+ if (existing?.install_id)
5250
+ return existing;
5251
+ const created = createInstallTelemetryState2();
5252
+ saveInstallTelemetryState2(created);
5253
+ return created;
5232
5254
  }
5233
5255
  function getInstallId2() {
5234
5256
  return getOrCreateInstallTelemetryState2().install_id;
@@ -5274,7 +5296,6 @@ async function ensureCliInstallTracked2(hostType = detectTelemetryHostType2()) {
5274
5296
  const createdAt = new Date().toISOString();
5275
5297
  const ok = await postTelemetry2("/v1/telemetry/install", {
5276
5298
  install_id: state.install_id,
5277
- landing_token: state.landing_token,
5278
5299
  source: "cli-first-seen",
5279
5300
  host_type: hostType,
5280
5301
  skill: "unbrowse",
@@ -5294,7 +5315,6 @@ async function recordInstallTelemetryEvent2(source, options) {
5294
5315
  const createdAt = options?.createdAt ?? new Date().toISOString();
5295
5316
  await postTelemetry2("/v1/telemetry/install", {
5296
5317
  install_id: getInstallId2(),
5297
- landing_token: getOrCreateInstallTelemetryState2().landing_token,
5298
5318
  source,
5299
5319
  host_type: options?.hostType ?? detectTelemetryHostType2(),
5300
5320
  skill: options?.skill ?? "unbrowse",
@@ -5309,7 +5329,6 @@ async function recordFunnelTelemetryEvent2(name, options) {
5309
5329
  await postTelemetry2("/v1/telemetry/events", {
5310
5330
  install_id: getInstallId2(),
5311
5331
  session_id: options?.sessionId,
5312
- landing_token: getOrCreateInstallTelemetryState2().landing_token,
5313
5332
  name,
5314
5333
  source: options?.source ?? "cli",
5315
5334
  host_type: options?.hostType ?? detectTelemetryHostType2(),
@@ -5530,23 +5549,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
5530
5549
  rl.close();
5531
5550
  }
5532
5551
  }
5533
- async function checkTosStatus2() {
5552
+ async function checkTosStatus2(options) {
5553
+ const exitOnFailure = options?.exitOnFailure ?? true;
5534
5554
  const config = loadConfig2();
5535
5555
  let tosInfo;
5536
5556
  try {
5537
5557
  tosInfo = await api2("GET", "/v1/tos/current");
5538
5558
  } catch {
5539
- return;
5559
+ return true;
5540
5560
  }
5541
5561
  if (config?.tos_accepted_version === tosInfo.version) {
5542
- return;
5562
+ return true;
5543
5563
  }
5544
5564
  console.log(`
5545
5565
  The Unbrowse Terms of Service have been updated.`);
5546
5566
  const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
5547
5567
  if (!accepted) {
5548
5568
  console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
5549
- process.exit(1);
5569
+ if (exitOnFailure)
5570
+ process.exit(1);
5571
+ return false;
5550
5572
  }
5551
5573
  try {
5552
5574
  await api2("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
@@ -5559,16 +5581,20 @@ The Unbrowse Terms of Service have been updated.`);
5559
5581
  } catch (err) {
5560
5582
  console.warn(`Failed to record ToS acceptance: ${err.message}`);
5561
5583
  }
5584
+ return true;
5562
5585
  }
5563
5586
  async function ensureRegistered2(options) {
5564
5587
  if (LOCAL_ONLY2)
5565
5588
  return;
5589
+ const exitOnFailure = options?.exitOnFailure ?? true;
5566
5590
  const usableKey = await findUsableApiKey2();
5567
5591
  if (usableKey) {
5568
5592
  if (usableKey.source === "config") {
5569
5593
  console.log("[unbrowse] Restored saved registration.");
5570
5594
  }
5571
- await checkTosStatus2();
5595
+ const accepted2 = await checkTosStatus2({ exitOnFailure });
5596
+ if (!accepted2)
5597
+ return;
5572
5598
  try {
5573
5599
  const profile = await getMyProfile2();
5574
5600
  const wallet = getLocalWalletContext2();
@@ -5589,7 +5615,9 @@ async function ensureRegistered2(options) {
5589
5615
  const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
5590
5616
  if (!accepted) {
5591
5617
  console.log("You must accept the Terms of Service to use Unbrowse.");
5592
- process.exit(1);
5618
+ if (exitOnFailure)
5619
+ process.exit(1);
5620
+ return;
5593
5621
  }
5594
5622
  const fallbackName = buildDefaultAgentName2();
5595
5623
  const name = options?.promptForEmail ? await promptAgentEmail2(fallbackName) : resolveAgentName2(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
@@ -5617,8 +5645,36 @@ async function ensureRegistered2(options) {
5617
5645
  } catch (err) {
5618
5646
  console.warn(`Registration failed: ${err.message}`);
5619
5647
  console.warn("Set UNBROWSE_API_KEY manually or try again.");
5620
- 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;
5621
5673
  }
5674
+ await Promise.race([
5675
+ backgroundRegistrationPromise,
5676
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
5677
+ ]);
5622
5678
  }
5623
5679
  function skillCachePath(skillId) {
5624
5680
  return join3(getSkillCacheDir(), `${skillId}.json`);
@@ -6009,7 +6065,7 @@ async function setSkillPrice(skillId, priceUsd) {
6009
6065
  async function setSkillSplitConfig(skillId, splitConfig) {
6010
6066
  return api2("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
6011
6067
  }
6012
- 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;
6013
6069
  var init_client2 = __esm(() => {
6014
6070
  init_cascade();
6015
6071
  API_URL2 = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
@@ -6128,20 +6184,11 @@ function computeCodeHash() {
6128
6184
  function getGitSha() {
6129
6185
  return "unknown";
6130
6186
  }
6131
- function getPackageVersion() {
6132
- try {
6133
- const pkg = JSON.parse(readFileSync3(join4(MODULE_DIR, "..", "package.json"), "utf-8"));
6134
- return typeof pkg.version === "string" ? pkg.version : "unknown";
6135
- } catch {
6136
- return "unknown";
6137
- }
6138
- }
6139
- var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, TRACE_VERSION;
6187
+ var MODULE_DIR, CODE_HASH, GIT_SHA, TRACE_VERSION;
6140
6188
  var init_version = __esm(() => {
6141
6189
  MODULE_DIR = dirname(fileURLToPath2(import.meta.url));
6142
6190
  CODE_HASH = computeCodeHash();
6143
6191
  GIT_SHA = getGitSha();
6144
- PACKAGE_VERSION = getPackageVersion();
6145
6192
  TRACE_VERSION = `${CODE_HASH}@${GIT_SHA}`;
6146
6193
  });
6147
6194
 
@@ -11922,8 +11969,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
11922
11969
  } catch {}
11923
11970
  }
11924
11971
  }
11925
- const hasAuthContext = cookies.length > 0 || Object.keys(authHeaders).length > 0 || !!skill.auth_profile_ref || endpoint.semantic?.auth_required === true;
11926
- if (!options?.skip_robots_check && !hasAuthContext) {
11972
+ if (!options?.skip_robots_check) {
11927
11973
  const allowed = await isAllowedByRobots(url);
11928
11974
  if (!allowed) {
11929
11975
  const traceId = nanoid5();
@@ -15315,7 +15361,6 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15315
15361
  result: {
15316
15362
  message: `Found ${epRanked.length} endpoint(s). Pick one and call POST /v1/skills/${resolvedSkill.skill_id}/execute with params.endpoint_id.`,
15317
15363
  skill_id: resolvedSkill.skill_id,
15318
- suggested_next_operation_id: chunk.available_operation_ids[0],
15319
15364
  available_operations: chunk.operations.map((operation) => ({
15320
15365
  operation_id: operation.operation_id,
15321
15366
  endpoint_id: operation.endpoint_id,
@@ -16752,7 +16797,7 @@ var init_orchestrator = __esm(async () => {
16752
16797
  captureDomainLocks = new Map;
16753
16798
  skillRouteCache = new Map;
16754
16799
  ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
16755
- 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");
16756
16801
  domainSkillCache = new Map;
16757
16802
  DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
16758
16803
  try {
@@ -17165,7 +17210,7 @@ async function processIndexJob(job) {
17165
17210
  }
17166
17211
  console.log(`[background-index] completed for ${domain} -> ${published.skill_id}`);
17167
17212
  }
17168
- 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;
17169
17214
  var init_indexer = __esm(async () => {
17170
17215
  init_graph();
17171
17216
  init_client2();
@@ -17173,6 +17218,7 @@ var init_indexer = __esm(async () => {
17173
17218
  init_domain();
17174
17219
  await init_orchestrator();
17175
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");
17176
17222
  SECRET_VALUE_PATTERNS = [
17177
17223
  /^eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
17178
17224
  /^Bearer\s+\S+/i,
@@ -17186,7 +17232,6 @@ var init_indexer = __esm(async () => {
17186
17232
  /^v2\.[A-Za-z0-9_-]{20,}/
17187
17233
  ];
17188
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;
17189
- SKILL_SNAPSHOT_DIR2 = join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
17190
17235
  indexInFlight = new Map;
17191
17236
  });
17192
17237
 
@@ -17332,113 +17377,6 @@ var init_session_logs = __esm(() => {
17332
17377
  init_domain();
17333
17378
  });
17334
17379
 
17335
- // ../../src/agent-outcome.ts
17336
- function edgePriority(kind) {
17337
- switch (kind) {
17338
- case "parent_child":
17339
- return 4;
17340
- case "pagination":
17341
- return 3;
17342
- case "dependency":
17343
- return 2;
17344
- case "hint":
17345
- return 1;
17346
- case "auth":
17347
- return 0;
17348
- default:
17349
- return -1;
17350
- }
17351
- }
17352
- function nextActionWhy(kind, bindingKey, title) {
17353
- switch (kind) {
17354
- case "parent_child":
17355
- return `Likely next detail step after this result. Exposes ${title}.`;
17356
- case "pagination":
17357
- return `Likely next page or continuation step. Carries ${bindingKey || "cursor"} forward.`;
17358
- case "dependency":
17359
- return `Unlocks the next dependent call using ${bindingKey || "known bindings"}.`;
17360
- case "auth":
17361
- return "Useful once authentication is in place.";
17362
- case "hint":
17363
- return "Common follow-up action from the current result.";
17364
- default:
17365
- return "Likely follow-up action.";
17366
- }
17367
- }
17368
- function operationTitle(operation) {
17369
- const semantic = [operation.action_kind, operation.resource_kind].filter(Boolean).join(" ").replace(/_/g, " ").trim();
17370
- return operation.description_out || semantic || operation.endpoint_id;
17371
- }
17372
- function buildAgentImpact(timing) {
17373
- if (!timing?.source)
17374
- return;
17375
- return {
17376
- source: timing.source,
17377
- cache_hit: timing.cache_hit === true,
17378
- browser_avoided: !BROWSER_SOURCES.has(timing.source),
17379
- baseline_total_ms: timing.baseline_total_ms,
17380
- actual_total_ms: timing.actual_total_ms,
17381
- time_saved_ms: timing.time_saved_ms,
17382
- time_saved_pct: timing.time_saved_pct ?? 0,
17383
- tokens_saved: timing.tokens_saved ?? 0,
17384
- tokens_saved_pct: timing.tokens_saved_pct ?? 0,
17385
- baseline_cost_uc: timing.baseline_cost_uc,
17386
- actual_cost_uc: timing.actual_cost_uc,
17387
- cost_saved_uc: timing.cost_saved_uc
17388
- };
17389
- }
17390
- function buildNextActions(skill, endpointId, maxActions = 3) {
17391
- if (!skill?.operation_graph || !endpointId)
17392
- return [];
17393
- const graph = skill.operation_graph;
17394
- const current = graph.operations.find((operation) => operation.endpoint_id === endpointId);
17395
- if (!current)
17396
- return [];
17397
- const byOperationId = new Map(graph.operations.map((operation) => [operation.operation_id, operation]));
17398
- const scored = new Map;
17399
- for (const edge of graph.edges) {
17400
- if (edge.from_operation_id !== current.operation_id)
17401
- continue;
17402
- const target = byOperationId.get(edge.to_operation_id);
17403
- if (!target)
17404
- continue;
17405
- const candidate = {
17406
- operation_id: target.operation_id,
17407
- endpoint_id: target.endpoint_id,
17408
- title: operationTitle(target),
17409
- why: nextActionWhy(edge.kind, edge.binding_key, operationTitle(target)),
17410
- score: edgePriority(edge.kind) * 10 + Math.round(edge.confidence * 10)
17411
- };
17412
- const existing = scored.get(target.operation_id);
17413
- if (!existing || candidate.score > existing.score) {
17414
- scored.set(target.operation_id, candidate);
17415
- }
17416
- }
17417
- return [...scored.values()].sort((a, b) => b.score - a.score || a.title.localeCompare(b.title)).slice(0, maxActions).map((candidate) => ({
17418
- endpoint_id: candidate.endpoint_id,
17419
- operation_id: candidate.operation_id,
17420
- title: candidate.title,
17421
- why: candidate.why,
17422
- command: `unbrowse execute --skill ${skill.skill_id} --endpoint ${candidate.endpoint_id}`
17423
- }));
17424
- }
17425
- function attachAgentOutcomeHints(payload, opts) {
17426
- const target = payload;
17427
- const impact = buildAgentImpact(opts?.timing);
17428
- if (impact) {
17429
- target.impact = impact;
17430
- }
17431
- const nextActions = buildNextActions(opts?.skill, opts?.endpointId);
17432
- if (nextActions.length > 0) {
17433
- target.next_actions = nextActions;
17434
- }
17435
- return target;
17436
- }
17437
- var BROWSER_SOURCES;
17438
- var init_agent_outcome = __esm(() => {
17439
- BROWSER_SOURCES = new Set(["live-capture", "first-pass", "browser-action"]);
17440
- });
17441
-
17442
17380
  // ../../src/api/browse-session.ts
17443
17381
  function extractBrowseFailureMessage(value) {
17444
17382
  return typeof value === "string" ? value : getKuriErrorMessage(value);
@@ -17453,6 +17391,8 @@ function isRecoverableBrowseFailure(value) {
17453
17391
  async function createBrowseSession(sessions, client, injectInterceptor2) {
17454
17392
  await client.start().catch(() => {});
17455
17393
  const tabId = await client.newTab();
17394
+ if (!tabId)
17395
+ throw new Error("Failed to create browser tab");
17456
17396
  await client.harStart(tabId).catch(() => {});
17457
17397
  await injectInterceptor2(tabId);
17458
17398
  const session = { tabId, url: "about:blank", harActive: true, domain: "" };
@@ -17475,7 +17415,7 @@ async function adoptExistingBrowseTab(sessions, client, injectInterceptor2, pref
17475
17415
  const candidate = tabs.find((tab) => {
17476
17416
  const domain = extractDomain2(tab.url);
17477
17417
  return !!domain && !!normalizedPreferred && domain === normalizedPreferred;
17478
- }) ?? 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);
17479
17419
  if (!candidate?.id)
17480
17420
  return null;
17481
17421
  await client.harStart(candidate.id).catch(() => {});
@@ -18032,6 +17972,21 @@ function parseJsonString(value) {
18032
17972
  return null;
18033
17973
  }
18034
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
+ }
18035
17990
  async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, options) {
18036
17991
  const timeoutMs = options.timeoutMs ?? DEFAULT_SUBMIT_TIMEOUT_MS;
18037
17992
  const deadline = Date.now() + timeoutMs;
@@ -18042,7 +17997,7 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
18042
17997
  if (waitResult?.status === "found" || waitResult?.status === "ready") {
18043
17998
  const url = await client.getCurrentUrl(tabId).catch(() => beforeUrl);
18044
17999
  const html = await client.getPageHtml(tabId).catch(() => beforeHtml);
18045
- return { ok: true, url, html };
18000
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
18046
18001
  }
18047
18002
  } catch {}
18048
18003
  }
@@ -18050,20 +18005,20 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
18050
18005
  const url = await client.getCurrentUrl(tabId).catch(() => "");
18051
18006
  const html = await client.getPageHtml(tabId).catch(() => "");
18052
18007
  if (waitFor && isUrlWaitHint(waitFor) && url.includes(waitFor)) {
18053
- return { ok: true, url, html };
18008
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
18054
18009
  }
18055
18010
  if (url && url !== beforeUrl && !url.startsWith("about:blank")) {
18056
- return { ok: true, url, html };
18011
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
18057
18012
  }
18058
18013
  if (hasMeaningfulPageChange(beforeHtml, html)) {
18059
- return { ok: true, url: url || beforeUrl, html };
18014
+ return { ok: true, ...await settleSubmitDestination(client, tabId, url || beforeUrl, html) };
18060
18015
  }
18061
18016
  await sleep(SUBMIT_POLL_INTERVAL_MS);
18062
18017
  }
18063
18018
  return { ok: false, url: beforeUrl, html: beforeHtml };
18064
18019
  }
18065
18020
  async function submitBrowseForm(deps, options = {}) {
18066
- const { client, session, flushCapture, restartCapture, rehydratePlugins } = deps;
18021
+ const { client, session, restartCapture, rehydratePlugins } = deps;
18067
18022
  const sameOriginFetchFallback = options.sameOriginFetchFallback !== false;
18068
18023
  const beforeUrl = await client.getCurrentUrl(session.tabId).catch(() => session.url);
18069
18024
  const beforeHtml = await client.getPageHtml(session.tabId).catch(() => "");
@@ -18089,14 +18044,6 @@ async function submitBrowseForm(deps, options = {}) {
18089
18044
  const domOutcome = await waitForSubmitOutcome(client, session.tabId, beforeUrl, beforeHtml, options);
18090
18045
  if (domOutcome.ok) {
18091
18046
  session.url = domOutcome.url || beforeUrl || session.url;
18092
- let captureSync2 = null;
18093
- if (flushCapture) {
18094
- try {
18095
- captureSync2 = await flushCapture(session);
18096
- } catch {
18097
- captureSync2 = null;
18098
- }
18099
- }
18100
18047
  await restartCapture(session);
18101
18048
  return {
18102
18049
  ok: true,
@@ -18105,8 +18052,7 @@ async function submitBrowseForm(deps, options = {}) {
18105
18052
  fallback_used: false,
18106
18053
  same_origin_html_rehydrated: false,
18107
18054
  wait_for: options.waitFor,
18108
- submit_meta: submitMeta,
18109
- capture_sync: captureSync2
18055
+ submit_meta: submitMeta
18110
18056
  };
18111
18057
  }
18112
18058
  if (submitError && !isRecoverableBrowseFailure(submitError) && !sameOriginFetchFallback) {
@@ -18144,14 +18090,6 @@ async function submitBrowseForm(deps, options = {}) {
18144
18090
  if (!rehydrate) {
18145
18091
  rehydrate = await rehydratePlugins(session.tabId).catch(() => null);
18146
18092
  }
18147
- let captureSync = null;
18148
- if (flushCapture) {
18149
- try {
18150
- captureSync = await flushCapture(session);
18151
- } catch {
18152
- captureSync = null;
18153
- }
18154
- }
18155
18093
  await restartCapture(session);
18156
18094
  return {
18157
18095
  ok: true,
@@ -18162,11 +18100,10 @@ async function submitBrowseForm(deps, options = {}) {
18162
18100
  status: typeof fallbackPayload.status === "number" ? fallbackPayload.status : undefined,
18163
18101
  wait_for: options.waitFor,
18164
18102
  submit_meta: submitMeta,
18165
- capture_sync: captureSync,
18166
18103
  rehydrate
18167
18104
  };
18168
18105
  }
18169
- 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;
18170
18107
  var init_browse_submit = __esm(() => {
18171
18108
  init_browse_session();
18172
18109
  });
@@ -18291,7 +18228,6 @@ import { join as join12 } from "path";
18291
18228
  function buildAnalyticsSessionPayload(result, opts) {
18292
18229
  const source = result.timing?.source ?? result.source;
18293
18230
  const apiCalls = result.trace.endpoint_id ? 1 : 0;
18294
- const browserMode = opts.browser_mode ?? (source === "live-capture" || source === "first-pass" || source === "browser-action" ? "default" : "replaced");
18295
18231
  const cachedSkillCalls = opts.cached_skill_calls ?? (apiCalls > 0 && source !== "live-capture" && source !== "first-pass" ? 1 : 0);
18296
18232
  const freshIndexCalls = opts.fresh_index_calls ?? (apiCalls > 0 && (source === "live-capture" || source === "first-pass") ? 1 : 0);
18297
18233
  return {
@@ -18303,14 +18239,7 @@ function buildAnalyticsSessionPayload(result, opts) {
18303
18239
  discovery_queries: opts.discovery_queries,
18304
18240
  cached_skill_calls: cachedSkillCalls,
18305
18241
  fresh_index_calls: freshIndexCalls,
18306
- browser_mode: browserMode,
18307
- success: result.trace.success ?? true,
18308
- source,
18309
- time_saved_ms: result.timing?.time_saved_ms,
18310
- time_saved_pct: result.timing?.time_saved_pct,
18311
- tokens_saved: result.trace.tokens_saved ?? result.timing?.tokens_saved,
18312
- tokens_saved_pct: result.trace.tokens_saved_pct ?? result.timing?.tokens_saved_pct,
18313
- cost_saved_uc: result.timing?.cost_saved_uc
18242
+ browser_mode: opts.browser_mode ?? "unknown"
18314
18243
  };
18315
18244
  }
18316
18245
  function passiveIndexFromRequests(requests, pageUrl) {
@@ -18491,7 +18420,11 @@ async function registerRoutes(app) {
18491
18420
  app.addHook("onRequest", async (req, reply) => {
18492
18421
  if (req.url === "/health" || req.url === "/v1/stats")
18493
18422
  return;
18494
- const key = getApiKey2();
18423
+ let key = getApiKey2();
18424
+ if (!key) {
18425
+ await waitForBackgroundRegistration(15000);
18426
+ key = getApiKey2();
18427
+ }
18495
18428
  if (!key) {
18496
18429
  return reply.code(401).send({
18497
18430
  error: "api_key_required",
@@ -18507,11 +18440,7 @@ async function registerRoutes(app) {
18507
18440
  return reply.code(400).send({ error: "intent required" });
18508
18441
  try {
18509
18442
  const result = await resolveAndExecute(intent, params ?? {}, context, projection, { confirm_unsafe, dry_run, force_capture, client_scope: clientScope });
18510
- const res = attachAgentOutcomeHints({ ...result }, {
18511
- skill: result.skill,
18512
- endpointId: result.trace.endpoint_id,
18513
- timing: result.timing
18514
- });
18443
+ const res = result;
18515
18444
  if (result.timing) {
18516
18445
  res.timing = result.timing;
18517
18446
  }
@@ -18520,9 +18449,10 @@ async function registerRoutes(app) {
18520
18449
  res.available_endpoints = innerResult.available_endpoints;
18521
18450
  }
18522
18451
  await recordAnalyticsSession(buildAnalyticsSessionPayload(result, {
18452
+ browser_mode: "replaced",
18523
18453
  discovery_queries: 1
18524
18454
  })).catch(() => {});
18525
- return reply.send(res);
18455
+ return reply.send(result);
18526
18456
  } catch (err) {
18527
18457
  return reply.code(500).send({ error: err.message });
18528
18458
  }
@@ -18732,33 +18662,26 @@ async function registerRoutes(app) {
18732
18662
  recordExecution(freshResult.trace.skill_id, freshResult.trace.endpoint_id, freshResult.trace, skill).catch(() => {});
18733
18663
  }
18734
18664
  await recordAnalyticsSession(buildAnalyticsSessionPayload(freshResult, {
18665
+ browser_mode: "manual",
18735
18666
  discovery_queries: 1
18736
18667
  })).catch(() => {});
18737
- const recovered = attachAgentOutcomeHints({
18668
+ return reply.send({
18738
18669
  ...freshResult,
18739
18670
  _recovery: {
18740
18671
  reason: "stale_endpoint_404",
18741
18672
  original_skill_id: skill_id,
18742
18673
  message: "Original endpoint returned 404. Auto-recovered with fresh capture."
18743
18674
  }
18744
- }, {
18745
- skill: freshResult.skill ?? skill,
18746
- endpointId: freshResult.trace.endpoint_id,
18747
- timing: freshResult.timing
18748
- });
18749
- return reply.send({
18750
- ...recovered
18751
18675
  });
18752
18676
  } catch {}
18753
18677
  }
18754
18678
  await recordAnalyticsSession(buildAnalyticsSessionPayload(execResult, {
18755
- 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
18756
18683
  })).catch(() => {});
18757
- const response = attachAgentOutcomeHints({ ...execResult }, {
18758
- skill,
18759
- endpointId: execResult.trace.endpoint_id
18760
- });
18761
- return reply.send(response);
18684
+ return reply.send(execResult);
18762
18685
  } catch (err) {
18763
18686
  return reply.code(500).send({ error: err.message });
18764
18687
  }
@@ -18860,13 +18783,7 @@ async function registerRoutes(app) {
18860
18783
  return reply.code(500).send({ error: err.message });
18861
18784
  }
18862
18785
  });
18863
- app.get("/health", async (_req, reply) => reply.send({
18864
- status: "ok",
18865
- trace_version: TRACE_VERSION,
18866
- code_hash: CODE_HASH,
18867
- git_sha: GIT_SHA,
18868
- package_version: PACKAGE_VERSION
18869
- }));
18786
+ app.get("/health", async (_req, reply) => reply.send({ status: "ok", trace_version: TRACE_VERSION, code_hash: CODE_HASH, git_sha: GIT_SHA }));
18870
18787
  app.get("/v1/sessions/:domain", async (req, reply) => {
18871
18788
  const { domain } = req.params;
18872
18789
  const query = req.query;
@@ -18913,71 +18830,6 @@ async function registerRoutes(app) {
18913
18830
  session.harActive = true;
18914
18831
  await injectInterceptor(session.tabId).catch(() => {});
18915
18832
  }
18916
- async function flushBrowseCapture(session, options = {}) {
18917
- let intercepted = [];
18918
- try {
18919
- const raw = await collectInterceptedRequests(session.tabId);
18920
- intercepted = raw.map((request) => ({
18921
- url: request.url,
18922
- method: request.method,
18923
- request_headers: request.request_headers ?? {},
18924
- request_body: request.request_body,
18925
- response_status: request.response_status,
18926
- response_headers: request.response_headers ?? {},
18927
- response_body: request.response_body,
18928
- timestamp: request.timestamp
18929
- }));
18930
- } catch {}
18931
- let harEntries = [];
18932
- if (session.harActive) {
18933
- try {
18934
- const { entries } = await harStop(session.tabId);
18935
- harEntries = entries;
18936
- } catch {}
18937
- }
18938
- session.harActive = false;
18939
- const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
18940
- const syncResult = await cacheBrowseRequests({
18941
- sessionUrl: session.url,
18942
- sessionDomain: session.domain,
18943
- requests: allRequests,
18944
- getPageHtml: () => getPageHtml(session.tabId)
18945
- });
18946
- let backgroundPublishQueued = false;
18947
- if (options.queueBackgroundPublish) {
18948
- if (allRequests.length > 0) {
18949
- passiveIndexFromRequests(allRequests, session.url);
18950
- backgroundPublishQueued = true;
18951
- } else if (syncResult.skill) {
18952
- queueBackgroundIndex({
18953
- skill: { ...syncResult.skill },
18954
- domain: syncResult.domain,
18955
- intent: syncResult.skill.intent_signature || `browse ${syncResult.domain}`,
18956
- contextUrl: session.url,
18957
- cacheKey: `browse-submit:${syncResult.domain}:${Date.now()}`
18958
- });
18959
- backgroundPublishQueued = true;
18960
- }
18961
- }
18962
- return {
18963
- indexed: syncResult.indexed,
18964
- mode: syncResult.mode,
18965
- domain: syncResult.domain,
18966
- skill_id: syncResult.skill?.skill_id ?? null,
18967
- endpoint_count: syncResult.skill?.endpoints.length ?? 0,
18968
- endpoints: (syncResult.skill?.endpoints ?? []).map((endpoint) => ({
18969
- endpoint_id: endpoint.endpoint_id,
18970
- method: endpoint.method,
18971
- url_template: endpoint.url_template,
18972
- description: endpoint.description,
18973
- trigger_url: endpoint.trigger_url,
18974
- action_kind: endpoint.semantic?.action_kind,
18975
- resource_kind: endpoint.semantic?.resource_kind
18976
- })),
18977
- request_count: allRequests.length,
18978
- background_publish_queued: backgroundPublishQueued
18979
- };
18980
- }
18981
18833
  app.post("/v1/browse/go", async (req, reply) => {
18982
18834
  const { url } = req.body;
18983
18835
  if (!url)
@@ -19034,7 +18886,6 @@ async function registerRoutes(app) {
19034
18886
  const { session, result, recovered } = await withRecoveredBrowseSession(browseSessions, exports_client, injectInterceptor, async (session2) => submitBrowseForm({
19035
18887
  client: exports_client,
19036
18888
  session: session2,
19037
- flushCapture: async (session3) => await flushBrowseCapture(session3, { queueBackgroundPublish: true }),
19038
18889
  restartCapture: restartBrowseCapture,
19039
18890
  rehydratePlugins: bestEffortRehydratePlugins
19040
18891
  }, {
@@ -19047,10 +18898,8 @@ async function registerRoutes(app) {
19047
18898
  session.url = result.url || await getCurrentUrl(session.tabId).catch(() => session.url);
19048
18899
  session.domain = profileName(session.url);
19049
18900
  const statusCode = result.ok ? 200 : result.recoverable ? 502 : 400;
19050
- 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.";
19051
18901
  return reply.code(statusCode).send({
19052
18902
  ...result,
19053
- next_step: nextStep,
19054
18903
  recovered,
19055
18904
  tab_id: session.tabId,
19056
18905
  url: session.url
@@ -19160,18 +19009,57 @@ async function registerRoutes(app) {
19160
19009
  const session = browseSessions.get("default");
19161
19010
  if (!session)
19162
19011
  return reply.send({ ok: false, error: "no active session" });
19163
- const syncResult = await flushBrowseCapture(session);
19164
- 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(() => {});
19165
19046
  return reply.send({
19166
19047
  ok: true,
19167
19048
  tab_id: session.tabId,
19168
19049
  indexed: syncResult.indexed,
19169
19050
  mode: syncResult.mode,
19170
19051
  domain: syncResult.domain,
19171
- skill_id: syncResult.skill_id,
19172
- endpoint_count: syncResult.endpoint_count,
19173
- endpoints: syncResult.endpoints,
19174
- 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
+ }))
19175
19063
  });
19176
19064
  });
19177
19065
  app.post("/v1/browse/close", async (_req, reply) => {
@@ -19181,16 +19069,42 @@ async function registerRoutes(app) {
19181
19069
  if (session.domain) {
19182
19070
  await authProfileSave(session.tabId, session.domain).catch(() => {});
19183
19071
  }
19184
- 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);
19185
19101
  await closeTab(session.tabId).catch(() => {});
19186
19102
  browseSessions.delete("default");
19187
19103
  return reply.send({
19188
19104
  ok: true,
19189
19105
  indexed: syncResult.indexed,
19190
19106
  mode: syncResult.mode,
19191
- endpoint_count: syncResult.endpoint_count,
19192
- request_count: syncResult.request_count,
19193
- background_publish_queued: syncResult.background_publish_queued,
19107
+ endpoint_count: syncResult.skill?.endpoints.length ?? 0,
19194
19108
  auth_saved: session.domain || null
19195
19109
  });
19196
19110
  });
@@ -19217,7 +19131,6 @@ var init_routes = __esm(async () => {
19217
19131
  init_ratelimit();
19218
19132
  init_graph();
19219
19133
  init_session_logs();
19220
- init_agent_outcome();
19221
19134
  init_browse_session();
19222
19135
  init_browse_submit();
19223
19136
  await __promiseAll([
@@ -19274,7 +19187,7 @@ async function startUnbrowseServer(options = {}) {
19274
19187
  try {
19275
19188
  execSync2("pkill -f chrome-headless-shell", { stdio: "ignore" });
19276
19189
  } catch {}
19277
- await ensureRegistered2();
19190
+ startBackgroundRegistration();
19278
19191
  const app = Fastify({ logger: options.logger ?? true });
19279
19192
  await app.register(cors, { origin: true });
19280
19193
  await registerRateLimiter(app);
@@ -19309,7 +19222,6 @@ var init_server = __esm(async () => {
19309
19222
 
19310
19223
  // ../../src/cli.ts
19311
19224
  import { config as loadEnv } from "dotenv";
19312
- import { spawn as spawn3 } from "child_process";
19313
19225
 
19314
19226
  // ../../src/client/index.ts
19315
19227
  init_cascade();
@@ -19394,18 +19306,11 @@ function createInstallTelemetryState() {
19394
19306
  }
19395
19307
  function getOrCreateInstallTelemetryState() {
19396
19308
  const existing = loadInstallTelemetryState();
19397
- const state = existing?.install_id ? existing : createInstallTelemetryState();
19398
- const landingToken = process.env.UNBROWSE_LANDING_TOKEN?.trim();
19399
- if (landingToken && state.landing_token !== landingToken) {
19400
- state.landing_token = landingToken;
19401
- state.landing_token_seen_at = new Date().toISOString();
19402
- saveInstallTelemetryState(state);
19403
- return state;
19404
- }
19405
- if (!existing?.install_id) {
19406
- saveInstallTelemetryState(state);
19407
- }
19408
- return state;
19309
+ if (existing?.install_id)
19310
+ return existing;
19311
+ const created = createInstallTelemetryState();
19312
+ saveInstallTelemetryState(created);
19313
+ return created;
19409
19314
  }
19410
19315
  function getInstallId() {
19411
19316
  return getOrCreateInstallTelemetryState().install_id;
@@ -19451,7 +19356,6 @@ async function ensureCliInstallTracked(hostType = detectTelemetryHostType()) {
19451
19356
  const createdAt = new Date().toISOString();
19452
19357
  const ok = await postTelemetry("/v1/telemetry/install", {
19453
19358
  install_id: state.install_id,
19454
- landing_token: state.landing_token,
19455
19359
  source: "cli-first-seen",
19456
19360
  host_type: hostType,
19457
19361
  skill: "unbrowse",
@@ -19471,7 +19375,6 @@ async function recordInstallTelemetryEvent(source, options) {
19471
19375
  const createdAt = options?.createdAt ?? new Date().toISOString();
19472
19376
  await postTelemetry("/v1/telemetry/install", {
19473
19377
  install_id: getInstallId(),
19474
- landing_token: getOrCreateInstallTelemetryState().landing_token,
19475
19378
  source,
19476
19379
  host_type: options?.hostType ?? detectTelemetryHostType(),
19477
19380
  skill: options?.skill ?? "unbrowse",
@@ -19486,7 +19389,6 @@ async function recordFunnelTelemetryEvent(name, options) {
19486
19389
  await postTelemetry("/v1/telemetry/events", {
19487
19390
  install_id: getInstallId(),
19488
19391
  session_id: options?.sessionId,
19489
- landing_token: getOrCreateInstallTelemetryState().landing_token,
19490
19392
  name,
19491
19393
  source: options?.source ?? "cli",
19492
19394
  host_type: options?.hostType ?? detectTelemetryHostType(),
@@ -19701,23 +19603,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
19701
19603
  rl.close();
19702
19604
  }
19703
19605
  }
19704
- async function checkTosStatus() {
19606
+ async function checkTosStatus(options) {
19607
+ const exitOnFailure = options?.exitOnFailure ?? true;
19705
19608
  const config = loadConfig();
19706
19609
  let tosInfo;
19707
19610
  try {
19708
19611
  tosInfo = await api("GET", "/v1/tos/current");
19709
19612
  } catch {
19710
- return;
19613
+ return true;
19711
19614
  }
19712
19615
  if (config?.tos_accepted_version === tosInfo.version) {
19713
- return;
19616
+ return true;
19714
19617
  }
19715
19618
  console.log(`
19716
19619
  The Unbrowse Terms of Service have been updated.`);
19717
19620
  const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
19718
19621
  if (!accepted) {
19719
19622
  console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
19720
- process.exit(1);
19623
+ if (exitOnFailure)
19624
+ process.exit(1);
19625
+ return false;
19721
19626
  }
19722
19627
  try {
19723
19628
  await api("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
@@ -19730,16 +19635,20 @@ The Unbrowse Terms of Service have been updated.`);
19730
19635
  } catch (err) {
19731
19636
  console.warn(`Failed to record ToS acceptance: ${err.message}`);
19732
19637
  }
19638
+ return true;
19733
19639
  }
19734
19640
  async function ensureRegistered(options) {
19735
19641
  if (LOCAL_ONLY)
19736
19642
  return;
19643
+ const exitOnFailure = options?.exitOnFailure ?? true;
19737
19644
  const usableKey = await findUsableApiKey();
19738
19645
  if (usableKey) {
19739
19646
  if (usableKey.source === "config") {
19740
19647
  console.log("[unbrowse] Restored saved registration.");
19741
19648
  }
19742
- await checkTosStatus();
19649
+ const accepted2 = await checkTosStatus({ exitOnFailure });
19650
+ if (!accepted2)
19651
+ return;
19743
19652
  try {
19744
19653
  const profile = await getMyProfile();
19745
19654
  const wallet = getLocalWalletContext();
@@ -19760,7 +19669,9 @@ async function ensureRegistered(options) {
19760
19669
  const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
19761
19670
  if (!accepted) {
19762
19671
  console.log("You must accept the Terms of Service to use Unbrowse.");
19763
- process.exit(1);
19672
+ if (exitOnFailure)
19673
+ process.exit(1);
19674
+ return;
19764
19675
  }
19765
19676
  const fallbackName = buildDefaultAgentName();
19766
19677
  const name = options?.promptForEmail ? await promptAgentEmail(fallbackName) : resolveAgentName(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
@@ -19788,7 +19699,8 @@ async function ensureRegistered(options) {
19788
19699
  } catch (err) {
19789
19700
  console.warn(`Registration failed: ${err.message}`);
19790
19701
  console.warn("Set UNBROWSE_API_KEY manually or try again.");
19791
- process.exit(1);
19702
+ if (exitOnFailure)
19703
+ process.exit(1);
19792
19704
  }
19793
19705
  }
19794
19706
  async function getMyProfile() {
@@ -20005,22 +19917,14 @@ init_supervisor();
20005
19917
  import { openSync, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
20006
19918
  import path6 from "node:path";
20007
19919
  import { spawn as spawn2 } from "node:child_process";
20008
- function isServerVersionMismatch(runningVersion, installedVersion) {
20009
- return !!runningVersion && runningVersion !== installedVersion;
20010
- }
20011
- async function fetchServerHealth(baseUrl, timeoutMs = 2000) {
19920
+ async function isServerHealthy(baseUrl, timeoutMs = 2000) {
20012
19921
  try {
20013
19922
  const res = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(timeoutMs) });
20014
- if (!res.ok)
20015
- return null;
20016
- return await res.json();
19923
+ return res.ok;
20017
19924
  } catch {
20018
- return null;
19925
+ return false;
20019
19926
  }
20020
19927
  }
20021
- async function isServerHealthy(baseUrl, timeoutMs = 2000) {
20022
- return !!await fetchServerHealth(baseUrl, timeoutMs);
20023
- }
20024
19928
  async function waitForHealthy(baseUrl, timeoutMs) {
20025
19929
  const start2 = Date.now();
20026
19930
  while (Date.now() - start2 < timeoutMs) {
@@ -20102,23 +20006,10 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
20102
20006
  }
20103
20007
  var supervisor = new LocalSupervisor;
20104
20008
  async function ensureLocalServer(baseUrl, noAutoStart, metaUrl) {
20105
- const installedVersion = getVersion(metaUrl);
20106
- const initialHealth = await fetchServerHealth(baseUrl);
20107
- if (initialHealth) {
20108
- const runningVersion = initialHealth.package_version;
20109
- if (isServerVersionMismatch(runningVersion, installedVersion)) {
20110
- const versionInfo = checkServerVersion(baseUrl, metaUrl);
20111
- if (versionInfo?.needs_restart) {
20112
- stopServer(baseUrl);
20113
- await new Promise((resolve) => setTimeout(resolve, 1000));
20114
- } else {
20115
- throw new Error(`Server version mismatch on ${baseUrl}: running ${runningVersion}, installed ${installedVersion}. Run "unbrowse restart" or stop the stale server bound to that port.`);
20116
- }
20117
- } else {
20118
- if (!supervisor.isRunning())
20119
- await supervisor.start();
20120
- return;
20121
- }
20009
+ if (await isServerHealthy(baseUrl)) {
20010
+ if (!supervisor.isRunning())
20011
+ await supervisor.start();
20012
+ return;
20122
20013
  }
20123
20014
  const pidFile = getServerPidFile(baseUrl);
20124
20015
  const existing = readPidState(pidFile);
@@ -20207,26 +20098,7 @@ async function restartServer(baseUrl, metaUrl) {
20207
20098
  // ../../src/runtime/paths.ts
20208
20099
  import { existsSync as existsSync13, mkdirSync as mkdirSync11, realpathSync as realpathSync2 } from "node:fs";
20209
20100
  import path7 from "node:path";
20210
- import { createRequire as createRequire3 } from "node:module";
20211
20101
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
20212
- function resolveSiblingEntrypoint2(metaUrl, basename) {
20213
- const file = fileURLToPath3(metaUrl);
20214
- return path7.join(path7.dirname(file), `${basename}${path7.extname(file) || ".js"}`);
20215
- }
20216
- function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
20217
- if (path7.extname(entrypoint) !== ".ts")
20218
- return [entrypoint];
20219
- if (process.versions.bun)
20220
- return [entrypoint];
20221
- try {
20222
- const req = createRequire3(metaUrl);
20223
- const tsxPkg = req.resolve("tsx/package.json");
20224
- const tsxLoader = path7.join(path7.dirname(tsxPkg), "dist", "loader.mjs");
20225
- if (existsSync13(tsxLoader))
20226
- return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
20227
- } catch {}
20228
- return ["--import", "tsx", entrypoint];
20229
- }
20230
20102
  function isMainModule(metaUrl) {
20231
20103
  const entry = process.argv[1];
20232
20104
  if (!entry)
@@ -20248,7 +20120,7 @@ await init_orchestrator();
20248
20120
  import { join as join13 } from "node:path";
20249
20121
  import { homedir as homedir7 } from "node:os";
20250
20122
  var UNBROWSE_CONFIG_PATH2 = join13(homedir7(), ".unbrowse", "config.json");
20251
- 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");
20252
20124
  var indexInFlight2 = new Map;
20253
20125
  async function drainPendingIndexJobs() {
20254
20126
  const pending = [...indexInFlight2.values()];
@@ -20277,209 +20149,9 @@ init_paths();
20277
20149
  init_client();
20278
20150
  init_logger();
20279
20151
  import { execFileSync as execFileSync3 } from "node:child_process";
20280
- import { existsSync as existsSync15, mkdirSync as mkdirSync13, writeFileSync as writeFileSync12 } from "node:fs";
20281
- import os5 from "node:os";
20282
- import path9 from "node:path";
20283
-
20284
- // ../../src/runtime/update-hints.ts
20285
- init_paths();
20286
- import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "node:fs";
20152
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "node:fs";
20287
20153
  import os4 from "node:os";
20288
20154
  import path8 from "node:path";
20289
- var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
20290
- var CODEX_MARKER = "# Unbrowse update hints — managed by unbrowse setup";
20291
- function getHomeDir() {
20292
- return process.env.HOME || os4.homedir();
20293
- }
20294
- function getConfigDir3() {
20295
- if (process.env.UNBROWSE_CONFIG_DIR)
20296
- return process.env.UNBROWSE_CONFIG_DIR;
20297
- return path8.join(getHomeDir(), ".unbrowse");
20298
- }
20299
- function ensureDir2(dir) {
20300
- if (!existsSync14(dir))
20301
- mkdirSync12(dir, { recursive: true });
20302
- return dir;
20303
- }
20304
- function readJsonFile(file) {
20305
- try {
20306
- return JSON.parse(readFileSync11(file, "utf8"));
20307
- } catch {
20308
- return null;
20309
- }
20310
- }
20311
- function writeJsonFile(file, value) {
20312
- ensureDir2(path8.dirname(file));
20313
- writeFileSync11(file, `${JSON.stringify(value, null, 2)}
20314
- `);
20315
- }
20316
- function getInstallSourcePath() {
20317
- return path8.join(getConfigDir3(), "install-source.json");
20318
- }
20319
- function detectRepoRoot(start2) {
20320
- let dir = path8.resolve(start2);
20321
- const root = path8.parse(dir).root;
20322
- while (dir !== root) {
20323
- if (existsSync14(path8.join(dir, ".git")))
20324
- return dir;
20325
- dir = path8.dirname(dir);
20326
- }
20327
- return;
20328
- }
20329
- function detectInstallMethod(packageRoot) {
20330
- if (process.env.UNBROWSE_SETUP_METHOD === "repo-clone")
20331
- return "repo-clone";
20332
- if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
20333
- return "npm-global";
20334
- if (packageRoot.includes(`${path8.sep}node_modules${path8.sep}`))
20335
- return "npm-global";
20336
- return detectRepoRoot(packageRoot) ? "repo-clone" : "unknown";
20337
- }
20338
- function detectInstallHost(repoRoot) {
20339
- const explicit = process.env.UNBROWSE_SETUP_HOST;
20340
- if (explicit)
20341
- return explicit;
20342
- if (!repoRoot)
20343
- return "unknown";
20344
- const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir(), ".codex");
20345
- if (repoRoot === path8.join(codexHome, "skills", "unbrowse"))
20346
- return "codex";
20347
- if (repoRoot === path8.join(getHomeDir(), ".claude", "skills", "unbrowse"))
20348
- return "claude";
20349
- if (repoRoot === path8.join(getHomeDir(), "unbrowse"))
20350
- return "off";
20351
- return "unknown";
20352
- }
20353
- function resolveInstallSource(metaUrl) {
20354
- const packageRoot = getPackageRoot(metaUrl);
20355
- const envRepoRoot = process.env.UNBROWSE_SETUP_ROOT || undefined;
20356
- const repoRoot = envRepoRoot || detectRepoRoot(packageRoot);
20357
- return {
20358
- method: detectInstallMethod(packageRoot),
20359
- host: detectInstallHost(repoRoot),
20360
- package_root: packageRoot,
20361
- repo_root: repoRoot,
20362
- recorded_at: new Date().toISOString()
20363
- };
20364
- }
20365
- function saveInstallSource(metaUrl) {
20366
- const state = resolveInstallSource(metaUrl);
20367
- writeJsonFile(getInstallSourcePath(), state);
20368
- return state;
20369
- }
20370
- function loadInstallSource(metaUrl) {
20371
- return readJsonFile(getInstallSourcePath()) ?? resolveInstallSource(metaUrl);
20372
- }
20373
- function commandIncludesHook(command, marker) {
20374
- return typeof command === "string" && command.includes(marker);
20375
- }
20376
- function getCodexConfigPath() {
20377
- const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir(), ".codex");
20378
- return path8.join(codexHome, "config.toml");
20379
- }
20380
- function getClaudeSettingsPath() {
20381
- return path8.join(getHomeDir(), ".claude", "settings.json");
20382
- }
20383
- function getHookScriptPath(metaUrl) {
20384
- return path8.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
20385
- }
20386
- function ensureCodexHooksFeature(content) {
20387
- if (/\bcodex_hooks\s*=\s*true\b/.test(content))
20388
- return content;
20389
- if (/\[features\]/.test(content)) {
20390
- return content.replace(/\[features\]\r?\n/, (match) => `${match}codex_hooks = true
20391
- `);
20392
- }
20393
- const prefix = content && !content.endsWith(`
20394
- `) ? `
20395
- ` : "";
20396
- return `${content}${prefix}[features]
20397
- codex_hooks = true
20398
- `;
20399
- }
20400
- function writeCodexHook(metaUrl) {
20401
- const configPath = getCodexConfigPath();
20402
- if (!existsSync14(path8.dirname(configPath))) {
20403
- return { host: "codex", action: "not-detected", config_file: configPath };
20404
- }
20405
- try {
20406
- const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
20407
- const fileExistsBefore = existsSync14(configPath);
20408
- let content = fileExistsBefore ? readFileSync11(configPath, "utf8") : "";
20409
- const previous = content;
20410
- content = ensureCodexHooksFeature(content);
20411
- if (!content.includes("unbrowse-update-hint.mjs")) {
20412
- const command = `node "${hookScript}"`;
20413
- const prefix = content && !content.endsWith(`
20414
- `) ? `
20415
- ` : "";
20416
- content += `${prefix}${CODEX_MARKER}
20417
- [[hooks]]
20418
- event = "SessionStart"
20419
- command = ${JSON.stringify(command)}
20420
- `;
20421
- }
20422
- if (content !== previous) {
20423
- writeFileSync11(configPath, content, "utf8");
20424
- return {
20425
- host: "codex",
20426
- action: fileExistsBefore ? "updated" : "installed",
20427
- config_file: configPath
20428
- };
20429
- }
20430
- return { host: "codex", action: "already-installed", config_file: configPath };
20431
- } catch (error) {
20432
- return {
20433
- host: "codex",
20434
- action: "failed",
20435
- config_file: configPath,
20436
- message: error instanceof Error ? error.message : String(error)
20437
- };
20438
- }
20439
- }
20440
- function writeClaudeHook(metaUrl) {
20441
- const settingsPath = getClaudeSettingsPath();
20442
- if (!existsSync14(path8.dirname(settingsPath))) {
20443
- return { host: "claude", action: "not-detected", config_file: settingsPath };
20444
- }
20445
- try {
20446
- const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
20447
- const command = `node "${hookScript}"`;
20448
- const fileExistsBefore = existsSync14(settingsPath);
20449
- const settings = readJsonFile(settingsPath) ?? {};
20450
- settings.hooks ??= {};
20451
- settings.hooks.SessionStart ??= [];
20452
- const existing = settings.hooks.SessionStart.some((entry) => Array.isArray(entry.hooks) && entry.hooks.some((hook) => commandIncludesHook(hook.command, "unbrowse-update-hint.mjs")));
20453
- if (!existing) {
20454
- settings.hooks.SessionStart.push({
20455
- hooks: [{ type: "command", command }]
20456
- });
20457
- writeJsonFile(settingsPath, settings);
20458
- return {
20459
- host: "claude",
20460
- action: fileExistsBefore ? "updated" : "installed",
20461
- config_file: settingsPath
20462
- };
20463
- }
20464
- return { host: "claude", action: "already-installed", config_file: settingsPath };
20465
- } catch (error) {
20466
- return {
20467
- host: "claude",
20468
- action: "failed",
20469
- config_file: settingsPath,
20470
- message: error instanceof Error ? error.message : String(error)
20471
- };
20472
- }
20473
- }
20474
- function configureUpdateHintHooks(metaUrl, install) {
20475
- if (process.env.UNBROWSE_DISABLE_UPDATE_HINTS === "1")
20476
- return [];
20477
- const source = install ?? loadInstallSource(metaUrl);
20478
- const configuredHosts = source.host === "codex" || source.host === "claude" ? [source.host] : ["codex", "claude"];
20479
- return configuredHosts.map((host) => host === "codex" ? writeCodexHook(metaUrl) : writeClaudeHook(metaUrl));
20480
- }
20481
-
20482
- // ../../src/runtime/setup.ts
20483
20155
  function hasBinary(name) {
20484
20156
  const checker = process.platform === "win32" ? "where" : "which";
20485
20157
  try {
@@ -20499,18 +20171,18 @@ function detectPackageManagers() {
20499
20171
  }
20500
20172
  function resolveConfigHome() {
20501
20173
  if (process.platform === "win32") {
20502
- return process.env.APPDATA || path9.join(os5.homedir(), "AppData", "Roaming");
20174
+ return process.env.APPDATA || path8.join(os4.homedir(), "AppData", "Roaming");
20503
20175
  }
20504
- return process.env.XDG_CONFIG_HOME || path9.join(os5.homedir(), ".config");
20176
+ return process.env.XDG_CONFIG_HOME || path8.join(os4.homedir(), ".config");
20505
20177
  }
20506
20178
  function getOpenCodeGlobalCommandsDir() {
20507
- return path9.join(resolveConfigHome(), "opencode", "commands");
20179
+ return path8.join(resolveConfigHome(), "opencode", "commands");
20508
20180
  }
20509
20181
  function getOpenCodeProjectCommandsDir(cwd) {
20510
- return path9.join(cwd, ".opencode", "commands");
20182
+ return path8.join(cwd, ".opencode", "commands");
20511
20183
  }
20512
20184
  function detectOpenCode(cwd) {
20513
- return hasBinary("opencode") || existsSync15(path9.join(resolveConfigHome(), "opencode")) || existsSync15(path9.join(cwd, ".opencode"));
20185
+ return hasBinary("opencode") || existsSync14(path8.join(resolveConfigHome(), "opencode")) || existsSync14(path8.join(cwd, ".opencode"));
20514
20186
  }
20515
20187
  function renderOpenCodeCommand() {
20516
20188
  return `---
@@ -20538,13 +20210,13 @@ function writeOpenCodeCommand(scope, cwd) {
20538
20210
  if (scope === "auto" && !detected) {
20539
20211
  return { detected: false, action: "not-detected", scope: "off" };
20540
20212
  }
20541
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync15(path9.join(cwd, ".opencode")) ? "project" : "global";
20213
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path8.join(cwd, ".opencode")) ? "project" : "global";
20542
20214
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
20543
- const commandFile = path9.join(ensureDir(commandsDir), "unbrowse.md");
20215
+ const commandFile = path8.join(ensureDir(commandsDir), "unbrowse.md");
20544
20216
  const content = renderOpenCodeCommand();
20545
- const action2 = existsSync15(commandFile) ? "updated" : "installed";
20546
- mkdirSync13(path9.dirname(commandFile), { recursive: true });
20547
- writeFileSync12(commandFile, content);
20217
+ const action2 = existsSync14(commandFile) ? "updated" : "installed";
20218
+ mkdirSync12(path8.dirname(commandFile), { recursive: true });
20219
+ writeFileSync11(commandFile, content);
20548
20220
  return {
20549
20221
  detected: detected || scope !== "auto",
20550
20222
  action: action2,
@@ -20554,10 +20226,10 @@ function writeOpenCodeCommand(scope, cwd) {
20554
20226
  }
20555
20227
  async function ensureBrowserEngineInstalled() {
20556
20228
  const binary = findKuriBinary();
20557
- if (existsSync15(binary)) {
20229
+ if (existsSync14(binary)) {
20558
20230
  return { installed: true, action: "already-installed" };
20559
20231
  }
20560
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync15(path9.join(candidate, "build.zig")));
20232
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path8.join(candidate, "build.zig")));
20561
20233
  if (!sourceDir) {
20562
20234
  return {
20563
20235
  installed: false,
@@ -20579,7 +20251,7 @@ async function ensureBrowserEngineInstalled() {
20579
20251
  timeout: 300000
20580
20252
  });
20581
20253
  const builtBinary = findKuriBinary();
20582
- if (existsSync15(builtBinary)) {
20254
+ if (existsSync14(builtBinary)) {
20583
20255
  return {
20584
20256
  installed: true,
20585
20257
  action: "installed",
@@ -20598,15 +20270,14 @@ async function ensureBrowserEngineInstalled() {
20598
20270
  }
20599
20271
  async function runSetup(options) {
20600
20272
  const cwd = options?.cwd || process.cwd();
20601
- const installSource = saveInstallSource(import.meta.url);
20602
20273
  const hostEnv = detectHostEnvironment();
20603
20274
  log("setup", `detected host environment: ${hostEnv}`);
20604
20275
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
20605
20276
  const walletCheck = checkWalletConfigured();
20606
20277
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
20607
- const lobsterInstalled = hasBinary("lobstercash") || existsSync15(path9.join(os5.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
20278
+ const lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
20608
20279
  if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
20609
- 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...");
20610
20281
  try {
20611
20282
  execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
20612
20283
  stdio: "inherit",
@@ -20617,202 +20288,30 @@ async function runSetup(options) {
20617
20288
  console.log(`[unbrowse] wallet configured (${recheck.provider})`);
20618
20289
  }
20619
20290
  } catch {
20620
- 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");
20621
20292
  }
20622
20293
  }
20623
20294
  const finalWalletCheck = checkWalletConfigured();
20624
20295
  const wallet = {
20625
20296
  ...finalWalletCheck,
20626
20297
  lobster_installed: lobsterInstalled,
20627
- 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.",
20628
- 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"
20629
20300
  };
20630
20301
  return {
20631
20302
  os: {
20632
20303
  platform: process.platform,
20633
- release: os5.release(),
20304
+ release: os4.release(),
20634
20305
  arch: process.arch
20635
20306
  },
20636
20307
  host_environment: hostEnv,
20637
20308
  package_managers: detectPackageManagers(),
20638
20309
  browser_engine: browser,
20639
20310
  opencode: writeOpenCodeCommand(options?.opencode ?? "auto", cwd),
20640
- update_hints: configureUpdateHintHooks(import.meta.url, installSource),
20641
20311
  wallet
20642
20312
  };
20643
20313
  }
20644
20314
 
20645
- // ../../src/runtime/update-hints.ts
20646
- init_paths();
20647
- import { existsSync as existsSync16, mkdirSync as mkdirSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync13 } from "node:fs";
20648
- import os6 from "node:os";
20649
- import path10 from "node:path";
20650
- var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
20651
- var DEFAULT_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
20652
- function getHomeDir2() {
20653
- return process.env.HOME || os6.homedir();
20654
- }
20655
- function getConfigDir4() {
20656
- if (process.env.UNBROWSE_CONFIG_DIR)
20657
- return process.env.UNBROWSE_CONFIG_DIR;
20658
- return path10.join(getHomeDir2(), ".unbrowse");
20659
- }
20660
- function ensureDir3(dir) {
20661
- if (!existsSync16(dir))
20662
- mkdirSync14(dir, { recursive: true });
20663
- return dir;
20664
- }
20665
- function readJsonFile2(file) {
20666
- try {
20667
- return JSON.parse(readFileSync12(file, "utf8"));
20668
- } catch {
20669
- return null;
20670
- }
20671
- }
20672
- function writeJsonFile2(file, value) {
20673
- ensureDir3(path10.dirname(file));
20674
- writeFileSync13(file, `${JSON.stringify(value, null, 2)}
20675
- `);
20676
- }
20677
- function getInstallSourcePath2() {
20678
- return path10.join(getConfigDir4(), "install-source.json");
20679
- }
20680
- function getUpdateCheckStatePath() {
20681
- return path10.join(getConfigDir4(), "update-check.json");
20682
- }
20683
- function detectRepoRoot2(start2) {
20684
- let dir = path10.resolve(start2);
20685
- const root = path10.parse(dir).root;
20686
- while (dir !== root) {
20687
- if (existsSync16(path10.join(dir, ".git")))
20688
- return dir;
20689
- dir = path10.dirname(dir);
20690
- }
20691
- return;
20692
- }
20693
- function detectInstallMethod2(packageRoot) {
20694
- if (process.env.UNBROWSE_SETUP_METHOD === "repo-clone")
20695
- return "repo-clone";
20696
- if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
20697
- return "npm-global";
20698
- if (packageRoot.includes(`${path10.sep}node_modules${path10.sep}`))
20699
- return "npm-global";
20700
- return detectRepoRoot2(packageRoot) ? "repo-clone" : "unknown";
20701
- }
20702
- function detectInstallHost2(repoRoot) {
20703
- const explicit = process.env.UNBROWSE_SETUP_HOST;
20704
- if (explicit)
20705
- return explicit;
20706
- if (!repoRoot)
20707
- return "unknown";
20708
- const codexHome = process.env.CODEX_HOME || path10.join(getHomeDir2(), ".codex");
20709
- if (repoRoot === path10.join(codexHome, "skills", "unbrowse"))
20710
- return "codex";
20711
- if (repoRoot === path10.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
20712
- return "claude";
20713
- if (repoRoot === path10.join(getHomeDir2(), "unbrowse"))
20714
- return "off";
20715
- return "unknown";
20716
- }
20717
- function getInstalledVersion(metaUrl) {
20718
- const packageRoot = getPackageRoot(metaUrl);
20719
- try {
20720
- const pkg = JSON.parse(readFileSync12(path10.join(packageRoot, "package.json"), "utf8"));
20721
- return pkg.version ?? "unknown";
20722
- } catch {
20723
- return "unknown";
20724
- }
20725
- }
20726
- function resolveInstallSource2(metaUrl) {
20727
- const packageRoot = getPackageRoot(metaUrl);
20728
- const envRepoRoot = process.env.UNBROWSE_SETUP_ROOT || undefined;
20729
- const repoRoot = envRepoRoot || detectRepoRoot2(packageRoot);
20730
- return {
20731
- method: detectInstallMethod2(packageRoot),
20732
- host: detectInstallHost2(repoRoot),
20733
- package_root: packageRoot,
20734
- repo_root: repoRoot,
20735
- recorded_at: new Date().toISOString()
20736
- };
20737
- }
20738
- function loadInstallSource2(metaUrl) {
20739
- return readJsonFile2(getInstallSourcePath2()) ?? resolveInstallSource2(metaUrl);
20740
- }
20741
- function compareSemver(a, b) {
20742
- const parse = (value) => value.split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
20743
- const left = parse(a);
20744
- const right = parse(b);
20745
- const max = Math.max(left.length, right.length);
20746
- for (let i = 0;i < max; i++) {
20747
- const diff = (left[i] ?? 0) - (right[i] ?? 0);
20748
- if (diff !== 0)
20749
- return diff;
20750
- }
20751
- return 0;
20752
- }
20753
- async function fetchLatestVersion() {
20754
- try {
20755
- const res = await fetch("https://registry.npmjs.org/unbrowse/latest", {
20756
- signal: AbortSignal.timeout(8000),
20757
- headers: { Accept: "application/json" }
20758
- });
20759
- if (!res.ok)
20760
- return null;
20761
- const body = await res.json();
20762
- return typeof body.version === "string" && body.version.trim() ? body.version.trim() : null;
20763
- } catch {
20764
- return null;
20765
- }
20766
- }
20767
- function getUpdateIntervalMs() {
20768
- const value = Number.parseInt(process.env.UNBROWSE_UPDATE_CHECK_INTERVAL_MS ?? "", 10);
20769
- return Number.isFinite(value) && value > 0 ? value : DEFAULT_INTERVAL_MS2;
20770
- }
20771
- function buildUpgradeCommand(install) {
20772
- if (install.method === "repo-clone" && install.repo_root) {
20773
- const host = install.host === "unknown" || install.host === "auto" ? "off" : install.host;
20774
- return `cd ${install.repo_root} && git pull --ff-only && ./setup --host ${host}`;
20775
- }
20776
- return `curl -fsSL ${INSTALL_SCRIPT_URL} | bash`;
20777
- }
20778
- async function checkForUpdates(metaUrl, options) {
20779
- const installed = getInstalledVersion(metaUrl);
20780
- const install = loadInstallSource2(metaUrl);
20781
- const statePath = getUpdateCheckStatePath();
20782
- const state = readJsonFile2(statePath) ?? {};
20783
- const checkedAt = new Date().toISOString();
20784
- const intervalMs = getUpdateIntervalMs();
20785
- const lastChecked = state.latest_checked_at ? Date.parse(state.latest_checked_at) : Number.NaN;
20786
- const useCache = !options?.force && !!state.latest_version && Number.isFinite(lastChecked) && Date.now() - lastChecked < intervalMs;
20787
- const latest = useCache ? state.latest_version ?? null : await fetchLatestVersion();
20788
- if (!useCache) {
20789
- writeJsonFile2(statePath, {
20790
- ...state,
20791
- checked_at: checkedAt,
20792
- latest_version: latest ?? undefined,
20793
- latest_checked_at: checkedAt
20794
- });
20795
- }
20796
- return {
20797
- installed,
20798
- latest,
20799
- has_update: !!latest && compareSemver(latest, installed) > 0,
20800
- install,
20801
- command: buildUpgradeCommand(install),
20802
- checked_at: checkedAt,
20803
- cached: useCache
20804
- };
20805
- }
20806
- function recordUpdateHint(latestVersion) {
20807
- const statePath = getUpdateCheckStatePath();
20808
- const state = readJsonFile2(statePath) ?? {};
20809
- writeJsonFile2(statePath, {
20810
- ...state,
20811
- notified_version: latestVersion,
20812
- notified_at: new Date().toISOString()
20813
- });
20814
- }
20815
-
20816
20315
  // ../../src/cli.ts
20817
20316
  loadEnv({ quiet: true });
20818
20317
  loadEnv({ path: ".env.runtime", quiet: true });
@@ -20841,8 +20340,8 @@ function parseArgs(argv) {
20841
20340
  }
20842
20341
  return { command, args: positional, flags };
20843
20342
  }
20844
- async function api3(method, path11, body) {
20845
- const res = await fetch(`${BASE_URL}${path11}`, {
20343
+ async function api3(method, path9, body) {
20344
+ const res = await fetch(`${BASE_URL}${path9}`, {
20846
20345
  method,
20847
20346
  headers: {
20848
20347
  ...body ? { "Content-Type": "application/json" } : {},
@@ -20925,73 +20424,15 @@ function slimTrace(obj) {
20925
20424
  out.result = obj.result;
20926
20425
  if (obj.available_endpoints)
20927
20426
  out.available_endpoints = obj.available_endpoints;
20928
- if (obj.impact)
20929
- out.impact = obj.impact;
20930
- if (obj.next_actions)
20931
- out.next_actions = obj.next_actions;
20932
- if (obj.next_step)
20933
- out.next_step = obj.next_step;
20934
20427
  if (obj.source)
20935
20428
  out.source = obj.source;
20936
20429
  if (obj.skill)
20937
20430
  out.skill = obj.skill;
20938
20431
  return out;
20939
20432
  }
20940
- function formatSavedDuration(ms) {
20941
- if (ms >= 60000)
20942
- return `${(ms / 60000).toFixed(1)}m`;
20943
- if (ms >= 1e4)
20944
- return `${Math.round(ms / 1000)}s`;
20945
- if (ms >= 1000)
20946
- return `${(ms / 1000).toFixed(1)}s`;
20947
- return `${ms}ms`;
20948
- }
20949
- function emitImpactSummary(result) {
20950
- const impact = result.impact;
20951
- if (!impact)
20952
- return;
20953
- const timeSavedMs = typeof impact.time_saved_ms === "number" ? impact.time_saved_ms : 0;
20954
- const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
20955
- const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
20956
- const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
20957
- const browserAvoided = impact.browser_avoided === true;
20958
- if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
20959
- return;
20960
- const parts = [];
20961
- if (timeSavedMs > 0)
20962
- parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
20963
- if (tokensSaved > 0)
20964
- parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
20965
- if (browserAvoided)
20966
- parts.push("browser avoided");
20967
- info(parts.join(" \u2022 "));
20968
- }
20969
- function emitNextActionSummary(result) {
20970
- const nextActions = Array.isArray(result.next_actions) ? result.next_actions : [];
20971
- if (nextActions.length === 0)
20972
- return;
20973
- info("Likely next actions:");
20974
- for (const action2 of nextActions.slice(0, 3)) {
20975
- const command = typeof action2.command === "string" ? action2.command : "";
20976
- const title = typeof action2.title === "string" ? action2.title : action2.endpoint_id ?? "next step";
20977
- const why = typeof action2.why === "string" ? action2.why : "";
20978
- info(` ${command || title}${why ? ` # ${why}` : ""}`);
20979
- }
20980
- }
20981
20433
  async function cmdHealth(flags) {
20982
20434
  output(await api3("GET", "/health"), !!flags.pretty);
20983
20435
  }
20984
- function telemetryDomainFromInput(domain, url) {
20985
- if (domain?.trim())
20986
- return domain.trim().replace(/^www\./, "");
20987
- if (!url?.trim())
20988
- return null;
20989
- try {
20990
- return new URL(url).hostname.replace(/^www\./, "");
20991
- } catch {
20992
- return null;
20993
- }
20994
- }
20995
20436
  async function cmdResolve(flags) {
20996
20437
  const intent = flags.intent;
20997
20438
  if (!intent)
@@ -21008,9 +20449,6 @@ async function cmdResolve(flags) {
21008
20449
  hostType,
21009
20450
  properties: {
21010
20451
  command: "resolve",
21011
- intent,
21012
- domain: telemetryDomainFromInput(flags.domain, flags.url),
21013
- url: typeof flags.url === "string" ? flags.url : null,
21014
20452
  has_url: typeof flags.url === "string",
21015
20453
  has_domain: typeof flags.domain === "string",
21016
20454
  auto_execute: !!flags.execute
@@ -21137,9 +20575,6 @@ async function cmdResolve(flags) {
21137
20575
  hostType,
21138
20576
  properties: {
21139
20577
  command: "resolve",
21140
- intent,
21141
- domain: telemetryDomainFromInput(domain, url),
21142
- url: url ?? null,
21143
20578
  source: result.source,
21144
20579
  auto_execute: autoExecute,
21145
20580
  explicit_endpoint: explicitEndpointId ?? null
@@ -21147,8 +20582,6 @@ async function cmdResolve(flags) {
21147
20582
  });
21148
20583
  }
21149
20584
  result = slimTrace(result);
21150
- emitImpactSummary(result);
21151
- emitNextActionSummary(result);
21152
20585
  const skill = result.skill;
21153
20586
  const trace = result.trace;
21154
20587
  if (skill?.skill_id && trace) {
@@ -21162,9 +20595,6 @@ async function cmdResolve(flags) {
21162
20595
  hostType,
21163
20596
  properties: {
21164
20597
  command: "resolve",
21165
- intent,
21166
- domain: telemetryDomainFromInput(flags.domain, flags.url),
21167
- url: typeof flags.url === "string" ? flags.url : null,
21168
20598
  failure_stage: "resolve",
21169
20599
  failure_reason: message
21170
20600
  }
@@ -21172,8 +20602,8 @@ async function cmdResolve(flags) {
21172
20602
  throw error;
21173
20603
  }
21174
20604
  }
21175
- function drillPath(data, path11) {
21176
- const segments = path11.split(/\./).flatMap((s) => {
20605
+ function drillPath(data, path9) {
20606
+ const segments = path9.split(/\./).flatMap((s) => {
21177
20607
  const m = s.match(/^(.+)\[\]$/);
21178
20608
  return m ? [m[1], "[]"] : [s];
21179
20609
  });
@@ -21200,9 +20630,9 @@ function drillPath(data, path11) {
21200
20630
  }
21201
20631
  return values;
21202
20632
  }
21203
- function resolveDotPath(obj, path11) {
20633
+ function resolveDotPath(obj, path9) {
21204
20634
  let cur = obj;
21205
- for (const key of path11.split(".")) {
20635
+ for (const key of path9.split(".")) {
21206
20636
  if (cur == null || typeof cur !== "object")
21207
20637
  return;
21208
20638
  cur = cur[key];
@@ -21219,8 +20649,8 @@ function applyExtract(items, extractSpec) {
21219
20649
  return items.map((item) => {
21220
20650
  const row = {};
21221
20651
  let hasValue = false;
21222
- for (const { alias, path: path11 } of fields) {
21223
- const val = resolveDotPath(item, path11);
20652
+ for (const { alias, path: path9 } of fields) {
20653
+ const val = resolveDotPath(item, path9);
21224
20654
  row[alias] = val ?? null;
21225
20655
  if (val != null)
21226
20656
  hasValue = true;
@@ -21263,9 +20693,6 @@ async function cmdExecute(flags) {
21263
20693
  hostType,
21264
20694
  properties: {
21265
20695
  command: "execute",
21266
- intent: typeof flags.intent === "string" ? flags.intent : null,
21267
- domain: telemetryDomainFromInput(undefined, flags.url),
21268
- url: typeof flags.url === "string" ? flags.url : null,
21269
20696
  skill_id: skillId,
21270
20697
  endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
21271
20698
  }
@@ -21296,17 +20723,12 @@ async function cmdExecute(flags) {
21296
20723
  hostType,
21297
20724
  properties: {
21298
20725
  command: "execute",
21299
- intent: typeof flags.intent === "string" ? flags.intent : null,
21300
- domain: telemetryDomainFromInput(undefined, flags.url),
21301
- url: typeof flags.url === "string" ? flags.url : null,
21302
20726
  skill_id: skillId,
21303
20727
  endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
21304
20728
  }
21305
20729
  });
21306
20730
  }
21307
20731
  result = slimTrace(result);
21308
- emitImpactSummary(result);
21309
- emitNextActionSummary(result);
21310
20732
  const pathFlag = flags.path;
21311
20733
  const extractFlag = flags.extract;
21312
20734
  const limitFlag = flags.limit ? Number(flags.limit) : undefined;
@@ -21314,12 +20736,7 @@ async function cmdExecute(flags) {
21314
20736
  const rawFlag = !!flags.raw;
21315
20737
  if (schemaFlag && !rawFlag) {
21316
20738
  const data = result.result;
21317
- output({
21318
- trace: result.trace,
21319
- schema: schemaOf(data),
21320
- ...result.impact ? { impact: result.impact } : {},
21321
- ...result.next_actions ? { next_actions: result.next_actions } : {}
21322
- }, !!flags.pretty);
20739
+ output({ trace: result.trace, schema: schemaOf(data) }, !!flags.pretty);
21323
20740
  return;
21324
20741
  }
21325
20742
  if (!rawFlag && (pathFlag || extractFlag || limitFlag)) {
@@ -21328,13 +20745,7 @@ async function cmdExecute(flags) {
21328
20745
  const extracted = extractFlag ? applyExtract(items, extractFlag) : items;
21329
20746
  const limited = limitFlag ? extracted.slice(0, limitFlag) : extracted;
21330
20747
  const trace = result.trace;
21331
- const out = {
21332
- trace: result.trace,
21333
- data: limited,
21334
- count: limited.length,
21335
- ...result.impact ? { impact: result.impact } : {},
21336
- ...result.next_actions ? { next_actions: result.next_actions } : {}
21337
- };
20748
+ const out = { trace: result.trace, data: limited, count: limited.length };
21338
20749
  if (trace?.skill_id && trace?.endpoint_id && limited.length > 0) {
21339
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"}]'`;
21340
20751
  }
@@ -21347,8 +20758,6 @@ async function cmdExecute(flags) {
21347
20758
  const schema = schemaOf(result.result);
21348
20759
  output({
21349
20760
  trace: result.trace,
21350
- ...result.impact ? { impact: result.impact } : {},
21351
- ...result.next_actions ? { next_actions: result.next_actions } : {},
21352
20761
  extraction_hints: {
21353
20762
  message: "Response is large. Use --path/--extract/--limit to filter, or --schema to see structure, or --raw for full response.",
21354
20763
  schema_tree: schema,
@@ -21366,9 +20775,6 @@ async function cmdExecute(flags) {
21366
20775
  hostType,
21367
20776
  properties: {
21368
20777
  command: "execute",
21369
- intent: typeof flags.intent === "string" ? flags.intent : null,
21370
- domain: telemetryDomainFromInput(undefined, flags.url),
21371
- url: typeof flags.url === "string" ? flags.url : null,
21372
20778
  skill_id: skillId,
21373
20779
  failure_stage: "execute",
21374
20780
  failure_reason: message
@@ -21440,57 +20846,11 @@ async function cmdSearch(flags) {
21440
20846
  if (!intent)
21441
20847
  die("--intent is required");
21442
20848
  const domain = flags.domain;
21443
- const path11 = domain ? "/v1/search/domain" : "/v1/search";
20849
+ const path9 = domain ? "/v1/search/domain" : "/v1/search";
21444
20850
  const body = { intent, k: Number(flags.k) || 5 };
21445
20851
  if (domain)
21446
20852
  body.domain = domain;
21447
- const hostType = detectTelemetryHostType();
21448
- await ensureCliInstallTracked(hostType);
21449
- await recordFunnelTelemetryEvent("cli_invoked", {
21450
- source: "cli",
21451
- hostType,
21452
- properties: { command: "search" }
21453
- });
21454
- await recordFunnelTelemetryEvent("search_started", {
21455
- source: "cli",
21456
- hostType,
21457
- properties: {
21458
- command: "search",
21459
- intent,
21460
- domain: domain ?? null,
21461
- k: body.k
21462
- }
21463
- });
21464
- try {
21465
- const result = await api3("POST", path11, body);
21466
- const results = Array.isArray(result.results) ? result.results : [];
21467
- await recordFunnelTelemetryEvent("search_completed", {
21468
- source: "cli",
21469
- hostType,
21470
- properties: {
21471
- command: "search",
21472
- intent,
21473
- domain: domain ?? null,
21474
- k: body.k,
21475
- result_count: results.length
21476
- }
21477
- });
21478
- output(result, !!flags.pretty);
21479
- } catch (error) {
21480
- const message = error instanceof Error ? error.message : String(error);
21481
- await recordFunnelTelemetryEvent("search_failed", {
21482
- source: "cli",
21483
- hostType,
21484
- properties: {
21485
- command: "search",
21486
- intent,
21487
- domain: domain ?? null,
21488
- failure_stage: "search",
21489
- failure_reason: message
21490
- }
21491
- });
21492
- throw error;
21493
- }
20853
+ output(await api3("POST", path9, body), !!flags.pretty);
21494
20854
  }
21495
20855
  async function cmdSessions(flags) {
21496
20856
  const domain = flags.domain;
@@ -21521,11 +20881,6 @@ async function cmdSetup(flags) {
21521
20881
  if (report.opencode.action === "installed" || report.opencode.action === "updated") {
21522
20882
  info(`Open Code command installed at ${report.opencode.command_file}`);
21523
20883
  }
21524
- for (const hook of report.update_hints) {
21525
- if (hook.action === "installed" || hook.action === "updated") {
21526
- info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
21527
- }
21528
- }
21529
20884
  await recordInstallTelemetryEvent("setup", {
21530
20885
  hostType,
21531
20886
  status: report.browser_engine.action === "failed" ? "failed" : "installed",
@@ -21586,9 +20941,7 @@ async function cmdSetup(flags) {
21586
20941
  var CLI_REFERENCE = {
21587
20942
  commands: [
21588
20943
  { name: "health", usage: "", desc: "Server health check" },
21589
- { name: "mcp", usage: "[--no-auto-start]", desc: "Run the stdio MCP server" },
21590
20944
  { name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
21591
- { name: "upgrade", usage: "", desc: "Check latest release and print the right upgrade command" },
21592
20945
  { name: "resolve", usage: '--intent "..." --url "..." [opts]', desc: "Resolve intent \u2192 search/capture/execute" },
21593
20946
  { name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
21594
20947
  { name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
@@ -21600,7 +20953,7 @@ var CLI_REFERENCE = {
21600
20953
  { name: "search", usage: '--intent "..." [--domain "..."]', desc: "Search marketplace" },
21601
20954
  { name: "sessions", usage: '--domain "..." [--limit N]', desc: "Debug session logs" },
21602
20955
  { name: "go", usage: "<url>", desc: "Open a live Kuri browser tab for capture-first workflows" },
21603
- { 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" },
21604
20957
  { name: "snap", usage: "[--filter interactive]", desc: "A11y snapshot with @eN refs" },
21605
20958
  { name: "click", usage: "<ref>", desc: "Click element by ref (e.g. e5)" },
21606
20959
  { name: "fill", usage: "<ref> <value>", desc: "Fill input by ref" },
@@ -21637,7 +20990,6 @@ var CLI_REFERENCE = {
21637
20990
  ],
21638
20991
  examples: [
21639
20992
  "unbrowse setup",
21640
- "unbrowse mcp",
21641
20993
  'unbrowse resolve --intent "top stories" --url "https://news.ycombinator.com" --execute',
21642
20994
  'unbrowse resolve --intent "get timeline" --url "https://x.com"',
21643
20995
  'unbrowse go "https://www.mandai.com/en/ticketing/admission-and-rides/parks-selection.html"',
@@ -21676,7 +21028,7 @@ function printHelp() {
21676
21028
  for (const e of r.examples) {
21677
21029
  lines.push(` ${e}`);
21678
21030
  }
21679
- 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");
21680
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.");
21681
21033
  lines.push("");
21682
21034
  process.stderr.write(lines.join(`
@@ -21705,55 +21057,23 @@ function cmdStop(flags) {
21705
21057
  info("No server running.");
21706
21058
  }
21707
21059
  async function cmdUpgrade(flags) {
21708
- const hintOnly = !!flags["hint-only"];
21709
- if (!hintOnly)
21710
- info("Checking for updates...");
21060
+ info("Checking for updates...");
21061
+ const { execSync: execSync3 } = await import("child_process");
21711
21062
  try {
21712
- const result = await checkForUpdates(import.meta.url, { force: !hintOnly });
21713
- if (!result.latest) {
21714
- if (!hintOnly)
21715
- info("Could not check for updates right now.");
21063
+ const result = execSync3("npm view unbrowse version", { encoding: "utf-8", timeout: 1e4 }).trim();
21064
+ const versionInfo = checkServerVersion(BASE_URL, import.meta.url);
21065
+ const installed = versionInfo?.installed ?? "unknown";
21066
+ if (result === installed) {
21067
+ info(`Already at latest version: ${installed}`);
21716
21068
  return;
21717
21069
  }
21718
- if (!result.has_update) {
21719
- if (!hintOnly)
21720
- info(`Already at latest version: ${result.installed}`);
21721
- return;
21722
- }
21723
- info(`Update available: ${result.installed} -> ${result.latest}`);
21724
- info(`Run: ${result.command}`);
21725
- if (!hintOnly) {
21726
- info("Tip: `unbrowse setup` now installs session-start update hints for Codex and Claude when those hosts are present.");
21727
- }
21728
- recordUpdateHint(result.latest);
21070
+ info(`Update available: ${installed} -> ${result}`);
21071
+ info("Run: npm install -g unbrowse@latest");
21072
+ info("Then: unbrowse restart");
21729
21073
  } catch (err) {
21730
- if (!hintOnly)
21731
- info(`Could not check for updates: ${err.message}`);
21074
+ info(`Could not check for updates: ${err.message}`);
21732
21075
  }
21733
21076
  }
21734
- async function cmdMcp(flags) {
21735
- const entrypoint = resolveSiblingEntrypoint2(import.meta.url, "mcp");
21736
- const child = spawn3(process.execPath, [...runtimeArgsForEntrypoint2(import.meta.url, entrypoint), ...flags["no-auto-start"] ? ["--no-auto-start"] : []], {
21737
- cwd: process.cwd(),
21738
- stdio: "inherit",
21739
- env: {
21740
- ...process.env,
21741
- MCP_SERVER_MODE: "1"
21742
- }
21743
- });
21744
- const code = await new Promise((resolve, reject) => {
21745
- child.once("error", reject);
21746
- child.once("exit", (exitCode, signal) => {
21747
- if (signal) {
21748
- process.kill(process.pid, signal);
21749
- return;
21750
- }
21751
- resolve(exitCode ?? 1);
21752
- });
21753
- });
21754
- if (code !== 0)
21755
- process.exit(code);
21756
- }
21757
21077
  async function cmdSiteHelp(pack, flags) {
21758
21078
  if (flags.deps) {
21759
21079
  const graph = buildDepsGraph(pack);
@@ -22025,8 +21345,6 @@ async function main() {
22025
21345
  await cmdSetup(flags);
22026
21346
  return;
22027
21347
  }
22028
- if (command === "mcp")
22029
- return cmdMcp(flags);
22030
21348
  if (command === "status")
22031
21349
  return cmdStatus(flags);
22032
21350
  if (command === "stop") {
@@ -22041,7 +21359,6 @@ async function main() {
22041
21359
  return cmdConnectChrome();
22042
21360
  const KNOWN_COMMANDS = new Set([
22043
21361
  "health",
22044
- "mcp",
22045
21362
  "setup",
22046
21363
  "resolve",
22047
21364
  "execute",
@@ -22102,8 +21419,6 @@ async function main() {
22102
21419
  switch (command) {
22103
21420
  case "health":
22104
21421
  return cmdHealth(flags);
22105
- case "mcp":
22106
- return cmdMcp(flags);
22107
21422
  case "setup":
22108
21423
  return cmdSetup(flags);
22109
21424
  case "resolve":