unbrowse 3.0.4 → 3.1.0-experiments.3653a91

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.4", BUILD_GIT_SHA = "44d2baa5a0e0", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4wLjQiLCJnaXRfc2hhIjoiNDRkMmJhYTVhMGUwIiwiY29kZV9oYXNoIjoiMTQ4OGZjMWQ5MmI3IiwidHJhY2VfdmVyc2lvbiI6IjE0ODhmYzFkOTJiN0A0NGQyYmFhNWEwZTAiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTA1VDA0OjIyOjE2LjM2M1oifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "zLW181YNH-1BLhH-HL-OvhuGJAzfy-HrZnIo7xm0aVo", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
34
+ var BUILD_RELEASE_VERSION = "3.1.0-experiments.3653a91", BUILD_GIT_SHA = "3653a91e109b", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuMzY1M2E5MSIsImdpdF9zaGEiOiIzNjUzYTkxZTEwOWIiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDM2NTNhOTFlMTA5YiIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDZUMDI6MjU6MDAuMTQzWiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "ieb929-rx-l0FCQgy8cAPDboFJPIUvur5sJQTqvy6Zg", BUILD_DEFAULT_BACKEND_URL = "https://unbrowse-backend-experiments.lewis-6d8.workers.dev";
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
 
@@ -780,18 +899,6 @@ var init_extraction = __esm(() => {
780
899
  CHROME_TAGS = new Set(["nav", "footer", "header"]);
781
900
  });
782
901
 
783
- // ../../src/graph/agent-augment.ts
784
- var DEFAULT_MODEL, ENABLED, AUGMENT_TIMEOUT_MS, MAX_AUGMENT_ENDPOINTS, MAX_AUGMENT_PAYLOAD_CHARS, GENERIC_SEMANTIC_TYPES;
785
- var init_agent_augment = __esm(() => {
786
- init_graph();
787
- DEFAULT_MODEL = process.env.UNBROWSE_AGENT_SEMANTIC_MODEL ?? process.env.UNBROWSE_AGENT_JUDGE_MODEL ?? "gpt-4.1-mini";
788
- ENABLED = process.env.UNBROWSE_AGENT_SEMANTIC_AUGMENT !== "0";
789
- AUGMENT_TIMEOUT_MS = Number(process.env.UNBROWSE_AGENT_SEMANTIC_TIMEOUT_MS ?? 8000);
790
- MAX_AUGMENT_ENDPOINTS = Math.max(1, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_ENDPOINTS ?? 6));
791
- MAX_AUGMENT_PAYLOAD_CHARS = Math.max(4000, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_PAYLOAD_CHARS ?? 24000));
792
- GENERIC_SEMANTIC_TYPES = new Set(["identifier", "input", "resource", "entity", "item"]);
793
- });
794
-
795
902
  // ../../src/execution/search-forms.ts
796
903
  var SEARCH_FIELD_NAMES, LOGIN_FIELD_NAMES, SUPPORTED_INPUT_TYPES;
797
904
  var init_search_forms = __esm(() => {
@@ -854,7 +961,7 @@ var init_schema_review = __esm(() => {
854
961
  });
855
962
 
856
963
  // ../../src/indexer/index.ts
857
- import { join as join6 } from "node:path";
964
+ import { join as join8 } from "node:path";
858
965
  var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
859
966
  var init_indexer = __esm(async () => {
860
967
  init_graph();
@@ -869,7 +976,7 @@ var init_indexer = __esm(async () => {
869
976
  init_graph();
870
977
  init_schema_review();
871
978
  await init_orchestrator();
872
- SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join6(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
979
+ SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
873
980
  indexInFlight = new Map;
874
981
  pendingIndexJobs = new Map;
875
982
  });
@@ -917,7 +1024,6 @@ var init_execution = __esm(async () => {
917
1024
  init_domain();
918
1025
  init_extraction();
919
1026
  init_graph();
920
- init_agent_augment();
921
1027
  init_logger();
922
1028
  init_version();
923
1029
  init_search_forms();
@@ -1046,8 +1152,8 @@ var init_routing_telemetry = __esm(() => {
1046
1152
  });
1047
1153
  // ../../src/orchestrator/index.ts
1048
1154
  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";
1155
+ import { existsSync as existsSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "node:fs";
1156
+ import { dirname as dirname3, join as join9 } from "node:path";
1051
1157
  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
1158
  var init_orchestrator = __esm(async () => {
1053
1159
  init_client();
@@ -1077,13 +1183,13 @@ var init_orchestrator = __esm(async () => {
1077
1183
  captureInFlight = new Map;
1078
1184
  captureDomainLocks = new Map;
1079
1185
  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");
1186
+ ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
1187
+ SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1082
1188
  domainSkillCache = new Map;
1083
- DOMAIN_CACHE_FILE = join7(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1189
+ DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1084
1190
  try {
1085
- if (existsSync7(DOMAIN_CACHE_FILE)) {
1086
- const data = JSON.parse(readFileSync5(DOMAIN_CACHE_FILE, "utf-8"));
1191
+ if (existsSync9(DOMAIN_CACHE_FILE)) {
1192
+ const data = JSON.parse(readFileSync6(DOMAIN_CACHE_FILE, "utf-8"));
1087
1193
  for (const [k, v] of Object.entries(data)) {
1088
1194
  const entry = v;
1089
1195
  if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
@@ -1098,17 +1204,17 @@ var init_orchestrator = __esm(async () => {
1098
1204
  return;
1099
1205
  _routeCacheDirty = false;
1100
1206
  try {
1101
- const dir = dirname2(ROUTE_CACHE_FILE);
1102
- if (!existsSync7(dir))
1103
- mkdirSync4(dir, { recursive: true });
1207
+ const dir = dirname3(ROUTE_CACHE_FILE);
1208
+ if (!existsSync9(dir))
1209
+ mkdirSync5(dir, { recursive: true });
1104
1210
  const entries = Object.fromEntries(skillRouteCache);
1105
1211
  writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
1106
1212
  } catch {}
1107
1213
  }, 5000);
1108
1214
  routeCacheFlushTimer.unref?.();
1109
1215
  try {
1110
- if (existsSync7(ROUTE_CACHE_FILE)) {
1111
- const data = JSON.parse(readFileSync5(ROUTE_CACHE_FILE, "utf-8"));
1216
+ if (existsSync9(ROUTE_CACHE_FILE)) {
1217
+ const data = JSON.parse(readFileSync6(ROUTE_CACHE_FILE, "utf-8"));
1112
1218
  for (const [k, v] of Object.entries(data)) {
1113
1219
  const entry = v;
1114
1220
  if (Date.now() - entry.ts < 24 * 60 * 60000) {
@@ -1198,6 +1304,60 @@ var init_orchestrator = __esm(async () => {
1198
1304
  ]);
1199
1305
  });
1200
1306
 
1307
+ // ../../src/payments/wallet.ts
1308
+ var exports_wallet = {};
1309
+ __export(exports_wallet, {
1310
+ getWalletContext: () => getWalletContext2,
1311
+ checkWalletConfigured: () => checkWalletConfigured2
1312
+ });
1313
+ import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
1314
+ import { homedir as homedir6 } from "node:os";
1315
+ import { join as join11 } from "node:path";
1316
+ function asNonEmptyString2(value) {
1317
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
1318
+ }
1319
+ function getLobsterWalletFromLocalConfig2() {
1320
+ const agentsPath = join11(process.env.HOME || homedir6(), ".lobster", "agents.json");
1321
+ if (!existsSync13(agentsPath))
1322
+ return;
1323
+ try {
1324
+ const raw = JSON.parse(readFileSync9(agentsPath, "utf8"));
1325
+ const activeAgentId = asNonEmptyString2(raw.activeAgentId);
1326
+ const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString2(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
1327
+ return asNonEmptyString2(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString2(activeAgent?.walletAddress) ?? asNonEmptyString2(activeAgent?.wallet_address);
1328
+ } catch {
1329
+ return;
1330
+ }
1331
+ }
1332
+ function getWalletContext2() {
1333
+ const lobsterWallet = asNonEmptyString2(process.env.LOBSTER_WALLET_ADDRESS);
1334
+ if (lobsterWallet) {
1335
+ return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
1336
+ }
1337
+ const genericWallet = asNonEmptyString2(process.env.AGENT_WALLET_ADDRESS);
1338
+ if (genericWallet) {
1339
+ return {
1340
+ wallet_address: genericWallet,
1341
+ wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
1342
+ };
1343
+ }
1344
+ const localLobsterWallet = getLobsterWalletFromLocalConfig2();
1345
+ if (localLobsterWallet) {
1346
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
1347
+ }
1348
+ return {};
1349
+ }
1350
+ function checkWalletConfigured2() {
1351
+ const wallet = getWalletContext2();
1352
+ if (!wallet.wallet_address)
1353
+ return { configured: false };
1354
+ return {
1355
+ configured: true,
1356
+ provider: wallet.wallet_provider ?? "unknown"
1357
+ };
1358
+ }
1359
+ var init_wallet2 = () => {};
1360
+
1201
1361
  // ../../src/cli.ts
1202
1362
  import { config as loadEnv } from "dotenv";
1203
1363
  import { spawn as spawn3 } from "child_process";
@@ -1207,9 +1367,9 @@ init_version();
1207
1367
  init_cascade();
1208
1368
  init_wallet();
1209
1369
  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";
1370
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
1371
+ import { join as join4 } from "path";
1372
+ import { homedir as homedir3, hostname, release as osRelease } from "os";
1213
1373
  import { randomBytes, createHash as createHash2 } from "crypto";
1214
1374
  import { createInterface } from "readline";
1215
1375
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
@@ -1244,13 +1404,13 @@ function decodeBase64Json(value) {
1244
1404
  function getConfigDir() {
1245
1405
  if (process.env.UNBROWSE_CONFIG_DIR)
1246
1406
  return process.env.UNBROWSE_CONFIG_DIR;
1247
- return PROFILE_NAME ? join3(homedir2(), ".unbrowse", "profiles", PROFILE_NAME) : join3(homedir2(), ".unbrowse");
1407
+ return PROFILE_NAME ? join4(homedir3(), ".unbrowse", "profiles", PROFILE_NAME) : join4(homedir3(), ".unbrowse");
1248
1408
  }
1249
1409
  function getConfigPath() {
1250
- return join3(getConfigDir(), "config.json");
1410
+ return join4(getConfigDir(), "config.json");
1251
1411
  }
1252
1412
  function getInstallTelemetryPath() {
1253
- return join3(getConfigDir(), "install-state.json");
1413
+ return join4(getConfigDir(), "install-state.json");
1254
1414
  }
1255
1415
  function getLandingToken() {
1256
1416
  const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
@@ -1265,7 +1425,7 @@ function getActiveProfile() {
1265
1425
  function loadConfig() {
1266
1426
  try {
1267
1427
  const configPath = getConfigPath();
1268
- if (existsSync3(configPath)) {
1428
+ if (existsSync4(configPath)) {
1269
1429
  return JSON.parse(readFileSync3(configPath, "utf-8"));
1270
1430
  }
1271
1431
  } catch {}
@@ -1274,14 +1434,14 @@ function loadConfig() {
1274
1434
  function saveConfig(config) {
1275
1435
  const configDir = getConfigDir();
1276
1436
  const configPath = getConfigPath();
1277
- if (!existsSync3(configDir))
1437
+ if (!existsSync4(configDir))
1278
1438
  mkdirSync(configDir, { recursive: true });
1279
1439
  writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
1280
1440
  }
1281
1441
  function loadInstallTelemetryState() {
1282
1442
  try {
1283
1443
  const statePath = getInstallTelemetryPath();
1284
- if (existsSync3(statePath)) {
1444
+ if (existsSync4(statePath)) {
1285
1445
  return JSON.parse(readFileSync3(statePath, "utf-8"));
1286
1446
  }
1287
1447
  } catch {}
@@ -1290,7 +1450,7 @@ function loadInstallTelemetryState() {
1290
1450
  function saveInstallTelemetryState(state) {
1291
1451
  const configDir = getConfigDir();
1292
1452
  const statePath = getInstallTelemetryPath();
1293
- if (!existsSync3(configDir))
1453
+ if (!existsSync4(configDir))
1294
1454
  mkdirSync(configDir, { recursive: true });
1295
1455
  writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
1296
1456
  }
@@ -1402,9 +1562,19 @@ async function recordInstallTelemetryEvent(source, options) {
1402
1562
  skill_version: options?.skillVersion,
1403
1563
  status: options?.status ?? "installed",
1404
1564
  created_at: createdAt,
1405
- properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
1565
+ properties: mergeTelemetryProperties({ ...getRuntimeContext(), ...options?.properties }, getTelemetryAttribution())
1406
1566
  });
1407
1567
  }
1568
+ function getRuntimeContext() {
1569
+ return {
1570
+ cli_version: PACKAGE_VERSION,
1571
+ code_hash: CODE_HASH,
1572
+ node_version: process.version,
1573
+ platform: process.platform,
1574
+ arch: process.arch,
1575
+ os_release: osRelease()
1576
+ };
1577
+ }
1408
1578
  async function recordFunnelTelemetryEvent(name, options) {
1409
1579
  const createdAt = options?.createdAt ?? new Date().toISOString();
1410
1580
  const landingToken = getLandingToken();
@@ -1416,7 +1586,7 @@ async function recordFunnelTelemetryEvent(name, options) {
1416
1586
  source: options?.source ?? "cli",
1417
1587
  host_type: options?.hostType ?? detectTelemetryHostType(),
1418
1588
  created_at: createdAt,
1419
- properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
1589
+ properties: mergeTelemetryProperties({ ...getRuntimeContext(), ...options?.properties }, getTelemetryAttribution())
1420
1590
  });
1421
1591
  }
1422
1592
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/i;
@@ -1448,6 +1618,10 @@ function getApiKey() {
1448
1618
  }
1449
1619
  return "";
1450
1620
  }
1621
+ function getAgentId() {
1622
+ const config = loadConfig();
1623
+ return config?.agent_id ?? null;
1624
+ }
1451
1625
  var API_TIMEOUT_MS = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
1452
1626
  var PUBLISH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
1453
1627
  async function validateApiKey(key) {
@@ -1547,6 +1721,26 @@ async function apiRequest(method, path, body, opts) {
1547
1721
  const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
1548
1722
  const legacyPaymentTerms = res.headers.get("X-Payment-Required");
1549
1723
  const terms = paymentRequired ? decodeBase64Json(paymentRequired) : legacyPaymentTerms ? JSON.parse(legacyPaymentTerms) : data.terms;
1724
+ try {
1725
+ const { isLobsterAvailable: isLobsterAvailable2, payAndRetry: payAndRetry2 } = await Promise.resolve().then(() => (init_lobster_pay(), exports_lobster_pay));
1726
+ if (isLobsterAvailable2()) {
1727
+ const fullUrl = `${API_URL}${path}`;
1728
+ const paidResult = await payAndRetry2(fullUrl, {
1729
+ body,
1730
+ headers: {
1731
+ "Content-Type": "application/json",
1732
+ "Accept-Encoding": "gzip, deflate",
1733
+ ...releaseAttestationHeaders,
1734
+ ...key ? { Authorization: `Bearer ${key}` } : {}
1735
+ }
1736
+ });
1737
+ if (paidResult) {
1738
+ return { data: paidResult.data, headers: new Headers };
1739
+ }
1740
+ }
1741
+ } catch (payErr) {
1742
+ console.warn(`[x402] lobster pay-and-retry failed: ${payErr.message}`);
1743
+ }
1550
1744
  const err = new Error(`Payment required: ${data.error ?? "This skill requires payment"}`);
1551
1745
  err.x402 = true;
1552
1746
  err.terms = terms;
@@ -1739,6 +1933,170 @@ async function syncAgentWallet(wallet = getLocalWalletContext()) {
1739
1933
  return;
1740
1934
  saveConfig({ ...config, ...wallet });
1741
1935
  }
1936
+ async function getTransactionHistory(agentId) {
1937
+ return api("GET", `/v1/transactions/consumer/${agentId}`);
1938
+ }
1939
+ async function getCreatorEarnings(agentId) {
1940
+ return api("GET", `/v1/transactions/creator/${agentId}`);
1941
+ }
1942
+
1943
+ // ../../src/impact-log.ts
1944
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync } from "node:fs";
1945
+ import { homedir as homedir4 } from "node:os";
1946
+ import { dirname as dirname2, join as join5 } from "node:path";
1947
+ var MAX_LOG_BYTES = 5 * 1024 * 1024;
1948
+ var MAX_ROTATIONS = 3;
1949
+ function getLogDir() {
1950
+ if (process.env.UNBROWSE_CONFIG_DIR)
1951
+ return process.env.UNBROWSE_CONFIG_DIR;
1952
+ const profile = process.env.UNBROWSE_PROFILE?.trim();
1953
+ return profile ? join5(homedir4(), ".unbrowse", "profiles", profile) : join5(homedir4(), ".unbrowse");
1954
+ }
1955
+ function getImpactLogPath() {
1956
+ return join5(getLogDir(), "impact-log.jsonl");
1957
+ }
1958
+ function ensureDir(path) {
1959
+ const dir = dirname2(path);
1960
+ if (!existsSync5(dir))
1961
+ mkdirSync2(dir, { recursive: true });
1962
+ }
1963
+ function rotateIfNeeded(path) {
1964
+ try {
1965
+ if (!existsSync5(path))
1966
+ return;
1967
+ const size = statSync(path).size;
1968
+ if (size < MAX_LOG_BYTES)
1969
+ return;
1970
+ for (let i = MAX_ROTATIONS;i >= 1; i--) {
1971
+ const older = `${path}.${i}`;
1972
+ if (!existsSync5(older))
1973
+ continue;
1974
+ if (i === MAX_ROTATIONS) {
1975
+ try {
1976
+ unlinkSync(older);
1977
+ } catch {}
1978
+ } else {
1979
+ try {
1980
+ renameSync(older, `${path}.${i + 1}`);
1981
+ } catch {}
1982
+ }
1983
+ }
1984
+ renameSync(path, `${path}.1`);
1985
+ } catch {}
1986
+ }
1987
+ function appendImpact(entry) {
1988
+ try {
1989
+ const hasSignal = (entry.time_saved_ms ?? 0) > 0 || (entry.tokens_saved ?? 0) > 0 || (entry.cost_saved_uc ?? 0) > 0 || entry.browser_avoided === true;
1990
+ if (!hasSignal)
1991
+ return;
1992
+ const path = getImpactLogPath();
1993
+ ensureDir(path);
1994
+ rotateIfNeeded(path);
1995
+ appendFileSync(path, JSON.stringify(entry) + `
1996
+ `, "utf8");
1997
+ } catch {}
1998
+ }
1999
+ function impactFromResult(command, result, extras = {}) {
2000
+ if (!result || typeof result !== "object")
2001
+ return null;
2002
+ const r = result;
2003
+ const impact = r.impact ?? null;
2004
+ if (!impact || typeof impact !== "object")
2005
+ return null;
2006
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
2007
+ return {
2008
+ ts: new Date().toISOString(),
2009
+ command,
2010
+ source: typeof impact.source === "string" ? impact.source : undefined,
2011
+ domain: extras.domain,
2012
+ intent: extras.intent,
2013
+ skill_id: extras.skill_id ?? (typeof r.skill_id === "string" ? r.skill_id : undefined),
2014
+ endpoint_id: extras.endpoint_id ?? (typeof r.endpoint_id === "string" ? r.endpoint_id : undefined),
2015
+ time_saved_ms: num(impact.time_saved_ms),
2016
+ time_saved_pct: num(impact.time_saved_pct),
2017
+ tokens_saved: num(impact.tokens_saved),
2018
+ tokens_saved_pct: num(impact.tokens_saved_pct),
2019
+ cost_saved_uc: num(impact.cost_saved_uc),
2020
+ browser_avoided: impact.browser_avoided === true,
2021
+ success: r.error == null
2022
+ };
2023
+ }
2024
+ function readImpactSummary() {
2025
+ const path = getImpactLogPath();
2026
+ const summary = {
2027
+ total_runs: 0,
2028
+ successful_runs: 0,
2029
+ browser_avoided_runs: 0,
2030
+ total_time_saved_ms: 0,
2031
+ total_tokens_saved: 0,
2032
+ total_cost_saved_uc: 0,
2033
+ avg_time_saved_pct: 0,
2034
+ avg_tokens_saved_pct: 0,
2035
+ by_source: {},
2036
+ first_entry_at: null,
2037
+ last_entry_at: null
2038
+ };
2039
+ const files = [];
2040
+ for (let i = MAX_ROTATIONS;i >= 1; i--) {
2041
+ const rotated = `${path}.${i}`;
2042
+ if (existsSync5(rotated))
2043
+ files.push(rotated);
2044
+ }
2045
+ if (existsSync5(path))
2046
+ files.push(path);
2047
+ if (files.length === 0)
2048
+ return summary;
2049
+ let timePctSum = 0;
2050
+ let timePctCount = 0;
2051
+ let tokenPctSum = 0;
2052
+ let tokenPctCount = 0;
2053
+ for (const file of files) {
2054
+ let raw;
2055
+ try {
2056
+ raw = readFileSync4(file, "utf8");
2057
+ } catch {
2058
+ continue;
2059
+ }
2060
+ for (const line of raw.split(`
2061
+ `)) {
2062
+ const trimmed = line.trim();
2063
+ if (!trimmed)
2064
+ continue;
2065
+ let e;
2066
+ try {
2067
+ e = JSON.parse(trimmed);
2068
+ } catch {
2069
+ continue;
2070
+ }
2071
+ summary.total_runs += 1;
2072
+ if (e.success !== false)
2073
+ summary.successful_runs += 1;
2074
+ if (e.browser_avoided)
2075
+ summary.browser_avoided_runs += 1;
2076
+ summary.total_time_saved_ms += e.time_saved_ms ?? 0;
2077
+ summary.total_tokens_saved += e.tokens_saved ?? 0;
2078
+ summary.total_cost_saved_uc += e.cost_saved_uc ?? 0;
2079
+ if (typeof e.time_saved_pct === "number") {
2080
+ timePctSum += e.time_saved_pct;
2081
+ timePctCount += 1;
2082
+ }
2083
+ if (typeof e.tokens_saved_pct === "number") {
2084
+ tokenPctSum += e.tokens_saved_pct;
2085
+ tokenPctCount += 1;
2086
+ }
2087
+ if (e.source) {
2088
+ summary.by_source[e.source] = (summary.by_source[e.source] ?? 0) + 1;
2089
+ }
2090
+ if (!summary.first_entry_at || e.ts < summary.first_entry_at)
2091
+ summary.first_entry_at = e.ts;
2092
+ if (!summary.last_entry_at || e.ts > summary.last_entry_at)
2093
+ summary.last_entry_at = e.ts;
2094
+ }
2095
+ }
2096
+ summary.avg_time_saved_pct = timePctCount > 0 ? Math.round(timePctSum / timePctCount) : 0;
2097
+ summary.avg_tokens_saved_pct = tokenPctCount > 0 ? Math.round(tokenPctSum / tokenPctCount) : 0;
2098
+ return summary;
2099
+ }
1742
2100
 
1743
2101
  // ../../src/cli/shortcuts.ts
1744
2102
  var linkedin = {
@@ -1939,7 +2297,7 @@ function buildDepsMetadata(pack, taskName) {
1939
2297
  init_paths();
1940
2298
  init_supervisor();
1941
2299
  init_version();
1942
- import { openSync, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2300
+ import { openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
1943
2301
  import path2 from "node:path";
1944
2302
  import { spawn } from "node:child_process";
1945
2303
  function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
@@ -1977,14 +2335,14 @@ function isPidAlive(pid) {
1977
2335
  }
1978
2336
  function readPidState(pidFile) {
1979
2337
  try {
1980
- return JSON.parse(readFileSync4(pidFile, "utf-8"));
2338
+ return JSON.parse(readFileSync5(pidFile, "utf-8"));
1981
2339
  } catch {
1982
2340
  return null;
1983
2341
  }
1984
2342
  }
1985
2343
  function clearStalePidFile(pidFile) {
1986
2344
  try {
1987
- unlinkSync(pidFile);
2345
+ unlinkSync2(pidFile);
1988
2346
  } catch {}
1989
2347
  }
1990
2348
  function deriveListenEnv(baseUrl) {
@@ -2018,7 +2376,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
2018
2376
  const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
2019
2377
  const spawnSpec = getServerSpawnSpec(metaUrl, entrypoint);
2020
2378
  const logFile = getServerAutostartLogFile();
2021
- ensureDir(path2.dirname(logFile));
2379
+ ensureDir2(path2.dirname(logFile));
2022
2380
  const logFd = openSync(logFile, "a");
2023
2381
  const child = spawn(spawnSpec.command, spawnSpec.args, {
2024
2382
  cwd: spawnSpec.cwd,
@@ -2150,7 +2508,7 @@ async function restartServer(baseUrl, metaUrl) {
2150
2508
  }
2151
2509
 
2152
2510
  // ../../src/runtime/paths.ts
2153
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, realpathSync as realpathSync2 } from "node:fs";
2511
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
2154
2512
  import path3 from "node:path";
2155
2513
  import { createRequire as createRequire3 } from "node:module";
2156
2514
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -2170,7 +2528,7 @@ function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
2170
2528
  const req = createRequire3(metaUrl);
2171
2529
  const tsxPkg = req.resolve("tsx/package.json");
2172
2530
  const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
2173
- if (existsSync5(tsxLoader))
2531
+ if (existsSync7(tsxLoader))
2174
2532
  return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
2175
2533
  } catch {}
2176
2534
  return ["--import", "tsx", entrypoint];
@@ -2200,8 +2558,8 @@ init_publish();
2200
2558
  init_settings();
2201
2559
  init_graph();
2202
2560
  init_schema_review();
2203
- import { join as join8 } from "node:path";
2204
- var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
2561
+ import { join as join10 } from "node:path";
2562
+ var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
2205
2563
  var indexInFlight2 = new Map;
2206
2564
  var pendingIndexJobs2 = new Map;
2207
2565
  async function drainPendingIndexJobs() {
@@ -2238,14 +2596,14 @@ init_paths();
2238
2596
  init_client2();
2239
2597
  init_logger();
2240
2598
  init_wallet();
2241
- import { execFileSync as execFileSync2 } from "node:child_process";
2242
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
2599
+ import { execFileSync as execFileSync3 } from "node:child_process";
2600
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
2243
2601
  import os4 from "node:os";
2244
2602
  import path7 from "node:path";
2245
2603
 
2246
2604
  // ../../src/runtime/update-hints.ts
2247
2605
  init_paths();
2248
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
2606
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2249
2607
  import os3 from "node:os";
2250
2608
  import path6 from "node:path";
2251
2609
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
@@ -2258,20 +2616,20 @@ function getConfigDir2() {
2258
2616
  return process.env.UNBROWSE_CONFIG_DIR;
2259
2617
  return path6.join(getHomeDir(), ".unbrowse");
2260
2618
  }
2261
- function ensureDir2(dir) {
2262
- if (!existsSync8(dir))
2263
- mkdirSync5(dir, { recursive: true });
2619
+ function ensureDir3(dir) {
2620
+ if (!existsSync10(dir))
2621
+ mkdirSync6(dir, { recursive: true });
2264
2622
  return dir;
2265
2623
  }
2266
2624
  function readJsonFile(file) {
2267
2625
  try {
2268
- return JSON.parse(readFileSync6(file, "utf8"));
2626
+ return JSON.parse(readFileSync7(file, "utf8"));
2269
2627
  } catch {
2270
2628
  return null;
2271
2629
  }
2272
2630
  }
2273
2631
  function writeJsonFile(file, value) {
2274
- ensureDir2(path6.dirname(file));
2632
+ ensureDir3(path6.dirname(file));
2275
2633
  writeFileSync4(file, `${JSON.stringify(value, null, 2)}
2276
2634
  `);
2277
2635
  }
@@ -2282,7 +2640,7 @@ function detectRepoRoot(start2) {
2282
2640
  let dir = path6.resolve(start2);
2283
2641
  const root = path6.parse(dir).root;
2284
2642
  while (dir !== root) {
2285
- if (existsSync8(path6.join(dir, ".git")))
2643
+ if (existsSync10(path6.join(dir, ".git")))
2286
2644
  return dir;
2287
2645
  dir = path6.dirname(dir);
2288
2646
  }
@@ -2361,13 +2719,13 @@ codex_hooks = true
2361
2719
  }
2362
2720
  function writeCodexHook(metaUrl) {
2363
2721
  const configPath = getCodexConfigPath();
2364
- if (!existsSync8(path6.dirname(configPath))) {
2722
+ if (!existsSync10(path6.dirname(configPath))) {
2365
2723
  return { host: "codex", action: "not-detected", config_file: configPath };
2366
2724
  }
2367
2725
  try {
2368
2726
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2369
- const fileExistsBefore = existsSync8(configPath);
2370
- let content = fileExistsBefore ? readFileSync6(configPath, "utf8") : "";
2727
+ const fileExistsBefore = existsSync10(configPath);
2728
+ let content = fileExistsBefore ? readFileSync7(configPath, "utf8") : "";
2371
2729
  const previous = content;
2372
2730
  content = ensureCodexHooksFeature(content);
2373
2731
  if (!content.includes("unbrowse-update-hint.mjs")) {
@@ -2401,13 +2759,13 @@ command = ${JSON.stringify(command)}
2401
2759
  }
2402
2760
  function writeClaudeHook(metaUrl) {
2403
2761
  const settingsPath = getClaudeSettingsPath();
2404
- if (!existsSync8(path6.dirname(settingsPath))) {
2762
+ if (!existsSync10(path6.dirname(settingsPath))) {
2405
2763
  return { host: "claude", action: "not-detected", config_file: settingsPath };
2406
2764
  }
2407
2765
  try {
2408
2766
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2409
2767
  const command = `node "${hookScript}"`;
2410
- const fileExistsBefore = existsSync8(settingsPath);
2768
+ const fileExistsBefore = existsSync10(settingsPath);
2411
2769
  const settings = readJsonFile(settingsPath) ?? {};
2412
2770
  settings.hooks ??= {};
2413
2771
  settings.hooks.SessionStart ??= [];
@@ -2445,7 +2803,7 @@ function configureUpdateHintHooks(metaUrl, install) {
2445
2803
  function hasBinary(name) {
2446
2804
  const checker = process.platform === "win32" ? "where" : "which";
2447
2805
  try {
2448
- execFileSync2(checker, [name], { stdio: "ignore" });
2806
+ execFileSync3(checker, [name], { stdio: "ignore" });
2449
2807
  return true;
2450
2808
  } catch {
2451
2809
  return false;
@@ -2472,7 +2830,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
2472
2830
  return path7.join(cwd, ".opencode", "commands");
2473
2831
  }
2474
2832
  function detectOpenCode(cwd) {
2475
- return hasBinary("opencode") || existsSync9(path7.join(resolveConfigHome(), "opencode")) || existsSync9(path7.join(cwd, ".opencode"));
2833
+ return hasBinary("opencode") || existsSync11(path7.join(resolveConfigHome(), "opencode")) || existsSync11(path7.join(cwd, ".opencode"));
2476
2834
  }
2477
2835
  function renderOpenCodeCommand() {
2478
2836
  return `---
@@ -2500,12 +2858,12 @@ function writeOpenCodeCommand(scope, cwd) {
2500
2858
  if (scope === "auto" && !detected) {
2501
2859
  return { detected: false, action: "not-detected", scope: "off" };
2502
2860
  }
2503
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync9(path7.join(cwd, ".opencode")) ? "project" : "global";
2861
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync11(path7.join(cwd, ".opencode")) ? "project" : "global";
2504
2862
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
2505
- const commandFile = path7.join(ensureDir(commandsDir), "unbrowse.md");
2863
+ const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
2506
2864
  const content = renderOpenCodeCommand();
2507
- const action2 = existsSync9(commandFile) ? "updated" : "installed";
2508
- mkdirSync6(path7.dirname(commandFile), { recursive: true });
2865
+ const action2 = existsSync11(commandFile) ? "updated" : "installed";
2866
+ mkdirSync7(path7.dirname(commandFile), { recursive: true });
2509
2867
  writeFileSync5(commandFile, content);
2510
2868
  return {
2511
2869
  detected: detected || scope !== "auto",
@@ -2516,10 +2874,10 @@ function writeOpenCodeCommand(scope, cwd) {
2516
2874
  }
2517
2875
  async function ensureBrowserEngineInstalled() {
2518
2876
  const binary = findKuriBinary();
2519
- if (existsSync9(binary)) {
2877
+ if (existsSync11(binary)) {
2520
2878
  return { installed: true, action: "already-installed" };
2521
2879
  }
2522
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync9(path7.join(candidate, "build.zig")));
2880
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync11(path7.join(candidate, "build.zig")));
2523
2881
  if (!sourceDir) {
2524
2882
  return {
2525
2883
  installed: false,
@@ -2535,13 +2893,13 @@ async function ensureBrowserEngineInstalled() {
2535
2893
  };
2536
2894
  }
2537
2895
  try {
2538
- execFileSync2("zig", ["build", "-Doptimize=ReleaseFast"], {
2896
+ execFileSync3("zig", ["build", "-Doptimize=ReleaseFast"], {
2539
2897
  cwd: sourceDir,
2540
2898
  stdio: "inherit",
2541
2899
  timeout: 300000
2542
2900
  });
2543
2901
  const builtBinary = findKuriBinary();
2544
- if (existsSync9(builtBinary)) {
2902
+ if (existsSync11(builtBinary)) {
2545
2903
  return {
2546
2904
  installed: true,
2547
2905
  action: "installed",
@@ -2566,11 +2924,11 @@ async function runSetup(options) {
2566
2924
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
2567
2925
  const walletCheck = checkWalletConfigured();
2568
2926
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
2569
- const lobsterInstalled = hasBinary("lobstercash") || existsSync9(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2927
+ const lobsterInstalled = hasBinary("lobstercash") || existsSync11(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2570
2928
  if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
2571
2929
  console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
2572
2930
  try {
2573
- execFileSync2("npx", ["@crossmint/lobster-cli", "setup"], {
2931
+ execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
2574
2932
  stdio: "inherit",
2575
2933
  timeout: 60000
2576
2934
  });
@@ -2606,7 +2964,7 @@ async function runSetup(options) {
2606
2964
 
2607
2965
  // ../../src/runtime/update-hints.ts
2608
2966
  init_paths();
2609
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
2967
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
2610
2968
  import os5 from "node:os";
2611
2969
  import path8 from "node:path";
2612
2970
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
@@ -2619,20 +2977,20 @@ function getConfigDir3() {
2619
2977
  return process.env.UNBROWSE_CONFIG_DIR;
2620
2978
  return path8.join(getHomeDir2(), ".unbrowse");
2621
2979
  }
2622
- function ensureDir3(dir) {
2623
- if (!existsSync10(dir))
2624
- mkdirSync7(dir, { recursive: true });
2980
+ function ensureDir4(dir) {
2981
+ if (!existsSync12(dir))
2982
+ mkdirSync8(dir, { recursive: true });
2625
2983
  return dir;
2626
2984
  }
2627
2985
  function readJsonFile2(file) {
2628
2986
  try {
2629
- return JSON.parse(readFileSync7(file, "utf8"));
2987
+ return JSON.parse(readFileSync8(file, "utf8"));
2630
2988
  } catch {
2631
2989
  return null;
2632
2990
  }
2633
2991
  }
2634
2992
  function writeJsonFile2(file, value) {
2635
- ensureDir3(path8.dirname(file));
2993
+ ensureDir4(path8.dirname(file));
2636
2994
  writeFileSync6(file, `${JSON.stringify(value, null, 2)}
2637
2995
  `);
2638
2996
  }
@@ -2646,7 +3004,7 @@ function detectRepoRoot2(start2) {
2646
3004
  let dir = path8.resolve(start2);
2647
3005
  const root = path8.parse(dir).root;
2648
3006
  while (dir !== root) {
2649
- if (existsSync10(path8.join(dir, ".git")))
3007
+ if (existsSync12(path8.join(dir, ".git")))
2650
3008
  return dir;
2651
3009
  dir = path8.dirname(dir);
2652
3010
  }
@@ -2679,7 +3037,7 @@ function detectInstallHost2(repoRoot) {
2679
3037
  function getInstalledVersion(metaUrl) {
2680
3038
  const packageRoot = getPackageRoot(metaUrl);
2681
3039
  try {
2682
- const pkg = JSON.parse(readFileSync7(path8.join(packageRoot, "package.json"), "utf8"));
3040
+ const pkg = JSON.parse(readFileSync8(path8.join(packageRoot, "package.json"), "utf8"));
2683
3041
  return pkg.version ?? "unknown";
2684
3042
  } catch {
2685
3043
  return "unknown";
@@ -2780,6 +3138,7 @@ loadEnv({ quiet: true });
2780
3138
  loadEnv({ path: ".env.runtime", quiet: true });
2781
3139
  var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
2782
3140
  var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
3141
+ var walletNudgeShown = false;
2783
3142
  function parseArgs(argv) {
2784
3143
  const raw = argv.slice(2);
2785
3144
  const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : "help";
@@ -2926,6 +3285,14 @@ function formatSavedDuration(ms) {
2926
3285
  return `${(ms / 1000).toFixed(1)}s`;
2927
3286
  return `${ms}ms`;
2928
3287
  }
3288
+ function formatCostUsd(uc) {
3289
+ const usd = uc / 1e6;
3290
+ if (usd >= 1)
3291
+ return `$${usd.toFixed(2)}`;
3292
+ if (usd >= 0.01)
3293
+ return `$${usd.toFixed(3)}`;
3294
+ return `$${usd.toFixed(4)}`;
3295
+ }
2929
3296
  function emitImpactSummary(result) {
2930
3297
  const impact = result.impact;
2931
3298
  if (!impact)
@@ -2934,14 +3301,17 @@ function emitImpactSummary(result) {
2934
3301
  const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
2935
3302
  const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
2936
3303
  const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
3304
+ const costSavedUc = typeof impact.cost_saved_uc === "number" ? impact.cost_saved_uc : 0;
2937
3305
  const browserAvoided = impact.browser_avoided === true;
2938
- if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
3306
+ if (timeSavedMs <= 0 && tokensSaved <= 0 && costSavedUc <= 0 && !browserAvoided)
2939
3307
  return;
2940
3308
  const parts = [];
2941
3309
  if (timeSavedMs > 0)
2942
3310
  parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
2943
3311
  if (tokensSaved > 0)
2944
3312
  parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
3313
+ if (costSavedUc > 0)
3314
+ parts.push(`${formatCostUsd(costSavedUc)} saved`);
2945
3315
  if (browserAvoided)
2946
3316
  parts.push("browser avoided");
2947
3317
  info(parts.join(" \u2022 "));
@@ -3086,9 +3456,24 @@ async function cmdResolve(flags) {
3086
3456
  }
3087
3457
  result = slimTrace(result);
3088
3458
  emitImpactSummary(result);
3459
+ {
3460
+ const entry = impactFromResult("resolve", result, { intent, domain });
3461
+ if (entry)
3462
+ appendImpact(entry);
3463
+ }
3089
3464
  emitNextActionSummary(result);
3090
3465
  const skill = result.skill;
3091
3466
  const trace = result.trace;
3467
+ if (trace?.success && !walletNudgeShown) {
3468
+ try {
3469
+ const { checkWalletConfigured: checkWalletConfigured3 } = await Promise.resolve().then(() => (init_wallet2(), exports_wallet));
3470
+ const wallet = checkWalletConfigured3();
3471
+ if (!wallet.configured) {
3472
+ info("You're indexing routes but have no payout wallet. Run: npx @crossmint/lobster-cli setup");
3473
+ walletNudgeShown = true;
3474
+ }
3475
+ } catch (_e) {}
3476
+ }
3092
3477
  if (skill?.skill_id && trace) {
3093
3478
  result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
3094
3479
  }
@@ -3246,6 +3631,14 @@ async function cmdExecute(flags) {
3246
3631
  }
3247
3632
  result = slimTrace(result);
3248
3633
  emitImpactSummary(result);
3634
+ {
3635
+ const entry = impactFromResult("execute", result, {
3636
+ skill_id: skillId,
3637
+ endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : undefined
3638
+ });
3639
+ if (entry)
3640
+ appendImpact(entry);
3641
+ }
3249
3642
  emitNextActionSummary(result);
3250
3643
  const pathFlag = flags.path;
3251
3644
  const extractFlag = flags.extract;
@@ -3472,6 +3865,16 @@ async function cmdSetup(flags) {
3472
3865
  info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
3473
3866
  }
3474
3867
  }
3868
+ if (report.wallet.configured) {
3869
+ info(`Wallet configured (${report.wallet.provider}): ${report.wallet.wallet_address ?? "linked"}`);
3870
+ } else if (report.wallet.lobster_installed) {
3871
+ info("Wallet not paired \u2014 your indexed routes won't earn payouts.");
3872
+ info("Run: npx @crossmint/lobster-cli setup");
3873
+ } else {
3874
+ info("No wallet configured \u2014 you're indexing routes for free.");
3875
+ info("Set up a wallet so you earn when agents use your routes:");
3876
+ info(" npx @crossmint/lobster-cli setup");
3877
+ }
3475
3878
  await recordInstallTelemetryEvent("setup", {
3476
3879
  hostType,
3477
3880
  status: report.browser_engine.action === "failed" ? "failed" : "installed",
@@ -3565,7 +3968,8 @@ var CLI_REFERENCE = {
3565
3968
  { name: "back", usage: "[--session id]", desc: "Navigate back" },
3566
3969
  { name: "forward", usage: "[--session id]", desc: "Navigate forward" },
3567
3970
  { name: "sync", usage: "[--session id]", desc: "Checkpoint current capture, keep tab open, queue background index + publish, then inspect via skill/publish review" },
3568
- { name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" }
3971
+ { name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" },
3972
+ { name: "stats", usage: "[--json] [--pretty]", desc: "Show lifetime time/tokens/cost saved and marketplace earnings/spending" }
3569
3973
  ],
3570
3974
  globalFlags: [
3571
3975
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -3606,6 +4010,131 @@ var CLI_REFERENCE = {
3606
4010
  `unbrowse publish --skill abc --endpoints '[{"endpoint_id":"def","description":"Search court judgments by keywords","action_kind":"search","resource_kind":"judgment"}]'`
3607
4011
  ]
3608
4012
  };
4013
+ function formatTotalDuration(ms) {
4014
+ if (ms >= 3600000)
4015
+ return `${(ms / 3600000).toFixed(1)}h`;
4016
+ if (ms >= 60000)
4017
+ return `${(ms / 60000).toFixed(1)}m`;
4018
+ if (ms >= 1000)
4019
+ return `${(ms / 1000).toFixed(1)}s`;
4020
+ return `${ms}ms`;
4021
+ }
4022
+ async function cmdStats(flags) {
4023
+ const pretty = !!flags.pretty;
4024
+ const jsonOnly = !!flags.json;
4025
+ const local = readImpactSummary();
4026
+ const agentId = getAgentId();
4027
+ let profile = null;
4028
+ let earnings = null;
4029
+ let spending = null;
4030
+ const remoteErrors = {};
4031
+ if (agentId) {
4032
+ const results = await Promise.allSettled([
4033
+ getMyProfile(),
4034
+ getCreatorEarnings(agentId),
4035
+ getTransactionHistory(agentId)
4036
+ ]);
4037
+ if (results[0].status === "fulfilled")
4038
+ profile = results[0].value;
4039
+ else
4040
+ remoteErrors.profile = results[0].reason?.message ?? String(results[0].reason);
4041
+ if (results[1].status === "fulfilled")
4042
+ earnings = results[1].value;
4043
+ else
4044
+ remoteErrors.earnings = results[1].reason?.message ?? String(results[1].reason);
4045
+ if (results[2].status === "fulfilled")
4046
+ spending = results[2].value;
4047
+ else
4048
+ remoteErrors.spending = results[2].reason?.message ?? String(results[2].reason);
4049
+ } else {
4050
+ remoteErrors.profile = "No agent_id in local config. Run `unbrowse setup` to register.";
4051
+ }
4052
+ const earnedUsd = earnings?.ledger?.total_earned_usd ?? 0;
4053
+ const spentUsd = spending?.ledger?.total_spent_usd ?? 0;
4054
+ const netUsd = earnedUsd - spentUsd;
4055
+ const savedUsd = local.total_cost_saved_uc / 1e6;
4056
+ const payload = {
4057
+ agent_id: agentId,
4058
+ profile,
4059
+ impact: {
4060
+ total_runs: local.total_runs,
4061
+ successful_runs: local.successful_runs,
4062
+ browser_avoided_runs: local.browser_avoided_runs,
4063
+ total_time_saved_ms: local.total_time_saved_ms,
4064
+ total_time_saved_human: formatTotalDuration(local.total_time_saved_ms),
4065
+ total_tokens_saved: local.total_tokens_saved,
4066
+ total_cost_saved_usd: Number(savedUsd.toFixed(6)),
4067
+ avg_time_saved_pct: local.avg_time_saved_pct,
4068
+ avg_tokens_saved_pct: local.avg_tokens_saved_pct,
4069
+ by_source: local.by_source,
4070
+ first_entry_at: local.first_entry_at,
4071
+ last_entry_at: local.last_entry_at,
4072
+ log_path: getImpactLogPath()
4073
+ },
4074
+ earnings: {
4075
+ total_earned_usd: earnedUsd,
4076
+ total_earned_uc: earnings?.ledger?.total_earned_uc ?? 0,
4077
+ transaction_count: earnings?.ledger?.transaction_count ?? 0,
4078
+ last_transaction_at: earnings?.ledger?.last_transaction_at ?? null
4079
+ },
4080
+ spending: {
4081
+ total_spent_usd: spentUsd,
4082
+ total_spent_uc: spending?.ledger?.total_spent_uc ?? 0,
4083
+ transaction_count: spending?.ledger?.transaction_count ?? 0,
4084
+ last_transaction_at: spending?.ledger?.last_transaction_at ?? null
4085
+ },
4086
+ net_usd: netUsd,
4087
+ ...Object.keys(remoteErrors).length > 0 ? { remote_errors: remoteErrors } : {}
4088
+ };
4089
+ if (jsonOnly) {
4090
+ output(payload, pretty);
4091
+ return;
4092
+ }
4093
+ const lines = [];
4094
+ lines.push("Unbrowse stats");
4095
+ lines.push(` agent_id: ${agentId ?? "(not registered \u2014 run `unbrowse setup`)"}`);
4096
+ if (profile?.name)
4097
+ lines.push(` name: ${profile.name}`);
4098
+ lines.push("");
4099
+ lines.push("Impact (local, this machine):");
4100
+ if (local.total_runs === 0) {
4101
+ lines.push(" No resolve/execute runs recorded yet.");
4102
+ lines.push(` Log file: ${getImpactLogPath()}`);
4103
+ } else {
4104
+ lines.push(` Runs: ${local.total_runs} (${local.successful_runs} successful, ${local.browser_avoided_runs} browser-avoided)`);
4105
+ if (local.total_time_saved_ms > 0) {
4106
+ lines.push(` Time saved: ${formatTotalDuration(local.total_time_saved_ms)} (avg ${local.avg_time_saved_pct}% faster)`);
4107
+ }
4108
+ if (local.total_tokens_saved > 0) {
4109
+ lines.push(` Tokens saved: ${local.total_tokens_saved.toLocaleString("en-US")} (avg ${local.avg_tokens_saved_pct}% less context)`);
4110
+ }
4111
+ if (savedUsd > 0) {
4112
+ lines.push(` Cost saved: ${formatCostUsd(local.total_cost_saved_uc)}`);
4113
+ }
4114
+ if (Object.keys(local.by_source).length > 0) {
4115
+ const topSources = Object.entries(local.by_source).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k}=${v}`).join(", ");
4116
+ lines.push(` By source: ${topSources}`);
4117
+ }
4118
+ }
4119
+ lines.push("");
4120
+ lines.push("Money (backend ledger):");
4121
+ if (!agentId) {
4122
+ lines.push(" (not registered \u2014 earnings/spending unavailable)");
4123
+ } else if (remoteErrors.earnings || remoteErrors.spending) {
4124
+ if (remoteErrors.earnings)
4125
+ lines.push(` earnings: error \u2014 ${remoteErrors.earnings}`);
4126
+ if (remoteErrors.spending)
4127
+ lines.push(` spending: error \u2014 ${remoteErrors.spending}`);
4128
+ } else {
4129
+ lines.push(` Earned: $${earnedUsd.toFixed(4)} (${earnings?.ledger?.transaction_count ?? 0} payouts)`);
4130
+ lines.push(` Spent: $${spentUsd.toFixed(4)} (${spending?.ledger?.transaction_count ?? 0} payments)`);
4131
+ lines.push(` Net: ${netUsd >= 0 ? "+" : ""}$${netUsd.toFixed(4)}`);
4132
+ }
4133
+ lines.push("");
4134
+ info(lines.join(`
4135
+ `));
4136
+ output(payload, pretty);
4137
+ }
3609
4138
  function printHelp() {
3610
4139
  const r = CLI_REFERENCE;
3611
4140
  const lines = ["unbrowse \u2014 shell-safe CLI for the local API", ""];
@@ -4027,6 +4556,8 @@ async function main() {
4027
4556
  return cmdUpgrade(flags);
4028
4557
  if (command === "connect-chrome")
4029
4558
  return cmdConnectChrome();
4559
+ if (command === "stats")
4560
+ return cmdStats(flags);
4030
4561
  const KNOWN_COMMANDS = new Set([
4031
4562
  "health",
4032
4563
  "mcp",
@@ -4070,7 +4601,8 @@ async function main() {
4070
4601
  "forward",
4071
4602
  "sync",
4072
4603
  "close",
4073
- "connect-chrome"
4604
+ "connect-chrome",
4605
+ "stats"
4074
4606
  ]);
4075
4607
  if (!KNOWN_COMMANDS.has(command)) {
4076
4608
  const pack = findSitePack(command);
@@ -4166,6 +4698,8 @@ async function main() {
4166
4698
  return cmdClose(flags);
4167
4699
  case "connect-chrome":
4168
4700
  return cmdConnectChrome();
4701
+ case "stats":
4702
+ return cmdStats(flags);
4169
4703
  default:
4170
4704
  info(`Unknown command: ${command}`);
4171
4705
  printHelp();