unbrowse 6.4.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.4.0", BUILD_GIT_SHA = "5445eb4fd89e", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi40LjAiLCJnaXRfc2hhIjoiNTQ0NWViNGZkODllIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA1NDQ1ZWI0ZmQ4OWUiLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTAxVDA5OjQ5OjE0LjcyMFoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "3l8L_1jJ1G9weeuFgVBrYwcglXLwH_AnfmsnXqdPigg", 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);
@@ -834,7 +837,7 @@ var init_reverse_engineer = __esm(() => {
834
837
 
835
838
  // ../../src/vault/index.ts
836
839
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
837
- 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";
838
841
  import { join as join7 } from "path";
839
842
  import { homedir as homedir5 } from "os";
840
843
  function normalizeKeytarModule(mod) {
@@ -877,11 +880,11 @@ async function callKeytar(op) {
877
880
  }
878
881
  function getOrCreateKey() {
879
882
  if (!existsSync10(VAULT_DIR))
880
- mkdirSync5(VAULT_DIR, { recursive: true, mode: 448 });
883
+ mkdirSync6(VAULT_DIR, { recursive: true, mode: 448 });
881
884
  if (existsSync10(KEY_FILE))
882
885
  return readFileSync6(KEY_FILE);
883
886
  const key = randomBytes2(32);
884
- writeFileSync3(KEY_FILE, key, { mode: 384 });
887
+ writeFileSync4(KEY_FILE, key, { mode: 384 });
885
888
  return key;
886
889
  }
887
890
  function withVaultLock(fn) {
@@ -912,7 +915,7 @@ function writeVaultFile(data) {
912
915
  const iv = randomBytes2(16);
913
916
  const cipher = createCipheriv("aes-256-cbc", key, iv);
914
917
  const enc = Buffer.concat([cipher.update(JSON.stringify(data), "utf8"), cipher.final()]);
915
- writeFileSync3(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
918
+ writeFileSync4(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
916
919
  }
917
920
  async function storeCredential(account, value, opts) {
918
921
  const wrapped = {
@@ -1305,6 +1308,23 @@ function extractFromFirefox(domain, opts) {
1305
1308
  }
1306
1309
  }
1307
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) {
1308
1328
  if (opts?.browser === "firefox") {
1309
1329
  return extractFromFirefox(domain, { profile: opts.firefoxProfile });
1310
1330
  }
@@ -1324,6 +1344,20 @@ function extractBrowserCookies(domain, opts) {
1324
1344
  }
1325
1345
  const chrome = extractFromChrome(domain, { profile: opts?.chromeProfile });
1326
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
+ }
1327
1361
  return chrome;
1328
1362
  }
1329
1363
  function scanAllBrowserSessions(domain) {
@@ -1490,22 +1524,25 @@ class LocalAuthRuntime {
1490
1524
  if (session && session.expires > Date.now()) {
1491
1525
  return { authenticated: true, session_token: session.token, method: "cached" };
1492
1526
  }
1493
- try {
1494
- const cookies = await getStoredAuth(dep.domain);
1495
- if (cookies && cookies.length > 0) {
1496
- log("auth-runtime", `found ${cookies.length} stored cookies for ${dep.domain}`);
1497
- this.setSession(dep.domain, "vault-cookies", 3600000);
1498
- return { authenticated: true, method: "cookies" };
1499
- }
1500
- } catch {}
1501
- try {
1502
- const result = await extractBrowserAuth(dep.domain);
1503
- if (result.success && result.cookies_stored > 0) {
1504
- log("auth-runtime", `extracted ${result.cookies_stored} browser cookies for ${dep.domain}`);
1505
- this.setSession(dep.domain, "browser-cookies", 3600000);
1506
- return { authenticated: true, method: "cookies" };
1507
- }
1508
- } 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
+ }
1509
1546
  if (dep.strategy === "refresh_session" && session) {
1510
1547
  const refreshed = await this.refreshSession(dep.domain);
1511
1548
  if (refreshed) {
@@ -1828,6 +1865,7 @@ var init_compile = () => {};
1828
1865
  import { nanoid as nanoid6 } from "nanoid";
1829
1866
  var VALID_VERIFICATION_STATUSES, STOPWORDS;
1830
1867
  var init_execution = __esm(async () => {
1868
+ init_client2();
1831
1869
  init_reverse_engineer();
1832
1870
  init_bundle_scanner();
1833
1871
  init_token_resolver();
@@ -1980,19 +2018,23 @@ var init_resolve_race = __esm(async () => {
1980
2018
  });
1981
2019
  // ../../src/orchestrator/index.ts
1982
2020
  import { nanoid as nanoid9 } from "nanoid";
1983
- 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";
1984
2022
  import { dirname as dirname3, join as join10 } from "node:path";
1985
2023
  function _writeRouteCacheToDisk() {
2024
+ if (!LOCAL_CACHES_ENABLED) {
2025
+ _routeCacheDirty = false;
2026
+ return;
2027
+ }
1986
2028
  try {
1987
2029
  const dir = dirname3(ROUTE_CACHE_FILE);
1988
2030
  if (!existsSync12(dir))
1989
- mkdirSync6(dir, { recursive: true });
2031
+ mkdirSync7(dir, { recursive: true });
1990
2032
  const entries = Object.fromEntries(skillRouteCache);
1991
- writeFileSync4(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
2033
+ writeFileSync5(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
1992
2034
  } catch {}
1993
2035
  _routeCacheDirty = false;
1994
2036
  }
1995
- 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;
1996
2038
  var init_orchestrator = __esm(async () => {
1997
2039
  init_client();
1998
2040
  init_client2();
@@ -2026,36 +2068,41 @@ var init_orchestrator = __esm(async () => {
2026
2068
  SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
2027
2069
  domainSkillCache = new Map;
2028
2070
  DOMAIN_CACHE_FILE = join10(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
2029
- try {
2030
- if (existsSync12(DOMAIN_CACHE_FILE)) {
2031
- const data = JSON.parse(readFileSync7(DOMAIN_CACHE_FILE, "utf-8"));
2032
- for (const [k, v] of Object.entries(data)) {
2033
- const entry = v;
2034
- if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
2035
- 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
+ }
2036
2081
  }
2082
+ console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
2037
2083
  }
2038
- console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
2039
- }
2040
- } catch {}
2084
+ } catch {}
2085
+ }
2041
2086
  routeCacheFlushTimer = setInterval(() => {
2042
2087
  if (!_routeCacheDirty)
2043
2088
  return;
2044
2089
  _writeRouteCacheToDisk();
2045
2090
  }, 5000);
2046
2091
  routeCacheFlushTimer.unref?.();
2047
- try {
2048
- if (existsSync12(ROUTE_CACHE_FILE)) {
2049
- const data = JSON.parse(readFileSync7(ROUTE_CACHE_FILE, "utf-8"));
2050
- for (const [k, v] of Object.entries(data)) {
2051
- const entry = v;
2052
- if (Date.now() - entry.ts < 24 * 60 * 60000) {
2053
- 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
+ }
2054
2101
  }
2102
+ console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
2055
2103
  }
2056
- console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
2057
- }
2058
- } catch {}
2104
+ } catch {}
2105
+ }
2059
2106
  routeResultCache = new Map;
2060
2107
  ROUTE_CACHE_TTL = 24 * 60 * 60000;
2061
2108
  MARKETPLACE_HYDRATE_LIMIT = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_HYDRATE_LIMIT ?? 4));
@@ -2173,9 +2220,11 @@ function getWalletContext2() {
2173
2220
  wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
2174
2221
  };
2175
2222
  }
2176
- const localLobsterWallet = getLobsterWalletFromLocalConfig2();
2177
- if (localLobsterWallet) {
2178
- 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
+ }
2179
2228
  }
2180
2229
  return {};
2181
2230
  }
@@ -2190,6 +2239,100 @@ function checkWalletConfigured2() {
2190
2239
  }
2191
2240
  var init_wallet2 = () => {};
2192
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
+
2193
2336
  // ../../src/version.ts
2194
2337
  var exports_version = {};
2195
2338
  __export(exports_version, {
@@ -2202,6 +2345,7 @@ __export(exports_version, {
2202
2345
  RELEASE_MANIFEST_BASE64: () => RELEASE_MANIFEST_BASE642,
2203
2346
  PACKAGE_VERSION: () => PACKAGE_VERSION2,
2204
2347
  GIT_SHA: () => GIT_SHA2,
2348
+ DEFAULT_PROFILE: () => DEFAULT_PROFILE2,
2205
2349
  DEFAULT_BACKEND_URL: () => DEFAULT_BACKEND_URL2,
2206
2350
  CODE_HASH: () => CODE_HASH2
2207
2351
  });
@@ -2303,13 +2447,14 @@ function getPackageVersion2() {
2303
2447
  return packageVersion;
2304
2448
  return getEmbeddedReleaseVersion2() ?? "unknown";
2305
2449
  }
2306
- 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;
2307
2451
  var init_version2 = __esm(() => {
2308
2452
  MODULE_DIR2 = dirname4(fileURLToPath4(import.meta.url));
2309
2453
  CODE_HASH2 = BUILD_CODE_HASH?.trim() || computeCodeHash2();
2310
2454
  GIT_SHA2 = getGitSha2();
2311
2455
  PACKAGE_VERSION2 = getPackageVersion2();
2312
2456
  DEFAULT_BACKEND_URL2 = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
2457
+ DEFAULT_PROFILE2 = BUILD_DEFAULT_PROFILE?.trim() || "";
2313
2458
  TRACE_VERSION2 = `${CODE_HASH2}@${GIT_SHA2}`;
2314
2459
  RELEASE_MANIFEST_BASE642 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
2315
2460
  RELEASE_MANIFEST_SIGNATURE2 = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
@@ -2399,9 +2544,9 @@ function getChromiumKeychainServiceName2(opts) {
2399
2544
  }
2400
2545
  function getChromiumDecryptionKey2(opts) {
2401
2546
  const service = getChromiumKeychainServiceName2(opts);
2402
- const cached2 = _chromiumKeyCache2.get(service);
2403
- if (cached2)
2404
- return cached2;
2547
+ const cached3 = _chromiumKeyCache2.get(service);
2548
+ if (cached3)
2549
+ return cached3;
2405
2550
  if (platform2() !== "darwin")
2406
2551
  return null;
2407
2552
  try {
@@ -2601,6 +2746,23 @@ function extractFromFirefox2(domain, opts) {
2601
2746
  }
2602
2747
  }
2603
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) {
2604
2766
  if (opts?.browser === "firefox") {
2605
2767
  return extractFromFirefox2(domain, { profile: opts.firefoxProfile });
2606
2768
  }
@@ -2620,6 +2782,20 @@ function extractBrowserCookies2(domain, opts) {
2620
2782
  }
2621
2783
  const chrome = extractFromChrome2(domain, { profile: opts?.chromeProfile });
2622
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
+ }
2623
2799
  return chrome;
2624
2800
  }
2625
2801
  function scanAllBrowserSessions2(domain) {
@@ -2690,14 +2866,14 @@ init_version();
2690
2866
  init_cascade();
2691
2867
  init_wallet();
2692
2868
  init_telemetry_attribution();
2693
- 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";
2694
2870
  import { join as join4 } from "path";
2695
2871
  import { homedir as homedir3, hostname, release as osRelease } from "os";
2696
2872
  import { randomBytes, createHash as createHash2 } from "crypto";
2697
2873
  import { createInterface } from "readline";
2698
2874
  import { execSync } from "child_process";
2699
2875
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
2700
- var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
2876
+ var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
2701
2877
  var recentLocalSkills = new Map;
2702
2878
  var LOCAL_ONLY = process.env.UNBROWSE_LOCAL_ONLY === "1";
2703
2879
  function buildReleaseAttestationHeaders(manifestBase64, signature) {
@@ -2773,12 +2949,40 @@ function loadConfig() {
2773
2949
  } catch {}
2774
2950
  return null;
2775
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
+ }
2776
2963
  function saveConfig(config) {
2777
2964
  const configDir = getConfigDir();
2778
2965
  const configPath = getConfigPath();
2779
2966
  if (!existsSync4(configDir))
2780
- mkdirSync(configDir, { recursive: true });
2781
- 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;
2782
2986
  }
2783
2987
  function loadInstallTelemetryState() {
2784
2988
  try {
@@ -2793,8 +2997,8 @@ function saveInstallTelemetryState(state) {
2793
2997
  const configDir = getConfigDir();
2794
2998
  const statePath = getInstallTelemetryPath();
2795
2999
  if (!existsSync4(configDir))
2796
- mkdirSync(configDir, { recursive: true });
2797
- writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
3000
+ mkdirSync2(configDir, { recursive: true });
3001
+ writeFileSync2(statePath, JSON.stringify(state, null, 2), { mode: 384 });
2798
3002
  }
2799
3003
  function createInstallTelemetryState() {
2800
3004
  return {
@@ -3295,9 +3499,99 @@ You have $2.00 in free credits — start resolving to use them.`);
3295
3499
  process.exit(1);
3296
3500
  }
3297
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
+ }
3298
3576
  async function getMyProfile() {
3299
3577
  return api("GET", "/v1/agents/me", undefined);
3300
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
+ }
3301
3595
  async function syncAgentWallet(wallet = getLocalWalletContext()) {
3302
3596
  if (!wallet.wallet_address)
3303
3597
  return;
@@ -3318,7 +3612,7 @@ async function getFlywheelPulse() {
3318
3612
  }
3319
3613
 
3320
3614
  // ../../src/impact-log.ts
3321
- 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";
3322
3616
  import { homedir as homedir4 } from "node:os";
3323
3617
  import { dirname as dirname2, join as join5 } from "node:path";
3324
3618
  var MAX_LOG_BYTES = 5 * 1024 * 1024;
@@ -3335,7 +3629,7 @@ function getImpactLogPath() {
3335
3629
  function ensureDir(path) {
3336
3630
  const dir = dirname2(path);
3337
3631
  if (!existsSync5(dir))
3338
- mkdirSync2(dir, { recursive: true });
3632
+ mkdirSync3(dir, { recursive: true });
3339
3633
  }
3340
3634
  function rotateIfNeeded(path) {
3341
3635
  try {
@@ -3674,7 +3968,7 @@ function buildDepsMetadata(pack, taskName) {
3674
3968
  init_paths();
3675
3969
  init_supervisor();
3676
3970
  init_version();
3677
- 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";
3678
3972
  import path2 from "node:path";
3679
3973
  import { spawn } from "node:child_process";
3680
3974
  function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
@@ -3781,7 +4075,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
3781
4075
  code_hash: CODE_HASH,
3782
4076
  restart_count: restartCount
3783
4077
  };
3784
- writeFileSync2(pidFile, JSON.stringify(state, null, 2));
4078
+ writeFileSync3(pidFile, JSON.stringify(state, null, 2));
3785
4079
  return state;
3786
4080
  }
3787
4081
  var supervisor = new LocalSupervisor;
@@ -3889,7 +4183,7 @@ async function restartServer(baseUrl, metaUrl) {
3889
4183
  }
3890
4184
 
3891
4185
  // ../../src/runtime/paths.ts
3892
- 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";
3893
4187
  import path3 from "node:path";
3894
4188
  import { createRequire as createRequire3 } from "node:module";
3895
4189
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -3902,7 +4196,7 @@ function isBundledVirtualEntrypoint(entrypoint) {
3902
4196
  }
3903
4197
  function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
3904
4198
  if (path3.extname(entrypoint) !== ".ts") {
3905
- return [pathToFileURL2(entrypoint).href];
4199
+ return process.platform === "win32" ? [pathToFileURL2(entrypoint).href] : [entrypoint];
3906
4200
  }
3907
4201
  if (process.versions.bun)
3908
4202
  return [entrypoint];
@@ -3985,13 +4279,13 @@ init_client2();
3985
4279
  init_logger();
3986
4280
  init_wallet();
3987
4281
  import { execFileSync as execFileSync4 } from "node:child_process";
3988
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "node:fs";
4282
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "node:fs";
3989
4283
  import os5 from "node:os";
3990
4284
  import path8 from "node:path";
3991
4285
 
3992
4286
  // ../../src/runtime/update-hints.ts
3993
4287
  init_paths();
3994
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
4288
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
3995
4289
  import os4 from "node:os";
3996
4290
  import path7 from "node:path";
3997
4291
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
@@ -4006,7 +4300,7 @@ function getConfigDir2() {
4006
4300
  }
4007
4301
  function ensureDir3(dir) {
4008
4302
  if (!existsSync13(dir))
4009
- mkdirSync7(dir, { recursive: true });
4303
+ mkdirSync8(dir, { recursive: true });
4010
4304
  return dir;
4011
4305
  }
4012
4306
  function readJsonFile(file) {
@@ -4018,7 +4312,7 @@ function readJsonFile(file) {
4018
4312
  }
4019
4313
  function writeJsonFile(file, value) {
4020
4314
  ensureDir3(path7.dirname(file));
4021
- writeFileSync5(file, `${JSON.stringify(value, null, 2)}
4315
+ writeFileSync6(file, `${JSON.stringify(value, null, 2)}
4022
4316
  `);
4023
4317
  }
4024
4318
  function getInstallSourcePath() {
@@ -4105,6 +4399,10 @@ function ensureCodexHooksFeature(content) {
4105
4399
  codex_hooks = true
4106
4400
  `;
4107
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
+ }
4108
4406
  function writeCodexHook(metaUrl) {
4109
4407
  const configPath2 = getCodexConfigPath();
4110
4408
  if (!existsSync13(path7.dirname(configPath2))) {
@@ -4116,19 +4414,20 @@ function writeCodexHook(metaUrl) {
4116
4414
  let content = fileExistsBefore ? readFileSync8(configPath2, "utf8") : "";
4117
4415
  const previous = content;
4118
4416
  content = ensureCodexHooksFeature(content);
4417
+ content = repairManagedCodexHookTable(content);
4119
4418
  if (!content.includes("unbrowse-update-hint.mjs")) {
4120
4419
  const command = `node "${hookScript}"`;
4121
4420
  const prefix = content && !content.endsWith(`
4122
4421
  `) ? `
4123
4422
  ` : "";
4124
4423
  content += `${prefix}${CODEX_MARKER}
4125
- [[hooks]]
4424
+ [hooks]
4126
4425
  event = "SessionStart"
4127
4426
  command = ${JSON.stringify(command)}
4128
4427
  `;
4129
4428
  }
4130
4429
  if (content !== previous) {
4131
- writeFileSync5(configPath2, content, "utf8");
4430
+ writeFileSync6(configPath2, content, "utf8");
4132
4431
  return {
4133
4432
  host: "codex",
4134
4433
  action: fileExistsBefore ? "updated" : "installed",
@@ -4251,8 +4550,8 @@ function writeOpenCodeCommand(scope, cwd) {
4251
4550
  const commandFile = path8.join(ensureDir2(commandsDir), "unbrowse.md");
4252
4551
  const content = renderOpenCodeCommand();
4253
4552
  const action2 = existsSync14(commandFile) ? "updated" : "installed";
4254
- mkdirSync8(path8.dirname(commandFile), { recursive: true });
4255
- writeFileSync6(commandFile, content);
4553
+ mkdirSync9(path8.dirname(commandFile), { recursive: true });
4554
+ writeFileSync7(commandFile, content);
4256
4555
  return {
4257
4556
  detected: detected || scope !== "auto",
4258
4557
  action: action2,
@@ -4366,7 +4665,7 @@ async function runSetup(options) {
4366
4665
 
4367
4666
  // ../../src/runtime/update-hints.ts
4368
4667
  init_paths();
4369
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
4668
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
4370
4669
  import os6 from "node:os";
4371
4670
  import path9 from "node:path";
4372
4671
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
@@ -4381,7 +4680,7 @@ function getConfigDir3() {
4381
4680
  }
4382
4681
  function ensureDir4(dir) {
4383
4682
  if (!existsSync15(dir))
4384
- mkdirSync9(dir, { recursive: true });
4683
+ mkdirSync10(dir, { recursive: true });
4385
4684
  return dir;
4386
4685
  }
4387
4686
  function readJsonFile2(file) {
@@ -4393,7 +4692,7 @@ function readJsonFile2(file) {
4393
4692
  }
4394
4693
  function writeJsonFile2(file, value) {
4395
4694
  ensureDir4(path9.dirname(file));
4396
- writeFileSync7(file, `${JSON.stringify(value, null, 2)}
4695
+ writeFileSync8(file, `${JSON.stringify(value, null, 2)}
4397
4696
  `);
4398
4697
  }
4399
4698
  function getInstallSourcePath2() {
@@ -4560,12 +4859,20 @@ async function defaultReadChoice(allowed, dflt) {
4560
4859
  rl.close();
4561
4860
  }
4562
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
+ }
4563
4867
  async function promptContributionMode(opts = {}) {
4564
4868
  const log2 = opts.log ?? ((msg) => console.log(msg));
4565
4869
  const cfg = getContributionConfig();
4566
4870
  if (!opts.force && cfg.contribution.set_via && cfg.contribution.set_via !== "default") {
4567
4871
  return null;
4568
4872
  }
4873
+ if (shouldSkipInteractivePrompt(opts)) {
4874
+ return null;
4875
+ }
4569
4876
  log2("");
4570
4877
  log2("How do you want unbrowse to handle the routes you discover?");
4571
4878
  log2("");
@@ -4610,11 +4917,100 @@ function maybeShowContributionNotice() {
4610
4917
  return true;
4611
4918
  }
4612
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
+
4613
5008
  // ../../src/cli.ts
4614
5009
  loadEnv({ quiet: true });
4615
5010
  loadEnv({ path: ".env.runtime", quiet: true });
4616
5011
  var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
4617
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(/\/+$/, "");
4618
5014
  var walletNudgeShown = false;
4619
5015
  function parseArgs(argv) {
4620
5016
  const raw = argv.slice(2);
@@ -4641,8 +5037,12 @@ function parseArgs(argv) {
4641
5037
  if (valueExpectedFlags.has(key)) {
4642
5038
  if (next === undefined)
4643
5039
  die(`--${key} requires a value`);
4644
- flags[key] = next;
4645
- i++;
5040
+ if (next === "-p" || next === "--param" || next.startsWith("--")) {
5041
+ flags[key] = true;
5042
+ } else {
5043
+ flags[key] = next;
5044
+ i++;
5045
+ }
4646
5046
  } else if (!next || next.startsWith("--") || next === "-p" || next === "--param") {
4647
5047
  flags[key] = true;
4648
5048
  } else {
@@ -4655,8 +5055,8 @@ function parseArgs(argv) {
4655
5055
  }
4656
5056
  return { command, args: positional, flags, params };
4657
5057
  }
4658
- async function api2(method, path10, body) {
4659
- let target = `${BASE_URL}${path10}`;
5058
+ async function api2(method, path11, body, opts) {
5059
+ let target = `${BASE_URL}${path11}`;
4660
5060
  let requestBody = body;
4661
5061
  if (method === "GET" && body && typeof body === "object") {
4662
5062
  const params = new URLSearchParams;
@@ -4670,14 +5070,34 @@ async function api2(method, path10, body) {
4670
5070
  target += `${target.includes("?") ? "&" : "?"}${query}`;
4671
5071
  requestBody = undefined;
4672
5072
  }
4673
- const res = await fetch(target, {
4674
- method,
4675
- headers: {
4676
- ...requestBody ? { "Content-Type": "application/json" } : {},
4677
- "x-unbrowse-client-id": CLI_CLIENT_ID
4678
- },
4679
- body: requestBody ? JSON.stringify(requestBody) : undefined
4680
- });
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
+ }
4681
5101
  if (!res.ok && res.headers.get("content-type")?.includes("json")) {
4682
5102
  return res.json();
4683
5103
  }
@@ -4698,6 +5118,14 @@ function info(msg) {
4698
5118
  process.stderr.write(`[unbrowse] ${msg}
4699
5119
  `);
4700
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
+ }
4701
5129
  function resolveResultError(result) {
4702
5130
  return result.result?.error ?? result.error;
4703
5131
  }
@@ -4707,6 +5135,9 @@ function resolveLoginUrl(result, fallbackUrl) {
4707
5135
  function isResolveSuccessResult(result) {
4708
5136
  if (resolveResultError(result))
4709
5137
  return false;
5138
+ const status = result.result?.status;
5139
+ if (status === "no_match" || status === "auth_required" || status === "error")
5140
+ return false;
4710
5141
  return !!result.result || Array.isArray(result.available_endpoints);
4711
5142
  }
4712
5143
  async function withPendingNotice(promise, message, delayMs = 3000) {
@@ -4971,7 +5402,8 @@ async function cmdResolve(flags) {
4971
5402
  body.projection = { raw: true };
4972
5403
  const startedAt = Date.now();
4973
5404
  async function resolveOnce(message = "Still working. Searching cached routes...") {
4974
- 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);
4975
5407
  }
4976
5408
  let result = await resolveOnce();
4977
5409
  const resultError = resolveResultError(result);
@@ -4991,8 +5423,13 @@ async function cmdResolve(flags) {
4991
5423
  const skillId = resolveSkillId();
4992
5424
  if (skillId && endpoints.length > 0) {
4993
5425
  const bestEndpoint = endpoints[0];
4994
- info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
4995
- 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
+ }
4996
5433
  }
4997
5434
  }
4998
5435
  if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
@@ -5054,8 +5491,8 @@ async function cmdResolve(flags) {
5054
5491
  throw error;
5055
5492
  }
5056
5493
  }
5057
- function drillPath(data, path10) {
5058
- const segments = path10.split(/\./).flatMap((s) => {
5494
+ function drillPath(data, path11) {
5495
+ const segments = path11.split(/\./).flatMap((s) => {
5059
5496
  const m = s.match(/^(.+)\[\]$/);
5060
5497
  return m ? [m[1], "[]"] : [s];
5061
5498
  });
@@ -5082,9 +5519,9 @@ function drillPath(data, path10) {
5082
5519
  }
5083
5520
  return values;
5084
5521
  }
5085
- function resolveDotPath(obj, path10) {
5522
+ function resolveDotPath(obj, path11) {
5086
5523
  let cur = obj;
5087
- for (const key of path10.split(".")) {
5524
+ for (const key of path11.split(".")) {
5088
5525
  if (cur == null || typeof cur !== "object")
5089
5526
  return;
5090
5527
  cur = cur[key];
@@ -5101,8 +5538,8 @@ function applyExtract(items, extractSpec) {
5101
5538
  return items.map((item) => {
5102
5539
  const row = {};
5103
5540
  let hasValue = false;
5104
- for (const { alias, path: path10 } of fields) {
5105
- const val = resolveDotPath(item, path10);
5541
+ for (const { alias, path: path11 } of fields) {
5542
+ const val = resolveDotPath(item, path11);
5106
5543
  row[alias] = val ?? null;
5107
5544
  if (val != null)
5108
5545
  hasValue = true;
@@ -5419,7 +5856,67 @@ async function cmdCleanupStale(flags) {
5419
5856
  output(await withPendingNotice(api2("POST", "/v1/stale/cleanup", body), "Cleaning stale endpoints..."), !!flags.pretty);
5420
5857
  }
5421
5858
  async function cmdSearch(flags) {
5422
- 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
+ }
5423
5920
  }
5424
5921
  async function cmdSessions(flags) {
5425
5922
  const domain = flags.domain;
@@ -5461,7 +5958,7 @@ async function cmdSetup(flags) {
5461
5958
  info("Wallet not paired \u2014 you won't earn when other agents use routes you discovered.");
5462
5959
  info("Run: npx @crossmint/lobster-cli setup");
5463
5960
  } else {
5464
- 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.");
5465
5962
  info("Set up a wallet to start earning:");
5466
5963
  info(" npx @crossmint/lobster-cli setup");
5467
5964
  }
@@ -5531,6 +6028,10 @@ async function cmdSetup(flags) {
5531
6028
  output(report, true);
5532
6029
  if (report.browser_engine.action === "failed")
5533
6030
  process.exit(1);
6031
+ if (getApiKey()) {
6032
+ info("Dashboard connected:");
6033
+ info(" unbrowse dashboard");
6034
+ }
5534
6035
  try {
5535
6036
  info("Trying your first resolve...");
5536
6037
  const demoUrl = "https://jsonplaceholder.typicode.com";
@@ -5567,8 +6068,15 @@ async function cmdSetup(flags) {
5567
6068
  info("That's unbrowse. Try your own:");
5568
6069
  info(' unbrowse resolve --intent "search for shoes" --url "https://amazon.com"');
5569
6070
  } else {
5570
- info("Guided resolve returned no results \u2014 try manually:");
5571
- 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"');
5572
6080
  }
5573
6081
  } catch {
5574
6082
  info("Setup complete. Try your first resolve:");
@@ -5577,6 +6085,107 @@ async function cmdSetup(flags) {
5577
6085
  }
5578
6086
  async function cmdMode(_flags) {
5579
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);
5580
6189
  }
5581
6190
  async function runPostSetupContributionPrompt() {
5582
6191
  try {
@@ -5594,16 +6203,68 @@ async function cmdCapture(flags) {
5594
6203
  const endpoints = Array.isArray(result.endpoints) ? result.endpoints : Array.isArray(result.available_endpoints) ? result.available_endpoints : [];
5595
6204
  const skill = result.skill ?? null;
5596
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, "\\\"");
5597
6216
  const envelope = {
5598
6217
  skill_id: skillId,
5599
6218
  endpoints_discovered: typeof result.endpoints_discovered === "number" ? result.endpoints_discovered : endpoints.length,
5600
6219
  marketplace_published: !!result.marketplace_published,
5601
6220
  ms: typeof result.ms === "number" ? result.ms : Date.now() - t0,
5602
- next_step: endpoints.length > 0 ? `unbrowse resolve --intent "${intent.replace(/"/g, "\\\"")}" --url "${url.replace(/"/g, "\\\"")}"` : "no endpoints discovered; site may need authentication or different intent",
5603
- ...result.error ? { error: result.error } : {}
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 } : {}
5604
6228
  };
5605
6229
  output(envelope, !!flags.pretty);
5606
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
+ }
5607
6268
  var CLI_REFERENCE = {
5608
6269
  commands: [
5609
6270
  { name: "health", usage: "", desc: "Server health check" },
@@ -5648,9 +6309,12 @@ var CLI_REFERENCE = {
5648
6309
  { name: "earnings", usage: "[--json]", desc: "Show your credit balance, earnings from indexing, and spending" },
5649
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" },
5650
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" },
5651
- { 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" },
5652
6315
  { name: "mode", usage: "", desc: "Re-prompt for contribution mode (private / share / share + earn)" },
5653
- { 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`." }
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." }
5654
6318
  ],
5655
6319
  globalFlags: [
5656
6320
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -6245,7 +6909,62 @@ async function cmdClose(flags) {
6245
6909
  output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
6246
6910
  }
6247
6911
  async function cmdRegister(flags) {
6248
- 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()) {
6249
6968
  info("Already registered. API key loaded from env or ~/.unbrowse/config.json");
6250
6969
  return;
6251
6970
  }
@@ -6541,6 +7260,10 @@ async function main() {
6541
7260
  await cmdMode(flags);
6542
7261
  return;
6543
7262
  }
7263
+ if (command === "account")
7264
+ return cmdAccount(flags);
7265
+ if (command === "dashboard")
7266
+ return cmdDashboard(flags);
6544
7267
  if (command === "mcp")
6545
7268
  return cmdMcp(flags);
6546
7269
  if (command === "status")
@@ -6565,6 +7288,7 @@ async function main() {
6565
7288
  return cmdSessionsScan(flags);
6566
7289
  if (command === "register")
6567
7290
  return cmdRegister(flags);
7291
+ await refreshContributionPreferenceFromServer(false);
6568
7292
  const KNOWN_COMMANDS = new Set([
6569
7293
  "health",
6570
7294
  "mcp",
@@ -6619,6 +7343,8 @@ async function main() {
6619
7343
  "cache-clear",
6620
7344
  "register",
6621
7345
  "mode",
7346
+ "account",
7347
+ "dashboard",
6622
7348
  "capture"
6623
7349
  ]);
6624
7350
  if (!KNOWN_COMMANDS.has(command)) {
@@ -6735,8 +7461,14 @@ async function main() {
6735
7461
  return cmdRegister(flags);
6736
7462
  case "mode":
6737
7463
  return cmdMode(flags);
7464
+ case "account":
7465
+ return cmdAccount(flags);
7466
+ case "dashboard":
7467
+ return cmdDashboard(flags);
6738
7468
  case "capture":
6739
7469
  return cmdCapture(flags);
7470
+ case "note":
7471
+ return cmdNote(flags, args);
6740
7472
  default:
6741
7473
  info(`Unknown command: ${command}`);
6742
7474
  printHelp();