unbrowse 6.4.0 → 6.5.0-preview.6

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.6", BUILD_GIT_SHA = "6b2137b5a385", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAtcHJldmlldy42IiwiZ2l0X3NoYSI6IjZiMjEzN2I1YTM4NSIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFANmIyMTM3YjVhMzg1IiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wM1QwNzo0OTowNy4xMTdaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "HK4ezp0MGhPIwLNZiI7h2EX8hMjw3QRTyKXC97y8VUQ", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
35
35
 
36
36
  // ../../src/version.ts
37
37
  import { createHash } from "crypto";
@@ -132,13 +132,14 @@ function getPackageVersion() {
132
132
  return packageVersion;
133
133
  return getEmbeddedReleaseVersion() ?? "unknown";
134
134
  }
135
- var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, DEFAULT_BACKEND_URL, TRACE_VERSION, RELEASE_MANIFEST_BASE64, RELEASE_MANIFEST_SIGNATURE;
135
+ var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, DEFAULT_BACKEND_URL, DEFAULT_PROFILE, TRACE_VERSION, RELEASE_MANIFEST_BASE64, RELEASE_MANIFEST_SIGNATURE;
136
136
  var init_version = __esm(() => {
137
137
  MODULE_DIR = dirname(fileURLToPath(import.meta.url));
138
138
  CODE_HASH = BUILD_CODE_HASH?.trim() || computeCodeHash();
139
139
  GIT_SHA = getGitSha();
140
140
  PACKAGE_VERSION = getPackageVersion();
141
141
  DEFAULT_BACKEND_URL = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
142
+ DEFAULT_PROFILE = BUILD_DEFAULT_PROFILE?.trim() || "";
142
143
  TRACE_VERSION = `${CODE_HASH}@${GIT_SHA}`;
143
144
  RELEASE_MANIFEST_BASE64 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
144
145
  RELEASE_MANIFEST_SIGNATURE = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
@@ -180,9 +181,11 @@ function getWalletContext() {
180
181
  wallet_provider: asNonEmptyString(process.env.AGENT_WALLET_PROVIDER)
181
182
  };
182
183
  }
183
- const localLobsterWallet = getLobsterWalletFromLocalConfig();
184
- if (localLobsterWallet) {
185
- return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
184
+ if (process.env.UNBROWSE_DISABLE_LOCAL_WALLET !== "1") {
185
+ const localLobsterWallet = getLobsterWalletFromLocalConfig();
186
+ if (localLobsterWallet) {
187
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
188
+ }
186
189
  }
187
190
  return {};
188
191
  }
@@ -426,7 +429,7 @@ function resolveSiblingEntrypoint(metaUrl, basename) {
426
429
  }
427
430
  function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
428
431
  if (path.extname(entrypoint) !== ".ts") {
429
- return [pathToFileURL(entrypoint).href];
432
+ return process.platform === "win32" ? [pathToFileURL(entrypoint).href] : [entrypoint];
430
433
  }
431
434
  if (process.versions.bun)
432
435
  return [entrypoint];
@@ -506,7 +509,7 @@ var init_client = __esm(() => {
506
509
  init_wallet();
507
510
  init_telemetry_attribution();
508
511
  API_URL2 = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
509
- PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? "");
512
+ PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
510
513
  recentLocalSkills2 = new Map;
511
514
  LOCAL_ONLY2 = process.env.UNBROWSE_LOCAL_ONLY === "1";
512
515
  API_TIMEOUT_MS2 = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
@@ -1324,6 +1327,20 @@ function extractBrowserCookies(domain, opts) {
1324
1327
  }
1325
1328
  const chrome = extractFromChrome(domain, { profile: opts?.chromeProfile });
1326
1329
  chrome.warnings.push(...ff.warnings);
1330
+ if (chrome.cookies.length > 0)
1331
+ return chrome;
1332
+ const sessions = scanAllBrowserSessions(domain);
1333
+ const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
1334
+ if (best) {
1335
+ return {
1336
+ cookies: best.cookies,
1337
+ source: best.source,
1338
+ warnings: [
1339
+ ...chrome.warnings,
1340
+ `Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
1341
+ ]
1342
+ };
1343
+ }
1327
1344
  return chrome;
1328
1345
  }
1329
1346
  function scanAllBrowserSessions(domain) {
@@ -1490,22 +1507,25 @@ class LocalAuthRuntime {
1490
1507
  if (session && session.expires > Date.now()) {
1491
1508
  return { authenticated: true, session_token: session.token, method: "cached" };
1492
1509
  }
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 {}
1510
+ const skipFallback = process.env.UNBROWSE_DISABLE_AUTH_FALLBACK === "1";
1511
+ if (!skipFallback) {
1512
+ try {
1513
+ const cookies = await getStoredAuth(dep.domain);
1514
+ if (cookies && cookies.length > 0) {
1515
+ log("auth-runtime", `found ${cookies.length} stored cookies for ${dep.domain}`);
1516
+ this.setSession(dep.domain, "vault-cookies", 3600000);
1517
+ return { authenticated: true, method: "cookies" };
1518
+ }
1519
+ } catch {}
1520
+ try {
1521
+ const result = await extractBrowserAuth(dep.domain);
1522
+ if (result.success && result.cookies_stored > 0) {
1523
+ log("auth-runtime", `extracted ${result.cookies_stored} browser cookies for ${dep.domain}`);
1524
+ this.setSession(dep.domain, "browser-cookies", 3600000);
1525
+ return { authenticated: true, method: "cookies" };
1526
+ }
1527
+ } catch {}
1528
+ }
1509
1529
  if (dep.strategy === "refresh_session" && session) {
1510
1530
  const refreshed = await this.refreshSession(dep.domain);
1511
1531
  if (refreshed) {
@@ -1828,6 +1848,7 @@ var init_compile = () => {};
1828
1848
  import { nanoid as nanoid6 } from "nanoid";
1829
1849
  var VALID_VERIFICATION_STATUSES, STOPWORDS;
1830
1850
  var init_execution = __esm(async () => {
1851
+ init_client2();
1831
1852
  init_reverse_engineer();
1832
1853
  init_bundle_scanner();
1833
1854
  init_token_resolver();
@@ -1983,6 +2004,10 @@ import { nanoid as nanoid9 } from "nanoid";
1983
2004
  import { existsSync as existsSync12, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "node:fs";
1984
2005
  import { dirname as dirname3, join as join10 } from "node:path";
1985
2006
  function _writeRouteCacheToDisk() {
2007
+ if (!LOCAL_CACHES_ENABLED) {
2008
+ _routeCacheDirty = false;
2009
+ return;
2010
+ }
1986
2011
  try {
1987
2012
  const dir = dirname3(ROUTE_CACHE_FILE);
1988
2013
  if (!existsSync12(dir))
@@ -1992,7 +2017,7 @@ function _writeRouteCacheToDisk() {
1992
2017
  } catch {}
1993
2018
  _routeCacheDirty = false;
1994
2019
  }
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;
2020
+ var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, LOCAL_CACHES_ENABLED, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
1996
2021
  var init_orchestrator = __esm(async () => {
1997
2022
  init_client();
1998
2023
  init_client2();
@@ -2026,36 +2051,41 @@ var init_orchestrator = __esm(async () => {
2026
2051
  SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
2027
2052
  domainSkillCache = new Map;
2028
2053
  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);
2054
+ LOCAL_CACHES_ENABLED = process.env.UNBROWSE_LOCAL_CACHES === "1";
2055
+ if (LOCAL_CACHES_ENABLED) {
2056
+ try {
2057
+ if (existsSync12(DOMAIN_CACHE_FILE)) {
2058
+ const data = JSON.parse(readFileSync7(DOMAIN_CACHE_FILE, "utf-8"));
2059
+ for (const [k, v] of Object.entries(data)) {
2060
+ const entry = v;
2061
+ if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
2062
+ domainSkillCache.set(k, entry);
2063
+ }
2036
2064
  }
2065
+ console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
2037
2066
  }
2038
- console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
2039
- }
2040
- } catch {}
2067
+ } catch {}
2068
+ }
2041
2069
  routeCacheFlushTimer = setInterval(() => {
2042
2070
  if (!_routeCacheDirty)
2043
2071
  return;
2044
2072
  _writeRouteCacheToDisk();
2045
2073
  }, 5000);
2046
2074
  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);
2075
+ if (LOCAL_CACHES_ENABLED) {
2076
+ try {
2077
+ if (existsSync12(ROUTE_CACHE_FILE)) {
2078
+ const data = JSON.parse(readFileSync7(ROUTE_CACHE_FILE, "utf-8"));
2079
+ for (const [k, v] of Object.entries(data)) {
2080
+ const entry = v;
2081
+ if (Date.now() - entry.ts < 24 * 60 * 60000) {
2082
+ skillRouteCache.set(k, entry);
2083
+ }
2054
2084
  }
2085
+ console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
2055
2086
  }
2056
- console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
2057
- }
2058
- } catch {}
2087
+ } catch {}
2088
+ }
2059
2089
  routeResultCache = new Map;
2060
2090
  ROUTE_CACHE_TTL = 24 * 60 * 60000;
2061
2091
  MARKETPLACE_HYDRATE_LIMIT = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_HYDRATE_LIMIT ?? 4));
@@ -2173,9 +2203,11 @@ function getWalletContext2() {
2173
2203
  wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
2174
2204
  };
2175
2205
  }
2176
- const localLobsterWallet = getLobsterWalletFromLocalConfig2();
2177
- if (localLobsterWallet) {
2178
- return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
2206
+ if (process.env.UNBROWSE_DISABLE_LOCAL_WALLET !== "1") {
2207
+ const localLobsterWallet = getLobsterWalletFromLocalConfig2();
2208
+ if (localLobsterWallet) {
2209
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
2210
+ }
2179
2211
  }
2180
2212
  return {};
2181
2213
  }
@@ -2190,6 +2222,100 @@ function checkWalletConfigured2() {
2190
2222
  }
2191
2223
  var init_wallet2 = () => {};
2192
2224
 
2225
+ // ../../src/telemetry-attribution.ts
2226
+ var exports_telemetry_attribution = {};
2227
+ __export(exports_telemetry_attribution, {
2228
+ sanitizeTelemetryAttribution: () => sanitizeTelemetryAttribution2,
2229
+ sanitizeAttributionValue: () => sanitizeAttributionValue2,
2230
+ mergeTelemetryProperties: () => mergeTelemetryProperties2,
2231
+ mergeTelemetryAttribution: () => mergeTelemetryAttribution2,
2232
+ hasTelemetryAttribution: () => hasTelemetryAttribution2,
2233
+ decodeTelemetryAttribution: () => decodeTelemetryAttribution2,
2234
+ TELEMETRY_ATTRIBUTION_KEYS: () => TELEMETRY_ATTRIBUTION_KEYS2
2235
+ });
2236
+ function sanitizeAttributionValue2(value) {
2237
+ if (typeof value !== "string")
2238
+ return;
2239
+ const trimmed = value.trim();
2240
+ if (!trimmed)
2241
+ return;
2242
+ return trimmed.slice(0, MAX_ATTRIBUTION_VALUE_LENGTH2);
2243
+ }
2244
+ function hasTelemetryAttribution2(value) {
2245
+ if (!value)
2246
+ return false;
2247
+ return TELEMETRY_ATTRIBUTION_KEYS2.some((key) => Boolean(sanitizeAttributionValue2(value[key])));
2248
+ }
2249
+ function sanitizeTelemetryAttribution2(raw) {
2250
+ if (!raw)
2251
+ return;
2252
+ const cleaned = {};
2253
+ for (const key of TELEMETRY_ATTRIBUTION_KEYS2) {
2254
+ const value = sanitizeAttributionValue2(raw[key]);
2255
+ if (value)
2256
+ cleaned[key] = value;
2257
+ }
2258
+ return hasTelemetryAttribution2(cleaned) ? cleaned : undefined;
2259
+ }
2260
+ function mergeTelemetryAttribution2(base, incoming) {
2261
+ const merged = sanitizeTelemetryAttribution2({
2262
+ ...base ?? {},
2263
+ ...incoming ?? {}
2264
+ });
2265
+ return merged;
2266
+ }
2267
+ function mergeTelemetryProperties2(properties, attribution) {
2268
+ const cleanedAttribution = sanitizeTelemetryAttribution2(attribution);
2269
+ if (!cleanedAttribution)
2270
+ return properties;
2271
+ return {
2272
+ ...cleanedAttribution,
2273
+ ...properties ?? {}
2274
+ };
2275
+ }
2276
+ function decodeTelemetryAttribution2(value) {
2277
+ if (!value)
2278
+ return;
2279
+ try {
2280
+ const decoded = Buffer.from(value, "base64").toString("utf8");
2281
+ return sanitizeTelemetryAttribution2(JSON.parse(decoded));
2282
+ } catch {
2283
+ return;
2284
+ }
2285
+ }
2286
+ var TELEMETRY_ATTRIBUTION_KEYS2, MAX_ATTRIBUTION_VALUE_LENGTH2 = 160;
2287
+ var init_telemetry_attribution2 = __esm(() => {
2288
+ TELEMETRY_ATTRIBUTION_KEYS2 = [
2289
+ "utm_source",
2290
+ "utm_medium",
2291
+ "utm_campaign",
2292
+ "utm_content",
2293
+ "utm_term",
2294
+ "utm_id",
2295
+ "gclid",
2296
+ "wbraid",
2297
+ "gbraid",
2298
+ "fbclid",
2299
+ "twclid",
2300
+ "ttclid",
2301
+ "msclkid",
2302
+ "li_fat_id",
2303
+ "referrer_host",
2304
+ "channel",
2305
+ "campaign_id",
2306
+ "campaign_name",
2307
+ "content_id",
2308
+ "content_type",
2309
+ "creative_id",
2310
+ "ad_id",
2311
+ "adset_id",
2312
+ "inferred_icp",
2313
+ "variant_id",
2314
+ "experiment_id",
2315
+ "icp"
2316
+ ];
2317
+ });
2318
+
2193
2319
  // ../../src/version.ts
2194
2320
  var exports_version = {};
2195
2321
  __export(exports_version, {
@@ -2202,6 +2328,7 @@ __export(exports_version, {
2202
2328
  RELEASE_MANIFEST_BASE64: () => RELEASE_MANIFEST_BASE642,
2203
2329
  PACKAGE_VERSION: () => PACKAGE_VERSION2,
2204
2330
  GIT_SHA: () => GIT_SHA2,
2331
+ DEFAULT_PROFILE: () => DEFAULT_PROFILE2,
2205
2332
  DEFAULT_BACKEND_URL: () => DEFAULT_BACKEND_URL2,
2206
2333
  CODE_HASH: () => CODE_HASH2
2207
2334
  });
@@ -2303,13 +2430,14 @@ function getPackageVersion2() {
2303
2430
  return packageVersion;
2304
2431
  return getEmbeddedReleaseVersion2() ?? "unknown";
2305
2432
  }
2306
- var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
2433
+ var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, DEFAULT_PROFILE2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
2307
2434
  var init_version2 = __esm(() => {
2308
2435
  MODULE_DIR2 = dirname4(fileURLToPath4(import.meta.url));
2309
2436
  CODE_HASH2 = BUILD_CODE_HASH?.trim() || computeCodeHash2();
2310
2437
  GIT_SHA2 = getGitSha2();
2311
2438
  PACKAGE_VERSION2 = getPackageVersion2();
2312
2439
  DEFAULT_BACKEND_URL2 = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
2440
+ DEFAULT_PROFILE2 = BUILD_DEFAULT_PROFILE?.trim() || "";
2313
2441
  TRACE_VERSION2 = `${CODE_HASH2}@${GIT_SHA2}`;
2314
2442
  RELEASE_MANIFEST_BASE642 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
2315
2443
  RELEASE_MANIFEST_SIGNATURE2 = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
@@ -2399,9 +2527,9 @@ function getChromiumKeychainServiceName2(opts) {
2399
2527
  }
2400
2528
  function getChromiumDecryptionKey2(opts) {
2401
2529
  const service = getChromiumKeychainServiceName2(opts);
2402
- const cached2 = _chromiumKeyCache2.get(service);
2403
- if (cached2)
2404
- return cached2;
2530
+ const cached3 = _chromiumKeyCache2.get(service);
2531
+ if (cached3)
2532
+ return cached3;
2405
2533
  if (platform2() !== "darwin")
2406
2534
  return null;
2407
2535
  try {
@@ -2620,6 +2748,20 @@ function extractBrowserCookies2(domain, opts) {
2620
2748
  }
2621
2749
  const chrome = extractFromChrome2(domain, { profile: opts?.chromeProfile });
2622
2750
  chrome.warnings.push(...ff.warnings);
2751
+ if (chrome.cookies.length > 0)
2752
+ return chrome;
2753
+ const sessions = scanAllBrowserSessions2(domain);
2754
+ const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
2755
+ if (best) {
2756
+ return {
2757
+ cookies: best.cookies,
2758
+ source: best.source,
2759
+ warnings: [
2760
+ ...chrome.warnings,
2761
+ `Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
2762
+ ]
2763
+ };
2764
+ }
2623
2765
  return chrome;
2624
2766
  }
2625
2767
  function scanAllBrowserSessions2(domain) {
@@ -2697,7 +2839,7 @@ import { randomBytes, createHash as createHash2 } from "crypto";
2697
2839
  import { createInterface } from "readline";
2698
2840
  import { execSync } from "child_process";
2699
2841
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
2700
- var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
2842
+ var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
2701
2843
  var recentLocalSkills = new Map;
2702
2844
  var LOCAL_ONLY = process.env.UNBROWSE_LOCAL_ONLY === "1";
2703
2845
  function buildReleaseAttestationHeaders(manifestBase64, signature) {
@@ -3295,9 +3437,99 @@ You have $2.00 in free credits — start resolving to use them.`);
3295
3437
  process.exit(1);
3296
3438
  }
3297
3439
  }
3440
+ async function magicRegister(opts) {
3441
+ const timeoutMs = opts.timeoutMs ?? 300000;
3442
+ const pollMs = opts.pollMs ?? 1500;
3443
+ const startRes = await fetch(`${API_URL}/v1/auth/email/start`, {
3444
+ method: "POST",
3445
+ headers: {
3446
+ "Content-Type": "application/json",
3447
+ "Accept-Encoding": "gzip, deflate"
3448
+ },
3449
+ body: JSON.stringify({ email: opts.email, return_url: opts.returnUrl })
3450
+ });
3451
+ if (startRes.status === 503) {
3452
+ throw new Error("Backend RESEND_API_KEY not set — magic-link signup unavailable. Use anon `unbrowse register` (no --email).");
3453
+ }
3454
+ let startData;
3455
+ try {
3456
+ startData = await startRes.json();
3457
+ } catch {
3458
+ throw new Error(`Magic-link start failed: HTTP ${startRes.status}`);
3459
+ }
3460
+ if (startRes.status === 400) {
3461
+ throw new Error(startData.error ?? "invalid_email");
3462
+ }
3463
+ if (!startRes.ok || !startData.token) {
3464
+ const msg = startData.error ?? `HTTP ${startRes.status}`;
3465
+ throw new Error(`Magic-link start failed: ${msg}`);
3466
+ }
3467
+ const token = startData.token;
3468
+ const verifyUrl = `${API_URL}/v1/auth/email/verify?token=${encodeURIComponent(token)}`;
3469
+ if (opts.openBrowser) {
3470
+ try {
3471
+ await opts.openBrowser(verifyUrl);
3472
+ } catch {}
3473
+ } else {
3474
+ try {
3475
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
3476
+ execSync(`${cmd} ${JSON.stringify(verifyUrl)}`, { stdio: "ignore", timeout: 5000 });
3477
+ } catch {}
3478
+ }
3479
+ const deadline = Date.now() + timeoutMs;
3480
+ while (Date.now() < deadline) {
3481
+ await new Promise((r) => setTimeout(r, pollMs));
3482
+ const pollRes = await fetch(`${API_URL}/v1/auth/email/poll?token=${encodeURIComponent(token)}`, {
3483
+ method: "GET",
3484
+ headers: { "Accept-Encoding": "gzip, deflate" }
3485
+ });
3486
+ let pollData;
3487
+ try {
3488
+ pollData = await pollRes.json();
3489
+ } catch {
3490
+ throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
3491
+ }
3492
+ if (pollData.status === "verified") {
3493
+ if (!pollData.api_key || !pollData.user_id || !pollData.email) {
3494
+ throw new Error("Magic-link poll returned verified without api_key/user_id/email.");
3495
+ }
3496
+ return {
3497
+ api_key: pollData.api_key,
3498
+ agent_id: pollData.user_id,
3499
+ email: pollData.email,
3500
+ user_id: pollData.user_id
3501
+ };
3502
+ }
3503
+ if (pollData.status === "expired" || pollRes.status === 410) {
3504
+ throw new Error("Magic link expired. Re-run `unbrowse register --email …`.");
3505
+ }
3506
+ if (pollData.status === "pending")
3507
+ continue;
3508
+ if (!pollRes.ok) {
3509
+ throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
3510
+ }
3511
+ }
3512
+ throw new Error(`Magic-link timed out after ${Math.round(timeoutMs / 1000)}s. Check your inbox and re-run.`);
3513
+ }
3298
3514
  async function getMyProfile() {
3299
3515
  return api("GET", "/v1/agents/me", undefined);
3300
3516
  }
3517
+ async function fetchAccountPreferences() {
3518
+ try {
3519
+ const data = await api("GET", "/v1/account/preferences", undefined);
3520
+ return { share_pointers: !!data?.share_pointers };
3521
+ } catch (err) {
3522
+ const msg = err.message ?? "";
3523
+ if (msg.includes("account_required") || msg.includes("HTTP 403") || msg.includes("HTTP 404")) {
3524
+ return null;
3525
+ }
3526
+ throw err;
3527
+ }
3528
+ }
3529
+ async function pushAccountPreferences(patch) {
3530
+ const data = await api("PATCH", "/v1/account/preferences", patch);
3531
+ return { share_pointers: !!data?.share_pointers };
3532
+ }
3301
3533
  async function syncAgentWallet(wallet = getLocalWalletContext()) {
3302
3534
  if (!wallet.wallet_address)
3303
3535
  return;
@@ -3902,7 +4134,7 @@ function isBundledVirtualEntrypoint(entrypoint) {
3902
4134
  }
3903
4135
  function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
3904
4136
  if (path3.extname(entrypoint) !== ".ts") {
3905
- return [pathToFileURL2(entrypoint).href];
4137
+ return process.platform === "win32" ? [pathToFileURL2(entrypoint).href] : [entrypoint];
3906
4138
  }
3907
4139
  if (process.versions.bun)
3908
4140
  return [entrypoint];
@@ -4610,6 +4842,94 @@ function maybeShowContributionNotice() {
4610
4842
  return true;
4611
4843
  }
4612
4844
 
4845
+ // ../../src/config/contribution.ts
4846
+ import fs3 from "node:fs";
4847
+ import path10 from "node:path";
4848
+ import os7 from "node:os";
4849
+ function configPath2() {
4850
+ return process.env.UNBROWSE_CONFIG_PATH || path10.join(os7.homedir(), ".unbrowse", "config.json");
4851
+ }
4852
+ var DEFAULT2 = {
4853
+ contribution: { share_pointers: false, set_via: "default" },
4854
+ rev_share: { opted_in: false },
4855
+ notice_shown_count: 0
4856
+ };
4857
+ function freshDefault2() {
4858
+ return {
4859
+ contribution: { ...DEFAULT2.contribution },
4860
+ rev_share: { ...DEFAULT2.rev_share },
4861
+ notice_shown_count: 0
4862
+ };
4863
+ }
4864
+ var cached2 = null;
4865
+ function getContributionConfig2() {
4866
+ if (cached2)
4867
+ return cached2;
4868
+ const p = configPath2();
4869
+ let fileExisted = false;
4870
+ let raw = null;
4871
+ try {
4872
+ const content = fs3.readFileSync(p, "utf-8");
4873
+ fileExisted = true;
4874
+ if (content.trim().length > 0) {
4875
+ raw = JSON.parse(content);
4876
+ }
4877
+ } catch {}
4878
+ if (!raw) {
4879
+ cached2 = freshDefault2();
4880
+ return cached2;
4881
+ }
4882
+ const contributionRaw = raw.contribution ?? null;
4883
+ const revShareRaw = raw.rev_share ?? null;
4884
+ const isExistingUserMigration = fileExisted && !contributionRaw;
4885
+ cached2 = {
4886
+ contribution: {
4887
+ share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT2.contribution.share_pointers),
4888
+ set_via: contributionRaw?.set_via ?? DEFAULT2.contribution.set_via,
4889
+ ...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
4890
+ },
4891
+ rev_share: {
4892
+ opted_in: !!(revShareRaw?.opted_in ?? DEFAULT2.rev_share.opted_in),
4893
+ ...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
4894
+ },
4895
+ notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
4896
+ };
4897
+ if (isExistingUserMigration) {
4898
+ try {
4899
+ const merged = { ...raw, ...cached2 };
4900
+ fs3.mkdirSync(path10.dirname(p), { recursive: true });
4901
+ fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
4902
+ } catch {}
4903
+ }
4904
+ return cached2;
4905
+ }
4906
+ function setContributionConfig2(updates) {
4907
+ const current = getContributionConfig2();
4908
+ const next = {
4909
+ contribution: {
4910
+ ...current.contribution,
4911
+ ...updates.contribution ?? {},
4912
+ set_at: new Date().toISOString()
4913
+ },
4914
+ rev_share: {
4915
+ ...current.rev_share,
4916
+ ...updates.rev_share ?? {}
4917
+ },
4918
+ notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
4919
+ };
4920
+ const p = configPath2();
4921
+ let existing = {};
4922
+ try {
4923
+ const content = fs3.readFileSync(p, "utf-8");
4924
+ if (content.trim())
4925
+ existing = JSON.parse(content);
4926
+ } catch {}
4927
+ const merged = { ...existing, ...next };
4928
+ fs3.mkdirSync(path10.dirname(p), { recursive: true });
4929
+ fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
4930
+ cached2 = next;
4931
+ }
4932
+
4613
4933
  // ../../src/cli.ts
4614
4934
  loadEnv({ quiet: true });
4615
4935
  loadEnv({ path: ".env.runtime", quiet: true });
@@ -4641,8 +4961,12 @@ function parseArgs(argv) {
4641
4961
  if (valueExpectedFlags.has(key)) {
4642
4962
  if (next === undefined)
4643
4963
  die(`--${key} requires a value`);
4644
- flags[key] = next;
4645
- i++;
4964
+ if (next === "-p" || next === "--param" || next.startsWith("--")) {
4965
+ flags[key] = true;
4966
+ } else {
4967
+ flags[key] = next;
4968
+ i++;
4969
+ }
4646
4970
  } else if (!next || next.startsWith("--") || next === "-p" || next === "--param") {
4647
4971
  flags[key] = true;
4648
4972
  } else {
@@ -4655,8 +4979,8 @@ function parseArgs(argv) {
4655
4979
  }
4656
4980
  return { command, args: positional, flags, params };
4657
4981
  }
4658
- async function api2(method, path10, body) {
4659
- let target = `${BASE_URL}${path10}`;
4982
+ async function api2(method, path11, body, opts) {
4983
+ let target = `${BASE_URL}${path11}`;
4660
4984
  let requestBody = body;
4661
4985
  if (method === "GET" && body && typeof body === "object") {
4662
4986
  const params = new URLSearchParams;
@@ -4670,14 +4994,34 @@ async function api2(method, path10, body) {
4670
4994
  target += `${target.includes("?") ? "&" : "?"}${query}`;
4671
4995
  requestBody = undefined;
4672
4996
  }
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
- });
4997
+ const ctrl = new AbortController;
4998
+ const timeoutMs = opts?.timeoutMs;
4999
+ const timer = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
5000
+ let res;
5001
+ try {
5002
+ res = await fetch(target, {
5003
+ method,
5004
+ headers: {
5005
+ ...requestBody ? { "Content-Type": "application/json" } : {},
5006
+ "x-unbrowse-client-id": CLI_CLIENT_ID
5007
+ },
5008
+ body: requestBody ? JSON.stringify(requestBody) : undefined,
5009
+ signal: ctrl.signal
5010
+ });
5011
+ } catch (err) {
5012
+ if (timer)
5013
+ clearTimeout(timer);
5014
+ if (err?.name === "AbortError") {
5015
+ return {
5016
+ error: "cli_timeout",
5017
+ message: `CLI gave up waiting on local server after ${timeoutMs}ms. Try: pkill -9 -f 'unbrowse|kuri'`
5018
+ };
5019
+ }
5020
+ throw err;
5021
+ } finally {
5022
+ if (timer)
5023
+ clearTimeout(timer);
5024
+ }
4681
5025
  if (!res.ok && res.headers.get("content-type")?.includes("json")) {
4682
5026
  return res.json();
4683
5027
  }
@@ -4971,7 +5315,8 @@ async function cmdResolve(flags) {
4971
5315
  body.projection = { raw: true };
4972
5316
  const startedAt = Date.now();
4973
5317
  async function resolveOnce(message = "Still working. Searching cached routes...") {
4974
- return withPendingNotice(api2("POST", "/v1/intent/resolve", body), message);
5318
+ const cliTimeoutMs = (typeof body.budget_ms === "number" ? body.budget_ms : 8000) + 30000;
5319
+ return withPendingNotice(api2("POST", "/v1/intent/resolve", body, { timeoutMs: cliTimeoutMs }), message);
4975
5320
  }
4976
5321
  let result = await resolveOnce();
4977
5322
  const resultError = resolveResultError(result);
@@ -4991,8 +5336,13 @@ async function cmdResolve(flags) {
4991
5336
  const skillId = resolveSkillId();
4992
5337
  if (skillId && endpoints.length > 0) {
4993
5338
  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...");
5339
+ if (endpointNeedsThirdPartyTermsConfirmation(bestEndpoint) && !flags["confirm-third-party-terms"]) {
5340
+ process.stderr.write(`Skipping auto-execute: endpoint ${bestEndpoint.endpoint_id ?? "?"} ` + `requires explicit third-party terms confirmation. ` + `Re-run with --confirm-third-party-terms to proceed.
5341
+ `);
5342
+ } else {
5343
+ info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
5344
+ result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
5345
+ }
4996
5346
  }
4997
5347
  }
4998
5348
  if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
@@ -5054,8 +5404,8 @@ async function cmdResolve(flags) {
5054
5404
  throw error;
5055
5405
  }
5056
5406
  }
5057
- function drillPath(data, path10) {
5058
- const segments = path10.split(/\./).flatMap((s) => {
5407
+ function drillPath(data, path11) {
5408
+ const segments = path11.split(/\./).flatMap((s) => {
5059
5409
  const m = s.match(/^(.+)\[\]$/);
5060
5410
  return m ? [m[1], "[]"] : [s];
5061
5411
  });
@@ -5082,9 +5432,9 @@ function drillPath(data, path10) {
5082
5432
  }
5083
5433
  return values;
5084
5434
  }
5085
- function resolveDotPath(obj, path10) {
5435
+ function resolveDotPath(obj, path11) {
5086
5436
  let cur = obj;
5087
- for (const key of path10.split(".")) {
5437
+ for (const key of path11.split(".")) {
5088
5438
  if (cur == null || typeof cur !== "object")
5089
5439
  return;
5090
5440
  cur = cur[key];
@@ -5101,8 +5451,8 @@ function applyExtract(items, extractSpec) {
5101
5451
  return items.map((item) => {
5102
5452
  const row = {};
5103
5453
  let hasValue = false;
5104
- for (const { alias, path: path10 } of fields) {
5105
- const val = resolveDotPath(item, path10);
5454
+ for (const { alias, path: path11 } of fields) {
5455
+ const val = resolveDotPath(item, path11);
5106
5456
  row[alias] = val ?? null;
5107
5457
  if (val != null)
5108
5458
  hasValue = true;
@@ -5419,7 +5769,67 @@ async function cmdCleanupStale(flags) {
5419
5769
  output(await withPendingNotice(api2("POST", "/v1/stale/cleanup", body), "Cleaning stale endpoints..."), !!flags.pretty);
5420
5770
  }
5421
5771
  async function cmdSearch(flags) {
5422
- await cmdResolve(flags);
5772
+ const intent = flags.intent;
5773
+ const hostType = detectTelemetryHostType();
5774
+ const { decodeTelemetryAttribution: decodeTelemetryAttribution3 } = await Promise.resolve().then(() => (init_telemetry_attribution2(), exports_telemetry_attribution));
5775
+ const attr = decodeTelemetryAttribution3(process.env.UNBROWSE_ATTRIBUTION_B64) ?? {};
5776
+ const attrProps = {};
5777
+ for (const k of ["channel", "campaign_id", "content_id", "variant_id"]) {
5778
+ const v = attr[k];
5779
+ if (v != null)
5780
+ attrProps[k] = v;
5781
+ }
5782
+ const domain = telemetryDomainFromInput(flags.domain, flags.url);
5783
+ if (intent) {
5784
+ await recordFunnelTelemetryEvent("search_started", {
5785
+ source: "cli",
5786
+ hostType,
5787
+ properties: {
5788
+ command: "search",
5789
+ intent,
5790
+ domain,
5791
+ url: typeof flags.url === "string" ? flags.url : null,
5792
+ ...attrProps
5793
+ }
5794
+ });
5795
+ }
5796
+ let resultCount = 0;
5797
+ try {
5798
+ if (intent && domain) {
5799
+ try {
5800
+ const searchRes = await api2("GET", `/v1/search/domain?intent=${encodeURIComponent(intent)}&domain=${encodeURIComponent(domain)}`);
5801
+ resultCount = Array.isArray(searchRes?.results) ? searchRes.results.length : 0;
5802
+ } catch {}
5803
+ }
5804
+ await cmdResolve(flags);
5805
+ if (intent) {
5806
+ await recordFunnelTelemetryEvent("search_completed", {
5807
+ source: "cli",
5808
+ hostType,
5809
+ properties: {
5810
+ command: "search",
5811
+ intent,
5812
+ domain,
5813
+ result_count: resultCount,
5814
+ ...attrProps
5815
+ }
5816
+ });
5817
+ }
5818
+ } catch (err) {
5819
+ if (intent) {
5820
+ await recordFunnelTelemetryEvent("search_failed", {
5821
+ source: "cli",
5822
+ hostType,
5823
+ properties: {
5824
+ command: "search",
5825
+ intent,
5826
+ error: err instanceof Error ? err.message : String(err),
5827
+ ...attrProps
5828
+ }
5829
+ });
5830
+ }
5831
+ throw err;
5832
+ }
5423
5833
  }
5424
5834
  async function cmdSessions(flags) {
5425
5835
  const domain = flags.domain;
@@ -5577,6 +5987,22 @@ async function cmdSetup(flags) {
5577
5987
  }
5578
5988
  async function cmdMode(_flags) {
5579
5989
  await promptContributionMode({ force: true });
5990
+ await syncContributionPreferenceToServer();
5991
+ }
5992
+ async function syncContributionPreferenceToServer() {
5993
+ const cfg = loadConfig();
5994
+ if (!cfg?.api_key)
5995
+ return;
5996
+ const share_pointers = !!getContributionConfig2().contribution.share_pointers;
5997
+ try {
5998
+ await pushAccountPreferences({ share_pointers });
5999
+ info("Synced preference to your account.");
6000
+ } catch (err) {
6001
+ const msg = err.message ?? "";
6002
+ if (msg.includes("account_required") || msg.includes("HTTP 403"))
6003
+ return;
6004
+ info(`Local mode set, but server sync failed: ${msg}`);
6005
+ }
5580
6006
  }
5581
6007
  async function runPostSetupContributionPrompt() {
5582
6008
  try {
@@ -5594,16 +6020,68 @@ async function cmdCapture(flags) {
5594
6020
  const endpoints = Array.isArray(result.endpoints) ? result.endpoints : Array.isArray(result.available_endpoints) ? result.available_endpoints : [];
5595
6021
  const skill = result.skill ?? null;
5596
6022
  const skillId = result.skill_id ?? skill?.skill_id ?? (typeof result.learned_skill_id === "string" ? result.learned_skill_id : undefined);
6023
+ const isThinDocumentOnly = endpoints.length === 1 && (() => {
6024
+ const e0 = endpoints[0];
6025
+ if (!e0 || e0.method !== "GET")
6026
+ return false;
6027
+ const tmpl = (e0.url_template ?? "").split("?")[0].replace(/\/$/, "");
6028
+ const target = url.split("?")[0].replace(/\/$/, "");
6029
+ return tmpl === target;
6030
+ })();
6031
+ const escapedIntent = intent.replace(/"/g, "\\\"");
6032
+ const escapedUrl = url.replace(/"/g, "\\\"");
5597
6033
  const envelope = {
5598
6034
  skill_id: skillId,
5599
6035
  endpoints_discovered: typeof result.endpoints_discovered === "number" ? result.endpoints_discovered : endpoints.length,
5600
6036
  marketplace_published: !!result.marketplace_published,
5601
6037
  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 } : {}
6038
+ next_step: endpoints.length === 0 ? "no endpoints discovered; site may need authentication or different intent" : `unbrowse resolve --intent "${escapedIntent}" --url "${escapedUrl}"`,
6039
+ ...isThinDocumentOnly ? { capture_pattern: "doc_only", capture_observation: "only the input URL was captured (1 GET, no XHR fired during the auto-capture window)" } : {},
6040
+ ...result.error ? { error: result.error } : {},
6041
+ ...result.captured_meta ? { captured_meta: result.captured_meta } : {},
6042
+ ...typeof result.capture_path === "string" ? { capture_path: result.capture_path } : {},
6043
+ ...result.prior_domain_note ? { prior_domain_note: result.prior_domain_note } : {},
6044
+ ...result.note_evidence ? { note_evidence: result.note_evidence } : {}
5604
6045
  };
5605
6046
  output(envelope, !!flags.pretty);
5606
6047
  }
6048
+ async function cmdNote(flags, args) {
6049
+ const subRaw = args?.[0] ?? flags.action;
6050
+ const sub = (typeof subRaw === "string" ? subRaw : "").toLowerCase();
6051
+ if (!sub || !["read", "write", "list"].includes(sub)) {
6052
+ die('usage: unbrowse note <read|write|list> --domain <domain> [--body "..."]');
6053
+ }
6054
+ if (sub === "list") {
6055
+ const { homedir: homedir9 } = await import("os");
6056
+ const { join: join15 } = await import("path");
6057
+ const { readdirSync: readdirSync7, existsSync: existsSync19 } = await import("fs");
6058
+ const profile = process.env.UNBROWSE_PROFILE ?? "";
6059
+ const dir = process.env.UNBROWSE_DOMAIN_NOTES_DIR ?? (profile ? join15(homedir9(), ".unbrowse", "profiles", profile, "domain-notes") : join15(homedir9(), ".unbrowse", "domain-notes"));
6060
+ if (!existsSync19(dir)) {
6061
+ output({ notes: [], dir }, !!flags.pretty);
6062
+ return;
6063
+ }
6064
+ const files = readdirSync7(dir).filter((f) => f.endsWith(".md"));
6065
+ output({ dir, notes: files.map((f) => f.replace(/\.md$/, "")) }, !!flags.pretty);
6066
+ return;
6067
+ }
6068
+ const domain = flags.domain;
6069
+ if (!domain)
6070
+ die("--domain required");
6071
+ if (sub === "read") {
6072
+ const res2 = await api2("GET", `/v1/domain-notes/${encodeURIComponent(domain)}`).catch((err) => ({
6073
+ error: err.message
6074
+ }));
6075
+ output(res2, !!flags.pretty);
6076
+ return;
6077
+ }
6078
+ const body = flags.body;
6079
+ if (typeof body !== "string" || body.trim().length === 0) {
6080
+ die("--body required (non-empty markdown string)");
6081
+ }
6082
+ const res = await api2("POST", `/v1/domain-notes/${encodeURIComponent(domain)}`, { body });
6083
+ output(res, !!flags.pretty);
6084
+ }
5607
6085
  var CLI_REFERENCE = {
5608
6086
  commands: [
5609
6087
  { name: "health", usage: "", desc: "Server health check" },
@@ -5648,9 +6126,10 @@ var CLI_REFERENCE = {
5648
6126
  { name: "earnings", usage: "[--json]", desc: "Show your credit balance, earnings from indexing, and spending" },
5649
6127
  { name: "corpus-test", usage: "--url <url> [--id <id>] [--retries N]", desc: "Capture a single URL with retry logic; keeps best result across N attempts" },
5650
6128
  { name: "corpus-run", usage: "--corpus <file> --out <file> [--retries N]", desc: "Run corpus-test over all cases in a corpus JSON file and write a comparable snapshot" },
5651
- { name: "register", usage: "[--no-prompt]", desc: "Optional: register an API key to publish skills, check earnings, and access backend analytics" },
6129
+ { name: "register", usage: "[--email lewis@example.com] [--no-prompt]", desc: "Register an API key. With --email, sends a magic link via Resend; otherwise creates an anonymous key." },
5652
6130
  { 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`." }
6131
+ { name: "capture", usage: "--url <url> --intent <intent>", desc: "Live-browser capture for a single URL \u2014 discovers + indexes API endpoints. Marketplace publish gated by `unbrowse mode`." },
6132
+ { name: "note", usage: '<read|write|list> --domain <domain> [--body "..."]', desc: "Read/write per-domain LLM-prose notes consumed by augment on next capture. Agent populates after reading capture's note_evidence." }
5654
6133
  ],
5655
6134
  globalFlags: [
5656
6135
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -6245,6 +6724,43 @@ async function cmdClose(flags) {
6245
6724
  output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
6246
6725
  }
6247
6726
  async function cmdRegister(flags) {
6727
+ if (typeof flags.email === "string" && flags.email.length > 0) {
6728
+ const email = flags.email;
6729
+ if (getApiKey()) {
6730
+ info("Already registered. Re-running with --email will mint a new key and overwrite ~/.unbrowse/config.json.");
6731
+ }
6732
+ info(`Sending magic link to ${email}\u2026`);
6733
+ const result = await magicRegister({
6734
+ email,
6735
+ openBrowser: (url) => {
6736
+ info(`Opening browser: ${url}`);
6737
+ try {
6738
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
6739
+ spawn3(cmd, [url], { detached: true, stdio: "ignore" }).unref();
6740
+ } catch {}
6741
+ }
6742
+ });
6743
+ saveConfig({
6744
+ api_key: result.api_key,
6745
+ agent_id: result.agent_id,
6746
+ agent_name: result.email,
6747
+ registered_at: new Date().toISOString(),
6748
+ tos_accepted_version: null,
6749
+ tos_accepted_at: null
6750
+ });
6751
+ process.env.UNBROWSE_API_KEY = result.api_key;
6752
+ info(`Signed in as ${result.email}. API key saved to ~/.unbrowse/config.json.`);
6753
+ try {
6754
+ const serverPrefs = await fetchAccountPreferences();
6755
+ if (serverPrefs) {
6756
+ setContributionConfig2({
6757
+ contribution: { share_pointers: serverPrefs.share_pointers, set_via: "mode-command" }
6758
+ });
6759
+ info(`Auto-publish to marketplace: ${serverPrefs.share_pointers ? "ON" : "off"} (synced from your account).`);
6760
+ }
6761
+ } catch {}
6762
+ return;
6763
+ }
6248
6764
  if (getApiKey()) {
6249
6765
  info("Already registered. API key loaded from env or ~/.unbrowse/config.json");
6250
6766
  return;
@@ -6737,6 +7253,8 @@ async function main() {
6737
7253
  return cmdMode(flags);
6738
7254
  case "capture":
6739
7255
  return cmdCapture(flags);
7256
+ case "note":
7257
+ return cmdNote(flags, args);
6740
7258
  default:
6741
7259
  info(`Unknown command: ${command}`);
6742
7260
  printHelp();