unbrowse 3.0.2 → 3.1.0-experiments.f9b4a71

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
@@ -17,12 +17,21 @@ var __toESM = (mod, isNodeMode, target) => {
17
17
  });
18
18
  return to;
19
19
  };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
20
29
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
21
30
  var __promiseAll = (args) => Promise.all(args);
22
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
23
32
 
24
33
  // ../../src/build-info.generated.ts
25
- var BUILD_RELEASE_VERSION = "3.0.2", BUILD_GIT_SHA = "25aed2ccf282", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4wLjIiLCJnaXRfc2hhIjoiMjVhZWQyY2NmMjgyIiwiY29kZV9oYXNoIjoiMTQ4OGZjMWQ5MmI3IiwidHJhY2VfdmVyc2lvbiI6IjE0ODhmYzFkOTJiN0AyNWFlZDJjY2YyODIiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTA0VDE0OjExOjM3LjQ4NloifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "KPLG1erp1N-qP2bkczhQp8g-pod6ObDr845_DElQzdM", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
34
+ var BUILD_RELEASE_VERSION = "3.1.0-experiments.f9b4a71", BUILD_GIT_SHA = "f9b4a71af04a", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuZjliNGE3MSIsImdpdF9zaGEiOiJmOWI0YTcxYWYwNGEiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QGY5YjRhNzFhZjA0YSIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDVUMTM6MjI6NDYuMTM0WiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "_s5u-H651GXE5q8GnEVaA7daM2EoV_MS-tJDHAUiJIg", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
26
35
 
27
36
  // ../../src/version.ts
28
37
  import { createHash } from "crypto";
@@ -284,8 +293,114 @@ var init_telemetry_attribution = __esm(() => {
284
293
  ];
285
294
  });
286
295
 
296
+ // ../../src/payments/lobster-pay.ts
297
+ var exports_lobster_pay = {};
298
+ __export(exports_lobster_pay, {
299
+ payAndRetry: () => payAndRetry,
300
+ lobsterX402Fetch: () => lobsterX402Fetch,
301
+ isLobsterAvailable: () => isLobsterAvailable
302
+ });
303
+ import { execFile, execFileSync } from "node:child_process";
304
+ import { existsSync as existsSync3 } from "node:fs";
305
+ import { homedir as homedir2 } from "node:os";
306
+ import { join as join3 } from "node:path";
307
+ function getLobsterCommand() {
308
+ try {
309
+ execFileSync("lobstercash", ["--version"], { stdio: "ignore", timeout: 3000 });
310
+ return { cmd: "lobstercash", prefix: [] };
311
+ } catch (_e) {}
312
+ try {
313
+ const npmPrefix = execFileSync("npm", ["config", "get", "prefix"], { encoding: "utf8", timeout: 5000 }).trim();
314
+ const lobsterPath = join3(npmPrefix, "bin", "lobstercash");
315
+ if (existsSync3(lobsterPath)) {
316
+ execFileSync(lobsterPath, ["--version"], { stdio: "ignore", timeout: 3000 });
317
+ return { cmd: lobsterPath, prefix: [] };
318
+ }
319
+ } catch (_e) {}
320
+ return null;
321
+ }
322
+ function lobsterCmd() {
323
+ if (cachedCommand === undefined)
324
+ cachedCommand = getLobsterCommand();
325
+ return cachedCommand;
326
+ }
327
+ function isLobsterAvailable() {
328
+ const agentsPath = join3(process.env.HOME || homedir2(), ".lobster", "agents.json");
329
+ return existsSync3(agentsPath);
330
+ }
331
+ function lobsterX402Fetch(url, options) {
332
+ return new Promise((resolve) => {
333
+ const resolved = lobsterCmd();
334
+ if (!resolved) {
335
+ resolve({ success: false, body: "", error: "lobstercash CLI not in PATH" });
336
+ return;
337
+ }
338
+ const { cmd, prefix } = resolved;
339
+ const args = [...prefix, "x402", "fetch", url, "--debug"];
340
+ if (options?.jsonBody) {
341
+ args.push("--json", options.jsonBody);
342
+ }
343
+ if (options?.headers) {
344
+ for (const [key, value] of Object.entries(options.headers)) {
345
+ args.push("--header", `${key}:${value}`);
346
+ }
347
+ }
348
+ const timeout = options?.timeoutMs ?? LOBSTER_PAY_TIMEOUT_MS;
349
+ args.push("--timeout", String(timeout));
350
+ execFile(cmd, args, { timeout: timeout + 5000, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
351
+ if (err) {
352
+ const msg = stderr?.trim() || err.message;
353
+ console.warn(`[lobster-pay] x402 fetch failed: ${msg}`);
354
+ resolve({ success: false, body: "", error: msg });
355
+ return;
356
+ }
357
+ if (stderr) {
358
+ for (const line of stderr.split(`
359
+ `).filter(Boolean)) {
360
+ console.log(`[lobster-pay] ${line}`);
361
+ }
362
+ }
363
+ const statusMatch = stdout.match(/^Status:\s*(\d+)/m);
364
+ const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : undefined;
365
+ if (statusCode && statusCode >= 400) {
366
+ resolve({ success: false, body: stdout, statusCode, error: `HTTP ${statusCode}` });
367
+ return;
368
+ }
369
+ resolve({ success: true, body: stdout, statusCode });
370
+ });
371
+ });
372
+ }
373
+ async function payAndRetry(fullUrl, options) {
374
+ if (!isLobsterAvailable()) {
375
+ console.log("[lobster-pay] lobster.cash not configured — skipping payment");
376
+ return null;
377
+ }
378
+ console.log(`[lobster-pay] attempting x402 payment for ${fullUrl}`);
379
+ const result = await lobsterX402Fetch(fullUrl, {
380
+ jsonBody: options?.body ? JSON.stringify(options.body) : undefined,
381
+ headers: options?.headers
382
+ });
383
+ if (!result.success) {
384
+ console.warn(`[lobster-pay] payment failed: ${result.error}`);
385
+ return null;
386
+ }
387
+ try {
388
+ const raw = result.body;
389
+ const jsonStart = Math.min(...[raw.indexOf("{"), raw.indexOf("[")].filter((i) => i >= 0));
390
+ const jsonStr = jsonStart >= 0 ? raw.slice(jsonStart) : raw;
391
+ const data = JSON.parse(jsonStr);
392
+ console.log("[lobster-pay] payment successful — got paid response");
393
+ return { data, paid: true };
394
+ } catch (_e) {
395
+ console.warn("[lobster-pay] paid response was not valid JSON");
396
+ return null;
397
+ }
398
+ }
399
+ var LOBSTER_PAY_TIMEOUT_MS = 30000, cachedCommand = undefined;
400
+ var init_lobster_pay = () => {};
401
+
287
402
  // ../../src/runtime/paths.ts
288
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, realpathSync } from "node:fs";
403
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, realpathSync } from "node:fs";
289
404
  import os from "node:os";
290
405
  import path from "node:path";
291
406
  import { createRequire as createRequire2 } from "node:module";
@@ -299,7 +414,7 @@ function getPackageRoot(metaUrl) {
299
414
  let dir = getModuleDir(metaUrl);
300
415
  const root = path.parse(dir).root;
301
416
  while (dir !== root) {
302
- if (existsSync4(path.join(dir, "package.json")))
417
+ if (existsSync6(path.join(dir, "package.json")))
303
418
  return dir;
304
419
  dir = path.dirname(dir);
305
420
  }
@@ -318,7 +433,7 @@ function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
318
433
  const req = createRequire2(metaUrl);
319
434
  const tsxPkg = req.resolve("tsx/package.json");
320
435
  const tsxLoader = path.join(path.dirname(tsxPkg), "dist", "loader.mjs");
321
- if (existsSync4(tsxLoader))
436
+ if (existsSync6(tsxLoader))
322
437
  return ["--import", pathToFileURL(tsxLoader).href, entrypoint];
323
438
  } catch {}
324
439
  return ["--import", "tsx", entrypoint];
@@ -326,16 +441,16 @@ function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
326
441
  function getUnbrowseHome() {
327
442
  return path.join(os.homedir(), ".unbrowse");
328
443
  }
329
- function ensureDir(dir) {
330
- if (!existsSync4(dir))
331
- mkdirSync2(dir, { recursive: true });
444
+ function ensureDir2(dir) {
445
+ if (!existsSync6(dir))
446
+ mkdirSync3(dir, { recursive: true });
332
447
  return dir;
333
448
  }
334
449
  function getLogsDir() {
335
- return ensureDir(path.join(getUnbrowseHome(), "logs"));
450
+ return ensureDir2(path.join(getUnbrowseHome(), "logs"));
336
451
  }
337
452
  function getRunDir() {
338
- return ensureDir(process.env.UNBROWSE_RUN_DIR || path.join(getUnbrowseHome(), "run"));
453
+ return ensureDir2(process.env.UNBROWSE_RUN_DIR || path.join(getUnbrowseHome(), "run"));
339
454
  }
340
455
  function sanitizeSegment(value) {
341
456
  return value.replace(/[^a-zA-Z0-9.-]+/g, "_");
@@ -470,8 +585,8 @@ var init_logger = __esm(() => {
470
585
  });
471
586
 
472
587
  // ../../src/kuri/client.ts
473
- import { execFileSync, spawn as spawn2 } from "node:child_process";
474
- import { existsSync as existsSync6 } from "node:fs";
588
+ import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
589
+ import { existsSync as existsSync8 } from "node:fs";
475
590
  import path5 from "node:path";
476
591
  function createBrokerState(port = KURI_DEFAULT_PORT) {
477
592
  return {
@@ -496,12 +611,14 @@ function currentBundledKuriTarget() {
496
611
  return "linux-arm64";
497
612
  if (process.platform === "linux" && process.arch === "x64")
498
613
  return "linux-x64";
614
+ if (process.platform === "win32" && process.arch === "x64")
615
+ return "win-x64";
499
616
  return null;
500
617
  }
501
618
  function resolveBinaryOnPath(name) {
502
619
  const checker = process.platform === "win32" ? "where" : "which";
503
620
  try {
504
- const output = execFileSync(checker, [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
621
+ const output = execFileSync2(checker, [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
505
622
  const match = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
506
623
  return match || null;
507
624
  } catch {
@@ -544,7 +661,7 @@ function findKuriBinary() {
544
661
  if (process.env.KURI_BIN)
545
662
  return process.env.KURI_BIN;
546
663
  const candidates = getKuriBinaryCandidates();
547
- return candidates.find((candidate) => existsSync6(candidate)) ?? candidates[0] ?? kuriBinaryName();
664
+ return candidates.find((candidate) => existsSync8(candidate)) ?? candidates[0] ?? kuriBinaryName();
548
665
  }
549
666
  var KURI_DEFAULT_PORT = 7700, defaultBrokerState, brokerClients;
550
667
  var init_client2 = __esm(() => {
@@ -567,7 +684,7 @@ var init_browser_access = () => {};
567
684
 
568
685
  // ../../src/capture/index.ts
569
686
  import { nanoid as nanoid2 } from "nanoid";
570
- var activeTabRegistry, interceptorInjectedTabs;
687
+ var activeTabRegistry, interceptorInjectedTabs, cdpDocStartTabs, cdpCapturedHeaders;
571
688
  var init_capture = __esm(() => {
572
689
  init_client2();
573
690
  init_domain();
@@ -575,6 +692,8 @@ var init_capture = __esm(() => {
575
692
  init_browser_access();
576
693
  activeTabRegistry = new Set;
577
694
  interceptorInjectedTabs = new Set;
695
+ cdpDocStartTabs = new Set;
696
+ cdpCapturedHeaders = new Map;
578
697
  });
579
698
 
580
699
  // ../../src/transform/index.ts
@@ -584,11 +703,11 @@ var init_transform = __esm(() => {
584
703
  });
585
704
 
586
705
  // ../../src/debug-trace.ts
587
- import { join as join4 } from "node:path";
706
+ import { join as join6 } from "node:path";
588
707
  import { nanoid as nanoid3 } from "nanoid";
589
708
  var TRACE_DIR;
590
709
  var init_debug_trace = __esm(() => {
591
- TRACE_DIR = process.env.TRACES_DIR ?? join4(process.cwd(), "traces");
710
+ TRACE_DIR = process.env.TRACES_DIR ?? join6(process.cwd(), "traces");
592
711
  });
593
712
 
594
713
  // ../../src/publish/sanitize.ts
@@ -678,8 +797,8 @@ var init_bundle_scanner = __esm(() => {
678
797
  });
679
798
 
680
799
  // ../../src/vault/index.ts
681
- import { join as join5 } from "path";
682
- import { homedir as homedir3 } from "os";
800
+ import { join as join7 } from "path";
801
+ import { homedir as homedir5 } from "os";
683
802
  function normalizeKeytarModule(mod) {
684
803
  let candidate = mod;
685
804
  for (let depth = 0;depth < 3; depth++) {
@@ -701,9 +820,9 @@ var init_vault = __esm(async () => {
701
820
  try {
702
821
  keytar = normalizeKeytarModule(await import("keytar"));
703
822
  } catch {}
704
- VAULT_DIR = join5(homedir3(), ".unbrowse", "vault");
705
- VAULT_FILE = join5(VAULT_DIR, "credentials.enc");
706
- KEY_FILE = join5(VAULT_DIR, ".key");
823
+ VAULT_DIR = join7(homedir5(), ".unbrowse", "vault");
824
+ VAULT_FILE = join7(VAULT_DIR, "credentials.enc");
825
+ KEY_FILE = join7(VAULT_DIR, ".key");
707
826
  vaultLock = Promise.resolve();
708
827
  });
709
828
 
@@ -854,7 +973,7 @@ var init_schema_review = __esm(() => {
854
973
  });
855
974
 
856
975
  // ../../src/indexer/index.ts
857
- import { join as join6 } from "node:path";
976
+ import { join as join8 } from "node:path";
858
977
  var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
859
978
  var init_indexer = __esm(async () => {
860
979
  init_graph();
@@ -869,7 +988,7 @@ var init_indexer = __esm(async () => {
869
988
  init_graph();
870
989
  init_schema_review();
871
990
  await init_orchestrator();
872
- SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join6(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
991
+ SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
873
992
  indexInFlight = new Map;
874
993
  pendingIndexJobs = new Map;
875
994
  });
@@ -1046,8 +1165,8 @@ var init_routing_telemetry = __esm(() => {
1046
1165
  });
1047
1166
  // ../../src/orchestrator/index.ts
1048
1167
  import { nanoid as nanoid9 } from "nanoid";
1049
- import { existsSync as existsSync7, writeFileSync as writeFileSync3, readFileSync as readFileSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "node:fs";
1050
- import { dirname as dirname2, join as join7 } from "node:path";
1168
+ import { existsSync as existsSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "node:fs";
1169
+ import { dirname as dirname3, join as join9 } from "node:path";
1051
1170
  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;
1052
1171
  var init_orchestrator = __esm(async () => {
1053
1172
  init_client();
@@ -1077,13 +1196,13 @@ var init_orchestrator = __esm(async () => {
1077
1196
  captureInFlight = new Map;
1078
1197
  captureDomainLocks = new Map;
1079
1198
  skillRouteCache = new Map;
1080
- ROUTE_CACHE_FILE = join7(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
1081
- SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join7(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1199
+ ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
1200
+ SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1082
1201
  domainSkillCache = new Map;
1083
- DOMAIN_CACHE_FILE = join7(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1202
+ DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1084
1203
  try {
1085
- if (existsSync7(DOMAIN_CACHE_FILE)) {
1086
- const data = JSON.parse(readFileSync5(DOMAIN_CACHE_FILE, "utf-8"));
1204
+ if (existsSync9(DOMAIN_CACHE_FILE)) {
1205
+ const data = JSON.parse(readFileSync6(DOMAIN_CACHE_FILE, "utf-8"));
1087
1206
  for (const [k, v] of Object.entries(data)) {
1088
1207
  const entry = v;
1089
1208
  if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
@@ -1098,17 +1217,17 @@ var init_orchestrator = __esm(async () => {
1098
1217
  return;
1099
1218
  _routeCacheDirty = false;
1100
1219
  try {
1101
- const dir = dirname2(ROUTE_CACHE_FILE);
1102
- if (!existsSync7(dir))
1103
- mkdirSync4(dir, { recursive: true });
1220
+ const dir = dirname3(ROUTE_CACHE_FILE);
1221
+ if (!existsSync9(dir))
1222
+ mkdirSync5(dir, { recursive: true });
1104
1223
  const entries = Object.fromEntries(skillRouteCache);
1105
1224
  writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
1106
1225
  } catch {}
1107
1226
  }, 5000);
1108
1227
  routeCacheFlushTimer.unref?.();
1109
1228
  try {
1110
- if (existsSync7(ROUTE_CACHE_FILE)) {
1111
- const data = JSON.parse(readFileSync5(ROUTE_CACHE_FILE, "utf-8"));
1229
+ if (existsSync9(ROUTE_CACHE_FILE)) {
1230
+ const data = JSON.parse(readFileSync6(ROUTE_CACHE_FILE, "utf-8"));
1112
1231
  for (const [k, v] of Object.entries(data)) {
1113
1232
  const entry = v;
1114
1233
  if (Date.now() - entry.ts < 24 * 60 * 60000) {
@@ -1198,6 +1317,60 @@ var init_orchestrator = __esm(async () => {
1198
1317
  ]);
1199
1318
  });
1200
1319
 
1320
+ // ../../src/payments/wallet.ts
1321
+ var exports_wallet = {};
1322
+ __export(exports_wallet, {
1323
+ getWalletContext: () => getWalletContext2,
1324
+ checkWalletConfigured: () => checkWalletConfigured2
1325
+ });
1326
+ import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
1327
+ import { homedir as homedir6 } from "node:os";
1328
+ import { join as join11 } from "node:path";
1329
+ function asNonEmptyString2(value) {
1330
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
1331
+ }
1332
+ function getLobsterWalletFromLocalConfig2() {
1333
+ const agentsPath = join11(process.env.HOME || homedir6(), ".lobster", "agents.json");
1334
+ if (!existsSync13(agentsPath))
1335
+ return;
1336
+ try {
1337
+ const raw = JSON.parse(readFileSync9(agentsPath, "utf8"));
1338
+ const activeAgentId = asNonEmptyString2(raw.activeAgentId);
1339
+ const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString2(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
1340
+ return asNonEmptyString2(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString2(activeAgent?.walletAddress) ?? asNonEmptyString2(activeAgent?.wallet_address);
1341
+ } catch {
1342
+ return;
1343
+ }
1344
+ }
1345
+ function getWalletContext2() {
1346
+ const lobsterWallet = asNonEmptyString2(process.env.LOBSTER_WALLET_ADDRESS);
1347
+ if (lobsterWallet) {
1348
+ return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
1349
+ }
1350
+ const genericWallet = asNonEmptyString2(process.env.AGENT_WALLET_ADDRESS);
1351
+ if (genericWallet) {
1352
+ return {
1353
+ wallet_address: genericWallet,
1354
+ wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
1355
+ };
1356
+ }
1357
+ const localLobsterWallet = getLobsterWalletFromLocalConfig2();
1358
+ if (localLobsterWallet) {
1359
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
1360
+ }
1361
+ return {};
1362
+ }
1363
+ function checkWalletConfigured2() {
1364
+ const wallet = getWalletContext2();
1365
+ if (!wallet.wallet_address)
1366
+ return { configured: false };
1367
+ return {
1368
+ configured: true,
1369
+ provider: wallet.wallet_provider ?? "unknown"
1370
+ };
1371
+ }
1372
+ var init_wallet2 = () => {};
1373
+
1201
1374
  // ../../src/cli.ts
1202
1375
  import { config as loadEnv } from "dotenv";
1203
1376
  import { spawn as spawn3 } from "child_process";
@@ -1207,9 +1380,9 @@ init_version();
1207
1380
  init_cascade();
1208
1381
  init_wallet();
1209
1382
  init_telemetry_attribution();
1210
- import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2 } from "fs";
1211
- import { join as join3 } from "path";
1212
- import { homedir as homedir2, hostname } from "os";
1383
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
1384
+ import { join as join4 } from "path";
1385
+ import { homedir as homedir3, hostname } from "os";
1213
1386
  import { randomBytes, createHash as createHash2 } from "crypto";
1214
1387
  import { createInterface } from "readline";
1215
1388
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
@@ -1244,13 +1417,13 @@ function decodeBase64Json(value) {
1244
1417
  function getConfigDir() {
1245
1418
  if (process.env.UNBROWSE_CONFIG_DIR)
1246
1419
  return process.env.UNBROWSE_CONFIG_DIR;
1247
- return PROFILE_NAME ? join3(homedir2(), ".unbrowse", "profiles", PROFILE_NAME) : join3(homedir2(), ".unbrowse");
1420
+ return PROFILE_NAME ? join4(homedir3(), ".unbrowse", "profiles", PROFILE_NAME) : join4(homedir3(), ".unbrowse");
1248
1421
  }
1249
1422
  function getConfigPath() {
1250
- return join3(getConfigDir(), "config.json");
1423
+ return join4(getConfigDir(), "config.json");
1251
1424
  }
1252
1425
  function getInstallTelemetryPath() {
1253
- return join3(getConfigDir(), "install-state.json");
1426
+ return join4(getConfigDir(), "install-state.json");
1254
1427
  }
1255
1428
  function getLandingToken() {
1256
1429
  const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
@@ -1265,7 +1438,7 @@ function getActiveProfile() {
1265
1438
  function loadConfig() {
1266
1439
  try {
1267
1440
  const configPath = getConfigPath();
1268
- if (existsSync3(configPath)) {
1441
+ if (existsSync4(configPath)) {
1269
1442
  return JSON.parse(readFileSync3(configPath, "utf-8"));
1270
1443
  }
1271
1444
  } catch {}
@@ -1274,14 +1447,14 @@ function loadConfig() {
1274
1447
  function saveConfig(config) {
1275
1448
  const configDir = getConfigDir();
1276
1449
  const configPath = getConfigPath();
1277
- if (!existsSync3(configDir))
1450
+ if (!existsSync4(configDir))
1278
1451
  mkdirSync(configDir, { recursive: true });
1279
1452
  writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
1280
1453
  }
1281
1454
  function loadInstallTelemetryState() {
1282
1455
  try {
1283
1456
  const statePath = getInstallTelemetryPath();
1284
- if (existsSync3(statePath)) {
1457
+ if (existsSync4(statePath)) {
1285
1458
  return JSON.parse(readFileSync3(statePath, "utf-8"));
1286
1459
  }
1287
1460
  } catch {}
@@ -1290,7 +1463,7 @@ function loadInstallTelemetryState() {
1290
1463
  function saveInstallTelemetryState(state) {
1291
1464
  const configDir = getConfigDir();
1292
1465
  const statePath = getInstallTelemetryPath();
1293
- if (!existsSync3(configDir))
1466
+ if (!existsSync4(configDir))
1294
1467
  mkdirSync(configDir, { recursive: true });
1295
1468
  writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
1296
1469
  }
@@ -1448,6 +1621,10 @@ function getApiKey() {
1448
1621
  }
1449
1622
  return "";
1450
1623
  }
1624
+ function getAgentId() {
1625
+ const config = loadConfig();
1626
+ return config?.agent_id ?? null;
1627
+ }
1451
1628
  var API_TIMEOUT_MS = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
1452
1629
  var PUBLISH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
1453
1630
  async function validateApiKey(key) {
@@ -1547,6 +1724,26 @@ async function apiRequest(method, path, body, opts) {
1547
1724
  const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
1548
1725
  const legacyPaymentTerms = res.headers.get("X-Payment-Required");
1549
1726
  const terms = paymentRequired ? decodeBase64Json(paymentRequired) : legacyPaymentTerms ? JSON.parse(legacyPaymentTerms) : data.terms;
1727
+ try {
1728
+ const { isLobsterAvailable: isLobsterAvailable2, payAndRetry: payAndRetry2 } = await Promise.resolve().then(() => (init_lobster_pay(), exports_lobster_pay));
1729
+ if (isLobsterAvailable2()) {
1730
+ const fullUrl = `${API_URL}${path}`;
1731
+ const paidResult = await payAndRetry2(fullUrl, {
1732
+ body,
1733
+ headers: {
1734
+ "Content-Type": "application/json",
1735
+ "Accept-Encoding": "gzip, deflate",
1736
+ ...releaseAttestationHeaders,
1737
+ ...key ? { Authorization: `Bearer ${key}` } : {}
1738
+ }
1739
+ });
1740
+ if (paidResult) {
1741
+ return { data: paidResult.data, headers: new Headers };
1742
+ }
1743
+ }
1744
+ } catch (payErr) {
1745
+ console.warn(`[x402] lobster pay-and-retry failed: ${payErr.message}`);
1746
+ }
1550
1747
  const err = new Error(`Payment required: ${data.error ?? "This skill requires payment"}`);
1551
1748
  err.x402 = true;
1552
1749
  err.terms = terms;
@@ -1565,19 +1762,10 @@ async function api(method, path, body, opts) {
1565
1762
  return data;
1566
1763
  }
1567
1764
  function parseInstallAttribution() {
1568
- const result = {};
1569
- const b64 = process.env.UNBROWSE_ATTRIBUTION_B64;
1570
- if (b64) {
1571
- try {
1572
- const decoded = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
1573
- if (decoded && typeof decoded === "object")
1574
- result.install_attribution = decoded;
1575
- } catch {}
1576
- }
1577
1765
  const token = process.env.UNBROWSE_LANDING_TOKEN;
1578
1766
  if (token && token.length < 2048)
1579
- result.landing_token = token;
1580
- return result;
1767
+ return { landing_token: token };
1768
+ return {};
1581
1769
  }
1582
1770
  async function promptTosAcceptance(summary, tosUrl) {
1583
1771
  if (process.env.UNBROWSE_NON_INTERACTIVE === "1") {
@@ -1748,6 +1936,170 @@ async function syncAgentWallet(wallet = getLocalWalletContext()) {
1748
1936
  return;
1749
1937
  saveConfig({ ...config, ...wallet });
1750
1938
  }
1939
+ async function getTransactionHistory(agentId) {
1940
+ return api("GET", `/v1/transactions/consumer/${agentId}`);
1941
+ }
1942
+ async function getCreatorEarnings(agentId) {
1943
+ return api("GET", `/v1/transactions/creator/${agentId}`);
1944
+ }
1945
+
1946
+ // ../../src/impact-log.ts
1947
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync } from "node:fs";
1948
+ import { homedir as homedir4 } from "node:os";
1949
+ import { dirname as dirname2, join as join5 } from "node:path";
1950
+ var MAX_LOG_BYTES = 5 * 1024 * 1024;
1951
+ var MAX_ROTATIONS = 3;
1952
+ function getLogDir() {
1953
+ if (process.env.UNBROWSE_CONFIG_DIR)
1954
+ return process.env.UNBROWSE_CONFIG_DIR;
1955
+ const profile = process.env.UNBROWSE_PROFILE?.trim();
1956
+ return profile ? join5(homedir4(), ".unbrowse", "profiles", profile) : join5(homedir4(), ".unbrowse");
1957
+ }
1958
+ function getImpactLogPath() {
1959
+ return join5(getLogDir(), "impact-log.jsonl");
1960
+ }
1961
+ function ensureDir(path) {
1962
+ const dir = dirname2(path);
1963
+ if (!existsSync5(dir))
1964
+ mkdirSync2(dir, { recursive: true });
1965
+ }
1966
+ function rotateIfNeeded(path) {
1967
+ try {
1968
+ if (!existsSync5(path))
1969
+ return;
1970
+ const size = statSync(path).size;
1971
+ if (size < MAX_LOG_BYTES)
1972
+ return;
1973
+ for (let i = MAX_ROTATIONS;i >= 1; i--) {
1974
+ const older = `${path}.${i}`;
1975
+ if (!existsSync5(older))
1976
+ continue;
1977
+ if (i === MAX_ROTATIONS) {
1978
+ try {
1979
+ unlinkSync(older);
1980
+ } catch {}
1981
+ } else {
1982
+ try {
1983
+ renameSync(older, `${path}.${i + 1}`);
1984
+ } catch {}
1985
+ }
1986
+ }
1987
+ renameSync(path, `${path}.1`);
1988
+ } catch {}
1989
+ }
1990
+ function appendImpact(entry) {
1991
+ try {
1992
+ const hasSignal = (entry.time_saved_ms ?? 0) > 0 || (entry.tokens_saved ?? 0) > 0 || (entry.cost_saved_uc ?? 0) > 0 || entry.browser_avoided === true;
1993
+ if (!hasSignal)
1994
+ return;
1995
+ const path = getImpactLogPath();
1996
+ ensureDir(path);
1997
+ rotateIfNeeded(path);
1998
+ appendFileSync(path, JSON.stringify(entry) + `
1999
+ `, "utf8");
2000
+ } catch {}
2001
+ }
2002
+ function impactFromResult(command, result, extras = {}) {
2003
+ if (!result || typeof result !== "object")
2004
+ return null;
2005
+ const r = result;
2006
+ const impact = r.impact ?? null;
2007
+ if (!impact || typeof impact !== "object")
2008
+ return null;
2009
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
2010
+ return {
2011
+ ts: new Date().toISOString(),
2012
+ command,
2013
+ source: typeof impact.source === "string" ? impact.source : undefined,
2014
+ domain: extras.domain,
2015
+ intent: extras.intent,
2016
+ skill_id: extras.skill_id ?? (typeof r.skill_id === "string" ? r.skill_id : undefined),
2017
+ endpoint_id: extras.endpoint_id ?? (typeof r.endpoint_id === "string" ? r.endpoint_id : undefined),
2018
+ time_saved_ms: num(impact.time_saved_ms),
2019
+ time_saved_pct: num(impact.time_saved_pct),
2020
+ tokens_saved: num(impact.tokens_saved),
2021
+ tokens_saved_pct: num(impact.tokens_saved_pct),
2022
+ cost_saved_uc: num(impact.cost_saved_uc),
2023
+ browser_avoided: impact.browser_avoided === true,
2024
+ success: r.error == null
2025
+ };
2026
+ }
2027
+ function readImpactSummary() {
2028
+ const path = getImpactLogPath();
2029
+ const summary = {
2030
+ total_runs: 0,
2031
+ successful_runs: 0,
2032
+ browser_avoided_runs: 0,
2033
+ total_time_saved_ms: 0,
2034
+ total_tokens_saved: 0,
2035
+ total_cost_saved_uc: 0,
2036
+ avg_time_saved_pct: 0,
2037
+ avg_tokens_saved_pct: 0,
2038
+ by_source: {},
2039
+ first_entry_at: null,
2040
+ last_entry_at: null
2041
+ };
2042
+ const files = [];
2043
+ for (let i = MAX_ROTATIONS;i >= 1; i--) {
2044
+ const rotated = `${path}.${i}`;
2045
+ if (existsSync5(rotated))
2046
+ files.push(rotated);
2047
+ }
2048
+ if (existsSync5(path))
2049
+ files.push(path);
2050
+ if (files.length === 0)
2051
+ return summary;
2052
+ let timePctSum = 0;
2053
+ let timePctCount = 0;
2054
+ let tokenPctSum = 0;
2055
+ let tokenPctCount = 0;
2056
+ for (const file of files) {
2057
+ let raw;
2058
+ try {
2059
+ raw = readFileSync4(file, "utf8");
2060
+ } catch {
2061
+ continue;
2062
+ }
2063
+ for (const line of raw.split(`
2064
+ `)) {
2065
+ const trimmed = line.trim();
2066
+ if (!trimmed)
2067
+ continue;
2068
+ let e;
2069
+ try {
2070
+ e = JSON.parse(trimmed);
2071
+ } catch {
2072
+ continue;
2073
+ }
2074
+ summary.total_runs += 1;
2075
+ if (e.success !== false)
2076
+ summary.successful_runs += 1;
2077
+ if (e.browser_avoided)
2078
+ summary.browser_avoided_runs += 1;
2079
+ summary.total_time_saved_ms += e.time_saved_ms ?? 0;
2080
+ summary.total_tokens_saved += e.tokens_saved ?? 0;
2081
+ summary.total_cost_saved_uc += e.cost_saved_uc ?? 0;
2082
+ if (typeof e.time_saved_pct === "number") {
2083
+ timePctSum += e.time_saved_pct;
2084
+ timePctCount += 1;
2085
+ }
2086
+ if (typeof e.tokens_saved_pct === "number") {
2087
+ tokenPctSum += e.tokens_saved_pct;
2088
+ tokenPctCount += 1;
2089
+ }
2090
+ if (e.source) {
2091
+ summary.by_source[e.source] = (summary.by_source[e.source] ?? 0) + 1;
2092
+ }
2093
+ if (!summary.first_entry_at || e.ts < summary.first_entry_at)
2094
+ summary.first_entry_at = e.ts;
2095
+ if (!summary.last_entry_at || e.ts > summary.last_entry_at)
2096
+ summary.last_entry_at = e.ts;
2097
+ }
2098
+ }
2099
+ summary.avg_time_saved_pct = timePctCount > 0 ? Math.round(timePctSum / timePctCount) : 0;
2100
+ summary.avg_tokens_saved_pct = tokenPctCount > 0 ? Math.round(tokenPctSum / tokenPctCount) : 0;
2101
+ return summary;
2102
+ }
1751
2103
 
1752
2104
  // ../../src/cli/shortcuts.ts
1753
2105
  var linkedin = {
@@ -1948,7 +2300,7 @@ function buildDepsMetadata(pack, taskName) {
1948
2300
  init_paths();
1949
2301
  init_supervisor();
1950
2302
  init_version();
1951
- import { openSync, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2303
+ import { openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
1952
2304
  import path2 from "node:path";
1953
2305
  import { spawn } from "node:child_process";
1954
2306
  function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
@@ -1986,14 +2338,14 @@ function isPidAlive(pid) {
1986
2338
  }
1987
2339
  function readPidState(pidFile) {
1988
2340
  try {
1989
- return JSON.parse(readFileSync4(pidFile, "utf-8"));
2341
+ return JSON.parse(readFileSync5(pidFile, "utf-8"));
1990
2342
  } catch {
1991
2343
  return null;
1992
2344
  }
1993
2345
  }
1994
2346
  function clearStalePidFile(pidFile) {
1995
2347
  try {
1996
- unlinkSync(pidFile);
2348
+ unlinkSync2(pidFile);
1997
2349
  } catch {}
1998
2350
  }
1999
2351
  function deriveListenEnv(baseUrl) {
@@ -2027,7 +2379,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
2027
2379
  const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
2028
2380
  const spawnSpec = getServerSpawnSpec(metaUrl, entrypoint);
2029
2381
  const logFile = getServerAutostartLogFile();
2030
- ensureDir(path2.dirname(logFile));
2382
+ ensureDir2(path2.dirname(logFile));
2031
2383
  const logFd = openSync(logFile, "a");
2032
2384
  const child = spawn(spawnSpec.command, spawnSpec.args, {
2033
2385
  cwd: spawnSpec.cwd,
@@ -2159,7 +2511,7 @@ async function restartServer(baseUrl, metaUrl) {
2159
2511
  }
2160
2512
 
2161
2513
  // ../../src/runtime/paths.ts
2162
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, realpathSync as realpathSync2 } from "node:fs";
2514
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
2163
2515
  import path3 from "node:path";
2164
2516
  import { createRequire as createRequire3 } from "node:module";
2165
2517
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -2179,7 +2531,7 @@ function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
2179
2531
  const req = createRequire3(metaUrl);
2180
2532
  const tsxPkg = req.resolve("tsx/package.json");
2181
2533
  const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
2182
- if (existsSync5(tsxLoader))
2534
+ if (existsSync7(tsxLoader))
2183
2535
  return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
2184
2536
  } catch {}
2185
2537
  return ["--import", "tsx", entrypoint];
@@ -2209,8 +2561,8 @@ init_publish();
2209
2561
  init_settings();
2210
2562
  init_graph();
2211
2563
  init_schema_review();
2212
- import { join as join8 } from "node:path";
2213
- var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
2564
+ import { join as join10 } from "node:path";
2565
+ var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
2214
2566
  var indexInFlight2 = new Map;
2215
2567
  var pendingIndexJobs2 = new Map;
2216
2568
  async function drainPendingIndexJobs() {
@@ -2247,14 +2599,14 @@ init_paths();
2247
2599
  init_client2();
2248
2600
  init_logger();
2249
2601
  init_wallet();
2250
- import { execFileSync as execFileSync2 } from "node:child_process";
2251
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
2602
+ import { execFileSync as execFileSync3 } from "node:child_process";
2603
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
2252
2604
  import os4 from "node:os";
2253
2605
  import path7 from "node:path";
2254
2606
 
2255
2607
  // ../../src/runtime/update-hints.ts
2256
2608
  init_paths();
2257
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
2609
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2258
2610
  import os3 from "node:os";
2259
2611
  import path6 from "node:path";
2260
2612
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
@@ -2267,20 +2619,20 @@ function getConfigDir2() {
2267
2619
  return process.env.UNBROWSE_CONFIG_DIR;
2268
2620
  return path6.join(getHomeDir(), ".unbrowse");
2269
2621
  }
2270
- function ensureDir2(dir) {
2271
- if (!existsSync8(dir))
2272
- mkdirSync5(dir, { recursive: true });
2622
+ function ensureDir3(dir) {
2623
+ if (!existsSync10(dir))
2624
+ mkdirSync6(dir, { recursive: true });
2273
2625
  return dir;
2274
2626
  }
2275
2627
  function readJsonFile(file) {
2276
2628
  try {
2277
- return JSON.parse(readFileSync6(file, "utf8"));
2629
+ return JSON.parse(readFileSync7(file, "utf8"));
2278
2630
  } catch {
2279
2631
  return null;
2280
2632
  }
2281
2633
  }
2282
2634
  function writeJsonFile(file, value) {
2283
- ensureDir2(path6.dirname(file));
2635
+ ensureDir3(path6.dirname(file));
2284
2636
  writeFileSync4(file, `${JSON.stringify(value, null, 2)}
2285
2637
  `);
2286
2638
  }
@@ -2291,7 +2643,7 @@ function detectRepoRoot(start2) {
2291
2643
  let dir = path6.resolve(start2);
2292
2644
  const root = path6.parse(dir).root;
2293
2645
  while (dir !== root) {
2294
- if (existsSync8(path6.join(dir, ".git")))
2646
+ if (existsSync10(path6.join(dir, ".git")))
2295
2647
  return dir;
2296
2648
  dir = path6.dirname(dir);
2297
2649
  }
@@ -2370,13 +2722,13 @@ codex_hooks = true
2370
2722
  }
2371
2723
  function writeCodexHook(metaUrl) {
2372
2724
  const configPath = getCodexConfigPath();
2373
- if (!existsSync8(path6.dirname(configPath))) {
2725
+ if (!existsSync10(path6.dirname(configPath))) {
2374
2726
  return { host: "codex", action: "not-detected", config_file: configPath };
2375
2727
  }
2376
2728
  try {
2377
2729
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2378
- const fileExistsBefore = existsSync8(configPath);
2379
- let content = fileExistsBefore ? readFileSync6(configPath, "utf8") : "";
2730
+ const fileExistsBefore = existsSync10(configPath);
2731
+ let content = fileExistsBefore ? readFileSync7(configPath, "utf8") : "";
2380
2732
  const previous = content;
2381
2733
  content = ensureCodexHooksFeature(content);
2382
2734
  if (!content.includes("unbrowse-update-hint.mjs")) {
@@ -2410,13 +2762,13 @@ command = ${JSON.stringify(command)}
2410
2762
  }
2411
2763
  function writeClaudeHook(metaUrl) {
2412
2764
  const settingsPath = getClaudeSettingsPath();
2413
- if (!existsSync8(path6.dirname(settingsPath))) {
2765
+ if (!existsSync10(path6.dirname(settingsPath))) {
2414
2766
  return { host: "claude", action: "not-detected", config_file: settingsPath };
2415
2767
  }
2416
2768
  try {
2417
2769
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2418
2770
  const command = `node "${hookScript}"`;
2419
- const fileExistsBefore = existsSync8(settingsPath);
2771
+ const fileExistsBefore = existsSync10(settingsPath);
2420
2772
  const settings = readJsonFile(settingsPath) ?? {};
2421
2773
  settings.hooks ??= {};
2422
2774
  settings.hooks.SessionStart ??= [];
@@ -2454,7 +2806,7 @@ function configureUpdateHintHooks(metaUrl, install) {
2454
2806
  function hasBinary(name) {
2455
2807
  const checker = process.platform === "win32" ? "where" : "which";
2456
2808
  try {
2457
- execFileSync2(checker, [name], { stdio: "ignore" });
2809
+ execFileSync3(checker, [name], { stdio: "ignore" });
2458
2810
  return true;
2459
2811
  } catch {
2460
2812
  return false;
@@ -2481,7 +2833,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
2481
2833
  return path7.join(cwd, ".opencode", "commands");
2482
2834
  }
2483
2835
  function detectOpenCode(cwd) {
2484
- return hasBinary("opencode") || existsSync9(path7.join(resolveConfigHome(), "opencode")) || existsSync9(path7.join(cwd, ".opencode"));
2836
+ return hasBinary("opencode") || existsSync11(path7.join(resolveConfigHome(), "opencode")) || existsSync11(path7.join(cwd, ".opencode"));
2485
2837
  }
2486
2838
  function renderOpenCodeCommand() {
2487
2839
  return `---
@@ -2509,12 +2861,12 @@ function writeOpenCodeCommand(scope, cwd) {
2509
2861
  if (scope === "auto" && !detected) {
2510
2862
  return { detected: false, action: "not-detected", scope: "off" };
2511
2863
  }
2512
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync9(path7.join(cwd, ".opencode")) ? "project" : "global";
2864
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync11(path7.join(cwd, ".opencode")) ? "project" : "global";
2513
2865
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
2514
- const commandFile = path7.join(ensureDir(commandsDir), "unbrowse.md");
2866
+ const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
2515
2867
  const content = renderOpenCodeCommand();
2516
- const action2 = existsSync9(commandFile) ? "updated" : "installed";
2517
- mkdirSync6(path7.dirname(commandFile), { recursive: true });
2868
+ const action2 = existsSync11(commandFile) ? "updated" : "installed";
2869
+ mkdirSync7(path7.dirname(commandFile), { recursive: true });
2518
2870
  writeFileSync5(commandFile, content);
2519
2871
  return {
2520
2872
  detected: detected || scope !== "auto",
@@ -2525,10 +2877,10 @@ function writeOpenCodeCommand(scope, cwd) {
2525
2877
  }
2526
2878
  async function ensureBrowserEngineInstalled() {
2527
2879
  const binary = findKuriBinary();
2528
- if (existsSync9(binary)) {
2880
+ if (existsSync11(binary)) {
2529
2881
  return { installed: true, action: "already-installed" };
2530
2882
  }
2531
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync9(path7.join(candidate, "build.zig")));
2883
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync11(path7.join(candidate, "build.zig")));
2532
2884
  if (!sourceDir) {
2533
2885
  return {
2534
2886
  installed: false,
@@ -2544,13 +2896,13 @@ async function ensureBrowserEngineInstalled() {
2544
2896
  };
2545
2897
  }
2546
2898
  try {
2547
- execFileSync2("zig", ["build", "-Doptimize=ReleaseFast"], {
2899
+ execFileSync3("zig", ["build", "-Doptimize=ReleaseFast"], {
2548
2900
  cwd: sourceDir,
2549
2901
  stdio: "inherit",
2550
2902
  timeout: 300000
2551
2903
  });
2552
2904
  const builtBinary = findKuriBinary();
2553
- if (existsSync9(builtBinary)) {
2905
+ if (existsSync11(builtBinary)) {
2554
2906
  return {
2555
2907
  installed: true,
2556
2908
  action: "installed",
@@ -2575,11 +2927,11 @@ async function runSetup(options) {
2575
2927
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
2576
2928
  const walletCheck = checkWalletConfigured();
2577
2929
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
2578
- const lobsterInstalled = hasBinary("lobstercash") || existsSync9(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2930
+ const lobsterInstalled = hasBinary("lobstercash") || existsSync11(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2579
2931
  if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
2580
2932
  console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
2581
2933
  try {
2582
- execFileSync2("npx", ["@crossmint/lobster-cli", "setup"], {
2934
+ execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
2583
2935
  stdio: "inherit",
2584
2936
  timeout: 60000
2585
2937
  });
@@ -2615,7 +2967,7 @@ async function runSetup(options) {
2615
2967
 
2616
2968
  // ../../src/runtime/update-hints.ts
2617
2969
  init_paths();
2618
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
2970
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
2619
2971
  import os5 from "node:os";
2620
2972
  import path8 from "node:path";
2621
2973
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
@@ -2628,20 +2980,20 @@ function getConfigDir3() {
2628
2980
  return process.env.UNBROWSE_CONFIG_DIR;
2629
2981
  return path8.join(getHomeDir2(), ".unbrowse");
2630
2982
  }
2631
- function ensureDir3(dir) {
2632
- if (!existsSync10(dir))
2633
- mkdirSync7(dir, { recursive: true });
2983
+ function ensureDir4(dir) {
2984
+ if (!existsSync12(dir))
2985
+ mkdirSync8(dir, { recursive: true });
2634
2986
  return dir;
2635
2987
  }
2636
2988
  function readJsonFile2(file) {
2637
2989
  try {
2638
- return JSON.parse(readFileSync7(file, "utf8"));
2990
+ return JSON.parse(readFileSync8(file, "utf8"));
2639
2991
  } catch {
2640
2992
  return null;
2641
2993
  }
2642
2994
  }
2643
2995
  function writeJsonFile2(file, value) {
2644
- ensureDir3(path8.dirname(file));
2996
+ ensureDir4(path8.dirname(file));
2645
2997
  writeFileSync6(file, `${JSON.stringify(value, null, 2)}
2646
2998
  `);
2647
2999
  }
@@ -2655,7 +3007,7 @@ function detectRepoRoot2(start2) {
2655
3007
  let dir = path8.resolve(start2);
2656
3008
  const root = path8.parse(dir).root;
2657
3009
  while (dir !== root) {
2658
- if (existsSync10(path8.join(dir, ".git")))
3010
+ if (existsSync12(path8.join(dir, ".git")))
2659
3011
  return dir;
2660
3012
  dir = path8.dirname(dir);
2661
3013
  }
@@ -2688,7 +3040,7 @@ function detectInstallHost2(repoRoot) {
2688
3040
  function getInstalledVersion(metaUrl) {
2689
3041
  const packageRoot = getPackageRoot(metaUrl);
2690
3042
  try {
2691
- const pkg = JSON.parse(readFileSync7(path8.join(packageRoot, "package.json"), "utf8"));
3043
+ const pkg = JSON.parse(readFileSync8(path8.join(packageRoot, "package.json"), "utf8"));
2692
3044
  return pkg.version ?? "unknown";
2693
3045
  } catch {
2694
3046
  return "unknown";
@@ -2789,6 +3141,7 @@ loadEnv({ quiet: true });
2789
3141
  loadEnv({ path: ".env.runtime", quiet: true });
2790
3142
  var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
2791
3143
  var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
3144
+ var walletNudgeShown = false;
2792
3145
  function parseArgs(argv) {
2793
3146
  const raw = argv.slice(2);
2794
3147
  const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : "help";
@@ -2935,6 +3288,14 @@ function formatSavedDuration(ms) {
2935
3288
  return `${(ms / 1000).toFixed(1)}s`;
2936
3289
  return `${ms}ms`;
2937
3290
  }
3291
+ function formatCostUsd(uc) {
3292
+ const usd = uc / 1e6;
3293
+ if (usd >= 1)
3294
+ return `$${usd.toFixed(2)}`;
3295
+ if (usd >= 0.01)
3296
+ return `$${usd.toFixed(3)}`;
3297
+ return `$${usd.toFixed(4)}`;
3298
+ }
2938
3299
  function emitImpactSummary(result) {
2939
3300
  const impact = result.impact;
2940
3301
  if (!impact)
@@ -2943,14 +3304,17 @@ function emitImpactSummary(result) {
2943
3304
  const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
2944
3305
  const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
2945
3306
  const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
3307
+ const costSavedUc = typeof impact.cost_saved_uc === "number" ? impact.cost_saved_uc : 0;
2946
3308
  const browserAvoided = impact.browser_avoided === true;
2947
- if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
3309
+ if (timeSavedMs <= 0 && tokensSaved <= 0 && costSavedUc <= 0 && !browserAvoided)
2948
3310
  return;
2949
3311
  const parts = [];
2950
3312
  if (timeSavedMs > 0)
2951
3313
  parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
2952
3314
  if (tokensSaved > 0)
2953
3315
  parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
3316
+ if (costSavedUc > 0)
3317
+ parts.push(`${formatCostUsd(costSavedUc)} saved`);
2954
3318
  if (browserAvoided)
2955
3319
  parts.push("browser avoided");
2956
3320
  info(parts.join(" \u2022 "));
@@ -3095,9 +3459,24 @@ async function cmdResolve(flags) {
3095
3459
  }
3096
3460
  result = slimTrace(result);
3097
3461
  emitImpactSummary(result);
3462
+ {
3463
+ const entry = impactFromResult("resolve", result, { intent, domain });
3464
+ if (entry)
3465
+ appendImpact(entry);
3466
+ }
3098
3467
  emitNextActionSummary(result);
3099
3468
  const skill = result.skill;
3100
3469
  const trace = result.trace;
3470
+ if (trace?.success && !walletNudgeShown) {
3471
+ try {
3472
+ const { checkWalletConfigured: checkWalletConfigured3 } = await Promise.resolve().then(() => (init_wallet2(), exports_wallet));
3473
+ const wallet = checkWalletConfigured3();
3474
+ if (!wallet.configured) {
3475
+ info("You're indexing routes but have no payout wallet. Run: npx @crossmint/lobster-cli setup");
3476
+ walletNudgeShown = true;
3477
+ }
3478
+ } catch (_e) {}
3479
+ }
3101
3480
  if (skill?.skill_id && trace) {
3102
3481
  result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
3103
3482
  }
@@ -3255,6 +3634,14 @@ async function cmdExecute(flags) {
3255
3634
  }
3256
3635
  result = slimTrace(result);
3257
3636
  emitImpactSummary(result);
3637
+ {
3638
+ const entry = impactFromResult("execute", result, {
3639
+ skill_id: skillId,
3640
+ endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : undefined
3641
+ });
3642
+ if (entry)
3643
+ appendImpact(entry);
3644
+ }
3258
3645
  emitNextActionSummary(result);
3259
3646
  const pathFlag = flags.path;
3260
3647
  const extractFlag = flags.extract;
@@ -3481,6 +3868,16 @@ async function cmdSetup(flags) {
3481
3868
  info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
3482
3869
  }
3483
3870
  }
3871
+ if (report.wallet.configured) {
3872
+ info(`Wallet configured (${report.wallet.provider}): ${report.wallet.wallet_address ?? "linked"}`);
3873
+ } else if (report.wallet.lobster_installed) {
3874
+ info("Wallet not paired \u2014 your indexed routes won't earn payouts.");
3875
+ info("Run: npx @crossmint/lobster-cli setup");
3876
+ } else {
3877
+ info("No wallet configured \u2014 you're indexing routes for free.");
3878
+ info("Set up a wallet so you earn when agents use your routes:");
3879
+ info(" npx @crossmint/lobster-cli setup");
3880
+ }
3484
3881
  await recordInstallTelemetryEvent("setup", {
3485
3882
  hostType,
3486
3883
  status: report.browser_engine.action === "failed" ? "failed" : "installed",
@@ -3574,7 +3971,8 @@ var CLI_REFERENCE = {
3574
3971
  { name: "back", usage: "[--session id]", desc: "Navigate back" },
3575
3972
  { name: "forward", usage: "[--session id]", desc: "Navigate forward" },
3576
3973
  { name: "sync", usage: "[--session id]", desc: "Checkpoint current capture, keep tab open, queue background index + publish, then inspect via skill/publish review" },
3577
- { name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" }
3974
+ { name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" },
3975
+ { name: "stats", usage: "[--json] [--pretty]", desc: "Show lifetime time/tokens/cost saved and marketplace earnings/spending" }
3578
3976
  ],
3579
3977
  globalFlags: [
3580
3978
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -3615,6 +4013,131 @@ var CLI_REFERENCE = {
3615
4013
  `unbrowse publish --skill abc --endpoints '[{"endpoint_id":"def","description":"Search court judgments by keywords","action_kind":"search","resource_kind":"judgment"}]'`
3616
4014
  ]
3617
4015
  };
4016
+ function formatTotalDuration(ms) {
4017
+ if (ms >= 3600000)
4018
+ return `${(ms / 3600000).toFixed(1)}h`;
4019
+ if (ms >= 60000)
4020
+ return `${(ms / 60000).toFixed(1)}m`;
4021
+ if (ms >= 1000)
4022
+ return `${(ms / 1000).toFixed(1)}s`;
4023
+ return `${ms}ms`;
4024
+ }
4025
+ async function cmdStats(flags) {
4026
+ const pretty = !!flags.pretty;
4027
+ const jsonOnly = !!flags.json;
4028
+ const local = readImpactSummary();
4029
+ const agentId = getAgentId();
4030
+ let profile = null;
4031
+ let earnings = null;
4032
+ let spending = null;
4033
+ const remoteErrors = {};
4034
+ if (agentId) {
4035
+ const results = await Promise.allSettled([
4036
+ getMyProfile(),
4037
+ getCreatorEarnings(agentId),
4038
+ getTransactionHistory(agentId)
4039
+ ]);
4040
+ if (results[0].status === "fulfilled")
4041
+ profile = results[0].value;
4042
+ else
4043
+ remoteErrors.profile = results[0].reason?.message ?? String(results[0].reason);
4044
+ if (results[1].status === "fulfilled")
4045
+ earnings = results[1].value;
4046
+ else
4047
+ remoteErrors.earnings = results[1].reason?.message ?? String(results[1].reason);
4048
+ if (results[2].status === "fulfilled")
4049
+ spending = results[2].value;
4050
+ else
4051
+ remoteErrors.spending = results[2].reason?.message ?? String(results[2].reason);
4052
+ } else {
4053
+ remoteErrors.profile = "No agent_id in local config. Run `unbrowse setup` to register.";
4054
+ }
4055
+ const earnedUsd = earnings?.ledger?.total_earned_usd ?? 0;
4056
+ const spentUsd = spending?.ledger?.total_spent_usd ?? 0;
4057
+ const netUsd = earnedUsd - spentUsd;
4058
+ const savedUsd = local.total_cost_saved_uc / 1e6;
4059
+ const payload = {
4060
+ agent_id: agentId,
4061
+ profile,
4062
+ impact: {
4063
+ total_runs: local.total_runs,
4064
+ successful_runs: local.successful_runs,
4065
+ browser_avoided_runs: local.browser_avoided_runs,
4066
+ total_time_saved_ms: local.total_time_saved_ms,
4067
+ total_time_saved_human: formatTotalDuration(local.total_time_saved_ms),
4068
+ total_tokens_saved: local.total_tokens_saved,
4069
+ total_cost_saved_usd: Number(savedUsd.toFixed(6)),
4070
+ avg_time_saved_pct: local.avg_time_saved_pct,
4071
+ avg_tokens_saved_pct: local.avg_tokens_saved_pct,
4072
+ by_source: local.by_source,
4073
+ first_entry_at: local.first_entry_at,
4074
+ last_entry_at: local.last_entry_at,
4075
+ log_path: getImpactLogPath()
4076
+ },
4077
+ earnings: {
4078
+ total_earned_usd: earnedUsd,
4079
+ total_earned_uc: earnings?.ledger?.total_earned_uc ?? 0,
4080
+ transaction_count: earnings?.ledger?.transaction_count ?? 0,
4081
+ last_transaction_at: earnings?.ledger?.last_transaction_at ?? null
4082
+ },
4083
+ spending: {
4084
+ total_spent_usd: spentUsd,
4085
+ total_spent_uc: spending?.ledger?.total_spent_uc ?? 0,
4086
+ transaction_count: spending?.ledger?.transaction_count ?? 0,
4087
+ last_transaction_at: spending?.ledger?.last_transaction_at ?? null
4088
+ },
4089
+ net_usd: netUsd,
4090
+ ...Object.keys(remoteErrors).length > 0 ? { remote_errors: remoteErrors } : {}
4091
+ };
4092
+ if (jsonOnly) {
4093
+ output(payload, pretty);
4094
+ return;
4095
+ }
4096
+ const lines = [];
4097
+ lines.push("Unbrowse stats");
4098
+ lines.push(` agent_id: ${agentId ?? "(not registered \u2014 run `unbrowse setup`)"}`);
4099
+ if (profile?.name)
4100
+ lines.push(` name: ${profile.name}`);
4101
+ lines.push("");
4102
+ lines.push("Impact (local, this machine):");
4103
+ if (local.total_runs === 0) {
4104
+ lines.push(" No resolve/execute runs recorded yet.");
4105
+ lines.push(` Log file: ${getImpactLogPath()}`);
4106
+ } else {
4107
+ lines.push(` Runs: ${local.total_runs} (${local.successful_runs} successful, ${local.browser_avoided_runs} browser-avoided)`);
4108
+ if (local.total_time_saved_ms > 0) {
4109
+ lines.push(` Time saved: ${formatTotalDuration(local.total_time_saved_ms)} (avg ${local.avg_time_saved_pct}% faster)`);
4110
+ }
4111
+ if (local.total_tokens_saved > 0) {
4112
+ lines.push(` Tokens saved: ${local.total_tokens_saved.toLocaleString("en-US")} (avg ${local.avg_tokens_saved_pct}% less context)`);
4113
+ }
4114
+ if (savedUsd > 0) {
4115
+ lines.push(` Cost saved: ${formatCostUsd(local.total_cost_saved_uc)}`);
4116
+ }
4117
+ if (Object.keys(local.by_source).length > 0) {
4118
+ const topSources = Object.entries(local.by_source).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k}=${v}`).join(", ");
4119
+ lines.push(` By source: ${topSources}`);
4120
+ }
4121
+ }
4122
+ lines.push("");
4123
+ lines.push("Money (backend ledger):");
4124
+ if (!agentId) {
4125
+ lines.push(" (not registered \u2014 earnings/spending unavailable)");
4126
+ } else if (remoteErrors.earnings || remoteErrors.spending) {
4127
+ if (remoteErrors.earnings)
4128
+ lines.push(` earnings: error \u2014 ${remoteErrors.earnings}`);
4129
+ if (remoteErrors.spending)
4130
+ lines.push(` spending: error \u2014 ${remoteErrors.spending}`);
4131
+ } else {
4132
+ lines.push(` Earned: $${earnedUsd.toFixed(4)} (${earnings?.ledger?.transaction_count ?? 0} payouts)`);
4133
+ lines.push(` Spent: $${spentUsd.toFixed(4)} (${spending?.ledger?.transaction_count ?? 0} payments)`);
4134
+ lines.push(` Net: ${netUsd >= 0 ? "+" : ""}$${netUsd.toFixed(4)}`);
4135
+ }
4136
+ lines.push("");
4137
+ info(lines.join(`
4138
+ `));
4139
+ output(payload, pretty);
4140
+ }
3618
4141
  function printHelp() {
3619
4142
  const r = CLI_REFERENCE;
3620
4143
  const lines = ["unbrowse \u2014 shell-safe CLI for the local API", ""];
@@ -4036,6 +4559,8 @@ async function main() {
4036
4559
  return cmdUpgrade(flags);
4037
4560
  if (command === "connect-chrome")
4038
4561
  return cmdConnectChrome();
4562
+ if (command === "stats")
4563
+ return cmdStats(flags);
4039
4564
  const KNOWN_COMMANDS = new Set([
4040
4565
  "health",
4041
4566
  "mcp",
@@ -4079,7 +4604,8 @@ async function main() {
4079
4604
  "forward",
4080
4605
  "sync",
4081
4606
  "close",
4082
- "connect-chrome"
4607
+ "connect-chrome",
4608
+ "stats"
4083
4609
  ]);
4084
4610
  if (!KNOWN_COMMANDS.has(command)) {
4085
4611
  const pack = findSitePack(command);
@@ -4175,6 +4701,8 @@ async function main() {
4175
4701
  return cmdClose(flags);
4176
4702
  case "connect-chrome":
4177
4703
  return cmdConnectChrome();
4704
+ case "stats":
4705
+ return cmdStats(flags);
4178
4706
  default:
4179
4707
  info(`Unknown command: ${command}`);
4180
4708
  printHelp();