unbrowse 6.3.0 → 6.5.0-preview.5

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
@@ -31,7 +31,7 @@ var __promiseAll = (args) => Promise.all(args);
31
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
32
32
 
33
33
  // ../../src/build-info.generated.ts
34
- var BUILD_RELEASE_VERSION = "6.3.0", BUILD_GIT_SHA = "ebf3580e8a7b", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi4zLjAiLCJnaXRfc2hhIjoiZWJmMzU4MGU4YTdiIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUBlYmYzNTgwZThhN2IiLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTAxVDA1OjU0OjI4LjgyNloifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "eROrMsDX6qJfzkUSaa6C9my4wf18yIDN6v0qG57deps", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
34
+ var BUILD_RELEASE_VERSION = "6.5.0-preview.5", BUILD_GIT_SHA = "764b78e2d7f4", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAtcHJldmlldy41IiwiZ2l0X3NoYSI6Ijc2NGI3OGUyZDdmNCIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFANzY0Yjc4ZTJkN2Y0IiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wM1QwNzo0NDozOS41MzZaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "3QFhQrBBJLGFXH5FxGlgs6hJtyMTYy7GCXeJoovWc5s", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
35
35
 
36
36
  // ../../src/version.ts
37
37
  import { createHash } from "crypto";
@@ -132,13 +132,14 @@ function getPackageVersion() {
132
132
  return packageVersion;
133
133
  return getEmbeddedReleaseVersion() ?? "unknown";
134
134
  }
135
- var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, DEFAULT_BACKEND_URL, TRACE_VERSION, RELEASE_MANIFEST_BASE64, RELEASE_MANIFEST_SIGNATURE;
135
+ var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, DEFAULT_BACKEND_URL, DEFAULT_PROFILE, TRACE_VERSION, RELEASE_MANIFEST_BASE64, RELEASE_MANIFEST_SIGNATURE;
136
136
  var init_version = __esm(() => {
137
137
  MODULE_DIR = dirname(fileURLToPath(import.meta.url));
138
138
  CODE_HASH = BUILD_CODE_HASH?.trim() || computeCodeHash();
139
139
  GIT_SHA = getGitSha();
140
140
  PACKAGE_VERSION = getPackageVersion();
141
141
  DEFAULT_BACKEND_URL = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
142
+ DEFAULT_PROFILE = BUILD_DEFAULT_PROFILE?.trim() || "";
142
143
  TRACE_VERSION = `${CODE_HASH}@${GIT_SHA}`;
143
144
  RELEASE_MANIFEST_BASE64 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
144
145
  RELEASE_MANIFEST_SIGNATURE = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
@@ -180,9 +181,11 @@ function getWalletContext() {
180
181
  wallet_provider: asNonEmptyString(process.env.AGENT_WALLET_PROVIDER)
181
182
  };
182
183
  }
183
- const localLobsterWallet = getLobsterWalletFromLocalConfig();
184
- if (localLobsterWallet) {
185
- return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
184
+ if (process.env.UNBROWSE_DISABLE_LOCAL_WALLET !== "1") {
185
+ const localLobsterWallet = getLobsterWalletFromLocalConfig();
186
+ if (localLobsterWallet) {
187
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
188
+ }
186
189
  }
187
190
  return {};
188
191
  }
@@ -426,7 +429,7 @@ function resolveSiblingEntrypoint(metaUrl, basename) {
426
429
  }
427
430
  function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
428
431
  if (path.extname(entrypoint) !== ".ts") {
429
- return [pathToFileURL(entrypoint).href];
432
+ return process.platform === "win32" ? [pathToFileURL(entrypoint).href] : [entrypoint];
430
433
  }
431
434
  if (process.versions.bun)
432
435
  return [entrypoint];
@@ -506,7 +509,7 @@ var init_client = __esm(() => {
506
509
  init_wallet();
507
510
  init_telemetry_attribution();
508
511
  API_URL2 = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
509
- PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? "");
512
+ PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
510
513
  recentLocalSkills2 = new Map;
511
514
  LOCAL_ONLY2 = process.env.UNBROWSE_LOCAL_ONLY === "1";
512
515
  API_TIMEOUT_MS2 = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
@@ -515,8 +518,11 @@ var init_client = __esm(() => {
515
518
 
516
519
  // ../../src/marketplace/index.ts
517
520
  import { nanoid } from "nanoid";
521
+ var TTL_MS, marketplaceCache;
518
522
  var init_marketplace = __esm(() => {
519
523
  init_client();
524
+ TTL_MS = 5 * 60 * 1000;
525
+ marketplaceCache = new Map;
520
526
  });
521
527
 
522
528
  // ../../src/domain.ts
@@ -1321,6 +1327,20 @@ function extractBrowserCookies(domain, opts) {
1321
1327
  }
1322
1328
  const chrome = extractFromChrome(domain, { profile: opts?.chromeProfile });
1323
1329
  chrome.warnings.push(...ff.warnings);
1330
+ if (chrome.cookies.length > 0)
1331
+ return chrome;
1332
+ const sessions = scanAllBrowserSessions(domain);
1333
+ const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
1334
+ if (best) {
1335
+ return {
1336
+ cookies: best.cookies,
1337
+ source: best.source,
1338
+ warnings: [
1339
+ ...chrome.warnings,
1340
+ `Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
1341
+ ]
1342
+ };
1343
+ }
1324
1344
  return chrome;
1325
1345
  }
1326
1346
  function scanAllBrowserSessions(domain) {
@@ -1487,22 +1507,25 @@ class LocalAuthRuntime {
1487
1507
  if (session && session.expires > Date.now()) {
1488
1508
  return { authenticated: true, session_token: session.token, method: "cached" };
1489
1509
  }
1490
- try {
1491
- const cookies = await getStoredAuth(dep.domain);
1492
- if (cookies && cookies.length > 0) {
1493
- log("auth-runtime", `found ${cookies.length} stored cookies for ${dep.domain}`);
1494
- this.setSession(dep.domain, "vault-cookies", 3600000);
1495
- return { authenticated: true, method: "cookies" };
1496
- }
1497
- } catch {}
1498
- try {
1499
- const result = await extractBrowserAuth(dep.domain);
1500
- if (result.success && result.cookies_stored > 0) {
1501
- log("auth-runtime", `extracted ${result.cookies_stored} browser cookies for ${dep.domain}`);
1502
- this.setSession(dep.domain, "browser-cookies", 3600000);
1503
- return { authenticated: true, method: "cookies" };
1504
- }
1505
- } catch {}
1510
+ const skipFallback = process.env.UNBROWSE_DISABLE_AUTH_FALLBACK === "1";
1511
+ if (!skipFallback) {
1512
+ try {
1513
+ const cookies = await getStoredAuth(dep.domain);
1514
+ if (cookies && cookies.length > 0) {
1515
+ log("auth-runtime", `found ${cookies.length} stored cookies for ${dep.domain}`);
1516
+ this.setSession(dep.domain, "vault-cookies", 3600000);
1517
+ return { authenticated: true, method: "cookies" };
1518
+ }
1519
+ } catch {}
1520
+ try {
1521
+ const result = await extractBrowserAuth(dep.domain);
1522
+ if (result.success && result.cookies_stored > 0) {
1523
+ log("auth-runtime", `extracted ${result.cookies_stored} browser cookies for ${dep.domain}`);
1524
+ this.setSession(dep.domain, "browser-cookies", 3600000);
1525
+ return { authenticated: true, method: "cookies" };
1526
+ }
1527
+ } catch {}
1528
+ }
1506
1529
  if (dep.strategy === "refresh_session" && session) {
1507
1530
  const refreshed = await this.refreshSession(dep.domain);
1508
1531
  if (refreshed) {
@@ -1633,6 +1656,102 @@ var init_schema_review = __esm(() => {
1633
1656
  init_sanitize();
1634
1657
  });
1635
1658
 
1659
+ // ../../src/config/contribution.ts
1660
+ import fs2 from "node:fs";
1661
+ import path6 from "node:path";
1662
+ import os3 from "node:os";
1663
+ function configPath() {
1664
+ return process.env.UNBROWSE_CONFIG_PATH || path6.join(os3.homedir(), ".unbrowse", "config.json");
1665
+ }
1666
+ function freshDefault() {
1667
+ return {
1668
+ contribution: { ...DEFAULT.contribution },
1669
+ rev_share: { ...DEFAULT.rev_share },
1670
+ notice_shown_count: 0
1671
+ };
1672
+ }
1673
+ function getContributionConfig() {
1674
+ if (cached)
1675
+ return cached;
1676
+ const p = configPath();
1677
+ let fileExisted = false;
1678
+ let raw = null;
1679
+ try {
1680
+ const content = fs2.readFileSync(p, "utf-8");
1681
+ fileExisted = true;
1682
+ if (content.trim().length > 0) {
1683
+ raw = JSON.parse(content);
1684
+ }
1685
+ } catch {}
1686
+ if (!raw) {
1687
+ cached = freshDefault();
1688
+ return cached;
1689
+ }
1690
+ const contributionRaw = raw.contribution ?? null;
1691
+ const revShareRaw = raw.rev_share ?? null;
1692
+ const isExistingUserMigration = fileExisted && !contributionRaw;
1693
+ cached = {
1694
+ contribution: {
1695
+ share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT.contribution.share_pointers),
1696
+ set_via: contributionRaw?.set_via ?? DEFAULT.contribution.set_via,
1697
+ ...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
1698
+ },
1699
+ rev_share: {
1700
+ opted_in: !!(revShareRaw?.opted_in ?? DEFAULT.rev_share.opted_in),
1701
+ ...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
1702
+ },
1703
+ notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
1704
+ };
1705
+ if (isExistingUserMigration) {
1706
+ try {
1707
+ const merged = { ...raw, ...cached };
1708
+ fs2.mkdirSync(path6.dirname(p), { recursive: true });
1709
+ fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
1710
+ } catch {}
1711
+ }
1712
+ return cached;
1713
+ }
1714
+ function setContributionConfig(updates) {
1715
+ const current = getContributionConfig();
1716
+ const next = {
1717
+ contribution: {
1718
+ ...current.contribution,
1719
+ ...updates.contribution ?? {},
1720
+ set_at: new Date().toISOString()
1721
+ },
1722
+ rev_share: {
1723
+ ...current.rev_share,
1724
+ ...updates.rev_share ?? {}
1725
+ },
1726
+ notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
1727
+ };
1728
+ const p = configPath();
1729
+ let existing = {};
1730
+ try {
1731
+ const content = fs2.readFileSync(p, "utf-8");
1732
+ if (content.trim())
1733
+ existing = JSON.parse(content);
1734
+ } catch {}
1735
+ const merged = { ...existing, ...next };
1736
+ fs2.mkdirSync(path6.dirname(p), { recursive: true });
1737
+ fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
1738
+ cached = next;
1739
+ }
1740
+ function decrementNoticeCounter() {
1741
+ const cfg = getContributionConfig();
1742
+ const next = Math.max(0, (cfg.notice_shown_count ?? 0) - 1);
1743
+ setContributionConfig({ notice_shown_count: next });
1744
+ return next;
1745
+ }
1746
+ var DEFAULT, cached = null;
1747
+ var init_contribution = __esm(() => {
1748
+ DEFAULT = {
1749
+ contribution: { share_pointers: false, set_via: "default" },
1750
+ rev_share: { opted_in: false },
1751
+ notice_shown_count: 0
1752
+ };
1753
+ });
1754
+
1636
1755
  // ../../src/indexer/index.ts
1637
1756
  import { join as join9 } from "node:path";
1638
1757
  var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
@@ -1648,6 +1767,7 @@ var init_indexer = __esm(async () => {
1648
1767
  init_settings();
1649
1768
  init_graph();
1650
1769
  init_schema_review();
1770
+ init_contribution();
1651
1771
  await init_orchestrator();
1652
1772
  SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1653
1773
  indexInFlight = new Map;
@@ -1664,9 +1784,9 @@ var init_payments = __esm(() => {
1664
1784
  });
1665
1785
 
1666
1786
  // ../../src/execution/robots.ts
1667
- var TTL_MS, cache;
1787
+ var TTL_MS2, cache;
1668
1788
  var init_robots = __esm(() => {
1669
- TTL_MS = 24 * 60 * 60 * 1000;
1789
+ TTL_MS2 = 24 * 60 * 60 * 1000;
1670
1790
  cache = new Map;
1671
1791
  });
1672
1792
 
@@ -1728,6 +1848,7 @@ var init_compile = () => {};
1728
1848
  import { nanoid as nanoid6 } from "nanoid";
1729
1849
  var VALID_VERIFICATION_STATUSES, STOPWORDS;
1730
1850
  var init_execution = __esm(async () => {
1851
+ init_client2();
1731
1852
  init_reverse_engineer();
1732
1853
  init_bundle_scanner();
1733
1854
  init_token_resolver();
@@ -1871,11 +1992,22 @@ import { nanoid as nanoid8 } from "nanoid";
1871
1992
  var init_routing_telemetry = __esm(() => {
1872
1993
  init_telemetry();
1873
1994
  });
1995
+
1996
+ // ../../src/orchestrator/resolve-race.ts
1997
+ var init_resolve_race = __esm(async () => {
1998
+ init_probe();
1999
+ init_marketplace();
2000
+ await init_execution();
2001
+ });
1874
2002
  // ../../src/orchestrator/index.ts
1875
2003
  import { nanoid as nanoid9 } from "nanoid";
1876
2004
  import { existsSync as existsSync12, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "node:fs";
1877
2005
  import { dirname as dirname3, join as join10 } from "node:path";
1878
2006
  function _writeRouteCacheToDisk() {
2007
+ if (!LOCAL_CACHES_ENABLED) {
2008
+ _routeCacheDirty = false;
2009
+ return;
2010
+ }
1879
2011
  try {
1880
2012
  const dir = dirname3(ROUTE_CACHE_FILE);
1881
2013
  if (!existsSync12(dir))
@@ -1885,7 +2017,7 @@ function _writeRouteCacheToDisk() {
1885
2017
  } catch {}
1886
2018
  _routeCacheDirty = false;
1887
2019
  }
1888
- var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
2020
+ var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, LOCAL_CACHES_ENABLED, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
1889
2021
  var init_orchestrator = __esm(async () => {
1890
2022
  init_client();
1891
2023
  init_client2();
@@ -1907,7 +2039,8 @@ var init_orchestrator = __esm(async () => {
1907
2039
  init_execution(),
1908
2040
  init_dag_advisor(),
1909
2041
  init_prefetch(),
1910
- init_runtime()
2042
+ init_runtime(),
2043
+ init_resolve_race()
1911
2044
  ]);
1912
2045
  LIVE_CAPTURE_TIMEOUT_MS = Number(process.env.UNBROWSE_LIVE_CAPTURE_TIMEOUT_MS ?? "120000");
1913
2046
  capturedDomainCache = new Map;
@@ -1918,36 +2051,41 @@ var init_orchestrator = __esm(async () => {
1918
2051
  SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1919
2052
  domainSkillCache = new Map;
1920
2053
  DOMAIN_CACHE_FILE = join10(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1921
- try {
1922
- if (existsSync12(DOMAIN_CACHE_FILE)) {
1923
- const data = JSON.parse(readFileSync7(DOMAIN_CACHE_FILE, "utf-8"));
1924
- for (const [k, v] of Object.entries(data)) {
1925
- const entry = v;
1926
- if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
1927
- domainSkillCache.set(k, entry);
2054
+ LOCAL_CACHES_ENABLED = process.env.UNBROWSE_LOCAL_CACHES === "1";
2055
+ if (LOCAL_CACHES_ENABLED) {
2056
+ try {
2057
+ if (existsSync12(DOMAIN_CACHE_FILE)) {
2058
+ const data = JSON.parse(readFileSync7(DOMAIN_CACHE_FILE, "utf-8"));
2059
+ for (const [k, v] of Object.entries(data)) {
2060
+ const entry = v;
2061
+ if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
2062
+ domainSkillCache.set(k, entry);
2063
+ }
1928
2064
  }
2065
+ console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
1929
2066
  }
1930
- console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
1931
- }
1932
- } catch {}
2067
+ } catch {}
2068
+ }
1933
2069
  routeCacheFlushTimer = setInterval(() => {
1934
2070
  if (!_routeCacheDirty)
1935
2071
  return;
1936
2072
  _writeRouteCacheToDisk();
1937
2073
  }, 5000);
1938
2074
  routeCacheFlushTimer.unref?.();
1939
- try {
1940
- if (existsSync12(ROUTE_CACHE_FILE)) {
1941
- const data = JSON.parse(readFileSync7(ROUTE_CACHE_FILE, "utf-8"));
1942
- for (const [k, v] of Object.entries(data)) {
1943
- const entry = v;
1944
- if (Date.now() - entry.ts < 24 * 60 * 60000) {
1945
- skillRouteCache.set(k, entry);
2075
+ if (LOCAL_CACHES_ENABLED) {
2076
+ try {
2077
+ if (existsSync12(ROUTE_CACHE_FILE)) {
2078
+ const data = JSON.parse(readFileSync7(ROUTE_CACHE_FILE, "utf-8"));
2079
+ for (const [k, v] of Object.entries(data)) {
2080
+ const entry = v;
2081
+ if (Date.now() - entry.ts < 24 * 60 * 60000) {
2082
+ skillRouteCache.set(k, entry);
2083
+ }
1946
2084
  }
2085
+ console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
1947
2086
  }
1948
- console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
1949
- }
1950
- } catch {}
2087
+ } catch {}
2088
+ }
1951
2089
  routeResultCache = new Map;
1952
2090
  ROUTE_CACHE_TTL = 24 * 60 * 60000;
1953
2091
  MARKETPLACE_HYDRATE_LIMIT = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_HYDRATE_LIMIT ?? 4));
@@ -2065,9 +2203,11 @@ function getWalletContext2() {
2065
2203
  wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
2066
2204
  };
2067
2205
  }
2068
- const localLobsterWallet = getLobsterWalletFromLocalConfig2();
2069
- if (localLobsterWallet) {
2070
- return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
2206
+ if (process.env.UNBROWSE_DISABLE_LOCAL_WALLET !== "1") {
2207
+ const localLobsterWallet = getLobsterWalletFromLocalConfig2();
2208
+ if (localLobsterWallet) {
2209
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
2210
+ }
2071
2211
  }
2072
2212
  return {};
2073
2213
  }
@@ -2082,6 +2222,100 @@ function checkWalletConfigured2() {
2082
2222
  }
2083
2223
  var init_wallet2 = () => {};
2084
2224
 
2225
+ // ../../src/telemetry-attribution.ts
2226
+ var exports_telemetry_attribution = {};
2227
+ __export(exports_telemetry_attribution, {
2228
+ sanitizeTelemetryAttribution: () => sanitizeTelemetryAttribution2,
2229
+ sanitizeAttributionValue: () => sanitizeAttributionValue2,
2230
+ mergeTelemetryProperties: () => mergeTelemetryProperties2,
2231
+ mergeTelemetryAttribution: () => mergeTelemetryAttribution2,
2232
+ hasTelemetryAttribution: () => hasTelemetryAttribution2,
2233
+ decodeTelemetryAttribution: () => decodeTelemetryAttribution2,
2234
+ TELEMETRY_ATTRIBUTION_KEYS: () => TELEMETRY_ATTRIBUTION_KEYS2
2235
+ });
2236
+ function sanitizeAttributionValue2(value) {
2237
+ if (typeof value !== "string")
2238
+ return;
2239
+ const trimmed = value.trim();
2240
+ if (!trimmed)
2241
+ return;
2242
+ return trimmed.slice(0, MAX_ATTRIBUTION_VALUE_LENGTH2);
2243
+ }
2244
+ function hasTelemetryAttribution2(value) {
2245
+ if (!value)
2246
+ return false;
2247
+ return TELEMETRY_ATTRIBUTION_KEYS2.some((key) => Boolean(sanitizeAttributionValue2(value[key])));
2248
+ }
2249
+ function sanitizeTelemetryAttribution2(raw) {
2250
+ if (!raw)
2251
+ return;
2252
+ const cleaned = {};
2253
+ for (const key of TELEMETRY_ATTRIBUTION_KEYS2) {
2254
+ const value = sanitizeAttributionValue2(raw[key]);
2255
+ if (value)
2256
+ cleaned[key] = value;
2257
+ }
2258
+ return hasTelemetryAttribution2(cleaned) ? cleaned : undefined;
2259
+ }
2260
+ function mergeTelemetryAttribution2(base, incoming) {
2261
+ const merged = sanitizeTelemetryAttribution2({
2262
+ ...base ?? {},
2263
+ ...incoming ?? {}
2264
+ });
2265
+ return merged;
2266
+ }
2267
+ function mergeTelemetryProperties2(properties, attribution) {
2268
+ const cleanedAttribution = sanitizeTelemetryAttribution2(attribution);
2269
+ if (!cleanedAttribution)
2270
+ return properties;
2271
+ return {
2272
+ ...cleanedAttribution,
2273
+ ...properties ?? {}
2274
+ };
2275
+ }
2276
+ function decodeTelemetryAttribution2(value) {
2277
+ if (!value)
2278
+ return;
2279
+ try {
2280
+ const decoded = Buffer.from(value, "base64").toString("utf8");
2281
+ return sanitizeTelemetryAttribution2(JSON.parse(decoded));
2282
+ } catch {
2283
+ return;
2284
+ }
2285
+ }
2286
+ var TELEMETRY_ATTRIBUTION_KEYS2, MAX_ATTRIBUTION_VALUE_LENGTH2 = 160;
2287
+ var init_telemetry_attribution2 = __esm(() => {
2288
+ TELEMETRY_ATTRIBUTION_KEYS2 = [
2289
+ "utm_source",
2290
+ "utm_medium",
2291
+ "utm_campaign",
2292
+ "utm_content",
2293
+ "utm_term",
2294
+ "utm_id",
2295
+ "gclid",
2296
+ "wbraid",
2297
+ "gbraid",
2298
+ "fbclid",
2299
+ "twclid",
2300
+ "ttclid",
2301
+ "msclkid",
2302
+ "li_fat_id",
2303
+ "referrer_host",
2304
+ "channel",
2305
+ "campaign_id",
2306
+ "campaign_name",
2307
+ "content_id",
2308
+ "content_type",
2309
+ "creative_id",
2310
+ "ad_id",
2311
+ "adset_id",
2312
+ "inferred_icp",
2313
+ "variant_id",
2314
+ "experiment_id",
2315
+ "icp"
2316
+ ];
2317
+ });
2318
+
2085
2319
  // ../../src/version.ts
2086
2320
  var exports_version = {};
2087
2321
  __export(exports_version, {
@@ -2094,6 +2328,7 @@ __export(exports_version, {
2094
2328
  RELEASE_MANIFEST_BASE64: () => RELEASE_MANIFEST_BASE642,
2095
2329
  PACKAGE_VERSION: () => PACKAGE_VERSION2,
2096
2330
  GIT_SHA: () => GIT_SHA2,
2331
+ DEFAULT_PROFILE: () => DEFAULT_PROFILE2,
2097
2332
  DEFAULT_BACKEND_URL: () => DEFAULT_BACKEND_URL2,
2098
2333
  CODE_HASH: () => CODE_HASH2
2099
2334
  });
@@ -2195,13 +2430,14 @@ function getPackageVersion2() {
2195
2430
  return packageVersion;
2196
2431
  return getEmbeddedReleaseVersion2() ?? "unknown";
2197
2432
  }
2198
- var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
2433
+ var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, DEFAULT_PROFILE2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
2199
2434
  var init_version2 = __esm(() => {
2200
2435
  MODULE_DIR2 = dirname4(fileURLToPath4(import.meta.url));
2201
2436
  CODE_HASH2 = BUILD_CODE_HASH?.trim() || computeCodeHash2();
2202
2437
  GIT_SHA2 = getGitSha2();
2203
2438
  PACKAGE_VERSION2 = getPackageVersion2();
2204
2439
  DEFAULT_BACKEND_URL2 = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
2440
+ DEFAULT_PROFILE2 = BUILD_DEFAULT_PROFILE?.trim() || "";
2205
2441
  TRACE_VERSION2 = `${CODE_HASH2}@${GIT_SHA2}`;
2206
2442
  RELEASE_MANIFEST_BASE642 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
2207
2443
  RELEASE_MANIFEST_SIGNATURE2 = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
@@ -2291,9 +2527,9 @@ function getChromiumKeychainServiceName2(opts) {
2291
2527
  }
2292
2528
  function getChromiumDecryptionKey2(opts) {
2293
2529
  const service = getChromiumKeychainServiceName2(opts);
2294
- const cached = _chromiumKeyCache2.get(service);
2295
- if (cached)
2296
- return cached;
2530
+ const cached3 = _chromiumKeyCache2.get(service);
2531
+ if (cached3)
2532
+ return cached3;
2297
2533
  if (platform2() !== "darwin")
2298
2534
  return null;
2299
2535
  try {
@@ -2512,6 +2748,20 @@ function extractBrowserCookies2(domain, opts) {
2512
2748
  }
2513
2749
  const chrome = extractFromChrome2(domain, { profile: opts?.chromeProfile });
2514
2750
  chrome.warnings.push(...ff.warnings);
2751
+ if (chrome.cookies.length > 0)
2752
+ return chrome;
2753
+ const sessions = scanAllBrowserSessions2(domain);
2754
+ const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
2755
+ if (best) {
2756
+ return {
2757
+ cookies: best.cookies,
2758
+ source: best.source,
2759
+ warnings: [
2760
+ ...chrome.warnings,
2761
+ `Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
2762
+ ]
2763
+ };
2764
+ }
2515
2765
  return chrome;
2516
2766
  }
2517
2767
  function scanAllBrowserSessions2(domain) {
@@ -2589,7 +2839,7 @@ import { randomBytes, createHash as createHash2 } from "crypto";
2589
2839
  import { createInterface } from "readline";
2590
2840
  import { execSync } from "child_process";
2591
2841
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
2592
- var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
2842
+ var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
2593
2843
  var recentLocalSkills = new Map;
2594
2844
  var LOCAL_ONLY = process.env.UNBROWSE_LOCAL_ONLY === "1";
2595
2845
  function buildReleaseAttestationHeaders(manifestBase64, signature) {
@@ -3187,9 +3437,99 @@ You have $2.00 in free credits — start resolving to use them.`);
3187
3437
  process.exit(1);
3188
3438
  }
3189
3439
  }
3440
+ async function magicRegister(opts) {
3441
+ const timeoutMs = opts.timeoutMs ?? 300000;
3442
+ const pollMs = opts.pollMs ?? 1500;
3443
+ const startRes = await fetch(`${API_URL}/v1/auth/email/start`, {
3444
+ method: "POST",
3445
+ headers: {
3446
+ "Content-Type": "application/json",
3447
+ "Accept-Encoding": "gzip, deflate"
3448
+ },
3449
+ body: JSON.stringify({ email: opts.email, return_url: opts.returnUrl })
3450
+ });
3451
+ if (startRes.status === 503) {
3452
+ throw new Error("Backend RESEND_API_KEY not set — magic-link signup unavailable. Use anon `unbrowse register` (no --email).");
3453
+ }
3454
+ let startData;
3455
+ try {
3456
+ startData = await startRes.json();
3457
+ } catch {
3458
+ throw new Error(`Magic-link start failed: HTTP ${startRes.status}`);
3459
+ }
3460
+ if (startRes.status === 400) {
3461
+ throw new Error(startData.error ?? "invalid_email");
3462
+ }
3463
+ if (!startRes.ok || !startData.token) {
3464
+ const msg = startData.error ?? `HTTP ${startRes.status}`;
3465
+ throw new Error(`Magic-link start failed: ${msg}`);
3466
+ }
3467
+ const token = startData.token;
3468
+ const verifyUrl = `${API_URL}/v1/auth/email/verify?token=${encodeURIComponent(token)}`;
3469
+ if (opts.openBrowser) {
3470
+ try {
3471
+ await opts.openBrowser(verifyUrl);
3472
+ } catch {}
3473
+ } else {
3474
+ try {
3475
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
3476
+ execSync(`${cmd} ${JSON.stringify(verifyUrl)}`, { stdio: "ignore", timeout: 5000 });
3477
+ } catch {}
3478
+ }
3479
+ const deadline = Date.now() + timeoutMs;
3480
+ while (Date.now() < deadline) {
3481
+ await new Promise((r) => setTimeout(r, pollMs));
3482
+ const pollRes = await fetch(`${API_URL}/v1/auth/email/poll?token=${encodeURIComponent(token)}`, {
3483
+ method: "GET",
3484
+ headers: { "Accept-Encoding": "gzip, deflate" }
3485
+ });
3486
+ let pollData;
3487
+ try {
3488
+ pollData = await pollRes.json();
3489
+ } catch {
3490
+ throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
3491
+ }
3492
+ if (pollData.status === "verified") {
3493
+ if (!pollData.api_key || !pollData.user_id || !pollData.email) {
3494
+ throw new Error("Magic-link poll returned verified without api_key/user_id/email.");
3495
+ }
3496
+ return {
3497
+ api_key: pollData.api_key,
3498
+ agent_id: pollData.user_id,
3499
+ email: pollData.email,
3500
+ user_id: pollData.user_id
3501
+ };
3502
+ }
3503
+ if (pollData.status === "expired" || pollRes.status === 410) {
3504
+ throw new Error("Magic link expired. Re-run `unbrowse register --email …`.");
3505
+ }
3506
+ if (pollData.status === "pending")
3507
+ continue;
3508
+ if (!pollRes.ok) {
3509
+ throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
3510
+ }
3511
+ }
3512
+ throw new Error(`Magic-link timed out after ${Math.round(timeoutMs / 1000)}s. Check your inbox and re-run.`);
3513
+ }
3190
3514
  async function getMyProfile() {
3191
3515
  return api("GET", "/v1/agents/me", undefined);
3192
3516
  }
3517
+ async function fetchAccountPreferences() {
3518
+ try {
3519
+ const data = await api("GET", "/v1/account/preferences", undefined);
3520
+ return { share_pointers: !!data?.share_pointers };
3521
+ } catch (err) {
3522
+ const msg = err.message ?? "";
3523
+ if (msg.includes("account_required") || msg.includes("HTTP 403") || msg.includes("HTTP 404")) {
3524
+ return null;
3525
+ }
3526
+ throw err;
3527
+ }
3528
+ }
3529
+ async function pushAccountPreferences(patch) {
3530
+ const data = await api("PATCH", "/v1/account/preferences", patch);
3531
+ return { share_pointers: !!data?.share_pointers };
3532
+ }
3193
3533
  async function syncAgentWallet(wallet = getLocalWalletContext()) {
3194
3534
  if (!wallet.wallet_address)
3195
3535
  return;
@@ -3794,7 +4134,7 @@ function isBundledVirtualEntrypoint(entrypoint) {
3794
4134
  }
3795
4135
  function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
3796
4136
  if (path3.extname(entrypoint) !== ".ts") {
3797
- return [pathToFileURL2(entrypoint).href];
4137
+ return process.platform === "win32" ? [pathToFileURL2(entrypoint).href] : [entrypoint];
3798
4138
  }
3799
4139
  if (process.versions.bun)
3800
4140
  return [entrypoint];
@@ -3837,6 +4177,7 @@ init_publish();
3837
4177
  init_settings();
3838
4178
  init_graph();
3839
4179
  init_schema_review();
4180
+ init_contribution();
3840
4181
  import { join as join11 } from "node:path";
3841
4182
  var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join11(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
3842
4183
  var indexInFlight2 = new Map;
@@ -3877,23 +4218,23 @@ init_logger();
3877
4218
  init_wallet();
3878
4219
  import { execFileSync as execFileSync4 } from "node:child_process";
3879
4220
  import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "node:fs";
3880
- import os4 from "node:os";
3881
- import path7 from "node:path";
4221
+ import os5 from "node:os";
4222
+ import path8 from "node:path";
3882
4223
 
3883
4224
  // ../../src/runtime/update-hints.ts
3884
4225
  init_paths();
3885
4226
  import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
3886
- import os3 from "node:os";
3887
- import path6 from "node:path";
4227
+ import os4 from "node:os";
4228
+ import path7 from "node:path";
3888
4229
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
3889
4230
  var CODEX_MARKER = "# Unbrowse update hints — managed by unbrowse setup";
3890
4231
  function getHomeDir() {
3891
- return process.env.HOME || os3.homedir();
4232
+ return process.env.HOME || os4.homedir();
3892
4233
  }
3893
4234
  function getConfigDir2() {
3894
4235
  if (process.env.UNBROWSE_CONFIG_DIR)
3895
4236
  return process.env.UNBROWSE_CONFIG_DIR;
3896
- return path6.join(getHomeDir(), ".unbrowse");
4237
+ return path7.join(getHomeDir(), ".unbrowse");
3897
4238
  }
3898
4239
  function ensureDir3(dir) {
3899
4240
  if (!existsSync13(dir))
@@ -3908,20 +4249,20 @@ function readJsonFile(file) {
3908
4249
  }
3909
4250
  }
3910
4251
  function writeJsonFile(file, value) {
3911
- ensureDir3(path6.dirname(file));
4252
+ ensureDir3(path7.dirname(file));
3912
4253
  writeFileSync5(file, `${JSON.stringify(value, null, 2)}
3913
4254
  `);
3914
4255
  }
3915
4256
  function getInstallSourcePath() {
3916
- return path6.join(getConfigDir2(), "install-source.json");
4257
+ return path7.join(getConfigDir2(), "install-source.json");
3917
4258
  }
3918
4259
  function detectRepoRoot(start2) {
3919
- let dir = path6.resolve(start2);
3920
- const root = path6.parse(dir).root;
4260
+ let dir = path7.resolve(start2);
4261
+ const root = path7.parse(dir).root;
3921
4262
  while (dir !== root) {
3922
- if (existsSync13(path6.join(dir, ".git")))
4263
+ if (existsSync13(path7.join(dir, ".git")))
3923
4264
  return dir;
3924
- dir = path6.dirname(dir);
4265
+ dir = path7.dirname(dir);
3925
4266
  }
3926
4267
  return;
3927
4268
  }
@@ -3930,7 +4271,7 @@ function detectInstallMethod(packageRoot) {
3930
4271
  return "repo-clone";
3931
4272
  if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
3932
4273
  return "npm-global";
3933
- if (packageRoot.includes(`${path6.sep}node_modules${path6.sep}`))
4274
+ if (packageRoot.includes(`${path7.sep}node_modules${path7.sep}`))
3934
4275
  return "npm-global";
3935
4276
  return detectRepoRoot(packageRoot) ? "repo-clone" : "unknown";
3936
4277
  }
@@ -3940,12 +4281,12 @@ function detectInstallHost(repoRoot) {
3940
4281
  return explicit;
3941
4282
  if (!repoRoot)
3942
4283
  return "unknown";
3943
- const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
3944
- if (repoRoot === path6.join(codexHome, "skills", "unbrowse"))
4284
+ const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
4285
+ if (repoRoot === path7.join(codexHome, "skills", "unbrowse"))
3945
4286
  return "codex";
3946
- if (repoRoot === path6.join(getHomeDir(), ".claude", "skills", "unbrowse"))
4287
+ if (repoRoot === path7.join(getHomeDir(), ".claude", "skills", "unbrowse"))
3947
4288
  return "claude";
3948
- if (repoRoot === path6.join(getHomeDir(), "unbrowse"))
4289
+ if (repoRoot === path7.join(getHomeDir(), "unbrowse"))
3949
4290
  return "off";
3950
4291
  return "unknown";
3951
4292
  }
@@ -3973,14 +4314,14 @@ function commandIncludesHook(command, marker) {
3973
4314
  return typeof command === "string" && command.includes(marker);
3974
4315
  }
3975
4316
  function getCodexConfigPath() {
3976
- const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
3977
- return path6.join(codexHome, "config.toml");
4317
+ const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
4318
+ return path7.join(codexHome, "config.toml");
3978
4319
  }
3979
4320
  function getClaudeSettingsPath() {
3980
- return path6.join(getHomeDir(), ".claude", "settings.json");
4321
+ return path7.join(getHomeDir(), ".claude", "settings.json");
3981
4322
  }
3982
4323
  function getHookScriptPath(metaUrl) {
3983
- return path6.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
4324
+ return path7.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
3984
4325
  }
3985
4326
  function ensureCodexHooksFeature(content) {
3986
4327
  if (/\bcodex_hooks\s*=\s*true\b/.test(content))
@@ -3997,14 +4338,14 @@ codex_hooks = true
3997
4338
  `;
3998
4339
  }
3999
4340
  function writeCodexHook(metaUrl) {
4000
- const configPath = getCodexConfigPath();
4001
- if (!existsSync13(path6.dirname(configPath))) {
4002
- return { host: "codex", action: "not-detected", config_file: configPath };
4341
+ const configPath2 = getCodexConfigPath();
4342
+ if (!existsSync13(path7.dirname(configPath2))) {
4343
+ return { host: "codex", action: "not-detected", config_file: configPath2 };
4003
4344
  }
4004
4345
  try {
4005
4346
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
4006
- const fileExistsBefore = existsSync13(configPath);
4007
- let content = fileExistsBefore ? readFileSync8(configPath, "utf8") : "";
4347
+ const fileExistsBefore = existsSync13(configPath2);
4348
+ let content = fileExistsBefore ? readFileSync8(configPath2, "utf8") : "";
4008
4349
  const previous = content;
4009
4350
  content = ensureCodexHooksFeature(content);
4010
4351
  if (!content.includes("unbrowse-update-hint.mjs")) {
@@ -4019,26 +4360,26 @@ command = ${JSON.stringify(command)}
4019
4360
  `;
4020
4361
  }
4021
4362
  if (content !== previous) {
4022
- writeFileSync5(configPath, content, "utf8");
4363
+ writeFileSync5(configPath2, content, "utf8");
4023
4364
  return {
4024
4365
  host: "codex",
4025
4366
  action: fileExistsBefore ? "updated" : "installed",
4026
- config_file: configPath
4367
+ config_file: configPath2
4027
4368
  };
4028
4369
  }
4029
- return { host: "codex", action: "already-installed", config_file: configPath };
4370
+ return { host: "codex", action: "already-installed", config_file: configPath2 };
4030
4371
  } catch (error) {
4031
4372
  return {
4032
4373
  host: "codex",
4033
4374
  action: "failed",
4034
- config_file: configPath,
4375
+ config_file: configPath2,
4035
4376
  message: error instanceof Error ? error.message : String(error)
4036
4377
  };
4037
4378
  }
4038
4379
  }
4039
4380
  function writeClaudeHook(metaUrl) {
4040
4381
  const settingsPath = getClaudeSettingsPath();
4041
- if (!existsSync13(path6.dirname(settingsPath))) {
4382
+ if (!existsSync13(path7.dirname(settingsPath))) {
4042
4383
  return { host: "claude", action: "not-detected", config_file: settingsPath };
4043
4384
  }
4044
4385
  try {
@@ -4098,18 +4439,18 @@ function detectPackageManagers() {
4098
4439
  }
4099
4440
  function resolveConfigHome() {
4100
4441
  if (process.platform === "win32") {
4101
- return process.env.APPDATA || path7.join(os4.homedir(), "AppData", "Roaming");
4442
+ return process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming");
4102
4443
  }
4103
- return process.env.XDG_CONFIG_HOME || path7.join(os4.homedir(), ".config");
4444
+ return process.env.XDG_CONFIG_HOME || path8.join(os5.homedir(), ".config");
4104
4445
  }
4105
4446
  function getOpenCodeGlobalCommandsDir() {
4106
- return path7.join(resolveConfigHome(), "opencode", "commands");
4447
+ return path8.join(resolveConfigHome(), "opencode", "commands");
4107
4448
  }
4108
4449
  function getOpenCodeProjectCommandsDir(cwd) {
4109
- return path7.join(cwd, ".opencode", "commands");
4450
+ return path8.join(cwd, ".opencode", "commands");
4110
4451
  }
4111
4452
  function detectOpenCode(cwd) {
4112
- return hasBinary("opencode") || existsSync14(path7.join(resolveConfigHome(), "opencode")) || existsSync14(path7.join(cwd, ".opencode"));
4453
+ return hasBinary("opencode") || existsSync14(path8.join(resolveConfigHome(), "opencode")) || existsSync14(path8.join(cwd, ".opencode"));
4113
4454
  }
4114
4455
  function renderOpenCodeCommand() {
4115
4456
  return `---
@@ -4137,12 +4478,12 @@ function writeOpenCodeCommand(scope, cwd) {
4137
4478
  if (scope === "auto" && !detected) {
4138
4479
  return { detected: false, action: "not-detected", scope: "off" };
4139
4480
  }
4140
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path7.join(cwd, ".opencode")) ? "project" : "global";
4481
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path8.join(cwd, ".opencode")) ? "project" : "global";
4141
4482
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
4142
- const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
4483
+ const commandFile = path8.join(ensureDir2(commandsDir), "unbrowse.md");
4143
4484
  const content = renderOpenCodeCommand();
4144
4485
  const action2 = existsSync14(commandFile) ? "updated" : "installed";
4145
- mkdirSync8(path7.dirname(commandFile), { recursive: true });
4486
+ mkdirSync8(path8.dirname(commandFile), { recursive: true });
4146
4487
  writeFileSync6(commandFile, content);
4147
4488
  return {
4148
4489
  detected: detected || scope !== "auto",
@@ -4156,7 +4497,7 @@ async function ensureBrowserEngineInstalled() {
4156
4497
  if (existsSync14(binary)) {
4157
4498
  return { installed: true, action: "already-installed" };
4158
4499
  }
4159
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path7.join(candidate, "build.zig")));
4500
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path8.join(candidate, "build.zig")));
4160
4501
  if (!sourceDir) {
4161
4502
  return {
4162
4503
  installed: false,
@@ -4203,7 +4544,7 @@ async function runSetup(options) {
4203
4544
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
4204
4545
  const walletCheck = checkWalletConfigured();
4205
4546
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
4206
- let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
4547
+ let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os5.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
4207
4548
  if (!skipWalletSetup && !walletCheck.configured) {
4208
4549
  if (!lobsterInstalled) {
4209
4550
  console.log("[unbrowse] Setting up Crossmint wallet (required for earning + payments)...");
@@ -4243,7 +4584,7 @@ async function runSetup(options) {
4243
4584
  return {
4244
4585
  os: {
4245
4586
  platform: process.platform,
4246
- release: os4.release(),
4587
+ release: os5.release(),
4247
4588
  arch: process.arch
4248
4589
  },
4249
4590
  host_environment: hostEnv,
@@ -4258,17 +4599,17 @@ async function runSetup(options) {
4258
4599
  // ../../src/runtime/update-hints.ts
4259
4600
  init_paths();
4260
4601
  import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
4261
- import os5 from "node:os";
4262
- import path8 from "node:path";
4602
+ import os6 from "node:os";
4603
+ import path9 from "node:path";
4263
4604
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
4264
4605
  var DEFAULT_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
4265
4606
  function getHomeDir2() {
4266
- return process.env.HOME || os5.homedir();
4607
+ return process.env.HOME || os6.homedir();
4267
4608
  }
4268
4609
  function getConfigDir3() {
4269
4610
  if (process.env.UNBROWSE_CONFIG_DIR)
4270
4611
  return process.env.UNBROWSE_CONFIG_DIR;
4271
- return path8.join(getHomeDir2(), ".unbrowse");
4612
+ return path9.join(getHomeDir2(), ".unbrowse");
4272
4613
  }
4273
4614
  function ensureDir4(dir) {
4274
4615
  if (!existsSync15(dir))
@@ -4283,23 +4624,23 @@ function readJsonFile2(file) {
4283
4624
  }
4284
4625
  }
4285
4626
  function writeJsonFile2(file, value) {
4286
- ensureDir4(path8.dirname(file));
4627
+ ensureDir4(path9.dirname(file));
4287
4628
  writeFileSync7(file, `${JSON.stringify(value, null, 2)}
4288
4629
  `);
4289
4630
  }
4290
4631
  function getInstallSourcePath2() {
4291
- return path8.join(getConfigDir3(), "install-source.json");
4632
+ return path9.join(getConfigDir3(), "install-source.json");
4292
4633
  }
4293
4634
  function getUpdateCheckStatePath() {
4294
- return path8.join(getConfigDir3(), "update-check.json");
4635
+ return path9.join(getConfigDir3(), "update-check.json");
4295
4636
  }
4296
4637
  function detectRepoRoot2(start2) {
4297
- let dir = path8.resolve(start2);
4298
- const root = path8.parse(dir).root;
4638
+ let dir = path9.resolve(start2);
4639
+ const root = path9.parse(dir).root;
4299
4640
  while (dir !== root) {
4300
- if (existsSync15(path8.join(dir, ".git")))
4641
+ if (existsSync15(path9.join(dir, ".git")))
4301
4642
  return dir;
4302
- dir = path8.dirname(dir);
4643
+ dir = path9.dirname(dir);
4303
4644
  }
4304
4645
  return;
4305
4646
  }
@@ -4308,7 +4649,7 @@ function detectInstallMethod2(packageRoot) {
4308
4649
  return "repo-clone";
4309
4650
  if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
4310
4651
  return "npm-global";
4311
- if (packageRoot.includes(`${path8.sep}node_modules${path8.sep}`))
4652
+ if (packageRoot.includes(`${path9.sep}node_modules${path9.sep}`))
4312
4653
  return "npm-global";
4313
4654
  return detectRepoRoot2(packageRoot) ? "repo-clone" : "unknown";
4314
4655
  }
@@ -4318,19 +4659,19 @@ function detectInstallHost2(repoRoot) {
4318
4659
  return explicit;
4319
4660
  if (!repoRoot)
4320
4661
  return "unknown";
4321
- const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir2(), ".codex");
4322
- if (repoRoot === path8.join(codexHome, "skills", "unbrowse"))
4662
+ const codexHome = process.env.CODEX_HOME || path9.join(getHomeDir2(), ".codex");
4663
+ if (repoRoot === path9.join(codexHome, "skills", "unbrowse"))
4323
4664
  return "codex";
4324
- if (repoRoot === path8.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
4665
+ if (repoRoot === path9.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
4325
4666
  return "claude";
4326
- if (repoRoot === path8.join(getHomeDir2(), "unbrowse"))
4667
+ if (repoRoot === path9.join(getHomeDir2(), "unbrowse"))
4327
4668
  return "off";
4328
4669
  return "unknown";
4329
4670
  }
4330
4671
  function getInstalledVersion(metaUrl) {
4331
4672
  const packageRoot = getPackageRoot(metaUrl);
4332
4673
  try {
4333
- const pkg = JSON.parse(readFileSync9(path8.join(packageRoot, "package.json"), "utf8"));
4674
+ const pkg = JSON.parse(readFileSync9(path9.join(packageRoot, "package.json"), "utf8"));
4334
4675
  return pkg.version ?? "unknown";
4335
4676
  } catch {
4336
4677
  return "unknown";
@@ -4426,6 +4767,169 @@ function recordUpdateHint(latestVersion) {
4426
4767
  });
4427
4768
  }
4428
4769
 
4770
+ // ../../src/cli-setup.ts
4771
+ init_contribution();
4772
+ import { createInterface as createInterface2 } from "node:readline";
4773
+ async function defaultReadChoice(allowed, dflt) {
4774
+ const isInteractive = !!process.stdin.isTTY && !!process.stdout.isTTY && process.env.UNBROWSE_NON_INTERACTIVE !== "1";
4775
+ if (!isInteractive)
4776
+ return dflt;
4777
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
4778
+ try {
4779
+ for (;; ) {
4780
+ const answer = await new Promise((resolve) => {
4781
+ rl.question(`Choice [${dflt}]: `, resolve);
4782
+ });
4783
+ const trimmed = answer.trim();
4784
+ if (!trimmed)
4785
+ return dflt;
4786
+ const n = parseInt(trimmed, 10);
4787
+ if (allowed.includes(n))
4788
+ return n;
4789
+ console.log(`Please enter ${allowed.join(", ")} (or press Enter for ${dflt}).`);
4790
+ }
4791
+ } finally {
4792
+ rl.close();
4793
+ }
4794
+ }
4795
+ async function promptContributionMode(opts = {}) {
4796
+ const log2 = opts.log ?? ((msg) => console.log(msg));
4797
+ const cfg = getContributionConfig();
4798
+ if (!opts.force && cfg.contribution.set_via && cfg.contribution.set_via !== "default") {
4799
+ return null;
4800
+ }
4801
+ log2("");
4802
+ log2("How do you want unbrowse to handle the routes you discover?");
4803
+ log2("");
4804
+ log2(" [1] Private (default) — cache locally, never publish");
4805
+ log2(" [2] Share — contribute pointers to the marketplace");
4806
+ log2(" [3] Share + earn — contribute and add a wallet for x402 rev-share");
4807
+ log2("");
4808
+ const reader = opts.readChoice ?? defaultReadChoice;
4809
+ const choice = await reader([1, 2, 3], 1);
4810
+ const set_via = opts.force ? "mode-command" : "setup-prompt";
4811
+ if (choice === 1) {
4812
+ setContributionConfig({
4813
+ contribution: { share_pointers: false, set_via },
4814
+ rev_share: { opted_in: false }
4815
+ });
4816
+ log2("Private mode set. Run `unbrowse mode` anytime to change.");
4817
+ } else if (choice === 2) {
4818
+ setContributionConfig({
4819
+ contribution: { share_pointers: true, set_via },
4820
+ rev_share: { opted_in: false }
4821
+ });
4822
+ log2("Sharing pointers enabled. Add a wallet via `unbrowse mode` to opt into rev-share.");
4823
+ } else {
4824
+ setContributionConfig({
4825
+ contribution: { share_pointers: true, set_via },
4826
+ rev_share: { opted_in: true }
4827
+ });
4828
+ log2("Sharing + rev-share enabled. Run `unbrowse wallet` to add your wallet for payouts.");
4829
+ }
4830
+ return choice;
4831
+ }
4832
+ function maybeShowContributionNotice() {
4833
+ const cfg = getContributionConfig();
4834
+ if (cfg.contribution.set_via !== "default")
4835
+ return false;
4836
+ const remaining = cfg.notice_shown_count ?? 0;
4837
+ if (remaining <= 0)
4838
+ return false;
4839
+ process.stderr.write("[unbrowse] contribution mode is now `private` by default — captures stay on your machine.\n" + `[unbrowse] run \`unbrowse mode\` to opt into sharing + rev-share. (notice will repeat ${remaining - 1} more time${remaining - 1 === 1 ? "" : "s"})
4840
+ `);
4841
+ decrementNoticeCounter();
4842
+ return true;
4843
+ }
4844
+
4845
+ // ../../src/config/contribution.ts
4846
+ import fs3 from "node:fs";
4847
+ import path10 from "node:path";
4848
+ import os7 from "node:os";
4849
+ function configPath2() {
4850
+ return process.env.UNBROWSE_CONFIG_PATH || path10.join(os7.homedir(), ".unbrowse", "config.json");
4851
+ }
4852
+ var DEFAULT2 = {
4853
+ contribution: { share_pointers: false, set_via: "default" },
4854
+ rev_share: { opted_in: false },
4855
+ notice_shown_count: 0
4856
+ };
4857
+ function freshDefault2() {
4858
+ return {
4859
+ contribution: { ...DEFAULT2.contribution },
4860
+ rev_share: { ...DEFAULT2.rev_share },
4861
+ notice_shown_count: 0
4862
+ };
4863
+ }
4864
+ var cached2 = null;
4865
+ function getContributionConfig2() {
4866
+ if (cached2)
4867
+ return cached2;
4868
+ const p = configPath2();
4869
+ let fileExisted = false;
4870
+ let raw = null;
4871
+ try {
4872
+ const content = fs3.readFileSync(p, "utf-8");
4873
+ fileExisted = true;
4874
+ if (content.trim().length > 0) {
4875
+ raw = JSON.parse(content);
4876
+ }
4877
+ } catch {}
4878
+ if (!raw) {
4879
+ cached2 = freshDefault2();
4880
+ return cached2;
4881
+ }
4882
+ const contributionRaw = raw.contribution ?? null;
4883
+ const revShareRaw = raw.rev_share ?? null;
4884
+ const isExistingUserMigration = fileExisted && !contributionRaw;
4885
+ cached2 = {
4886
+ contribution: {
4887
+ share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT2.contribution.share_pointers),
4888
+ set_via: contributionRaw?.set_via ?? DEFAULT2.contribution.set_via,
4889
+ ...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
4890
+ },
4891
+ rev_share: {
4892
+ opted_in: !!(revShareRaw?.opted_in ?? DEFAULT2.rev_share.opted_in),
4893
+ ...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
4894
+ },
4895
+ notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
4896
+ };
4897
+ if (isExistingUserMigration) {
4898
+ try {
4899
+ const merged = { ...raw, ...cached2 };
4900
+ fs3.mkdirSync(path10.dirname(p), { recursive: true });
4901
+ fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
4902
+ } catch {}
4903
+ }
4904
+ return cached2;
4905
+ }
4906
+ function setContributionConfig2(updates) {
4907
+ const current = getContributionConfig2();
4908
+ const next = {
4909
+ contribution: {
4910
+ ...current.contribution,
4911
+ ...updates.contribution ?? {},
4912
+ set_at: new Date().toISOString()
4913
+ },
4914
+ rev_share: {
4915
+ ...current.rev_share,
4916
+ ...updates.rev_share ?? {}
4917
+ },
4918
+ notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
4919
+ };
4920
+ const p = configPath2();
4921
+ let existing = {};
4922
+ try {
4923
+ const content = fs3.readFileSync(p, "utf-8");
4924
+ if (content.trim())
4925
+ existing = JSON.parse(content);
4926
+ } catch {}
4927
+ const merged = { ...existing, ...next };
4928
+ fs3.mkdirSync(path10.dirname(p), { recursive: true });
4929
+ fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
4930
+ cached2 = next;
4931
+ }
4932
+
4429
4933
  // ../../src/cli.ts
4430
4934
  loadEnv({ quiet: true });
4431
4935
  loadEnv({ path: ".env.runtime", quiet: true });
@@ -4457,8 +4961,12 @@ function parseArgs(argv) {
4457
4961
  if (valueExpectedFlags.has(key)) {
4458
4962
  if (next === undefined)
4459
4963
  die(`--${key} requires a value`);
4460
- flags[key] = next;
4461
- i++;
4964
+ if (next === "-p" || next === "--param" || next.startsWith("--")) {
4965
+ flags[key] = true;
4966
+ } else {
4967
+ flags[key] = next;
4968
+ i++;
4969
+ }
4462
4970
  } else if (!next || next.startsWith("--") || next === "-p" || next === "--param") {
4463
4971
  flags[key] = true;
4464
4972
  } else {
@@ -4471,8 +4979,8 @@ function parseArgs(argv) {
4471
4979
  }
4472
4980
  return { command, args: positional, flags, params };
4473
4981
  }
4474
- async function api2(method, path9, body) {
4475
- let target = `${BASE_URL}${path9}`;
4982
+ async function api2(method, path11, body, opts) {
4983
+ let target = `${BASE_URL}${path11}`;
4476
4984
  let requestBody = body;
4477
4985
  if (method === "GET" && body && typeof body === "object") {
4478
4986
  const params = new URLSearchParams;
@@ -4486,14 +4994,34 @@ async function api2(method, path9, body) {
4486
4994
  target += `${target.includes("?") ? "&" : "?"}${query}`;
4487
4995
  requestBody = undefined;
4488
4996
  }
4489
- const res = await fetch(target, {
4490
- method,
4491
- headers: {
4492
- ...requestBody ? { "Content-Type": "application/json" } : {},
4493
- "x-unbrowse-client-id": CLI_CLIENT_ID
4494
- },
4495
- body: requestBody ? JSON.stringify(requestBody) : undefined
4496
- });
4997
+ const ctrl = new AbortController;
4998
+ const timeoutMs = opts?.timeoutMs;
4999
+ const timer = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
5000
+ let res;
5001
+ try {
5002
+ res = await fetch(target, {
5003
+ method,
5004
+ headers: {
5005
+ ...requestBody ? { "Content-Type": "application/json" } : {},
5006
+ "x-unbrowse-client-id": CLI_CLIENT_ID
5007
+ },
5008
+ body: requestBody ? JSON.stringify(requestBody) : undefined,
5009
+ signal: ctrl.signal
5010
+ });
5011
+ } catch (err) {
5012
+ if (timer)
5013
+ clearTimeout(timer);
5014
+ if (err?.name === "AbortError") {
5015
+ return {
5016
+ error: "cli_timeout",
5017
+ message: `CLI gave up waiting on local server after ${timeoutMs}ms. Try: pkill -9 -f 'unbrowse|kuri'`
5018
+ };
5019
+ }
5020
+ throw err;
5021
+ } finally {
5022
+ if (timer)
5023
+ clearTimeout(timer);
5024
+ }
4497
5025
  if (!res.ok && res.headers.get("content-type")?.includes("json")) {
4498
5026
  return res.json();
4499
5027
  }
@@ -4714,6 +5242,7 @@ async function cmdResolve(flags) {
4714
5242
  const intent = flags.intent;
4715
5243
  if (!intent)
4716
5244
  die("--intent is required");
5245
+ maybeShowContributionNotice();
4717
5246
  const hostType = detectTelemetryHostType();
4718
5247
  await ensureCliInstallTracked(hostType);
4719
5248
  await recordFunnelTelemetryEvent("cli_invoked", {
@@ -4775,10 +5304,19 @@ async function cmdResolve(flags) {
4775
5304
  body.force_capture = true;
4776
5305
  if (flags["skip-robots"])
4777
5306
  body.skip_robots_check = true;
5307
+ const budgetFlag = flags.budget;
5308
+ if (typeof budgetFlag === "string") {
5309
+ const parsed = parseInt(budgetFlag, 10);
5310
+ if (Number.isFinite(parsed) && parsed > 0)
5311
+ body.budget_ms = parsed;
5312
+ } else if (typeof budgetFlag === "number" && Number.isFinite(budgetFlag) && budgetFlag > 0) {
5313
+ body.budget_ms = budgetFlag;
5314
+ }
4778
5315
  body.projection = { raw: true };
4779
5316
  const startedAt = Date.now();
4780
5317
  async function resolveOnce(message = "Still working. Searching cached routes...") {
4781
- return withPendingNotice(api2("POST", "/v1/intent/resolve", body), message);
5318
+ const cliTimeoutMs = (typeof body.budget_ms === "number" ? body.budget_ms : 8000) + 30000;
5319
+ return withPendingNotice(api2("POST", "/v1/intent/resolve", body, { timeoutMs: cliTimeoutMs }), message);
4782
5320
  }
4783
5321
  let result = await resolveOnce();
4784
5322
  const resultError = resolveResultError(result);
@@ -4798,8 +5336,13 @@ async function cmdResolve(flags) {
4798
5336
  const skillId = resolveSkillId();
4799
5337
  if (skillId && endpoints.length > 0) {
4800
5338
  const bestEndpoint = endpoints[0];
4801
- info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
4802
- result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
5339
+ if (endpointNeedsThirdPartyTermsConfirmation(bestEndpoint) && !flags["confirm-third-party-terms"]) {
5340
+ process.stderr.write(`Skipping auto-execute: endpoint ${bestEndpoint.endpoint_id ?? "?"} ` + `requires explicit third-party terms confirmation. ` + `Re-run with --confirm-third-party-terms to proceed.
5341
+ `);
5342
+ } else {
5343
+ info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
5344
+ result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
5345
+ }
4803
5346
  }
4804
5347
  }
4805
5348
  if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
@@ -4861,8 +5404,8 @@ async function cmdResolve(flags) {
4861
5404
  throw error;
4862
5405
  }
4863
5406
  }
4864
- function drillPath(data, path9) {
4865
- const segments = path9.split(/\./).flatMap((s) => {
5407
+ function drillPath(data, path11) {
5408
+ const segments = path11.split(/\./).flatMap((s) => {
4866
5409
  const m = s.match(/^(.+)\[\]$/);
4867
5410
  return m ? [m[1], "[]"] : [s];
4868
5411
  });
@@ -4889,9 +5432,9 @@ function drillPath(data, path9) {
4889
5432
  }
4890
5433
  return values;
4891
5434
  }
4892
- function resolveDotPath(obj, path9) {
5435
+ function resolveDotPath(obj, path11) {
4893
5436
  let cur = obj;
4894
- for (const key of path9.split(".")) {
5437
+ for (const key of path11.split(".")) {
4895
5438
  if (cur == null || typeof cur !== "object")
4896
5439
  return;
4897
5440
  cur = cur[key];
@@ -4908,8 +5451,8 @@ function applyExtract(items, extractSpec) {
4908
5451
  return items.map((item) => {
4909
5452
  const row = {};
4910
5453
  let hasValue = false;
4911
- for (const { alias, path: path9 } of fields) {
4912
- const val = resolveDotPath(item, path9);
5454
+ for (const { alias, path: path11 } of fields) {
5455
+ const val = resolveDotPath(item, path11);
4913
5456
  row[alias] = val ?? null;
4914
5457
  if (val != null)
4915
5458
  hasValue = true;
@@ -4940,6 +5483,7 @@ async function cmdExecute(flags) {
4940
5483
  const skillId = flags.skill;
4941
5484
  if (!skillId)
4942
5485
  die("--skill is required");
5486
+ maybeShowContributionNotice();
4943
5487
  const hostType = detectTelemetryHostType();
4944
5488
  await ensureCliInstallTracked(hostType);
4945
5489
  await recordFunnelTelemetryEvent("cli_invoked", {
@@ -5225,7 +5769,67 @@ async function cmdCleanupStale(flags) {
5225
5769
  output(await withPendingNotice(api2("POST", "/v1/stale/cleanup", body), "Cleaning stale endpoints..."), !!flags.pretty);
5226
5770
  }
5227
5771
  async function cmdSearch(flags) {
5228
- await cmdResolve(flags);
5772
+ const intent = flags.intent;
5773
+ const hostType = detectTelemetryHostType();
5774
+ const { decodeTelemetryAttribution: decodeTelemetryAttribution3 } = await Promise.resolve().then(() => (init_telemetry_attribution2(), exports_telemetry_attribution));
5775
+ const attr = decodeTelemetryAttribution3(process.env.UNBROWSE_ATTRIBUTION_B64) ?? {};
5776
+ const attrProps = {};
5777
+ for (const k of ["channel", "campaign_id", "content_id", "variant_id"]) {
5778
+ const v = attr[k];
5779
+ if (v != null)
5780
+ attrProps[k] = v;
5781
+ }
5782
+ const domain = telemetryDomainFromInput(flags.domain, flags.url);
5783
+ if (intent) {
5784
+ await recordFunnelTelemetryEvent("search_started", {
5785
+ source: "cli",
5786
+ hostType,
5787
+ properties: {
5788
+ command: "search",
5789
+ intent,
5790
+ domain,
5791
+ url: typeof flags.url === "string" ? flags.url : null,
5792
+ ...attrProps
5793
+ }
5794
+ });
5795
+ }
5796
+ let resultCount = 0;
5797
+ try {
5798
+ if (intent && domain) {
5799
+ try {
5800
+ const searchRes = await api2("GET", `/v1/search/domain?intent=${encodeURIComponent(intent)}&domain=${encodeURIComponent(domain)}`);
5801
+ resultCount = Array.isArray(searchRes?.results) ? searchRes.results.length : 0;
5802
+ } catch {}
5803
+ }
5804
+ await cmdResolve(flags);
5805
+ if (intent) {
5806
+ await recordFunnelTelemetryEvent("search_completed", {
5807
+ source: "cli",
5808
+ hostType,
5809
+ properties: {
5810
+ command: "search",
5811
+ intent,
5812
+ domain,
5813
+ result_count: resultCount,
5814
+ ...attrProps
5815
+ }
5816
+ });
5817
+ }
5818
+ } catch (err) {
5819
+ if (intent) {
5820
+ await recordFunnelTelemetryEvent("search_failed", {
5821
+ source: "cli",
5822
+ hostType,
5823
+ properties: {
5824
+ command: "search",
5825
+ intent,
5826
+ error: err instanceof Error ? err.message : String(err),
5827
+ ...attrProps
5828
+ }
5829
+ });
5830
+ }
5831
+ throw err;
5832
+ }
5229
5833
  }
5230
5834
  async function cmdSessions(flags) {
5231
5835
  const domain = flags.domain;
@@ -5381,6 +5985,103 @@ async function cmdSetup(flags) {
5381
5985
  info(' unbrowse resolve --intent "list posts" --url "https://jsonplaceholder.typicode.com"');
5382
5986
  }
5383
5987
  }
5988
+ async function cmdMode(_flags) {
5989
+ await promptContributionMode({ force: true });
5990
+ await syncContributionPreferenceToServer();
5991
+ }
5992
+ async function syncContributionPreferenceToServer() {
5993
+ const cfg = loadConfig();
5994
+ if (!cfg?.api_key)
5995
+ return;
5996
+ const share_pointers = !!getContributionConfig2().contribution.share_pointers;
5997
+ try {
5998
+ await pushAccountPreferences({ share_pointers });
5999
+ info("Synced preference to your account.");
6000
+ } catch (err) {
6001
+ const msg = err.message ?? "";
6002
+ if (msg.includes("account_required") || msg.includes("HTTP 403"))
6003
+ return;
6004
+ info(`Local mode set, but server sync failed: ${msg}`);
6005
+ }
6006
+ }
6007
+ async function runPostSetupContributionPrompt() {
6008
+ try {
6009
+ await promptContributionMode({ force: false });
6010
+ } catch {}
6011
+ }
6012
+ async function cmdCapture(flags) {
6013
+ const url = flags.url;
6014
+ const intent = flags.intent || "capture";
6015
+ if (!url)
6016
+ die("--url is required");
6017
+ maybeShowContributionNotice();
6018
+ const t0 = Date.now();
6019
+ const result = await api2("POST", "/v1/capture", { url, intent });
6020
+ const endpoints = Array.isArray(result.endpoints) ? result.endpoints : Array.isArray(result.available_endpoints) ? result.available_endpoints : [];
6021
+ const skill = result.skill ?? null;
6022
+ const skillId = result.skill_id ?? skill?.skill_id ?? (typeof result.learned_skill_id === "string" ? result.learned_skill_id : undefined);
6023
+ const isThinDocumentOnly = endpoints.length === 1 && (() => {
6024
+ const e0 = endpoints[0];
6025
+ if (!e0 || e0.method !== "GET")
6026
+ return false;
6027
+ const tmpl = (e0.url_template ?? "").split("?")[0].replace(/\/$/, "");
6028
+ const target = url.split("?")[0].replace(/\/$/, "");
6029
+ return tmpl === target;
6030
+ })();
6031
+ const escapedIntent = intent.replace(/"/g, "\\\"");
6032
+ const escapedUrl = url.replace(/"/g, "\\\"");
6033
+ const envelope = {
6034
+ skill_id: skillId,
6035
+ endpoints_discovered: typeof result.endpoints_discovered === "number" ? result.endpoints_discovered : endpoints.length,
6036
+ marketplace_published: !!result.marketplace_published,
6037
+ ms: typeof result.ms === "number" ? result.ms : Date.now() - t0,
6038
+ next_step: endpoints.length === 0 ? "no endpoints discovered; site may need authentication or different intent" : `unbrowse resolve --intent "${escapedIntent}" --url "${escapedUrl}"`,
6039
+ ...isThinDocumentOnly ? { capture_pattern: "doc_only", capture_observation: "only the input URL was captured (1 GET, no XHR fired during the auto-capture window)" } : {},
6040
+ ...result.error ? { error: result.error } : {},
6041
+ ...result.captured_meta ? { captured_meta: result.captured_meta } : {},
6042
+ ...typeof result.capture_path === "string" ? { capture_path: result.capture_path } : {},
6043
+ ...result.prior_domain_note ? { prior_domain_note: result.prior_domain_note } : {},
6044
+ ...result.note_evidence ? { note_evidence: result.note_evidence } : {}
6045
+ };
6046
+ output(envelope, !!flags.pretty);
6047
+ }
6048
+ async function cmdNote(flags, args) {
6049
+ const subRaw = args?.[0] ?? flags.action;
6050
+ const sub = (typeof subRaw === "string" ? subRaw : "").toLowerCase();
6051
+ if (!sub || !["read", "write", "list"].includes(sub)) {
6052
+ die('usage: unbrowse note <read|write|list> --domain <domain> [--body "..."]');
6053
+ }
6054
+ if (sub === "list") {
6055
+ const { homedir: homedir9 } = await import("os");
6056
+ const { join: join15 } = await import("path");
6057
+ const { readdirSync: readdirSync7, existsSync: existsSync19 } = await import("fs");
6058
+ const profile = process.env.UNBROWSE_PROFILE ?? "";
6059
+ const dir = process.env.UNBROWSE_DOMAIN_NOTES_DIR ?? (profile ? join15(homedir9(), ".unbrowse", "profiles", profile, "domain-notes") : join15(homedir9(), ".unbrowse", "domain-notes"));
6060
+ if (!existsSync19(dir)) {
6061
+ output({ notes: [], dir }, !!flags.pretty);
6062
+ return;
6063
+ }
6064
+ const files = readdirSync7(dir).filter((f) => f.endsWith(".md"));
6065
+ output({ dir, notes: files.map((f) => f.replace(/\.md$/, "")) }, !!flags.pretty);
6066
+ return;
6067
+ }
6068
+ const domain = flags.domain;
6069
+ if (!domain)
6070
+ die("--domain required");
6071
+ if (sub === "read") {
6072
+ const res2 = await api2("GET", `/v1/domain-notes/${encodeURIComponent(domain)}`).catch((err) => ({
6073
+ error: err.message
6074
+ }));
6075
+ output(res2, !!flags.pretty);
6076
+ return;
6077
+ }
6078
+ const body = flags.body;
6079
+ if (typeof body !== "string" || body.trim().length === 0) {
6080
+ die("--body required (non-empty markdown string)");
6081
+ }
6082
+ const res = await api2("POST", `/v1/domain-notes/${encodeURIComponent(domain)}`, { body });
6083
+ output(res, !!flags.pretty);
6084
+ }
5384
6085
  var CLI_REFERENCE = {
5385
6086
  commands: [
5386
6087
  { name: "health", usage: "", desc: "Server health check" },
@@ -5425,7 +6126,10 @@ var CLI_REFERENCE = {
5425
6126
  { name: "earnings", usage: "[--json]", desc: "Show your credit balance, earnings from indexing, and spending" },
5426
6127
  { name: "corpus-test", usage: "--url <url> [--id <id>] [--retries N]", desc: "Capture a single URL with retry logic; keeps best result across N attempts" },
5427
6128
  { name: "corpus-run", usage: "--corpus <file> --out <file> [--retries N]", desc: "Run corpus-test over all cases in a corpus JSON file and write a comparable snapshot" },
5428
- { name: "register", usage: "[--no-prompt]", desc: "Optional: register an API key to publish skills, check earnings, and access backend analytics" }
6129
+ { name: "register", usage: "[--email lewis@example.com] [--no-prompt]", desc: "Register an API key. With --email, sends a magic link via Resend; otherwise creates an anonymous key." },
6130
+ { name: "mode", usage: "", desc: "Re-prompt for contribution mode (private / share / share + earn)" },
6131
+ { name: "capture", usage: "--url <url> --intent <intent>", desc: "Live-browser capture for a single URL \u2014 discovers + indexes API endpoints. Marketplace publish gated by `unbrowse mode`." },
6132
+ { name: "note", usage: '<read|write|list> --domain <domain> [--body "..."]', desc: "Read/write per-domain LLM-prose notes consumed by augment on next capture. Agent populates after reading capture's note_evidence." }
5429
6133
  ],
5430
6134
  globalFlags: [
5431
6135
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -5882,6 +6586,7 @@ async function cmdGo(args, flags) {
5882
6586
  const url = args[0] ?? flags.url;
5883
6587
  if (!url)
5884
6588
  die("Usage: unbrowse go <url>");
6589
+ maybeShowContributionNotice();
5885
6590
  output(await api2("POST", "/v1/browse/go", {
5886
6591
  url,
5887
6592
  ...typeof flags.session === "string" ? { session_id: flags.session } : {}
@@ -6019,6 +6724,43 @@ async function cmdClose(flags) {
6019
6724
  output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
6020
6725
  }
6021
6726
  async function cmdRegister(flags) {
6727
+ if (typeof flags.email === "string" && flags.email.length > 0) {
6728
+ const email = flags.email;
6729
+ if (getApiKey()) {
6730
+ info("Already registered. Re-running with --email will mint a new key and overwrite ~/.unbrowse/config.json.");
6731
+ }
6732
+ info(`Sending magic link to ${email}\u2026`);
6733
+ const result = await magicRegister({
6734
+ email,
6735
+ openBrowser: (url) => {
6736
+ info(`Opening browser: ${url}`);
6737
+ try {
6738
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
6739
+ spawn3(cmd, [url], { detached: true, stdio: "ignore" }).unref();
6740
+ } catch {}
6741
+ }
6742
+ });
6743
+ saveConfig({
6744
+ api_key: result.api_key,
6745
+ agent_id: result.agent_id,
6746
+ agent_name: result.email,
6747
+ registered_at: new Date().toISOString(),
6748
+ tos_accepted_version: null,
6749
+ tos_accepted_at: null
6750
+ });
6751
+ process.env.UNBROWSE_API_KEY = result.api_key;
6752
+ info(`Signed in as ${result.email}. API key saved to ~/.unbrowse/config.json.`);
6753
+ try {
6754
+ const serverPrefs = await fetchAccountPreferences();
6755
+ if (serverPrefs) {
6756
+ setContributionConfig2({
6757
+ contribution: { share_pointers: serverPrefs.share_pointers, set_via: "mode-command" }
6758
+ });
6759
+ info(`Auto-publish to marketplace: ${serverPrefs.share_pointers ? "ON" : "off"} (synced from your account).`);
6760
+ }
6761
+ } catch {}
6762
+ return;
6763
+ }
6022
6764
  if (getApiKey()) {
6023
6765
  info("Already registered. API key loaded from env or ~/.unbrowse/config.json");
6024
6766
  return;
@@ -6308,6 +7050,11 @@ async function main() {
6308
7050
  }
6309
7051
  if (command === "setup") {
6310
7052
  await cmdSetup(flags);
7053
+ await runPostSetupContributionPrompt();
7054
+ return;
7055
+ }
7056
+ if (command === "mode") {
7057
+ await cmdMode(flags);
6311
7058
  return;
6312
7059
  }
6313
7060
  if (command === "mcp")
@@ -6386,7 +7133,9 @@ async function main() {
6386
7133
  "corpus-run",
6387
7134
  "sessions-scan",
6388
7135
  "cache-clear",
6389
- "register"
7136
+ "register",
7137
+ "mode",
7138
+ "capture"
6390
7139
  ]);
6391
7140
  if (!KNOWN_COMMANDS.has(command)) {
6392
7141
  const pack = findSitePack(command);
@@ -6500,6 +7249,12 @@ async function main() {
6500
7249
  return cmdSessionsScan(flags);
6501
7250
  case "register":
6502
7251
  return cmdRegister(flags);
7252
+ case "mode":
7253
+ return cmdMode(flags);
7254
+ case "capture":
7255
+ return cmdCapture(flags);
7256
+ case "note":
7257
+ return cmdNote(flags, args);
6503
7258
  default:
6504
7259
  info(`Unknown command: ${command}`);
6505
7260
  printHelp();