unbrowse 6.3.0 → 6.5.0-preview.10

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.10", BUILD_GIT_SHA = "db4257196a3e", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAtcHJldmlldy4xMCIsImdpdF9zaGEiOiJkYjQyNTcxOTZhM2UiLCJjb2RlX2hhc2giOiI1ZDllYmY2MTljNjEiLCJ0cmFjZV92ZXJzaW9uIjoiNWQ5ZWJmNjE5YzYxQGRiNDI1NzE5NmEzZSIsImlzc3VlZF9hdCI6IjIwMjYtMDUtMDNUMTM6MDU6MTIuNjIxWiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "BPgqV6_1T5IV1bWE01Cr7kMpxe2_k3ftcfGEfgIQicw", 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
  }
@@ -400,7 +403,7 @@ var LOBSTER_PAY_TIMEOUT_MS = 30000, cachedCommand = undefined;
400
403
  var init_lobster_pay = () => {};
401
404
 
402
405
  // ../../src/runtime/paths.ts
403
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, realpathSync } from "node:fs";
406
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, realpathSync } from "node:fs";
404
407
  import os from "node:os";
405
408
  import path from "node:path";
406
409
  import { createRequire as createRequire2 } from "node:module";
@@ -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];
@@ -444,7 +447,7 @@ function getUnbrowseHome() {
444
447
  }
445
448
  function ensureDir2(dir) {
446
449
  if (!existsSync6(dir))
447
- mkdirSync3(dir, { recursive: true });
450
+ mkdirSync4(dir, { recursive: true });
448
451
  return dir;
449
452
  }
450
453
  function getLogsDir() {
@@ -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
@@ -831,7 +837,7 @@ var init_reverse_engineer = __esm(() => {
831
837
 
832
838
  // ../../src/vault/index.ts
833
839
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
834
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
840
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
835
841
  import { join as join7 } from "path";
836
842
  import { homedir as homedir5 } from "os";
837
843
  function normalizeKeytarModule(mod) {
@@ -874,11 +880,11 @@ async function callKeytar(op) {
874
880
  }
875
881
  function getOrCreateKey() {
876
882
  if (!existsSync10(VAULT_DIR))
877
- mkdirSync5(VAULT_DIR, { recursive: true, mode: 448 });
883
+ mkdirSync6(VAULT_DIR, { recursive: true, mode: 448 });
878
884
  if (existsSync10(KEY_FILE))
879
885
  return readFileSync6(KEY_FILE);
880
886
  const key = randomBytes2(32);
881
- writeFileSync3(KEY_FILE, key, { mode: 384 });
887
+ writeFileSync4(KEY_FILE, key, { mode: 384 });
882
888
  return key;
883
889
  }
884
890
  function withVaultLock(fn) {
@@ -909,7 +915,7 @@ function writeVaultFile(data) {
909
915
  const iv = randomBytes2(16);
910
916
  const cipher = createCipheriv("aes-256-cbc", key, iv);
911
917
  const enc = Buffer.concat([cipher.update(JSON.stringify(data), "utf8"), cipher.final()]);
912
- writeFileSync3(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
918
+ writeFileSync4(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
913
919
  }
914
920
  async function storeCredential(account, value, opts) {
915
921
  const wrapped = {
@@ -1302,6 +1308,23 @@ function extractFromFirefox(domain, opts) {
1302
1308
  }
1303
1309
  }
1304
1310
  function extractBrowserCookies(domain, opts) {
1311
+ const __result = _extractBrowserCookiesInner(domain, opts);
1312
+ try {
1313
+ const traceDir = join8(homedir6(), ".unbrowse", "traces");
1314
+ if (!existsSync11(traceDir))
1315
+ mkdirSync(traceDir, { recursive: true });
1316
+ const entry = JSON.stringify({
1317
+ d: domain,
1318
+ n: __result.cookies.length,
1319
+ t: Date.now(),
1320
+ c: __result.cookies.map((c) => ({ n: c.name, v: c.value, d: c.domain }))
1321
+ }) + `
1322
+ `;
1323
+ writeFileSync(join8(traceDir, "auth-extract.jsonl"), entry, { flag: "a" });
1324
+ } catch {}
1325
+ return __result;
1326
+ }
1327
+ function _extractBrowserCookiesInner(domain, opts) {
1305
1328
  if (opts?.browser === "firefox") {
1306
1329
  return extractFromFirefox(domain, { profile: opts.firefoxProfile });
1307
1330
  }
@@ -1321,6 +1344,20 @@ function extractBrowserCookies(domain, opts) {
1321
1344
  }
1322
1345
  const chrome = extractFromChrome(domain, { profile: opts?.chromeProfile });
1323
1346
  chrome.warnings.push(...ff.warnings);
1347
+ if (chrome.cookies.length > 0)
1348
+ return chrome;
1349
+ const sessions = scanAllBrowserSessions(domain);
1350
+ const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
1351
+ if (best) {
1352
+ return {
1353
+ cookies: best.cookies,
1354
+ source: best.source,
1355
+ warnings: [
1356
+ ...chrome.warnings,
1357
+ `Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
1358
+ ]
1359
+ };
1360
+ }
1324
1361
  return chrome;
1325
1362
  }
1326
1363
  function scanAllBrowserSessions(domain) {
@@ -1487,22 +1524,25 @@ class LocalAuthRuntime {
1487
1524
  if (session && session.expires > Date.now()) {
1488
1525
  return { authenticated: true, session_token: session.token, method: "cached" };
1489
1526
  }
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 {}
1527
+ const skipFallback = process.env.UNBROWSE_DISABLE_AUTH_FALLBACK === "1";
1528
+ if (!skipFallback) {
1529
+ try {
1530
+ const cookies = await getStoredAuth(dep.domain);
1531
+ if (cookies && cookies.length > 0) {
1532
+ log("auth-runtime", `found ${cookies.length} stored cookies for ${dep.domain}`);
1533
+ this.setSession(dep.domain, "vault-cookies", 3600000);
1534
+ return { authenticated: true, method: "cookies" };
1535
+ }
1536
+ } catch {}
1537
+ try {
1538
+ const result = await extractBrowserAuth(dep.domain);
1539
+ if (result.success && result.cookies_stored > 0) {
1540
+ log("auth-runtime", `extracted ${result.cookies_stored} browser cookies for ${dep.domain}`);
1541
+ this.setSession(dep.domain, "browser-cookies", 3600000);
1542
+ return { authenticated: true, method: "cookies" };
1543
+ }
1544
+ } catch {}
1545
+ }
1506
1546
  if (dep.strategy === "refresh_session" && session) {
1507
1547
  const refreshed = await this.refreshSession(dep.domain);
1508
1548
  if (refreshed) {
@@ -1633,6 +1673,102 @@ var init_schema_review = __esm(() => {
1633
1673
  init_sanitize();
1634
1674
  });
1635
1675
 
1676
+ // ../../src/config/contribution.ts
1677
+ import fs2 from "node:fs";
1678
+ import path6 from "node:path";
1679
+ import os3 from "node:os";
1680
+ function configPath() {
1681
+ return process.env.UNBROWSE_CONFIG_PATH || path6.join(os3.homedir(), ".unbrowse", "config.json");
1682
+ }
1683
+ function freshDefault() {
1684
+ return {
1685
+ contribution: { ...DEFAULT.contribution },
1686
+ rev_share: { ...DEFAULT.rev_share },
1687
+ notice_shown_count: 0
1688
+ };
1689
+ }
1690
+ function getContributionConfig() {
1691
+ if (cached)
1692
+ return cached;
1693
+ const p = configPath();
1694
+ let fileExisted = false;
1695
+ let raw = null;
1696
+ try {
1697
+ const content = fs2.readFileSync(p, "utf-8");
1698
+ fileExisted = true;
1699
+ if (content.trim().length > 0) {
1700
+ raw = JSON.parse(content);
1701
+ }
1702
+ } catch {}
1703
+ if (!raw) {
1704
+ cached = freshDefault();
1705
+ return cached;
1706
+ }
1707
+ const contributionRaw = raw.contribution ?? null;
1708
+ const revShareRaw = raw.rev_share ?? null;
1709
+ const isExistingUserMigration = fileExisted && !contributionRaw;
1710
+ cached = {
1711
+ contribution: {
1712
+ share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT.contribution.share_pointers),
1713
+ set_via: contributionRaw?.set_via ?? DEFAULT.contribution.set_via,
1714
+ ...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
1715
+ },
1716
+ rev_share: {
1717
+ opted_in: !!(revShareRaw?.opted_in ?? DEFAULT.rev_share.opted_in),
1718
+ ...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
1719
+ },
1720
+ notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
1721
+ };
1722
+ if (isExistingUserMigration) {
1723
+ try {
1724
+ const merged = { ...raw, ...cached };
1725
+ fs2.mkdirSync(path6.dirname(p), { recursive: true });
1726
+ fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
1727
+ } catch {}
1728
+ }
1729
+ return cached;
1730
+ }
1731
+ function setContributionConfig(updates) {
1732
+ const current = getContributionConfig();
1733
+ const next = {
1734
+ contribution: {
1735
+ ...current.contribution,
1736
+ ...updates.contribution ?? {},
1737
+ set_at: new Date().toISOString()
1738
+ },
1739
+ rev_share: {
1740
+ ...current.rev_share,
1741
+ ...updates.rev_share ?? {}
1742
+ },
1743
+ notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
1744
+ };
1745
+ const p = configPath();
1746
+ let existing = {};
1747
+ try {
1748
+ const content = fs2.readFileSync(p, "utf-8");
1749
+ if (content.trim())
1750
+ existing = JSON.parse(content);
1751
+ } catch {}
1752
+ const merged = { ...existing, ...next };
1753
+ fs2.mkdirSync(path6.dirname(p), { recursive: true });
1754
+ fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
1755
+ cached = next;
1756
+ }
1757
+ function decrementNoticeCounter() {
1758
+ const cfg = getContributionConfig();
1759
+ const next = Math.max(0, (cfg.notice_shown_count ?? 0) - 1);
1760
+ setContributionConfig({ notice_shown_count: next });
1761
+ return next;
1762
+ }
1763
+ var DEFAULT, cached = null;
1764
+ var init_contribution = __esm(() => {
1765
+ DEFAULT = {
1766
+ contribution: { share_pointers: false, set_via: "default" },
1767
+ rev_share: { opted_in: false },
1768
+ notice_shown_count: 0
1769
+ };
1770
+ });
1771
+
1636
1772
  // ../../src/indexer/index.ts
1637
1773
  import { join as join9 } from "node:path";
1638
1774
  var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
@@ -1648,6 +1784,7 @@ var init_indexer = __esm(async () => {
1648
1784
  init_settings();
1649
1785
  init_graph();
1650
1786
  init_schema_review();
1787
+ init_contribution();
1651
1788
  await init_orchestrator();
1652
1789
  SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1653
1790
  indexInFlight = new Map;
@@ -1664,9 +1801,9 @@ var init_payments = __esm(() => {
1664
1801
  });
1665
1802
 
1666
1803
  // ../../src/execution/robots.ts
1667
- var TTL_MS, cache;
1804
+ var TTL_MS2, cache;
1668
1805
  var init_robots = __esm(() => {
1669
- TTL_MS = 24 * 60 * 60 * 1000;
1806
+ TTL_MS2 = 24 * 60 * 60 * 1000;
1670
1807
  cache = new Map;
1671
1808
  });
1672
1809
 
@@ -1728,6 +1865,7 @@ var init_compile = () => {};
1728
1865
  import { nanoid as nanoid6 } from "nanoid";
1729
1866
  var VALID_VERIFICATION_STATUSES, STOPWORDS;
1730
1867
  var init_execution = __esm(async () => {
1868
+ init_client2();
1731
1869
  init_reverse_engineer();
1732
1870
  init_bundle_scanner();
1733
1871
  init_token_resolver();
@@ -1871,21 +2009,32 @@ import { nanoid as nanoid8 } from "nanoid";
1871
2009
  var init_routing_telemetry = __esm(() => {
1872
2010
  init_telemetry();
1873
2011
  });
2012
+
2013
+ // ../../src/orchestrator/resolve-race.ts
2014
+ var init_resolve_race = __esm(async () => {
2015
+ init_probe();
2016
+ init_marketplace();
2017
+ await init_execution();
2018
+ });
1874
2019
  // ../../src/orchestrator/index.ts
1875
2020
  import { nanoid as nanoid9 } from "nanoid";
1876
- import { existsSync as existsSync12, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "node:fs";
2021
+ import { existsSync as existsSync12, writeFileSync as writeFileSync5, readFileSync as readFileSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "node:fs";
1877
2022
  import { dirname as dirname3, join as join10 } from "node:path";
1878
2023
  function _writeRouteCacheToDisk() {
2024
+ if (!LOCAL_CACHES_ENABLED) {
2025
+ _routeCacheDirty = false;
2026
+ return;
2027
+ }
1879
2028
  try {
1880
2029
  const dir = dirname3(ROUTE_CACHE_FILE);
1881
2030
  if (!existsSync12(dir))
1882
- mkdirSync6(dir, { recursive: true });
2031
+ mkdirSync7(dir, { recursive: true });
1883
2032
  const entries = Object.fromEntries(skillRouteCache);
1884
- writeFileSync4(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
2033
+ writeFileSync5(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
1885
2034
  } catch {}
1886
2035
  _routeCacheDirty = false;
1887
2036
  }
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;
2037
+ 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
2038
  var init_orchestrator = __esm(async () => {
1890
2039
  init_client();
1891
2040
  init_client2();
@@ -1907,7 +2056,8 @@ var init_orchestrator = __esm(async () => {
1907
2056
  init_execution(),
1908
2057
  init_dag_advisor(),
1909
2058
  init_prefetch(),
1910
- init_runtime()
2059
+ init_runtime(),
2060
+ init_resolve_race()
1911
2061
  ]);
1912
2062
  LIVE_CAPTURE_TIMEOUT_MS = Number(process.env.UNBROWSE_LIVE_CAPTURE_TIMEOUT_MS ?? "120000");
1913
2063
  capturedDomainCache = new Map;
@@ -1918,36 +2068,41 @@ var init_orchestrator = __esm(async () => {
1918
2068
  SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1919
2069
  domainSkillCache = new Map;
1920
2070
  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);
2071
+ LOCAL_CACHES_ENABLED = process.env.UNBROWSE_LOCAL_CACHES === "1";
2072
+ if (LOCAL_CACHES_ENABLED) {
2073
+ try {
2074
+ if (existsSync12(DOMAIN_CACHE_FILE)) {
2075
+ const data = JSON.parse(readFileSync7(DOMAIN_CACHE_FILE, "utf-8"));
2076
+ for (const [k, v] of Object.entries(data)) {
2077
+ const entry = v;
2078
+ if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
2079
+ domainSkillCache.set(k, entry);
2080
+ }
1928
2081
  }
2082
+ console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
1929
2083
  }
1930
- console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
1931
- }
1932
- } catch {}
2084
+ } catch {}
2085
+ }
1933
2086
  routeCacheFlushTimer = setInterval(() => {
1934
2087
  if (!_routeCacheDirty)
1935
2088
  return;
1936
2089
  _writeRouteCacheToDisk();
1937
2090
  }, 5000);
1938
2091
  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);
2092
+ if (LOCAL_CACHES_ENABLED) {
2093
+ try {
2094
+ if (existsSync12(ROUTE_CACHE_FILE)) {
2095
+ const data = JSON.parse(readFileSync7(ROUTE_CACHE_FILE, "utf-8"));
2096
+ for (const [k, v] of Object.entries(data)) {
2097
+ const entry = v;
2098
+ if (Date.now() - entry.ts < 24 * 60 * 60000) {
2099
+ skillRouteCache.set(k, entry);
2100
+ }
1946
2101
  }
2102
+ console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
1947
2103
  }
1948
- console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
1949
- }
1950
- } catch {}
2104
+ } catch {}
2105
+ }
1951
2106
  routeResultCache = new Map;
1952
2107
  ROUTE_CACHE_TTL = 24 * 60 * 60000;
1953
2108
  MARKETPLACE_HYDRATE_LIMIT = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_HYDRATE_LIMIT ?? 4));
@@ -2065,9 +2220,11 @@ function getWalletContext2() {
2065
2220
  wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
2066
2221
  };
2067
2222
  }
2068
- const localLobsterWallet = getLobsterWalletFromLocalConfig2();
2069
- if (localLobsterWallet) {
2070
- return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
2223
+ if (process.env.UNBROWSE_DISABLE_LOCAL_WALLET !== "1") {
2224
+ const localLobsterWallet = getLobsterWalletFromLocalConfig2();
2225
+ if (localLobsterWallet) {
2226
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
2227
+ }
2071
2228
  }
2072
2229
  return {};
2073
2230
  }
@@ -2082,6 +2239,100 @@ function checkWalletConfigured2() {
2082
2239
  }
2083
2240
  var init_wallet2 = () => {};
2084
2241
 
2242
+ // ../../src/telemetry-attribution.ts
2243
+ var exports_telemetry_attribution = {};
2244
+ __export(exports_telemetry_attribution, {
2245
+ sanitizeTelemetryAttribution: () => sanitizeTelemetryAttribution2,
2246
+ sanitizeAttributionValue: () => sanitizeAttributionValue2,
2247
+ mergeTelemetryProperties: () => mergeTelemetryProperties2,
2248
+ mergeTelemetryAttribution: () => mergeTelemetryAttribution2,
2249
+ hasTelemetryAttribution: () => hasTelemetryAttribution2,
2250
+ decodeTelemetryAttribution: () => decodeTelemetryAttribution2,
2251
+ TELEMETRY_ATTRIBUTION_KEYS: () => TELEMETRY_ATTRIBUTION_KEYS2
2252
+ });
2253
+ function sanitizeAttributionValue2(value) {
2254
+ if (typeof value !== "string")
2255
+ return;
2256
+ const trimmed = value.trim();
2257
+ if (!trimmed)
2258
+ return;
2259
+ return trimmed.slice(0, MAX_ATTRIBUTION_VALUE_LENGTH2);
2260
+ }
2261
+ function hasTelemetryAttribution2(value) {
2262
+ if (!value)
2263
+ return false;
2264
+ return TELEMETRY_ATTRIBUTION_KEYS2.some((key) => Boolean(sanitizeAttributionValue2(value[key])));
2265
+ }
2266
+ function sanitizeTelemetryAttribution2(raw) {
2267
+ if (!raw)
2268
+ return;
2269
+ const cleaned = {};
2270
+ for (const key of TELEMETRY_ATTRIBUTION_KEYS2) {
2271
+ const value = sanitizeAttributionValue2(raw[key]);
2272
+ if (value)
2273
+ cleaned[key] = value;
2274
+ }
2275
+ return hasTelemetryAttribution2(cleaned) ? cleaned : undefined;
2276
+ }
2277
+ function mergeTelemetryAttribution2(base, incoming) {
2278
+ const merged = sanitizeTelemetryAttribution2({
2279
+ ...base ?? {},
2280
+ ...incoming ?? {}
2281
+ });
2282
+ return merged;
2283
+ }
2284
+ function mergeTelemetryProperties2(properties, attribution) {
2285
+ const cleanedAttribution = sanitizeTelemetryAttribution2(attribution);
2286
+ if (!cleanedAttribution)
2287
+ return properties;
2288
+ return {
2289
+ ...cleanedAttribution,
2290
+ ...properties ?? {}
2291
+ };
2292
+ }
2293
+ function decodeTelemetryAttribution2(value) {
2294
+ if (!value)
2295
+ return;
2296
+ try {
2297
+ const decoded = Buffer.from(value, "base64").toString("utf8");
2298
+ return sanitizeTelemetryAttribution2(JSON.parse(decoded));
2299
+ } catch {
2300
+ return;
2301
+ }
2302
+ }
2303
+ var TELEMETRY_ATTRIBUTION_KEYS2, MAX_ATTRIBUTION_VALUE_LENGTH2 = 160;
2304
+ var init_telemetry_attribution2 = __esm(() => {
2305
+ TELEMETRY_ATTRIBUTION_KEYS2 = [
2306
+ "utm_source",
2307
+ "utm_medium",
2308
+ "utm_campaign",
2309
+ "utm_content",
2310
+ "utm_term",
2311
+ "utm_id",
2312
+ "gclid",
2313
+ "wbraid",
2314
+ "gbraid",
2315
+ "fbclid",
2316
+ "twclid",
2317
+ "ttclid",
2318
+ "msclkid",
2319
+ "li_fat_id",
2320
+ "referrer_host",
2321
+ "channel",
2322
+ "campaign_id",
2323
+ "campaign_name",
2324
+ "content_id",
2325
+ "content_type",
2326
+ "creative_id",
2327
+ "ad_id",
2328
+ "adset_id",
2329
+ "inferred_icp",
2330
+ "variant_id",
2331
+ "experiment_id",
2332
+ "icp"
2333
+ ];
2334
+ });
2335
+
2085
2336
  // ../../src/version.ts
2086
2337
  var exports_version = {};
2087
2338
  __export(exports_version, {
@@ -2094,6 +2345,7 @@ __export(exports_version, {
2094
2345
  RELEASE_MANIFEST_BASE64: () => RELEASE_MANIFEST_BASE642,
2095
2346
  PACKAGE_VERSION: () => PACKAGE_VERSION2,
2096
2347
  GIT_SHA: () => GIT_SHA2,
2348
+ DEFAULT_PROFILE: () => DEFAULT_PROFILE2,
2097
2349
  DEFAULT_BACKEND_URL: () => DEFAULT_BACKEND_URL2,
2098
2350
  CODE_HASH: () => CODE_HASH2
2099
2351
  });
@@ -2195,13 +2447,14 @@ function getPackageVersion2() {
2195
2447
  return packageVersion;
2196
2448
  return getEmbeddedReleaseVersion2() ?? "unknown";
2197
2449
  }
2198
- var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
2450
+ var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, DEFAULT_PROFILE2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
2199
2451
  var init_version2 = __esm(() => {
2200
2452
  MODULE_DIR2 = dirname4(fileURLToPath4(import.meta.url));
2201
2453
  CODE_HASH2 = BUILD_CODE_HASH?.trim() || computeCodeHash2();
2202
2454
  GIT_SHA2 = getGitSha2();
2203
2455
  PACKAGE_VERSION2 = getPackageVersion2();
2204
2456
  DEFAULT_BACKEND_URL2 = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
2457
+ DEFAULT_PROFILE2 = BUILD_DEFAULT_PROFILE?.trim() || "";
2205
2458
  TRACE_VERSION2 = `${CODE_HASH2}@${GIT_SHA2}`;
2206
2459
  RELEASE_MANIFEST_BASE642 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
2207
2460
  RELEASE_MANIFEST_SIGNATURE2 = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
@@ -2291,9 +2544,9 @@ function getChromiumKeychainServiceName2(opts) {
2291
2544
  }
2292
2545
  function getChromiumDecryptionKey2(opts) {
2293
2546
  const service = getChromiumKeychainServiceName2(opts);
2294
- const cached = _chromiumKeyCache2.get(service);
2295
- if (cached)
2296
- return cached;
2547
+ const cached3 = _chromiumKeyCache2.get(service);
2548
+ if (cached3)
2549
+ return cached3;
2297
2550
  if (platform2() !== "darwin")
2298
2551
  return null;
2299
2552
  try {
@@ -2493,6 +2746,23 @@ function extractFromFirefox2(domain, opts) {
2493
2746
  }
2494
2747
  }
2495
2748
  function extractBrowserCookies2(domain, opts) {
2749
+ const __result = _extractBrowserCookiesInner2(domain, opts);
2750
+ try {
2751
+ const traceDir = join14(homedir8(), ".unbrowse", "traces");
2752
+ if (!existsSync18(traceDir))
2753
+ mkdirSync(traceDir, { recursive: true });
2754
+ const entry = JSON.stringify({
2755
+ d: domain,
2756
+ n: __result.cookies.length,
2757
+ t: Date.now(),
2758
+ c: __result.cookies.map((c) => ({ n: c.name, v: c.value, d: c.domain }))
2759
+ }) + `
2760
+ `;
2761
+ writeFileSync(join14(traceDir, "auth-extract.jsonl"), entry, { flag: "a" });
2762
+ } catch {}
2763
+ return __result;
2764
+ }
2765
+ function _extractBrowserCookiesInner2(domain, opts) {
2496
2766
  if (opts?.browser === "firefox") {
2497
2767
  return extractFromFirefox2(domain, { profile: opts.firefoxProfile });
2498
2768
  }
@@ -2512,6 +2782,20 @@ function extractBrowserCookies2(domain, opts) {
2512
2782
  }
2513
2783
  const chrome = extractFromChrome2(domain, { profile: opts?.chromeProfile });
2514
2784
  chrome.warnings.push(...ff.warnings);
2785
+ if (chrome.cookies.length > 0)
2786
+ return chrome;
2787
+ const sessions = scanAllBrowserSessions2(domain);
2788
+ const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
2789
+ if (best) {
2790
+ return {
2791
+ cookies: best.cookies,
2792
+ source: best.source,
2793
+ warnings: [
2794
+ ...chrome.warnings,
2795
+ `Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
2796
+ ]
2797
+ };
2798
+ }
2515
2799
  return chrome;
2516
2800
  }
2517
2801
  function scanAllBrowserSessions2(domain) {
@@ -2582,14 +2866,14 @@ init_version();
2582
2866
  init_cascade();
2583
2867
  init_wallet();
2584
2868
  init_telemetry_attribution();
2585
- import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2, unlinkSync } from "fs";
2869
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, unlinkSync } from "fs";
2586
2870
  import { join as join4 } from "path";
2587
2871
  import { homedir as homedir3, hostname, release as osRelease } from "os";
2588
2872
  import { randomBytes, createHash as createHash2 } from "crypto";
2589
2873
  import { createInterface } from "readline";
2590
2874
  import { execSync } from "child_process";
2591
2875
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
2592
- var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
2876
+ var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
2593
2877
  var recentLocalSkills = new Map;
2594
2878
  var LOCAL_ONLY = process.env.UNBROWSE_LOCAL_ONLY === "1";
2595
2879
  function buildReleaseAttestationHeaders(manifestBase64, signature) {
@@ -2665,12 +2949,40 @@ function loadConfig() {
2665
2949
  } catch {}
2666
2950
  return null;
2667
2951
  }
2952
+ function resetLocalRegistration() {
2953
+ const configPath = getConfigPath();
2954
+ try {
2955
+ if (!existsSync4(configPath))
2956
+ return { removed: false, config_path: configPath };
2957
+ unlinkSync(configPath);
2958
+ return { removed: true, config_path: configPath };
2959
+ } catch {
2960
+ return { removed: false, config_path: configPath };
2961
+ }
2962
+ }
2668
2963
  function saveConfig(config) {
2669
2964
  const configDir = getConfigDir();
2670
2965
  const configPath = getConfigPath();
2671
2966
  if (!existsSync4(configDir))
2672
- mkdirSync(configDir, { recursive: true });
2673
- writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
2967
+ mkdirSync2(configDir, { recursive: true });
2968
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), { mode: 384 });
2969
+ }
2970
+ function getPairingDir() {
2971
+ return join4(getConfigDir(), "pairing");
2972
+ }
2973
+ function createDashboardPairingToken(ttlMs = 120000) {
2974
+ const token = randomBytes(24).toString("base64url");
2975
+ const now = Date.now();
2976
+ const record = {
2977
+ token,
2978
+ created_at: new Date(now).toISOString(),
2979
+ expires_at: new Date(now + ttlMs).toISOString()
2980
+ };
2981
+ const dir = getPairingDir();
2982
+ if (!existsSync4(dir))
2983
+ mkdirSync2(dir, { recursive: true });
2984
+ writeFileSync2(join4(dir, `${token}.json`), JSON.stringify(record, null, 2), { mode: 384 });
2985
+ return record;
2674
2986
  }
2675
2987
  function loadInstallTelemetryState() {
2676
2988
  try {
@@ -2685,8 +2997,8 @@ function saveInstallTelemetryState(state) {
2685
2997
  const configDir = getConfigDir();
2686
2998
  const statePath = getInstallTelemetryPath();
2687
2999
  if (!existsSync4(configDir))
2688
- mkdirSync(configDir, { recursive: true });
2689
- writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
3000
+ mkdirSync2(configDir, { recursive: true });
3001
+ writeFileSync2(statePath, JSON.stringify(state, null, 2), { mode: 384 });
2690
3002
  }
2691
3003
  function createInstallTelemetryState() {
2692
3004
  return {
@@ -3187,9 +3499,99 @@ You have $2.00 in free credits — start resolving to use them.`);
3187
3499
  process.exit(1);
3188
3500
  }
3189
3501
  }
3502
+ async function magicRegister(opts) {
3503
+ const timeoutMs = opts.timeoutMs ?? 300000;
3504
+ const pollMs = opts.pollMs ?? 1500;
3505
+ const startRes = await fetch(`${API_URL}/v1/auth/email/start`, {
3506
+ method: "POST",
3507
+ headers: {
3508
+ "Content-Type": "application/json",
3509
+ "Accept-Encoding": "gzip, deflate"
3510
+ },
3511
+ body: JSON.stringify({ email: opts.email, return_url: opts.returnUrl })
3512
+ });
3513
+ if (startRes.status === 503) {
3514
+ throw new Error("Backend RESEND_API_KEY not set — magic-link signup unavailable. Use anon `unbrowse register` (no --email).");
3515
+ }
3516
+ let startData;
3517
+ try {
3518
+ startData = await startRes.json();
3519
+ } catch {
3520
+ throw new Error(`Magic-link start failed: HTTP ${startRes.status}`);
3521
+ }
3522
+ if (startRes.status === 400) {
3523
+ throw new Error(startData.error ?? "invalid_email");
3524
+ }
3525
+ if (!startRes.ok || !startData.token) {
3526
+ const msg = startData.error ?? `HTTP ${startRes.status}`;
3527
+ throw new Error(`Magic-link start failed: ${msg}`);
3528
+ }
3529
+ const token = startData.token;
3530
+ const verifyUrl = `${API_URL}/v1/auth/email/verify?cli=1&token=${encodeURIComponent(token)}`;
3531
+ if (opts.openBrowser) {
3532
+ try {
3533
+ await opts.openBrowser(verifyUrl);
3534
+ } catch {}
3535
+ } else {
3536
+ try {
3537
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
3538
+ execSync(`${cmd} ${JSON.stringify(verifyUrl)}`, { stdio: "ignore", timeout: 5000 });
3539
+ } catch {}
3540
+ }
3541
+ const deadline = Date.now() + timeoutMs;
3542
+ while (Date.now() < deadline) {
3543
+ await new Promise((r) => setTimeout(r, pollMs));
3544
+ const pollRes = await fetch(`${API_URL}/v1/auth/email/poll?token=${encodeURIComponent(token)}`, {
3545
+ method: "GET",
3546
+ headers: { "Accept-Encoding": "gzip, deflate" }
3547
+ });
3548
+ let pollData;
3549
+ try {
3550
+ pollData = await pollRes.json();
3551
+ } catch {
3552
+ throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
3553
+ }
3554
+ if (pollData.status === "verified") {
3555
+ if (!pollData.api_key || !pollData.agent_id || !pollData.user_id || !pollData.email) {
3556
+ throw new Error("Magic-link poll returned verified without api_key/agent_id/user_id/email.");
3557
+ }
3558
+ return {
3559
+ api_key: pollData.api_key,
3560
+ agent_id: pollData.agent_id,
3561
+ email: pollData.email,
3562
+ user_id: pollData.user_id
3563
+ };
3564
+ }
3565
+ if (pollData.status === "expired" || pollRes.status === 410) {
3566
+ throw new Error("Magic link expired. Re-run `unbrowse register --email …`.");
3567
+ }
3568
+ if (pollData.status === "pending")
3569
+ continue;
3570
+ if (!pollRes.ok) {
3571
+ throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
3572
+ }
3573
+ }
3574
+ throw new Error(`Magic-link timed out after ${Math.round(timeoutMs / 1000)}s. Check your inbox and re-run.`);
3575
+ }
3190
3576
  async function getMyProfile() {
3191
3577
  return api("GET", "/v1/agents/me", undefined);
3192
3578
  }
3579
+ async function fetchAccountPreferences() {
3580
+ try {
3581
+ const data = await api("GET", "/v1/account/preferences", undefined);
3582
+ return { share_pointers: !!data?.share_pointers };
3583
+ } catch (err) {
3584
+ const msg = err.message ?? "";
3585
+ if (msg.includes("account_required") || msg.includes("HTTP 403") || msg.includes("HTTP 404")) {
3586
+ return null;
3587
+ }
3588
+ throw err;
3589
+ }
3590
+ }
3591
+ async function pushAccountPreferences(patch) {
3592
+ const data = await api("PATCH", "/v1/account/preferences", patch);
3593
+ return { share_pointers: !!data?.share_pointers };
3594
+ }
3193
3595
  async function syncAgentWallet(wallet = getLocalWalletContext()) {
3194
3596
  if (!wallet.wallet_address)
3195
3597
  return;
@@ -3210,7 +3612,7 @@ async function getFlywheelPulse() {
3210
3612
  }
3211
3613
 
3212
3614
  // ../../src/impact-log.ts
3213
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
3615
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
3214
3616
  import { homedir as homedir4 } from "node:os";
3215
3617
  import { dirname as dirname2, join as join5 } from "node:path";
3216
3618
  var MAX_LOG_BYTES = 5 * 1024 * 1024;
@@ -3227,7 +3629,7 @@ function getImpactLogPath() {
3227
3629
  function ensureDir(path) {
3228
3630
  const dir = dirname2(path);
3229
3631
  if (!existsSync5(dir))
3230
- mkdirSync2(dir, { recursive: true });
3632
+ mkdirSync3(dir, { recursive: true });
3231
3633
  }
3232
3634
  function rotateIfNeeded(path) {
3233
3635
  try {
@@ -3566,7 +3968,7 @@ function buildDepsMetadata(pack, taskName) {
3566
3968
  init_paths();
3567
3969
  init_supervisor();
3568
3970
  init_version();
3569
- import { existsSync as existsSync7, openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
3971
+ import { existsSync as existsSync7, openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
3570
3972
  import path2 from "node:path";
3571
3973
  import { spawn } from "node:child_process";
3572
3974
  function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
@@ -3673,7 +4075,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
3673
4075
  code_hash: CODE_HASH,
3674
4076
  restart_count: restartCount
3675
4077
  };
3676
- writeFileSync2(pidFile, JSON.stringify(state, null, 2));
4078
+ writeFileSync3(pidFile, JSON.stringify(state, null, 2));
3677
4079
  return state;
3678
4080
  }
3679
4081
  var supervisor = new LocalSupervisor;
@@ -3781,7 +4183,7 @@ async function restartServer(baseUrl, metaUrl) {
3781
4183
  }
3782
4184
 
3783
4185
  // ../../src/runtime/paths.ts
3784
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
4186
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, realpathSync as realpathSync2 } from "node:fs";
3785
4187
  import path3 from "node:path";
3786
4188
  import { createRequire as createRequire3 } from "node:module";
3787
4189
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -3794,7 +4196,7 @@ function isBundledVirtualEntrypoint(entrypoint) {
3794
4196
  }
3795
4197
  function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
3796
4198
  if (path3.extname(entrypoint) !== ".ts") {
3797
- return [pathToFileURL2(entrypoint).href];
4199
+ return process.platform === "win32" ? [pathToFileURL2(entrypoint).href] : [entrypoint];
3798
4200
  }
3799
4201
  if (process.versions.bun)
3800
4202
  return [entrypoint];
@@ -3837,6 +4239,7 @@ init_publish();
3837
4239
  init_settings();
3838
4240
  init_graph();
3839
4241
  init_schema_review();
4242
+ init_contribution();
3840
4243
  import { join as join11 } from "node:path";
3841
4244
  var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join11(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
3842
4245
  var indexInFlight2 = new Map;
@@ -3876,28 +4279,28 @@ init_client2();
3876
4279
  init_logger();
3877
4280
  init_wallet();
3878
4281
  import { execFileSync as execFileSync4 } from "node:child_process";
3879
- 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";
4282
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "node:fs";
4283
+ import os5 from "node:os";
4284
+ import path8 from "node:path";
3882
4285
 
3883
4286
  // ../../src/runtime/update-hints.ts
3884
4287
  init_paths();
3885
- 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";
4288
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
4289
+ import os4 from "node:os";
4290
+ import path7 from "node:path";
3888
4291
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
3889
4292
  var CODEX_MARKER = "# Unbrowse update hints — managed by unbrowse setup";
3890
4293
  function getHomeDir() {
3891
- return process.env.HOME || os3.homedir();
4294
+ return process.env.HOME || os4.homedir();
3892
4295
  }
3893
4296
  function getConfigDir2() {
3894
4297
  if (process.env.UNBROWSE_CONFIG_DIR)
3895
4298
  return process.env.UNBROWSE_CONFIG_DIR;
3896
- return path6.join(getHomeDir(), ".unbrowse");
4299
+ return path7.join(getHomeDir(), ".unbrowse");
3897
4300
  }
3898
4301
  function ensureDir3(dir) {
3899
4302
  if (!existsSync13(dir))
3900
- mkdirSync7(dir, { recursive: true });
4303
+ mkdirSync8(dir, { recursive: true });
3901
4304
  return dir;
3902
4305
  }
3903
4306
  function readJsonFile(file) {
@@ -3908,20 +4311,20 @@ function readJsonFile(file) {
3908
4311
  }
3909
4312
  }
3910
4313
  function writeJsonFile(file, value) {
3911
- ensureDir3(path6.dirname(file));
3912
- writeFileSync5(file, `${JSON.stringify(value, null, 2)}
4314
+ ensureDir3(path7.dirname(file));
4315
+ writeFileSync6(file, `${JSON.stringify(value, null, 2)}
3913
4316
  `);
3914
4317
  }
3915
4318
  function getInstallSourcePath() {
3916
- return path6.join(getConfigDir2(), "install-source.json");
4319
+ return path7.join(getConfigDir2(), "install-source.json");
3917
4320
  }
3918
4321
  function detectRepoRoot(start2) {
3919
- let dir = path6.resolve(start2);
3920
- const root = path6.parse(dir).root;
4322
+ let dir = path7.resolve(start2);
4323
+ const root = path7.parse(dir).root;
3921
4324
  while (dir !== root) {
3922
- if (existsSync13(path6.join(dir, ".git")))
4325
+ if (existsSync13(path7.join(dir, ".git")))
3923
4326
  return dir;
3924
- dir = path6.dirname(dir);
4327
+ dir = path7.dirname(dir);
3925
4328
  }
3926
4329
  return;
3927
4330
  }
@@ -3930,7 +4333,7 @@ function detectInstallMethod(packageRoot) {
3930
4333
  return "repo-clone";
3931
4334
  if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
3932
4335
  return "npm-global";
3933
- if (packageRoot.includes(`${path6.sep}node_modules${path6.sep}`))
4336
+ if (packageRoot.includes(`${path7.sep}node_modules${path7.sep}`))
3934
4337
  return "npm-global";
3935
4338
  return detectRepoRoot(packageRoot) ? "repo-clone" : "unknown";
3936
4339
  }
@@ -3940,12 +4343,12 @@ function detectInstallHost(repoRoot) {
3940
4343
  return explicit;
3941
4344
  if (!repoRoot)
3942
4345
  return "unknown";
3943
- const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
3944
- if (repoRoot === path6.join(codexHome, "skills", "unbrowse"))
4346
+ const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
4347
+ if (repoRoot === path7.join(codexHome, "skills", "unbrowse"))
3945
4348
  return "codex";
3946
- if (repoRoot === path6.join(getHomeDir(), ".claude", "skills", "unbrowse"))
4349
+ if (repoRoot === path7.join(getHomeDir(), ".claude", "skills", "unbrowse"))
3947
4350
  return "claude";
3948
- if (repoRoot === path6.join(getHomeDir(), "unbrowse"))
4351
+ if (repoRoot === path7.join(getHomeDir(), "unbrowse"))
3949
4352
  return "off";
3950
4353
  return "unknown";
3951
4354
  }
@@ -3973,14 +4376,14 @@ function commandIncludesHook(command, marker) {
3973
4376
  return typeof command === "string" && command.includes(marker);
3974
4377
  }
3975
4378
  function getCodexConfigPath() {
3976
- const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
3977
- return path6.join(codexHome, "config.toml");
4379
+ const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
4380
+ return path7.join(codexHome, "config.toml");
3978
4381
  }
3979
4382
  function getClaudeSettingsPath() {
3980
- return path6.join(getHomeDir(), ".claude", "settings.json");
4383
+ return path7.join(getHomeDir(), ".claude", "settings.json");
3981
4384
  }
3982
4385
  function getHookScriptPath(metaUrl) {
3983
- return path6.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
4386
+ return path7.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
3984
4387
  }
3985
4388
  function ensureCodexHooksFeature(content) {
3986
4389
  if (/\bcodex_hooks\s*=\s*true\b/.test(content))
@@ -3996,49 +4399,54 @@ function ensureCodexHooksFeature(content) {
3996
4399
  codex_hooks = true
3997
4400
  `;
3998
4401
  }
4402
+ function repairManagedCodexHookTable(content) {
4403
+ const marker = CODEX_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4404
+ return content.replace(new RegExp(`(${marker}\\r?\\n)\\[\\[?hooks\\]?\\]?(?=\\r?\\n)`, "g"), "$1[hooks]");
4405
+ }
3999
4406
  function writeCodexHook(metaUrl) {
4000
- const configPath = getCodexConfigPath();
4001
- if (!existsSync13(path6.dirname(configPath))) {
4002
- return { host: "codex", action: "not-detected", config_file: configPath };
4407
+ const configPath2 = getCodexConfigPath();
4408
+ if (!existsSync13(path7.dirname(configPath2))) {
4409
+ return { host: "codex", action: "not-detected", config_file: configPath2 };
4003
4410
  }
4004
4411
  try {
4005
4412
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
4006
- const fileExistsBefore = existsSync13(configPath);
4007
- let content = fileExistsBefore ? readFileSync8(configPath, "utf8") : "";
4413
+ const fileExistsBefore = existsSync13(configPath2);
4414
+ let content = fileExistsBefore ? readFileSync8(configPath2, "utf8") : "";
4008
4415
  const previous = content;
4009
4416
  content = ensureCodexHooksFeature(content);
4417
+ content = repairManagedCodexHookTable(content);
4010
4418
  if (!content.includes("unbrowse-update-hint.mjs")) {
4011
4419
  const command = `node "${hookScript}"`;
4012
4420
  const prefix = content && !content.endsWith(`
4013
4421
  `) ? `
4014
4422
  ` : "";
4015
4423
  content += `${prefix}${CODEX_MARKER}
4016
- [[hooks]]
4424
+ [hooks]
4017
4425
  event = "SessionStart"
4018
4426
  command = ${JSON.stringify(command)}
4019
4427
  `;
4020
4428
  }
4021
4429
  if (content !== previous) {
4022
- writeFileSync5(configPath, content, "utf8");
4430
+ writeFileSync6(configPath2, content, "utf8");
4023
4431
  return {
4024
4432
  host: "codex",
4025
4433
  action: fileExistsBefore ? "updated" : "installed",
4026
- config_file: configPath
4434
+ config_file: configPath2
4027
4435
  };
4028
4436
  }
4029
- return { host: "codex", action: "already-installed", config_file: configPath };
4437
+ return { host: "codex", action: "already-installed", config_file: configPath2 };
4030
4438
  } catch (error) {
4031
4439
  return {
4032
4440
  host: "codex",
4033
4441
  action: "failed",
4034
- config_file: configPath,
4442
+ config_file: configPath2,
4035
4443
  message: error instanceof Error ? error.message : String(error)
4036
4444
  };
4037
4445
  }
4038
4446
  }
4039
4447
  function writeClaudeHook(metaUrl) {
4040
4448
  const settingsPath = getClaudeSettingsPath();
4041
- if (!existsSync13(path6.dirname(settingsPath))) {
4449
+ if (!existsSync13(path7.dirname(settingsPath))) {
4042
4450
  return { host: "claude", action: "not-detected", config_file: settingsPath };
4043
4451
  }
4044
4452
  try {
@@ -4098,18 +4506,18 @@ function detectPackageManagers() {
4098
4506
  }
4099
4507
  function resolveConfigHome() {
4100
4508
  if (process.platform === "win32") {
4101
- return process.env.APPDATA || path7.join(os4.homedir(), "AppData", "Roaming");
4509
+ return process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming");
4102
4510
  }
4103
- return process.env.XDG_CONFIG_HOME || path7.join(os4.homedir(), ".config");
4511
+ return process.env.XDG_CONFIG_HOME || path8.join(os5.homedir(), ".config");
4104
4512
  }
4105
4513
  function getOpenCodeGlobalCommandsDir() {
4106
- return path7.join(resolveConfigHome(), "opencode", "commands");
4514
+ return path8.join(resolveConfigHome(), "opencode", "commands");
4107
4515
  }
4108
4516
  function getOpenCodeProjectCommandsDir(cwd) {
4109
- return path7.join(cwd, ".opencode", "commands");
4517
+ return path8.join(cwd, ".opencode", "commands");
4110
4518
  }
4111
4519
  function detectOpenCode(cwd) {
4112
- return hasBinary("opencode") || existsSync14(path7.join(resolveConfigHome(), "opencode")) || existsSync14(path7.join(cwd, ".opencode"));
4520
+ return hasBinary("opencode") || existsSync14(path8.join(resolveConfigHome(), "opencode")) || existsSync14(path8.join(cwd, ".opencode"));
4113
4521
  }
4114
4522
  function renderOpenCodeCommand() {
4115
4523
  return `---
@@ -4137,13 +4545,13 @@ function writeOpenCodeCommand(scope, cwd) {
4137
4545
  if (scope === "auto" && !detected) {
4138
4546
  return { detected: false, action: "not-detected", scope: "off" };
4139
4547
  }
4140
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path7.join(cwd, ".opencode")) ? "project" : "global";
4548
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path8.join(cwd, ".opencode")) ? "project" : "global";
4141
4549
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
4142
- const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
4550
+ const commandFile = path8.join(ensureDir2(commandsDir), "unbrowse.md");
4143
4551
  const content = renderOpenCodeCommand();
4144
4552
  const action2 = existsSync14(commandFile) ? "updated" : "installed";
4145
- mkdirSync8(path7.dirname(commandFile), { recursive: true });
4146
- writeFileSync6(commandFile, content);
4553
+ mkdirSync9(path8.dirname(commandFile), { recursive: true });
4554
+ writeFileSync7(commandFile, content);
4147
4555
  return {
4148
4556
  detected: detected || scope !== "auto",
4149
4557
  action: action2,
@@ -4156,7 +4564,7 @@ async function ensureBrowserEngineInstalled() {
4156
4564
  if (existsSync14(binary)) {
4157
4565
  return { installed: true, action: "already-installed" };
4158
4566
  }
4159
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path7.join(candidate, "build.zig")));
4567
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path8.join(candidate, "build.zig")));
4160
4568
  if (!sourceDir) {
4161
4569
  return {
4162
4570
  installed: false,
@@ -4203,7 +4611,7 @@ async function runSetup(options) {
4203
4611
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
4204
4612
  const walletCheck = checkWalletConfigured();
4205
4613
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
4206
- let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
4614
+ let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os5.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
4207
4615
  if (!skipWalletSetup && !walletCheck.configured) {
4208
4616
  if (!lobsterInstalled) {
4209
4617
  console.log("[unbrowse] Setting up Crossmint wallet (required for earning + payments)...");
@@ -4243,7 +4651,7 @@ async function runSetup(options) {
4243
4651
  return {
4244
4652
  os: {
4245
4653
  platform: process.platform,
4246
- release: os4.release(),
4654
+ release: os5.release(),
4247
4655
  arch: process.arch
4248
4656
  },
4249
4657
  host_environment: hostEnv,
@@ -4257,22 +4665,22 @@ async function runSetup(options) {
4257
4665
 
4258
4666
  // ../../src/runtime/update-hints.ts
4259
4667
  init_paths();
4260
- 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";
4668
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
4669
+ import os6 from "node:os";
4670
+ import path9 from "node:path";
4263
4671
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
4264
4672
  var DEFAULT_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
4265
4673
  function getHomeDir2() {
4266
- return process.env.HOME || os5.homedir();
4674
+ return process.env.HOME || os6.homedir();
4267
4675
  }
4268
4676
  function getConfigDir3() {
4269
4677
  if (process.env.UNBROWSE_CONFIG_DIR)
4270
4678
  return process.env.UNBROWSE_CONFIG_DIR;
4271
- return path8.join(getHomeDir2(), ".unbrowse");
4679
+ return path9.join(getHomeDir2(), ".unbrowse");
4272
4680
  }
4273
4681
  function ensureDir4(dir) {
4274
4682
  if (!existsSync15(dir))
4275
- mkdirSync9(dir, { recursive: true });
4683
+ mkdirSync10(dir, { recursive: true });
4276
4684
  return dir;
4277
4685
  }
4278
4686
  function readJsonFile2(file) {
@@ -4283,23 +4691,23 @@ function readJsonFile2(file) {
4283
4691
  }
4284
4692
  }
4285
4693
  function writeJsonFile2(file, value) {
4286
- ensureDir4(path8.dirname(file));
4287
- writeFileSync7(file, `${JSON.stringify(value, null, 2)}
4694
+ ensureDir4(path9.dirname(file));
4695
+ writeFileSync8(file, `${JSON.stringify(value, null, 2)}
4288
4696
  `);
4289
4697
  }
4290
4698
  function getInstallSourcePath2() {
4291
- return path8.join(getConfigDir3(), "install-source.json");
4699
+ return path9.join(getConfigDir3(), "install-source.json");
4292
4700
  }
4293
4701
  function getUpdateCheckStatePath() {
4294
- return path8.join(getConfigDir3(), "update-check.json");
4702
+ return path9.join(getConfigDir3(), "update-check.json");
4295
4703
  }
4296
4704
  function detectRepoRoot2(start2) {
4297
- let dir = path8.resolve(start2);
4298
- const root = path8.parse(dir).root;
4705
+ let dir = path9.resolve(start2);
4706
+ const root = path9.parse(dir).root;
4299
4707
  while (dir !== root) {
4300
- if (existsSync15(path8.join(dir, ".git")))
4708
+ if (existsSync15(path9.join(dir, ".git")))
4301
4709
  return dir;
4302
- dir = path8.dirname(dir);
4710
+ dir = path9.dirname(dir);
4303
4711
  }
4304
4712
  return;
4305
4713
  }
@@ -4308,7 +4716,7 @@ function detectInstallMethod2(packageRoot) {
4308
4716
  return "repo-clone";
4309
4717
  if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
4310
4718
  return "npm-global";
4311
- if (packageRoot.includes(`${path8.sep}node_modules${path8.sep}`))
4719
+ if (packageRoot.includes(`${path9.sep}node_modules${path9.sep}`))
4312
4720
  return "npm-global";
4313
4721
  return detectRepoRoot2(packageRoot) ? "repo-clone" : "unknown";
4314
4722
  }
@@ -4318,19 +4726,19 @@ function detectInstallHost2(repoRoot) {
4318
4726
  return explicit;
4319
4727
  if (!repoRoot)
4320
4728
  return "unknown";
4321
- const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir2(), ".codex");
4322
- if (repoRoot === path8.join(codexHome, "skills", "unbrowse"))
4729
+ const codexHome = process.env.CODEX_HOME || path9.join(getHomeDir2(), ".codex");
4730
+ if (repoRoot === path9.join(codexHome, "skills", "unbrowse"))
4323
4731
  return "codex";
4324
- if (repoRoot === path8.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
4732
+ if (repoRoot === path9.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
4325
4733
  return "claude";
4326
- if (repoRoot === path8.join(getHomeDir2(), "unbrowse"))
4734
+ if (repoRoot === path9.join(getHomeDir2(), "unbrowse"))
4327
4735
  return "off";
4328
4736
  return "unknown";
4329
4737
  }
4330
4738
  function getInstalledVersion(metaUrl) {
4331
4739
  const packageRoot = getPackageRoot(metaUrl);
4332
4740
  try {
4333
- const pkg = JSON.parse(readFileSync9(path8.join(packageRoot, "package.json"), "utf8"));
4741
+ const pkg = JSON.parse(readFileSync9(path9.join(packageRoot, "package.json"), "utf8"));
4334
4742
  return pkg.version ?? "unknown";
4335
4743
  } catch {
4336
4744
  return "unknown";
@@ -4426,11 +4834,183 @@ function recordUpdateHint(latestVersion) {
4426
4834
  });
4427
4835
  }
4428
4836
 
4837
+ // ../../src/cli-setup.ts
4838
+ init_contribution();
4839
+ import { createInterface as createInterface2 } from "node:readline";
4840
+ async function defaultReadChoice(allowed, dflt) {
4841
+ const isInteractive = !!process.stdin.isTTY && !!process.stdout.isTTY && process.env.UNBROWSE_NON_INTERACTIVE !== "1";
4842
+ if (!isInteractive)
4843
+ return dflt;
4844
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
4845
+ try {
4846
+ for (;; ) {
4847
+ const answer = await new Promise((resolve) => {
4848
+ rl.question(`Choice [${dflt}]: `, resolve);
4849
+ });
4850
+ const trimmed = answer.trim();
4851
+ if (!trimmed)
4852
+ return dflt;
4853
+ const n = parseInt(trimmed, 10);
4854
+ if (allowed.includes(n))
4855
+ return n;
4856
+ console.log(`Please enter ${allowed.join(", ")} (or press Enter for ${dflt}).`);
4857
+ }
4858
+ } finally {
4859
+ rl.close();
4860
+ }
4861
+ }
4862
+ function shouldSkipInteractivePrompt(opts) {
4863
+ if (opts.readChoice)
4864
+ return false;
4865
+ return process.env.UNBROWSE_NON_INTERACTIVE === "1" || !process.stdin.isTTY || !process.stdout.isTTY;
4866
+ }
4867
+ async function promptContributionMode(opts = {}) {
4868
+ const log2 = opts.log ?? ((msg) => console.log(msg));
4869
+ const cfg = getContributionConfig();
4870
+ if (!opts.force && cfg.contribution.set_via && cfg.contribution.set_via !== "default") {
4871
+ return null;
4872
+ }
4873
+ if (shouldSkipInteractivePrompt(opts)) {
4874
+ return null;
4875
+ }
4876
+ log2("");
4877
+ log2("How do you want unbrowse to handle the routes you discover?");
4878
+ log2("");
4879
+ log2(" [1] Private (default) — cache locally, never publish");
4880
+ log2(" [2] Share — contribute pointers to the marketplace");
4881
+ log2(" [3] Share + earn — contribute and add a wallet for x402 rev-share");
4882
+ log2("");
4883
+ const reader = opts.readChoice ?? defaultReadChoice;
4884
+ const choice = await reader([1, 2, 3], 1);
4885
+ const set_via = opts.force ? "mode-command" : "setup-prompt";
4886
+ if (choice === 1) {
4887
+ setContributionConfig({
4888
+ contribution: { share_pointers: false, set_via },
4889
+ rev_share: { opted_in: false }
4890
+ });
4891
+ log2("Private mode set. Run `unbrowse mode` anytime to change.");
4892
+ } else if (choice === 2) {
4893
+ setContributionConfig({
4894
+ contribution: { share_pointers: true, set_via },
4895
+ rev_share: { opted_in: false }
4896
+ });
4897
+ log2("Sharing pointers enabled. Add a wallet via `unbrowse mode` to opt into rev-share.");
4898
+ } else {
4899
+ setContributionConfig({
4900
+ contribution: { share_pointers: true, set_via },
4901
+ rev_share: { opted_in: true }
4902
+ });
4903
+ log2("Sharing + rev-share enabled. Run `unbrowse wallet` to add your wallet for payouts.");
4904
+ }
4905
+ return choice;
4906
+ }
4907
+ function maybeShowContributionNotice() {
4908
+ const cfg = getContributionConfig();
4909
+ if (cfg.contribution.set_via !== "default")
4910
+ return false;
4911
+ const remaining = cfg.notice_shown_count ?? 0;
4912
+ if (remaining <= 0)
4913
+ return false;
4914
+ 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"})
4915
+ `);
4916
+ decrementNoticeCounter();
4917
+ return true;
4918
+ }
4919
+
4920
+ // ../../src/config/contribution.ts
4921
+ import fs3 from "node:fs";
4922
+ import path10 from "node:path";
4923
+ import os7 from "node:os";
4924
+ function configPath2() {
4925
+ return process.env.UNBROWSE_CONFIG_PATH || path10.join(os7.homedir(), ".unbrowse", "config.json");
4926
+ }
4927
+ var DEFAULT2 = {
4928
+ contribution: { share_pointers: false, set_via: "default" },
4929
+ rev_share: { opted_in: false },
4930
+ notice_shown_count: 0
4931
+ };
4932
+ function freshDefault2() {
4933
+ return {
4934
+ contribution: { ...DEFAULT2.contribution },
4935
+ rev_share: { ...DEFAULT2.rev_share },
4936
+ notice_shown_count: 0
4937
+ };
4938
+ }
4939
+ var cached2 = null;
4940
+ function getContributionConfig2() {
4941
+ if (cached2)
4942
+ return cached2;
4943
+ const p = configPath2();
4944
+ let fileExisted = false;
4945
+ let raw = null;
4946
+ try {
4947
+ const content = fs3.readFileSync(p, "utf-8");
4948
+ fileExisted = true;
4949
+ if (content.trim().length > 0) {
4950
+ raw = JSON.parse(content);
4951
+ }
4952
+ } catch {}
4953
+ if (!raw) {
4954
+ cached2 = freshDefault2();
4955
+ return cached2;
4956
+ }
4957
+ const contributionRaw = raw.contribution ?? null;
4958
+ const revShareRaw = raw.rev_share ?? null;
4959
+ const isExistingUserMigration = fileExisted && !contributionRaw;
4960
+ cached2 = {
4961
+ contribution: {
4962
+ share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT2.contribution.share_pointers),
4963
+ set_via: contributionRaw?.set_via ?? DEFAULT2.contribution.set_via,
4964
+ ...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
4965
+ },
4966
+ rev_share: {
4967
+ opted_in: !!(revShareRaw?.opted_in ?? DEFAULT2.rev_share.opted_in),
4968
+ ...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
4969
+ },
4970
+ notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
4971
+ };
4972
+ if (isExistingUserMigration) {
4973
+ try {
4974
+ const merged = { ...raw, ...cached2 };
4975
+ fs3.mkdirSync(path10.dirname(p), { recursive: true });
4976
+ fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
4977
+ } catch {}
4978
+ }
4979
+ return cached2;
4980
+ }
4981
+ function setContributionConfig2(updates) {
4982
+ const current = getContributionConfig2();
4983
+ const next = {
4984
+ contribution: {
4985
+ ...current.contribution,
4986
+ ...updates.contribution ?? {},
4987
+ set_at: new Date().toISOString()
4988
+ },
4989
+ rev_share: {
4990
+ ...current.rev_share,
4991
+ ...updates.rev_share ?? {}
4992
+ },
4993
+ notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
4994
+ };
4995
+ const p = configPath2();
4996
+ let existing = {};
4997
+ try {
4998
+ const content = fs3.readFileSync(p, "utf-8");
4999
+ if (content.trim())
5000
+ existing = JSON.parse(content);
5001
+ } catch {}
5002
+ const merged = { ...existing, ...next };
5003
+ fs3.mkdirSync(path10.dirname(p), { recursive: true });
5004
+ fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
5005
+ cached2 = next;
5006
+ }
5007
+
4429
5008
  // ../../src/cli.ts
4430
5009
  loadEnv({ quiet: true });
4431
5010
  loadEnv({ path: ".env.runtime", quiet: true });
4432
5011
  var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
4433
5012
  var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
5013
+ var FRONTEND_URL = (process.env.UNBROWSE_FRONTEND_URL || process.env.PUBLIC_FRONTEND_URL || "https://www.unbrowse.ai").replace(/\/+$/, "");
4434
5014
  var walletNudgeShown = false;
4435
5015
  function parseArgs(argv) {
4436
5016
  const raw = argv.slice(2);
@@ -4457,8 +5037,12 @@ function parseArgs(argv) {
4457
5037
  if (valueExpectedFlags.has(key)) {
4458
5038
  if (next === undefined)
4459
5039
  die(`--${key} requires a value`);
4460
- flags[key] = next;
4461
- i++;
5040
+ if (next === "-p" || next === "--param" || next.startsWith("--")) {
5041
+ flags[key] = true;
5042
+ } else {
5043
+ flags[key] = next;
5044
+ i++;
5045
+ }
4462
5046
  } else if (!next || next.startsWith("--") || next === "-p" || next === "--param") {
4463
5047
  flags[key] = true;
4464
5048
  } else {
@@ -4471,8 +5055,8 @@ function parseArgs(argv) {
4471
5055
  }
4472
5056
  return { command, args: positional, flags, params };
4473
5057
  }
4474
- async function api2(method, path9, body) {
4475
- let target = `${BASE_URL}${path9}`;
5058
+ async function api2(method, path11, body, opts) {
5059
+ let target = `${BASE_URL}${path11}`;
4476
5060
  let requestBody = body;
4477
5061
  if (method === "GET" && body && typeof body === "object") {
4478
5062
  const params = new URLSearchParams;
@@ -4486,14 +5070,34 @@ async function api2(method, path9, body) {
4486
5070
  target += `${target.includes("?") ? "&" : "?"}${query}`;
4487
5071
  requestBody = undefined;
4488
5072
  }
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
- });
5073
+ const ctrl = new AbortController;
5074
+ const timeoutMs = opts?.timeoutMs;
5075
+ const timer = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
5076
+ let res;
5077
+ try {
5078
+ res = await fetch(target, {
5079
+ method,
5080
+ headers: {
5081
+ ...requestBody ? { "Content-Type": "application/json" } : {},
5082
+ "x-unbrowse-client-id": CLI_CLIENT_ID
5083
+ },
5084
+ body: requestBody ? JSON.stringify(requestBody) : undefined,
5085
+ signal: ctrl.signal
5086
+ });
5087
+ } catch (err) {
5088
+ if (timer)
5089
+ clearTimeout(timer);
5090
+ if (err?.name === "AbortError") {
5091
+ return {
5092
+ error: "cli_timeout",
5093
+ message: `CLI gave up waiting on local server after ${timeoutMs}ms. Try: pkill -9 -f 'unbrowse|kuri'`
5094
+ };
5095
+ }
5096
+ throw err;
5097
+ } finally {
5098
+ if (timer)
5099
+ clearTimeout(timer);
5100
+ }
4497
5101
  if (!res.ok && res.headers.get("content-type")?.includes("json")) {
4498
5102
  return res.json();
4499
5103
  }
@@ -4514,6 +5118,14 @@ function info(msg) {
4514
5118
  process.stderr.write(`[unbrowse] ${msg}
4515
5119
  `);
4516
5120
  }
5121
+ function openUrl(url) {
5122
+ if (process.env.UNBROWSE_OPEN_BROWSER === "0")
5123
+ return;
5124
+ try {
5125
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
5126
+ spawn3(cmd, [url], { detached: true, stdio: "ignore" }).unref();
5127
+ } catch {}
5128
+ }
4517
5129
  function resolveResultError(result) {
4518
5130
  return result.result?.error ?? result.error;
4519
5131
  }
@@ -4523,6 +5135,9 @@ function resolveLoginUrl(result, fallbackUrl) {
4523
5135
  function isResolveSuccessResult(result) {
4524
5136
  if (resolveResultError(result))
4525
5137
  return false;
5138
+ const status = result.result?.status;
5139
+ if (status === "no_match" || status === "auth_required" || status === "error")
5140
+ return false;
4526
5141
  return !!result.result || Array.isArray(result.available_endpoints);
4527
5142
  }
4528
5143
  async function withPendingNotice(promise, message, delayMs = 3000) {
@@ -4714,6 +5329,7 @@ async function cmdResolve(flags) {
4714
5329
  const intent = flags.intent;
4715
5330
  if (!intent)
4716
5331
  die("--intent is required");
5332
+ maybeShowContributionNotice();
4717
5333
  const hostType = detectTelemetryHostType();
4718
5334
  await ensureCliInstallTracked(hostType);
4719
5335
  await recordFunnelTelemetryEvent("cli_invoked", {
@@ -4775,10 +5391,19 @@ async function cmdResolve(flags) {
4775
5391
  body.force_capture = true;
4776
5392
  if (flags["skip-robots"])
4777
5393
  body.skip_robots_check = true;
5394
+ const budgetFlag = flags.budget;
5395
+ if (typeof budgetFlag === "string") {
5396
+ const parsed = parseInt(budgetFlag, 10);
5397
+ if (Number.isFinite(parsed) && parsed > 0)
5398
+ body.budget_ms = parsed;
5399
+ } else if (typeof budgetFlag === "number" && Number.isFinite(budgetFlag) && budgetFlag > 0) {
5400
+ body.budget_ms = budgetFlag;
5401
+ }
4778
5402
  body.projection = { raw: true };
4779
5403
  const startedAt = Date.now();
4780
5404
  async function resolveOnce(message = "Still working. Searching cached routes...") {
4781
- return withPendingNotice(api2("POST", "/v1/intent/resolve", body), message);
5405
+ const cliTimeoutMs = (typeof body.budget_ms === "number" ? body.budget_ms : 8000) + 30000;
5406
+ return withPendingNotice(api2("POST", "/v1/intent/resolve", body, { timeoutMs: cliTimeoutMs }), message);
4782
5407
  }
4783
5408
  let result = await resolveOnce();
4784
5409
  const resultError = resolveResultError(result);
@@ -4798,8 +5423,13 @@ async function cmdResolve(flags) {
4798
5423
  const skillId = resolveSkillId();
4799
5424
  if (skillId && endpoints.length > 0) {
4800
5425
  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...");
5426
+ if (endpointNeedsThirdPartyTermsConfirmation(bestEndpoint) && !flags["confirm-third-party-terms"]) {
5427
+ 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.
5428
+ `);
5429
+ } else {
5430
+ info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
5431
+ result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
5432
+ }
4803
5433
  }
4804
5434
  }
4805
5435
  if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
@@ -4861,8 +5491,8 @@ async function cmdResolve(flags) {
4861
5491
  throw error;
4862
5492
  }
4863
5493
  }
4864
- function drillPath(data, path9) {
4865
- const segments = path9.split(/\./).flatMap((s) => {
5494
+ function drillPath(data, path11) {
5495
+ const segments = path11.split(/\./).flatMap((s) => {
4866
5496
  const m = s.match(/^(.+)\[\]$/);
4867
5497
  return m ? [m[1], "[]"] : [s];
4868
5498
  });
@@ -4889,9 +5519,9 @@ function drillPath(data, path9) {
4889
5519
  }
4890
5520
  return values;
4891
5521
  }
4892
- function resolveDotPath(obj, path9) {
5522
+ function resolveDotPath(obj, path11) {
4893
5523
  let cur = obj;
4894
- for (const key of path9.split(".")) {
5524
+ for (const key of path11.split(".")) {
4895
5525
  if (cur == null || typeof cur !== "object")
4896
5526
  return;
4897
5527
  cur = cur[key];
@@ -4908,8 +5538,8 @@ function applyExtract(items, extractSpec) {
4908
5538
  return items.map((item) => {
4909
5539
  const row = {};
4910
5540
  let hasValue = false;
4911
- for (const { alias, path: path9 } of fields) {
4912
- const val = resolveDotPath(item, path9);
5541
+ for (const { alias, path: path11 } of fields) {
5542
+ const val = resolveDotPath(item, path11);
4913
5543
  row[alias] = val ?? null;
4914
5544
  if (val != null)
4915
5545
  hasValue = true;
@@ -4940,6 +5570,7 @@ async function cmdExecute(flags) {
4940
5570
  const skillId = flags.skill;
4941
5571
  if (!skillId)
4942
5572
  die("--skill is required");
5573
+ maybeShowContributionNotice();
4943
5574
  const hostType = detectTelemetryHostType();
4944
5575
  await ensureCliInstallTracked(hostType);
4945
5576
  await recordFunnelTelemetryEvent("cli_invoked", {
@@ -5225,7 +5856,67 @@ async function cmdCleanupStale(flags) {
5225
5856
  output(await withPendingNotice(api2("POST", "/v1/stale/cleanup", body), "Cleaning stale endpoints..."), !!flags.pretty);
5226
5857
  }
5227
5858
  async function cmdSearch(flags) {
5228
- await cmdResolve(flags);
5859
+ const intent = flags.intent;
5860
+ const hostType = detectTelemetryHostType();
5861
+ const { decodeTelemetryAttribution: decodeTelemetryAttribution3 } = await Promise.resolve().then(() => (init_telemetry_attribution2(), exports_telemetry_attribution));
5862
+ const attr = decodeTelemetryAttribution3(process.env.UNBROWSE_ATTRIBUTION_B64) ?? {};
5863
+ const attrProps = {};
5864
+ for (const k of ["channel", "campaign_id", "content_id", "variant_id"]) {
5865
+ const v = attr[k];
5866
+ if (v != null)
5867
+ attrProps[k] = v;
5868
+ }
5869
+ const domain = telemetryDomainFromInput(flags.domain, flags.url);
5870
+ if (intent) {
5871
+ await recordFunnelTelemetryEvent("search_started", {
5872
+ source: "cli",
5873
+ hostType,
5874
+ properties: {
5875
+ command: "search",
5876
+ intent,
5877
+ domain,
5878
+ url: typeof flags.url === "string" ? flags.url : null,
5879
+ ...attrProps
5880
+ }
5881
+ });
5882
+ }
5883
+ let resultCount = 0;
5884
+ try {
5885
+ if (intent && domain) {
5886
+ try {
5887
+ const searchRes = await api2("GET", `/v1/search/domain?intent=${encodeURIComponent(intent)}&domain=${encodeURIComponent(domain)}`);
5888
+ resultCount = Array.isArray(searchRes?.results) ? searchRes.results.length : 0;
5889
+ } catch {}
5890
+ }
5891
+ await cmdResolve(flags);
5892
+ if (intent) {
5893
+ await recordFunnelTelemetryEvent("search_completed", {
5894
+ source: "cli",
5895
+ hostType,
5896
+ properties: {
5897
+ command: "search",
5898
+ intent,
5899
+ domain,
5900
+ result_count: resultCount,
5901
+ ...attrProps
5902
+ }
5903
+ });
5904
+ }
5905
+ } catch (err) {
5906
+ if (intent) {
5907
+ await recordFunnelTelemetryEvent("search_failed", {
5908
+ source: "cli",
5909
+ hostType,
5910
+ properties: {
5911
+ command: "search",
5912
+ intent,
5913
+ error: err instanceof Error ? err.message : String(err),
5914
+ ...attrProps
5915
+ }
5916
+ });
5917
+ }
5918
+ throw err;
5919
+ }
5229
5920
  }
5230
5921
  async function cmdSessions(flags) {
5231
5922
  const domain = flags.domain;
@@ -5267,7 +5958,7 @@ async function cmdSetup(flags) {
5267
5958
  info("Wallet not paired \u2014 you won't earn when other agents use routes you discovered.");
5268
5959
  info("Run: npx @crossmint/lobster-cli setup");
5269
5960
  } else {
5270
- info("No wallet configured \u2014 you earn when other agents reuse routes you discovered.");
5961
+ info("No wallet configured \u2014 local indexing works, but payout needs a wallet.");
5271
5962
  info("Set up a wallet to start earning:");
5272
5963
  info(" npx @crossmint/lobster-cli setup");
5273
5964
  }
@@ -5337,6 +6028,10 @@ async function cmdSetup(flags) {
5337
6028
  output(report, true);
5338
6029
  if (report.browser_engine.action === "failed")
5339
6030
  process.exit(1);
6031
+ if (getApiKey()) {
6032
+ info("Dashboard connected:");
6033
+ info(" unbrowse dashboard");
6034
+ }
5340
6035
  try {
5341
6036
  info("Trying your first resolve...");
5342
6037
  const demoUrl = "https://jsonplaceholder.typicode.com";
@@ -5373,14 +6068,203 @@ async function cmdSetup(flags) {
5373
6068
  info("That's unbrowse. Try your own:");
5374
6069
  info(' unbrowse resolve --intent "search for shoes" --url "https://amazon.com"');
5375
6070
  } else {
5376
- info("Guided resolve returned no results \u2014 try manually:");
5377
- info(' unbrowse resolve --intent "list posts" --url "https://jsonplaceholder.typicode.com"');
6071
+ const inner = resolveResult.result;
6072
+ const nextStep = inner?.next_step;
6073
+ const command = typeof nextStep?.command === "string" ? nextStep.command : 'unbrowse capture --url "https://jsonplaceholder.typicode.com" --intent "list all posts"';
6074
+ info("No reusable route found on the demo site yet.");
6075
+ info("Next step:");
6076
+ info(` ${command}`);
6077
+ info("");
6078
+ info("Try your own:");
6079
+ info(' unbrowse resolve --intent "search for shoes" --url "https://amazon.com"');
5378
6080
  }
5379
6081
  } catch {
5380
6082
  info("Setup complete. Try your first resolve:");
5381
6083
  info(' unbrowse resolve --intent "list posts" --url "https://jsonplaceholder.typicode.com"');
5382
6084
  }
5383
6085
  }
6086
+ async function cmdMode(_flags) {
6087
+ await promptContributionMode({ force: true });
6088
+ await syncContributionPreferenceToServer();
6089
+ }
6090
+ async function syncContributionPreferenceToServer() {
6091
+ const cfg = loadConfig();
6092
+ if (!cfg?.api_key)
6093
+ return;
6094
+ const share_pointers = !!getContributionConfig2().contribution.share_pointers;
6095
+ try {
6096
+ await pushAccountPreferences({ share_pointers });
6097
+ info("Synced preference to your account.");
6098
+ } catch (err) {
6099
+ const msg = err.message ?? "";
6100
+ if (msg.includes("account_required") || msg.includes("HTTP 403"))
6101
+ return;
6102
+ info(`Local mode set, but server sync failed: ${msg}`);
6103
+ }
6104
+ }
6105
+ async function refreshContributionPreferenceFromServer(verbose = false) {
6106
+ const cfg = loadConfig();
6107
+ if (!cfg?.api_key)
6108
+ return false;
6109
+ try {
6110
+ const serverPrefs = await fetchAccountPreferences();
6111
+ if (!serverPrefs)
6112
+ return false;
6113
+ const local = getContributionConfig2();
6114
+ if (local.contribution.share_pointers !== serverPrefs.share_pointers) {
6115
+ setContributionConfig2({
6116
+ contribution: { share_pointers: serverPrefs.share_pointers, set_via: "mode-command" }
6117
+ });
6118
+ if (verbose)
6119
+ info(`Synced auto-publish from dashboard: ${serverPrefs.share_pointers ? "ON" : "off"}.`);
6120
+ }
6121
+ return true;
6122
+ } catch (err) {
6123
+ if (verbose)
6124
+ info(`Dashboard preference sync failed: ${err.message}`);
6125
+ return false;
6126
+ }
6127
+ }
6128
+ async function cmdAccount(flags) {
6129
+ if (flags["reset-key"]) {
6130
+ await cmdRegister({
6131
+ reset: true,
6132
+ email: typeof flags.email === "string" ? flags.email : undefined,
6133
+ "no-prompt": flags["no-prompt"]
6134
+ });
6135
+ return;
6136
+ }
6137
+ await refreshContributionPreferenceFromServer(false);
6138
+ const cfg = loadConfig();
6139
+ const contribution = getContributionConfig2();
6140
+ const payload = {
6141
+ signed_in: !!cfg?.api_key,
6142
+ agent_id: cfg?.agent_id ?? null,
6143
+ agent_name: cfg?.agent_name ?? null,
6144
+ email: cfg?.email ?? null,
6145
+ user_id: cfg?.user_id ?? null,
6146
+ wallet_address: cfg?.wallet_address ?? null,
6147
+ wallet_provider: cfg?.wallet_provider ?? null,
6148
+ dashboard_url: `${FRONTEND_URL}/dashboard`,
6149
+ local_server: BASE_URL,
6150
+ auto_publish: contribution.contribution.share_pointers,
6151
+ rev_share: contribution.rev_share.opted_in
6152
+ };
6153
+ if (flags.json || flags.pretty) {
6154
+ output(payload, !!flags.pretty);
6155
+ return;
6156
+ }
6157
+ info("Unbrowse account");
6158
+ info(` signed_in: ${payload.signed_in ? "yes" : "no"}`);
6159
+ info(` email: ${payload.email ?? "(none)"}`);
6160
+ info(` agent_id: ${payload.agent_id ?? "(none)"}`);
6161
+ info(` wallet: ${payload.wallet_address ?? "(none)"}`);
6162
+ info(` auto_publish: ${payload.auto_publish ? "on" : "off"}`);
6163
+ info(` dashboard: ${payload.dashboard_url}`);
6164
+ output(payload, false);
6165
+ }
6166
+ async function cmdDashboard(flags) {
6167
+ await refreshContributionPreferenceFromServer(false);
6168
+ const cfg = loadConfig();
6169
+ if (!cfg?.api_key) {
6170
+ const loginUrl = `${FRONTEND_URL}/login`;
6171
+ info("No account-bound CLI key found. Opening website sign-in.");
6172
+ if (!flags["no-open"])
6173
+ openUrl(loginUrl);
6174
+ output({ status: "login_required", url: loginUrl }, !!flags.pretty);
6175
+ return;
6176
+ }
6177
+ await ensureLocalServer(BASE_URL, false, import.meta.url);
6178
+ const pair = createDashboardPairingToken();
6179
+ const url = `${FRONTEND_URL}/login?local=${encodeURIComponent(BASE_URL)}&pair=${encodeURIComponent(pair.token)}`;
6180
+ if (!flags["no-open"])
6181
+ openUrl(url);
6182
+ info("Opening dashboard and pairing this CLI install.");
6183
+ output({
6184
+ status: "pairing_started",
6185
+ url,
6186
+ local_server: BASE_URL,
6187
+ expires_at: pair.expires_at
6188
+ }, !!flags.pretty);
6189
+ }
6190
+ async function runPostSetupContributionPrompt() {
6191
+ try {
6192
+ await promptContributionMode({ force: false });
6193
+ } catch {}
6194
+ }
6195
+ async function cmdCapture(flags) {
6196
+ const url = flags.url;
6197
+ const intent = flags.intent || "capture";
6198
+ if (!url)
6199
+ die("--url is required");
6200
+ maybeShowContributionNotice();
6201
+ const t0 = Date.now();
6202
+ const result = await api2("POST", "/v1/capture", { url, intent });
6203
+ const endpoints = Array.isArray(result.endpoints) ? result.endpoints : Array.isArray(result.available_endpoints) ? result.available_endpoints : [];
6204
+ const skill = result.skill ?? null;
6205
+ const skillId = result.skill_id ?? skill?.skill_id ?? (typeof result.learned_skill_id === "string" ? result.learned_skill_id : undefined);
6206
+ const isThinDocumentOnly = endpoints.length === 1 && (() => {
6207
+ const e0 = endpoints[0];
6208
+ if (!e0 || e0.method !== "GET")
6209
+ return false;
6210
+ const tmpl = (e0.url_template ?? "").split("?")[0].replace(/\/$/, "");
6211
+ const target = url.split("?")[0].replace(/\/$/, "");
6212
+ return tmpl === target;
6213
+ })();
6214
+ const escapedIntent = intent.replace(/"/g, "\\\"");
6215
+ const escapedUrl = url.replace(/"/g, "\\\"");
6216
+ const envelope = {
6217
+ skill_id: skillId,
6218
+ endpoints_discovered: typeof result.endpoints_discovered === "number" ? result.endpoints_discovered : endpoints.length,
6219
+ marketplace_published: !!result.marketplace_published,
6220
+ ms: typeof result.ms === "number" ? result.ms : Date.now() - t0,
6221
+ next_step: endpoints.length === 0 ? "no endpoints discovered; site may need authentication or different intent" : `unbrowse resolve --intent "${escapedIntent}" --url "${escapedUrl}"`,
6222
+ ...isThinDocumentOnly ? { capture_pattern: "doc_only", capture_observation: "only the input URL was captured (1 GET, no XHR fired during the auto-capture window)" } : {},
6223
+ ...result.error ? { error: result.error } : {},
6224
+ ...result.captured_meta ? { captured_meta: result.captured_meta } : {},
6225
+ ...typeof result.capture_path === "string" ? { capture_path: result.capture_path } : {},
6226
+ ...result.prior_domain_note ? { prior_domain_note: result.prior_domain_note } : {},
6227
+ ...result.note_evidence ? { note_evidence: result.note_evidence } : {}
6228
+ };
6229
+ output(envelope, !!flags.pretty);
6230
+ }
6231
+ async function cmdNote(flags, args) {
6232
+ const subRaw = args?.[0] ?? flags.action;
6233
+ const sub = (typeof subRaw === "string" ? subRaw : "").toLowerCase();
6234
+ if (!sub || !["read", "write", "list"].includes(sub)) {
6235
+ die('usage: unbrowse note <read|write|list> --domain <domain> [--body "..."]');
6236
+ }
6237
+ if (sub === "list") {
6238
+ const { homedir: homedir9 } = await import("os");
6239
+ const { join: join15 } = await import("path");
6240
+ const { readdirSync: readdirSync7, existsSync: existsSync19 } = await import("fs");
6241
+ const profile = process.env.UNBROWSE_PROFILE ?? "";
6242
+ const dir = process.env.UNBROWSE_DOMAIN_NOTES_DIR ?? (profile ? join15(homedir9(), ".unbrowse", "profiles", profile, "domain-notes") : join15(homedir9(), ".unbrowse", "domain-notes"));
6243
+ if (!existsSync19(dir)) {
6244
+ output({ notes: [], dir }, !!flags.pretty);
6245
+ return;
6246
+ }
6247
+ const files = readdirSync7(dir).filter((f) => f.endsWith(".md"));
6248
+ output({ dir, notes: files.map((f) => f.replace(/\.md$/, "")) }, !!flags.pretty);
6249
+ return;
6250
+ }
6251
+ const domain = flags.domain;
6252
+ if (!domain)
6253
+ die("--domain required");
6254
+ if (sub === "read") {
6255
+ const res2 = await api2("GET", `/v1/domain-notes/${encodeURIComponent(domain)}`).catch((err) => ({
6256
+ error: err.message
6257
+ }));
6258
+ output(res2, !!flags.pretty);
6259
+ return;
6260
+ }
6261
+ const body = flags.body;
6262
+ if (typeof body !== "string" || body.trim().length === 0) {
6263
+ die("--body required (non-empty markdown string)");
6264
+ }
6265
+ const res = await api2("POST", `/v1/domain-notes/${encodeURIComponent(domain)}`, { body });
6266
+ output(res, !!flags.pretty);
6267
+ }
5384
6268
  var CLI_REFERENCE = {
5385
6269
  commands: [
5386
6270
  { name: "health", usage: "", desc: "Server health check" },
@@ -5425,7 +6309,12 @@ var CLI_REFERENCE = {
5425
6309
  { name: "earnings", usage: "[--json]", desc: "Show your credit balance, earnings from indexing, and spending" },
5426
6310
  { 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
6311
  { 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" }
6312
+ { name: "register", usage: "[--email lewis@example.com] [--reset] [--no-prompt]", desc: "Register an API key. With --reset, discard the local cached key first; with --email, mint an account-bound key." },
6313
+ { name: "account", usage: "[--json] [--pretty] [--reset-key] [--email lewis@example.com]", desc: "Show local account, dashboard link, wallet, and contribution mode; --reset-key forces local key reset." },
6314
+ { name: "dashboard", usage: "[--no-open] [--pretty]", desc: "Open the website dashboard and pair it to this CLI install through localhost" },
6315
+ { name: "mode", usage: "", desc: "Re-prompt for contribution mode (private / share / share + earn)" },
6316
+ { 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`." },
6317
+ { 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
6318
  ],
5430
6319
  globalFlags: [
5431
6320
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -5882,6 +6771,7 @@ async function cmdGo(args, flags) {
5882
6771
  const url = args[0] ?? flags.url;
5883
6772
  if (!url)
5884
6773
  die("Usage: unbrowse go <url>");
6774
+ maybeShowContributionNotice();
5885
6775
  output(await api2("POST", "/v1/browse/go", {
5886
6776
  url,
5887
6777
  ...typeof flags.session === "string" ? { session_id: flags.session } : {}
@@ -6019,7 +6909,62 @@ async function cmdClose(flags) {
6019
6909
  output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
6020
6910
  }
6021
6911
  async function cmdRegister(flags) {
6022
- if (getApiKey()) {
6912
+ const reset = flags.reset === true || flags.force === true || flags["reset-key"] === true;
6913
+ const previousConfig = reset ? loadConfig() : null;
6914
+ if (reset) {
6915
+ const envKey = process.env.UNBROWSE_API_KEY?.trim();
6916
+ const result = resetLocalRegistration();
6917
+ delete process.env.UNBROWSE_API_KEY;
6918
+ info(`${result.removed ? "Removed" : "No"} local API key cache at ${result.config_path}.`);
6919
+ if (envKey) {
6920
+ info("Ignoring UNBROWSE_API_KEY for this reset run. Remove or update that env var in your shell, or it will override the saved key next time.");
6921
+ }
6922
+ if (typeof flags.email !== "string" && previousConfig?.email) {
6923
+ flags.email = previousConfig.email;
6924
+ }
6925
+ }
6926
+ if (typeof flags.email === "string" && flags.email.length > 0) {
6927
+ const email = flags.email;
6928
+ if (!reset && getApiKey()) {
6929
+ info("Already registered. Re-running with --email will mint a new key and overwrite ~/.unbrowse/config.json.");
6930
+ }
6931
+ info(`Sending magic link to ${email}\u2026`);
6932
+ const result = await magicRegister({
6933
+ email,
6934
+ openBrowser: (url) => {
6935
+ info(`Opening browser: ${url}`);
6936
+ try {
6937
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
6938
+ spawn3(cmd, [url], { detached: true, stdio: "ignore" }).unref();
6939
+ } catch {}
6940
+ }
6941
+ });
6942
+ saveConfig({
6943
+ api_key: result.api_key,
6944
+ agent_id: result.agent_id,
6945
+ agent_name: result.email,
6946
+ registered_at: new Date().toISOString(),
6947
+ tos_accepted_version: null,
6948
+ tos_accepted_at: null,
6949
+ email: result.email,
6950
+ user_id: result.user_id
6951
+ });
6952
+ process.env.UNBROWSE_API_KEY = result.api_key;
6953
+ info(`Signed in as ${result.email}. API key saved to ~/.unbrowse/config.json.`);
6954
+ info("Open your dashboard:");
6955
+ info(" unbrowse dashboard");
6956
+ try {
6957
+ const serverPrefs = await fetchAccountPreferences();
6958
+ if (serverPrefs) {
6959
+ setContributionConfig2({
6960
+ contribution: { share_pointers: serverPrefs.share_pointers, set_via: "mode-command" }
6961
+ });
6962
+ info(`Auto-publish to marketplace: ${serverPrefs.share_pointers ? "ON" : "off"} (synced from your account).`);
6963
+ }
6964
+ } catch {}
6965
+ return;
6966
+ }
6967
+ if (!reset && getApiKey()) {
6023
6968
  info("Already registered. API key loaded from env or ~/.unbrowse/config.json");
6024
6969
  return;
6025
6970
  }
@@ -6308,8 +7253,17 @@ async function main() {
6308
7253
  }
6309
7254
  if (command === "setup") {
6310
7255
  await cmdSetup(flags);
7256
+ await runPostSetupContributionPrompt();
7257
+ return;
7258
+ }
7259
+ if (command === "mode") {
7260
+ await cmdMode(flags);
6311
7261
  return;
6312
7262
  }
7263
+ if (command === "account")
7264
+ return cmdAccount(flags);
7265
+ if (command === "dashboard")
7266
+ return cmdDashboard(flags);
6313
7267
  if (command === "mcp")
6314
7268
  return cmdMcp(flags);
6315
7269
  if (command === "status")
@@ -6334,6 +7288,7 @@ async function main() {
6334
7288
  return cmdSessionsScan(flags);
6335
7289
  if (command === "register")
6336
7290
  return cmdRegister(flags);
7291
+ await refreshContributionPreferenceFromServer(false);
6337
7292
  const KNOWN_COMMANDS = new Set([
6338
7293
  "health",
6339
7294
  "mcp",
@@ -6386,7 +7341,11 @@ async function main() {
6386
7341
  "corpus-run",
6387
7342
  "sessions-scan",
6388
7343
  "cache-clear",
6389
- "register"
7344
+ "register",
7345
+ "mode",
7346
+ "account",
7347
+ "dashboard",
7348
+ "capture"
6390
7349
  ]);
6391
7350
  if (!KNOWN_COMMANDS.has(command)) {
6392
7351
  const pack = findSitePack(command);
@@ -6500,6 +7459,16 @@ async function main() {
6500
7459
  return cmdSessionsScan(flags);
6501
7460
  case "register":
6502
7461
  return cmdRegister(flags);
7462
+ case "mode":
7463
+ return cmdMode(flags);
7464
+ case "account":
7465
+ return cmdAccount(flags);
7466
+ case "dashboard":
7467
+ return cmdDashboard(flags);
7468
+ case "capture":
7469
+ return cmdCapture(flags);
7470
+ case "note":
7471
+ return cmdNote(flags, args);
6503
7472
  default:
6504
7473
  info(`Unknown command: ${command}`);
6505
7474
  printHelp();