unbrowse 3.0.2 → 3.1.0-experiments.01f0036

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.
Files changed (105) hide show
  1. package/dist/cli.js +677 -116
  2. package/dist/index.js +2 -6
  3. package/dist/mcp.js +765 -77
  4. package/dist/server.js +25994 -0
  5. package/package.json +1 -2
  6. package/vendor/kuri/darwin-arm64/kuri +0 -0
  7. package/vendor/kuri/darwin-x64/kuri +0 -0
  8. package/vendor/kuri/linux-arm64/kuri +0 -0
  9. package/vendor/kuri/linux-x64/kuri +0 -0
  10. package/vendor/kuri/manifest.json +7 -6
  11. package/runtime-src/agent-outcome.ts +0 -166
  12. package/runtime-src/analytics-session.ts +0 -55
  13. package/runtime-src/api/browse-index.ts +0 -317
  14. package/runtime-src/api/browse-session.ts +0 -572
  15. package/runtime-src/api/browse-submit-prereqs.ts +0 -48
  16. package/runtime-src/api/browse-submit.ts +0 -1184
  17. package/runtime-src/api/routes.ts +0 -1800
  18. package/runtime-src/auth/browser-cookies.ts +0 -423
  19. package/runtime-src/auth/index.ts +0 -535
  20. package/runtime-src/auth/runtime.ts +0 -116
  21. package/runtime-src/browser/index.ts +0 -659
  22. package/runtime-src/browser/types.ts +0 -41
  23. package/runtime-src/build-info.generated.ts +0 -6
  24. package/runtime-src/capture/index.ts +0 -1794
  25. package/runtime-src/capture/prefetch.ts +0 -95
  26. package/runtime-src/capture/rsc.ts +0 -45
  27. package/runtime-src/cli/shortcuts.ts +0 -273
  28. package/runtime-src/cli.ts +0 -1546
  29. package/runtime-src/client/graph-client.ts +0 -100
  30. package/runtime-src/client/index.ts +0 -1409
  31. package/runtime-src/debug-trace.ts +0 -18
  32. package/runtime-src/domain.ts +0 -38
  33. package/runtime-src/execution/index.ts +0 -3385
  34. package/runtime-src/execution/retry.ts +0 -46
  35. package/runtime-src/execution/robots.ts +0 -167
  36. package/runtime-src/execution/search-forms.ts +0 -188
  37. package/runtime-src/extraction/index.ts +0 -1507
  38. package/runtime-src/foundry/publish-bundle.ts +0 -392
  39. package/runtime-src/graph/agent-augment.ts +0 -315
  40. package/runtime-src/graph/index.ts +0 -1524
  41. package/runtime-src/graph/local-fixtures.ts +0 -393
  42. package/runtime-src/graph/local-harness.ts +0 -646
  43. package/runtime-src/graph/planner.ts +0 -411
  44. package/runtime-src/graph/session.ts +0 -294
  45. package/runtime-src/graph/trace-store.ts +0 -136
  46. package/runtime-src/index.ts +0 -24
  47. package/runtime-src/indexer/index.ts +0 -465
  48. package/runtime-src/intent-match.ts +0 -1515
  49. package/runtime-src/kuri/client.ts +0 -1835
  50. package/runtime-src/logger.ts +0 -30
  51. package/runtime-src/marketplace/index.ts +0 -103
  52. package/runtime-src/mcp.ts +0 -1698
  53. package/runtime-src/orchestrator/browser-agent.ts +0 -374
  54. package/runtime-src/orchestrator/dag-advisor.ts +0 -59
  55. package/runtime-src/orchestrator/dag-feedback.ts +0 -257
  56. package/runtime-src/orchestrator/first-pass-action.ts +0 -403
  57. package/runtime-src/orchestrator/index.ts +0 -4476
  58. package/runtime-src/orchestrator/passive-publish.ts +0 -187
  59. package/runtime-src/orchestrator/timing-economics.ts +0 -80
  60. package/runtime-src/payments/cascade.ts +0 -137
  61. package/runtime-src/payments/index.ts +0 -270
  62. package/runtime-src/payments/wallet.ts +0 -98
  63. package/runtime-src/publish/review-context.ts +0 -93
  64. package/runtime-src/publish/sanitize.ts +0 -197
  65. package/runtime-src/publish/schema-review.ts +0 -192
  66. package/runtime-src/publish-admission.ts +0 -388
  67. package/runtime-src/ratelimit/index.ts +0 -23
  68. package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
  69. package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
  70. package/runtime-src/reverse-engineer/index.ts +0 -1551
  71. package/runtime-src/router.ts +0 -17
  72. package/runtime-src/routing-telemetry.ts +0 -395
  73. package/runtime-src/runtime/browser-access.ts +0 -11
  74. package/runtime-src/runtime/browser-auth.ts +0 -12
  75. package/runtime-src/runtime/browser-host.ts +0 -48
  76. package/runtime-src/runtime/lifecycle.ts +0 -17
  77. package/runtime-src/runtime/local-server.ts +0 -311
  78. package/runtime-src/runtime/paths.ts +0 -99
  79. package/runtime-src/runtime/setup.ts +0 -251
  80. package/runtime-src/runtime/supervisor.ts +0 -69
  81. package/runtime-src/runtime/update-hints.ts +0 -351
  82. package/runtime-src/server.ts +0 -100
  83. package/runtime-src/session-logs.ts +0 -142
  84. package/runtime-src/settings.ts +0 -221
  85. package/runtime-src/single-binary.ts +0 -143
  86. package/runtime-src/site-policy.ts +0 -54
  87. package/runtime-src/stale-cleanup-runner.ts +0 -144
  88. package/runtime-src/stale-cleanup.ts +0 -133
  89. package/runtime-src/telemetry-attribution.ts +0 -120
  90. package/runtime-src/telemetry.ts +0 -253
  91. package/runtime-src/template-params.ts +0 -141
  92. package/runtime-src/transform/drift.ts +0 -60
  93. package/runtime-src/transform/index.ts +0 -277
  94. package/runtime-src/types/index.ts +0 -1
  95. package/runtime-src/types/skill.ts +0 -912
  96. package/runtime-src/vault/index.ts +0 -196
  97. package/runtime-src/verification/auth-gate.ts +0 -8
  98. package/runtime-src/verification/candidates.ts +0 -27
  99. package/runtime-src/verification/index.ts +0 -120
  100. package/runtime-src/verification/matrix.ts +0 -30
  101. package/runtime-src/version.ts +0 -148
  102. package/runtime-src/workflow/artifact.ts +0 -161
  103. package/runtime-src/workflow/compile.ts +0 -808
  104. package/runtime-src/workflow/publish.ts +0 -225
  105. package/runtime-src/workflow/runtime.ts +0 -213
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.01f0036", BUILD_GIT_SHA = "01f00363ffe9", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuMDFmMDAzNiIsImdpdF9zaGEiOiIwMWYwMDM2M2ZmZTkiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDAxZjAwMzYzZmZlOSIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDZUMDY6MDA6MDIuNjk0WiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "8Lba-sSVxIWGBqxHRyCgPwKJD9Rn4vURXK08W92ZEGM", 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 existsSync9 } 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) => existsSync9(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
@@ -677,9 +796,17 @@ var init_bundle_scanner = __esm(() => {
677
796
  init_logger();
678
797
  });
679
798
 
799
+ // ../../src/reverse-engineer/token-sources.ts
800
+ var init_token_sources = () => {};
801
+
802
+ // ../../src/execution/token-resolver.ts
803
+ var init_token_resolver = __esm(() => {
804
+ init_token_sources();
805
+ });
806
+
680
807
  // ../../src/vault/index.ts
681
- import { join as join5 } from "path";
682
- import { homedir as homedir3 } from "os";
808
+ import { join as join7 } from "path";
809
+ import { homedir as homedir5 } from "os";
683
810
  function normalizeKeytarModule(mod) {
684
811
  let candidate = mod;
685
812
  for (let depth = 0;depth < 3; depth++) {
@@ -701,9 +828,9 @@ var init_vault = __esm(async () => {
701
828
  try {
702
829
  keytar = normalizeKeytarModule(await import("keytar"));
703
830
  } catch {}
704
- VAULT_DIR = join5(homedir3(), ".unbrowse", "vault");
705
- VAULT_FILE = join5(VAULT_DIR, "credentials.enc");
706
- KEY_FILE = join5(VAULT_DIR, ".key");
831
+ VAULT_DIR = join7(homedir5(), ".unbrowse", "vault");
832
+ VAULT_FILE = join7(VAULT_DIR, "credentials.enc");
833
+ KEY_FILE = join7(VAULT_DIR, ".key");
707
834
  vaultLock = Promise.resolve();
708
835
  });
709
836
 
@@ -780,18 +907,6 @@ var init_extraction = __esm(() => {
780
907
  CHROME_TAGS = new Set(["nav", "footer", "header"]);
781
908
  });
782
909
 
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
910
  // ../../src/execution/search-forms.ts
796
911
  var SEARCH_FIELD_NAMES, LOGIN_FIELD_NAMES, SUPPORTED_INPUT_TYPES;
797
912
  var init_search_forms = __esm(() => {
@@ -854,7 +969,7 @@ var init_schema_review = __esm(() => {
854
969
  });
855
970
 
856
971
  // ../../src/indexer/index.ts
857
- import { join as join6 } from "node:path";
972
+ import { join as join8 } from "node:path";
858
973
  var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
859
974
  var init_indexer = __esm(async () => {
860
975
  init_graph();
@@ -869,7 +984,7 @@ var init_indexer = __esm(async () => {
869
984
  init_graph();
870
985
  init_schema_review();
871
986
  await init_orchestrator();
872
- SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join6(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
987
+ SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
873
988
  indexInFlight = new Map;
874
989
  pendingIndexJobs = new Map;
875
990
  });
@@ -907,6 +1022,7 @@ var init_execution = __esm(async () => {
907
1022
  init_capture();
908
1023
  init_reverse_engineer();
909
1024
  init_bundle_scanner();
1025
+ init_token_resolver();
910
1026
  init_marketplace();
911
1027
  init_runtime();
912
1028
  init_transform();
@@ -917,7 +1033,6 @@ var init_execution = __esm(async () => {
917
1033
  init_domain();
918
1034
  init_extraction();
919
1035
  init_graph();
920
- init_agent_augment();
921
1036
  init_logger();
922
1037
  init_version();
923
1038
  init_search_forms();
@@ -1046,8 +1161,8 @@ var init_routing_telemetry = __esm(() => {
1046
1161
  });
1047
1162
  // ../../src/orchestrator/index.ts
1048
1163
  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";
1164
+ import { existsSync as existsSync10, writeFileSync as writeFileSync3, readFileSync as readFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "node:fs";
1165
+ import { dirname as dirname3, join as join9 } from "node:path";
1051
1166
  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
1167
  var init_orchestrator = __esm(async () => {
1053
1168
  init_client();
@@ -1077,13 +1192,13 @@ var init_orchestrator = __esm(async () => {
1077
1192
  captureInFlight = new Map;
1078
1193
  captureDomainLocks = new Map;
1079
1194
  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");
1195
+ ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
1196
+ SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1082
1197
  domainSkillCache = new Map;
1083
- DOMAIN_CACHE_FILE = join7(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1198
+ DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1084
1199
  try {
1085
- if (existsSync7(DOMAIN_CACHE_FILE)) {
1086
- const data = JSON.parse(readFileSync5(DOMAIN_CACHE_FILE, "utf-8"));
1200
+ if (existsSync10(DOMAIN_CACHE_FILE)) {
1201
+ const data = JSON.parse(readFileSync6(DOMAIN_CACHE_FILE, "utf-8"));
1087
1202
  for (const [k, v] of Object.entries(data)) {
1088
1203
  const entry = v;
1089
1204
  if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
@@ -1098,17 +1213,17 @@ var init_orchestrator = __esm(async () => {
1098
1213
  return;
1099
1214
  _routeCacheDirty = false;
1100
1215
  try {
1101
- const dir = dirname2(ROUTE_CACHE_FILE);
1102
- if (!existsSync7(dir))
1103
- mkdirSync4(dir, { recursive: true });
1216
+ const dir = dirname3(ROUTE_CACHE_FILE);
1217
+ if (!existsSync10(dir))
1218
+ mkdirSync5(dir, { recursive: true });
1104
1219
  const entries = Object.fromEntries(skillRouteCache);
1105
1220
  writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
1106
1221
  } catch {}
1107
1222
  }, 5000);
1108
1223
  routeCacheFlushTimer.unref?.();
1109
1224
  try {
1110
- if (existsSync7(ROUTE_CACHE_FILE)) {
1111
- const data = JSON.parse(readFileSync5(ROUTE_CACHE_FILE, "utf-8"));
1225
+ if (existsSync10(ROUTE_CACHE_FILE)) {
1226
+ const data = JSON.parse(readFileSync6(ROUTE_CACHE_FILE, "utf-8"));
1112
1227
  for (const [k, v] of Object.entries(data)) {
1113
1228
  const entry = v;
1114
1229
  if (Date.now() - entry.ts < 24 * 60 * 60000) {
@@ -1198,6 +1313,60 @@ var init_orchestrator = __esm(async () => {
1198
1313
  ]);
1199
1314
  });
1200
1315
 
1316
+ // ../../src/payments/wallet.ts
1317
+ var exports_wallet = {};
1318
+ __export(exports_wallet, {
1319
+ getWalletContext: () => getWalletContext2,
1320
+ checkWalletConfigured: () => checkWalletConfigured2
1321
+ });
1322
+ import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
1323
+ import { homedir as homedir6 } from "node:os";
1324
+ import { join as join11 } from "node:path";
1325
+ function asNonEmptyString2(value) {
1326
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
1327
+ }
1328
+ function getLobsterWalletFromLocalConfig2() {
1329
+ const agentsPath = join11(process.env.HOME || homedir6(), ".lobster", "agents.json");
1330
+ if (!existsSync14(agentsPath))
1331
+ return;
1332
+ try {
1333
+ const raw = JSON.parse(readFileSync9(agentsPath, "utf8"));
1334
+ const activeAgentId = asNonEmptyString2(raw.activeAgentId);
1335
+ const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString2(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
1336
+ return asNonEmptyString2(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString2(activeAgent?.walletAddress) ?? asNonEmptyString2(activeAgent?.wallet_address);
1337
+ } catch {
1338
+ return;
1339
+ }
1340
+ }
1341
+ function getWalletContext2() {
1342
+ const lobsterWallet = asNonEmptyString2(process.env.LOBSTER_WALLET_ADDRESS);
1343
+ if (lobsterWallet) {
1344
+ return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
1345
+ }
1346
+ const genericWallet = asNonEmptyString2(process.env.AGENT_WALLET_ADDRESS);
1347
+ if (genericWallet) {
1348
+ return {
1349
+ wallet_address: genericWallet,
1350
+ wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
1351
+ };
1352
+ }
1353
+ const localLobsterWallet = getLobsterWalletFromLocalConfig2();
1354
+ if (localLobsterWallet) {
1355
+ return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
1356
+ }
1357
+ return {};
1358
+ }
1359
+ function checkWalletConfigured2() {
1360
+ const wallet = getWalletContext2();
1361
+ if (!wallet.wallet_address)
1362
+ return { configured: false };
1363
+ return {
1364
+ configured: true,
1365
+ provider: wallet.wallet_provider ?? "unknown"
1366
+ };
1367
+ }
1368
+ var init_wallet2 = () => {};
1369
+
1201
1370
  // ../../src/cli.ts
1202
1371
  import { config as loadEnv } from "dotenv";
1203
1372
  import { spawn as spawn3 } from "child_process";
@@ -1207,9 +1376,9 @@ init_version();
1207
1376
  init_cascade();
1208
1377
  init_wallet();
1209
1378
  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";
1379
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
1380
+ import { join as join4 } from "path";
1381
+ import { homedir as homedir3, hostname, release as osRelease } from "os";
1213
1382
  import { randomBytes, createHash as createHash2 } from "crypto";
1214
1383
  import { createInterface } from "readline";
1215
1384
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
@@ -1244,13 +1413,13 @@ function decodeBase64Json(value) {
1244
1413
  function getConfigDir() {
1245
1414
  if (process.env.UNBROWSE_CONFIG_DIR)
1246
1415
  return process.env.UNBROWSE_CONFIG_DIR;
1247
- return PROFILE_NAME ? join3(homedir2(), ".unbrowse", "profiles", PROFILE_NAME) : join3(homedir2(), ".unbrowse");
1416
+ return PROFILE_NAME ? join4(homedir3(), ".unbrowse", "profiles", PROFILE_NAME) : join4(homedir3(), ".unbrowse");
1248
1417
  }
1249
1418
  function getConfigPath() {
1250
- return join3(getConfigDir(), "config.json");
1419
+ return join4(getConfigDir(), "config.json");
1251
1420
  }
1252
1421
  function getInstallTelemetryPath() {
1253
- return join3(getConfigDir(), "install-state.json");
1422
+ return join4(getConfigDir(), "install-state.json");
1254
1423
  }
1255
1424
  function getLandingToken() {
1256
1425
  const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
@@ -1265,7 +1434,7 @@ function getActiveProfile() {
1265
1434
  function loadConfig() {
1266
1435
  try {
1267
1436
  const configPath = getConfigPath();
1268
- if (existsSync3(configPath)) {
1437
+ if (existsSync4(configPath)) {
1269
1438
  return JSON.parse(readFileSync3(configPath, "utf-8"));
1270
1439
  }
1271
1440
  } catch {}
@@ -1274,14 +1443,14 @@ function loadConfig() {
1274
1443
  function saveConfig(config) {
1275
1444
  const configDir = getConfigDir();
1276
1445
  const configPath = getConfigPath();
1277
- if (!existsSync3(configDir))
1446
+ if (!existsSync4(configDir))
1278
1447
  mkdirSync(configDir, { recursive: true });
1279
1448
  writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
1280
1449
  }
1281
1450
  function loadInstallTelemetryState() {
1282
1451
  try {
1283
1452
  const statePath = getInstallTelemetryPath();
1284
- if (existsSync3(statePath)) {
1453
+ if (existsSync4(statePath)) {
1285
1454
  return JSON.parse(readFileSync3(statePath, "utf-8"));
1286
1455
  }
1287
1456
  } catch {}
@@ -1290,7 +1459,7 @@ function loadInstallTelemetryState() {
1290
1459
  function saveInstallTelemetryState(state) {
1291
1460
  const configDir = getConfigDir();
1292
1461
  const statePath = getInstallTelemetryPath();
1293
- if (!existsSync3(configDir))
1462
+ if (!existsSync4(configDir))
1294
1463
  mkdirSync(configDir, { recursive: true });
1295
1464
  writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
1296
1465
  }
@@ -1402,9 +1571,19 @@ async function recordInstallTelemetryEvent(source, options) {
1402
1571
  skill_version: options?.skillVersion,
1403
1572
  status: options?.status ?? "installed",
1404
1573
  created_at: createdAt,
1405
- properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
1574
+ properties: mergeTelemetryProperties({ ...getRuntimeContext(), ...options?.properties }, getTelemetryAttribution())
1406
1575
  });
1407
1576
  }
1577
+ function getRuntimeContext() {
1578
+ return {
1579
+ cli_version: PACKAGE_VERSION,
1580
+ code_hash: CODE_HASH,
1581
+ node_version: process.version,
1582
+ platform: process.platform,
1583
+ arch: process.arch,
1584
+ os_release: osRelease()
1585
+ };
1586
+ }
1408
1587
  async function recordFunnelTelemetryEvent(name, options) {
1409
1588
  const createdAt = options?.createdAt ?? new Date().toISOString();
1410
1589
  const landingToken = getLandingToken();
@@ -1416,7 +1595,7 @@ async function recordFunnelTelemetryEvent(name, options) {
1416
1595
  source: options?.source ?? "cli",
1417
1596
  host_type: options?.hostType ?? detectTelemetryHostType(),
1418
1597
  created_at: createdAt,
1419
- properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
1598
+ properties: mergeTelemetryProperties({ ...getRuntimeContext(), ...options?.properties }, getTelemetryAttribution())
1420
1599
  });
1421
1600
  }
1422
1601
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/i;
@@ -1448,6 +1627,10 @@ function getApiKey() {
1448
1627
  }
1449
1628
  return "";
1450
1629
  }
1630
+ function getAgentId() {
1631
+ const config = loadConfig();
1632
+ return config?.agent_id ?? null;
1633
+ }
1451
1634
  var API_TIMEOUT_MS = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
1452
1635
  var PUBLISH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
1453
1636
  async function validateApiKey(key) {
@@ -1547,6 +1730,26 @@ async function apiRequest(method, path, body, opts) {
1547
1730
  const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
1548
1731
  const legacyPaymentTerms = res.headers.get("X-Payment-Required");
1549
1732
  const terms = paymentRequired ? decodeBase64Json(paymentRequired) : legacyPaymentTerms ? JSON.parse(legacyPaymentTerms) : data.terms;
1733
+ try {
1734
+ const { isLobsterAvailable: isLobsterAvailable2, payAndRetry: payAndRetry2 } = await Promise.resolve().then(() => (init_lobster_pay(), exports_lobster_pay));
1735
+ if (isLobsterAvailable2()) {
1736
+ const fullUrl = `${API_URL}${path}`;
1737
+ const paidResult = await payAndRetry2(fullUrl, {
1738
+ body,
1739
+ headers: {
1740
+ "Content-Type": "application/json",
1741
+ "Accept-Encoding": "gzip, deflate",
1742
+ ...releaseAttestationHeaders,
1743
+ ...key ? { Authorization: `Bearer ${key}` } : {}
1744
+ }
1745
+ });
1746
+ if (paidResult) {
1747
+ return { data: paidResult.data, headers: new Headers };
1748
+ }
1749
+ }
1750
+ } catch (payErr) {
1751
+ console.warn(`[x402] lobster pay-and-retry failed: ${payErr.message}`);
1752
+ }
1550
1753
  const err = new Error(`Payment required: ${data.error ?? "This skill requires payment"}`);
1551
1754
  err.x402 = true;
1552
1755
  err.terms = terms;
@@ -1565,19 +1768,10 @@ async function api(method, path, body, opts) {
1565
1768
  return data;
1566
1769
  }
1567
1770
  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
1771
  const token = process.env.UNBROWSE_LANDING_TOKEN;
1578
1772
  if (token && token.length < 2048)
1579
- result.landing_token = token;
1580
- return result;
1773
+ return { landing_token: token };
1774
+ return {};
1581
1775
  }
1582
1776
  async function promptTosAcceptance(summary, tosUrl) {
1583
1777
  if (process.env.UNBROWSE_NON_INTERACTIVE === "1") {
@@ -1748,6 +1942,170 @@ async function syncAgentWallet(wallet = getLocalWalletContext()) {
1748
1942
  return;
1749
1943
  saveConfig({ ...config, ...wallet });
1750
1944
  }
1945
+ async function getTransactionHistory(agentId) {
1946
+ return api("GET", `/v1/transactions/consumer/${agentId}`);
1947
+ }
1948
+ async function getCreatorEarnings(agentId) {
1949
+ return api("GET", `/v1/transactions/creator/${agentId}`);
1950
+ }
1951
+
1952
+ // ../../src/impact-log.ts
1953
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync } from "node:fs";
1954
+ import { homedir as homedir4 } from "node:os";
1955
+ import { dirname as dirname2, join as join5 } from "node:path";
1956
+ var MAX_LOG_BYTES = 5 * 1024 * 1024;
1957
+ var MAX_ROTATIONS = 3;
1958
+ function getLogDir() {
1959
+ if (process.env.UNBROWSE_CONFIG_DIR)
1960
+ return process.env.UNBROWSE_CONFIG_DIR;
1961
+ const profile = process.env.UNBROWSE_PROFILE?.trim();
1962
+ return profile ? join5(homedir4(), ".unbrowse", "profiles", profile) : join5(homedir4(), ".unbrowse");
1963
+ }
1964
+ function getImpactLogPath() {
1965
+ return join5(getLogDir(), "impact-log.jsonl");
1966
+ }
1967
+ function ensureDir(path) {
1968
+ const dir = dirname2(path);
1969
+ if (!existsSync5(dir))
1970
+ mkdirSync2(dir, { recursive: true });
1971
+ }
1972
+ function rotateIfNeeded(path) {
1973
+ try {
1974
+ if (!existsSync5(path))
1975
+ return;
1976
+ const size = statSync(path).size;
1977
+ if (size < MAX_LOG_BYTES)
1978
+ return;
1979
+ for (let i = MAX_ROTATIONS;i >= 1; i--) {
1980
+ const older = `${path}.${i}`;
1981
+ if (!existsSync5(older))
1982
+ continue;
1983
+ if (i === MAX_ROTATIONS) {
1984
+ try {
1985
+ unlinkSync(older);
1986
+ } catch {}
1987
+ } else {
1988
+ try {
1989
+ renameSync(older, `${path}.${i + 1}`);
1990
+ } catch {}
1991
+ }
1992
+ }
1993
+ renameSync(path, `${path}.1`);
1994
+ } catch {}
1995
+ }
1996
+ function appendImpact(entry) {
1997
+ try {
1998
+ const hasSignal = (entry.time_saved_ms ?? 0) > 0 || (entry.tokens_saved ?? 0) > 0 || (entry.cost_saved_uc ?? 0) > 0 || entry.browser_avoided === true;
1999
+ if (!hasSignal)
2000
+ return;
2001
+ const path = getImpactLogPath();
2002
+ ensureDir(path);
2003
+ rotateIfNeeded(path);
2004
+ appendFileSync(path, JSON.stringify(entry) + `
2005
+ `, "utf8");
2006
+ } catch {}
2007
+ }
2008
+ function impactFromResult(command, result, extras = {}) {
2009
+ if (!result || typeof result !== "object")
2010
+ return null;
2011
+ const r = result;
2012
+ const impact = r.impact ?? null;
2013
+ if (!impact || typeof impact !== "object")
2014
+ return null;
2015
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
2016
+ return {
2017
+ ts: new Date().toISOString(),
2018
+ command,
2019
+ source: typeof impact.source === "string" ? impact.source : undefined,
2020
+ domain: extras.domain,
2021
+ intent: extras.intent,
2022
+ skill_id: extras.skill_id ?? (typeof r.skill_id === "string" ? r.skill_id : undefined),
2023
+ endpoint_id: extras.endpoint_id ?? (typeof r.endpoint_id === "string" ? r.endpoint_id : undefined),
2024
+ time_saved_ms: num(impact.time_saved_ms),
2025
+ time_saved_pct: num(impact.time_saved_pct),
2026
+ tokens_saved: num(impact.tokens_saved),
2027
+ tokens_saved_pct: num(impact.tokens_saved_pct),
2028
+ cost_saved_uc: num(impact.cost_saved_uc),
2029
+ browser_avoided: impact.browser_avoided === true,
2030
+ success: r.error == null
2031
+ };
2032
+ }
2033
+ function readImpactSummary() {
2034
+ const path = getImpactLogPath();
2035
+ const summary = {
2036
+ total_runs: 0,
2037
+ successful_runs: 0,
2038
+ browser_avoided_runs: 0,
2039
+ total_time_saved_ms: 0,
2040
+ total_tokens_saved: 0,
2041
+ total_cost_saved_uc: 0,
2042
+ avg_time_saved_pct: 0,
2043
+ avg_tokens_saved_pct: 0,
2044
+ by_source: {},
2045
+ first_entry_at: null,
2046
+ last_entry_at: null
2047
+ };
2048
+ const files = [];
2049
+ for (let i = MAX_ROTATIONS;i >= 1; i--) {
2050
+ const rotated = `${path}.${i}`;
2051
+ if (existsSync5(rotated))
2052
+ files.push(rotated);
2053
+ }
2054
+ if (existsSync5(path))
2055
+ files.push(path);
2056
+ if (files.length === 0)
2057
+ return summary;
2058
+ let timePctSum = 0;
2059
+ let timePctCount = 0;
2060
+ let tokenPctSum = 0;
2061
+ let tokenPctCount = 0;
2062
+ for (const file of files) {
2063
+ let raw;
2064
+ try {
2065
+ raw = readFileSync4(file, "utf8");
2066
+ } catch {
2067
+ continue;
2068
+ }
2069
+ for (const line of raw.split(`
2070
+ `)) {
2071
+ const trimmed = line.trim();
2072
+ if (!trimmed)
2073
+ continue;
2074
+ let e;
2075
+ try {
2076
+ e = JSON.parse(trimmed);
2077
+ } catch {
2078
+ continue;
2079
+ }
2080
+ summary.total_runs += 1;
2081
+ if (e.success !== false)
2082
+ summary.successful_runs += 1;
2083
+ if (e.browser_avoided)
2084
+ summary.browser_avoided_runs += 1;
2085
+ summary.total_time_saved_ms += e.time_saved_ms ?? 0;
2086
+ summary.total_tokens_saved += e.tokens_saved ?? 0;
2087
+ summary.total_cost_saved_uc += e.cost_saved_uc ?? 0;
2088
+ if (typeof e.time_saved_pct === "number") {
2089
+ timePctSum += e.time_saved_pct;
2090
+ timePctCount += 1;
2091
+ }
2092
+ if (typeof e.tokens_saved_pct === "number") {
2093
+ tokenPctSum += e.tokens_saved_pct;
2094
+ tokenPctCount += 1;
2095
+ }
2096
+ if (e.source) {
2097
+ summary.by_source[e.source] = (summary.by_source[e.source] ?? 0) + 1;
2098
+ }
2099
+ if (!summary.first_entry_at || e.ts < summary.first_entry_at)
2100
+ summary.first_entry_at = e.ts;
2101
+ if (!summary.last_entry_at || e.ts > summary.last_entry_at)
2102
+ summary.last_entry_at = e.ts;
2103
+ }
2104
+ }
2105
+ summary.avg_time_saved_pct = timePctCount > 0 ? Math.round(timePctSum / timePctCount) : 0;
2106
+ summary.avg_tokens_saved_pct = tokenPctCount > 0 ? Math.round(tokenPctSum / tokenPctCount) : 0;
2107
+ return summary;
2108
+ }
1751
2109
 
1752
2110
  // ../../src/cli/shortcuts.ts
1753
2111
  var linkedin = {
@@ -1948,7 +2306,7 @@ function buildDepsMetadata(pack, taskName) {
1948
2306
  init_paths();
1949
2307
  init_supervisor();
1950
2308
  init_version();
1951
- import { openSync, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2309
+ import { existsSync as existsSync7, openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
1952
2310
  import path2 from "node:path";
1953
2311
  import { spawn } from "node:child_process";
1954
2312
  function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
@@ -1986,14 +2344,14 @@ function isPidAlive(pid) {
1986
2344
  }
1987
2345
  function readPidState(pidFile) {
1988
2346
  try {
1989
- return JSON.parse(readFileSync4(pidFile, "utf-8"));
2347
+ return JSON.parse(readFileSync5(pidFile, "utf-8"));
1990
2348
  } catch {
1991
2349
  return null;
1992
2350
  }
1993
2351
  }
1994
2352
  function clearStalePidFile(pidFile) {
1995
2353
  try {
1996
- unlinkSync(pidFile);
2354
+ unlinkSync2(pidFile);
1997
2355
  } catch {}
1998
2356
  }
1999
2357
  function deriveListenEnv(baseUrl) {
@@ -2014,6 +2372,10 @@ function getServerSpawnSpec(metaUrl, entrypoint = resolveSiblingEntrypoint(metaU
2014
2372
  recordedEntrypoint: `${process.execPath} serve`
2015
2373
  };
2016
2374
  }
2375
+ const serverJs = path2.join(path2.dirname(entrypoint), "server.js");
2376
+ if (existsSync7(serverJs) && path2.basename(entrypoint) !== "server.js") {
2377
+ entrypoint = serverJs;
2378
+ }
2017
2379
  return {
2018
2380
  command: process.execPath,
2019
2381
  args: runtimeArgsForEntrypoint(metaUrl, entrypoint),
@@ -2027,7 +2389,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
2027
2389
  const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
2028
2390
  const spawnSpec = getServerSpawnSpec(metaUrl, entrypoint);
2029
2391
  const logFile = getServerAutostartLogFile();
2030
- ensureDir(path2.dirname(logFile));
2392
+ ensureDir2(path2.dirname(logFile));
2031
2393
  const logFd = openSync(logFile, "a");
2032
2394
  const child = spawn(spawnSpec.command, spawnSpec.args, {
2033
2395
  cwd: spawnSpec.cwd,
@@ -2159,7 +2521,7 @@ async function restartServer(baseUrl, metaUrl) {
2159
2521
  }
2160
2522
 
2161
2523
  // ../../src/runtime/paths.ts
2162
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, realpathSync as realpathSync2 } from "node:fs";
2524
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
2163
2525
  import path3 from "node:path";
2164
2526
  import { createRequire as createRequire3 } from "node:module";
2165
2527
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -2179,7 +2541,7 @@ function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
2179
2541
  const req = createRequire3(metaUrl);
2180
2542
  const tsxPkg = req.resolve("tsx/package.json");
2181
2543
  const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
2182
- if (existsSync5(tsxLoader))
2544
+ if (existsSync8(tsxLoader))
2183
2545
  return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
2184
2546
  } catch {}
2185
2547
  return ["--import", "tsx", entrypoint];
@@ -2209,8 +2571,8 @@ init_publish();
2209
2571
  init_settings();
2210
2572
  init_graph();
2211
2573
  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");
2574
+ import { join as join10 } from "node:path";
2575
+ var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
2214
2576
  var indexInFlight2 = new Map;
2215
2577
  var pendingIndexJobs2 = new Map;
2216
2578
  async function drainPendingIndexJobs() {
@@ -2247,14 +2609,14 @@ init_paths();
2247
2609
  init_client2();
2248
2610
  init_logger();
2249
2611
  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";
2612
+ import { execFileSync as execFileSync3 } from "node:child_process";
2613
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
2252
2614
  import os4 from "node:os";
2253
2615
  import path7 from "node:path";
2254
2616
 
2255
2617
  // ../../src/runtime/update-hints.ts
2256
2618
  init_paths();
2257
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
2619
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2258
2620
  import os3 from "node:os";
2259
2621
  import path6 from "node:path";
2260
2622
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
@@ -2267,20 +2629,20 @@ function getConfigDir2() {
2267
2629
  return process.env.UNBROWSE_CONFIG_DIR;
2268
2630
  return path6.join(getHomeDir(), ".unbrowse");
2269
2631
  }
2270
- function ensureDir2(dir) {
2271
- if (!existsSync8(dir))
2272
- mkdirSync5(dir, { recursive: true });
2632
+ function ensureDir3(dir) {
2633
+ if (!existsSync11(dir))
2634
+ mkdirSync6(dir, { recursive: true });
2273
2635
  return dir;
2274
2636
  }
2275
2637
  function readJsonFile(file) {
2276
2638
  try {
2277
- return JSON.parse(readFileSync6(file, "utf8"));
2639
+ return JSON.parse(readFileSync7(file, "utf8"));
2278
2640
  } catch {
2279
2641
  return null;
2280
2642
  }
2281
2643
  }
2282
2644
  function writeJsonFile(file, value) {
2283
- ensureDir2(path6.dirname(file));
2645
+ ensureDir3(path6.dirname(file));
2284
2646
  writeFileSync4(file, `${JSON.stringify(value, null, 2)}
2285
2647
  `);
2286
2648
  }
@@ -2291,7 +2653,7 @@ function detectRepoRoot(start2) {
2291
2653
  let dir = path6.resolve(start2);
2292
2654
  const root = path6.parse(dir).root;
2293
2655
  while (dir !== root) {
2294
- if (existsSync8(path6.join(dir, ".git")))
2656
+ if (existsSync11(path6.join(dir, ".git")))
2295
2657
  return dir;
2296
2658
  dir = path6.dirname(dir);
2297
2659
  }
@@ -2370,13 +2732,13 @@ codex_hooks = true
2370
2732
  }
2371
2733
  function writeCodexHook(metaUrl) {
2372
2734
  const configPath = getCodexConfigPath();
2373
- if (!existsSync8(path6.dirname(configPath))) {
2735
+ if (!existsSync11(path6.dirname(configPath))) {
2374
2736
  return { host: "codex", action: "not-detected", config_file: configPath };
2375
2737
  }
2376
2738
  try {
2377
2739
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2378
- const fileExistsBefore = existsSync8(configPath);
2379
- let content = fileExistsBefore ? readFileSync6(configPath, "utf8") : "";
2740
+ const fileExistsBefore = existsSync11(configPath);
2741
+ let content = fileExistsBefore ? readFileSync7(configPath, "utf8") : "";
2380
2742
  const previous = content;
2381
2743
  content = ensureCodexHooksFeature(content);
2382
2744
  if (!content.includes("unbrowse-update-hint.mjs")) {
@@ -2410,13 +2772,13 @@ command = ${JSON.stringify(command)}
2410
2772
  }
2411
2773
  function writeClaudeHook(metaUrl) {
2412
2774
  const settingsPath = getClaudeSettingsPath();
2413
- if (!existsSync8(path6.dirname(settingsPath))) {
2775
+ if (!existsSync11(path6.dirname(settingsPath))) {
2414
2776
  return { host: "claude", action: "not-detected", config_file: settingsPath };
2415
2777
  }
2416
2778
  try {
2417
2779
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2418
2780
  const command = `node "${hookScript}"`;
2419
- const fileExistsBefore = existsSync8(settingsPath);
2781
+ const fileExistsBefore = existsSync11(settingsPath);
2420
2782
  const settings = readJsonFile(settingsPath) ?? {};
2421
2783
  settings.hooks ??= {};
2422
2784
  settings.hooks.SessionStart ??= [];
@@ -2454,7 +2816,7 @@ function configureUpdateHintHooks(metaUrl, install) {
2454
2816
  function hasBinary(name) {
2455
2817
  const checker = process.platform === "win32" ? "where" : "which";
2456
2818
  try {
2457
- execFileSync2(checker, [name], { stdio: "ignore" });
2819
+ execFileSync3(checker, [name], { stdio: "ignore" });
2458
2820
  return true;
2459
2821
  } catch {
2460
2822
  return false;
@@ -2481,7 +2843,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
2481
2843
  return path7.join(cwd, ".opencode", "commands");
2482
2844
  }
2483
2845
  function detectOpenCode(cwd) {
2484
- return hasBinary("opencode") || existsSync9(path7.join(resolveConfigHome(), "opencode")) || existsSync9(path7.join(cwd, ".opencode"));
2846
+ return hasBinary("opencode") || existsSync12(path7.join(resolveConfigHome(), "opencode")) || existsSync12(path7.join(cwd, ".opencode"));
2485
2847
  }
2486
2848
  function renderOpenCodeCommand() {
2487
2849
  return `---
@@ -2509,12 +2871,12 @@ function writeOpenCodeCommand(scope, cwd) {
2509
2871
  if (scope === "auto" && !detected) {
2510
2872
  return { detected: false, action: "not-detected", scope: "off" };
2511
2873
  }
2512
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync9(path7.join(cwd, ".opencode")) ? "project" : "global";
2874
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync12(path7.join(cwd, ".opencode")) ? "project" : "global";
2513
2875
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
2514
- const commandFile = path7.join(ensureDir(commandsDir), "unbrowse.md");
2876
+ const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
2515
2877
  const content = renderOpenCodeCommand();
2516
- const action2 = existsSync9(commandFile) ? "updated" : "installed";
2517
- mkdirSync6(path7.dirname(commandFile), { recursive: true });
2878
+ const action2 = existsSync12(commandFile) ? "updated" : "installed";
2879
+ mkdirSync7(path7.dirname(commandFile), { recursive: true });
2518
2880
  writeFileSync5(commandFile, content);
2519
2881
  return {
2520
2882
  detected: detected || scope !== "auto",
@@ -2525,10 +2887,10 @@ function writeOpenCodeCommand(scope, cwd) {
2525
2887
  }
2526
2888
  async function ensureBrowserEngineInstalled() {
2527
2889
  const binary = findKuriBinary();
2528
- if (existsSync9(binary)) {
2890
+ if (existsSync12(binary)) {
2529
2891
  return { installed: true, action: "already-installed" };
2530
2892
  }
2531
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync9(path7.join(candidate, "build.zig")));
2893
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync12(path7.join(candidate, "build.zig")));
2532
2894
  if (!sourceDir) {
2533
2895
  return {
2534
2896
  installed: false,
@@ -2544,13 +2906,13 @@ async function ensureBrowserEngineInstalled() {
2544
2906
  };
2545
2907
  }
2546
2908
  try {
2547
- execFileSync2("zig", ["build", "-Doptimize=ReleaseFast"], {
2909
+ execFileSync3("zig", ["build", "-Doptimize=ReleaseFast"], {
2548
2910
  cwd: sourceDir,
2549
2911
  stdio: "inherit",
2550
2912
  timeout: 300000
2551
2913
  });
2552
2914
  const builtBinary = findKuriBinary();
2553
- if (existsSync9(builtBinary)) {
2915
+ if (existsSync12(builtBinary)) {
2554
2916
  return {
2555
2917
  installed: true,
2556
2918
  action: "installed",
@@ -2575,11 +2937,11 @@ async function runSetup(options) {
2575
2937
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
2576
2938
  const walletCheck = checkWalletConfigured();
2577
2939
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
2578
- const lobsterInstalled = hasBinary("lobstercash") || existsSync9(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2940
+ const lobsterInstalled = hasBinary("lobstercash") || existsSync12(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2579
2941
  if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
2580
2942
  console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
2581
2943
  try {
2582
- execFileSync2("npx", ["@crossmint/lobster-cli", "setup"], {
2944
+ execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
2583
2945
  stdio: "inherit",
2584
2946
  timeout: 60000
2585
2947
  });
@@ -2615,7 +2977,7 @@ async function runSetup(options) {
2615
2977
 
2616
2978
  // ../../src/runtime/update-hints.ts
2617
2979
  init_paths();
2618
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
2980
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
2619
2981
  import os5 from "node:os";
2620
2982
  import path8 from "node:path";
2621
2983
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
@@ -2628,20 +2990,20 @@ function getConfigDir3() {
2628
2990
  return process.env.UNBROWSE_CONFIG_DIR;
2629
2991
  return path8.join(getHomeDir2(), ".unbrowse");
2630
2992
  }
2631
- function ensureDir3(dir) {
2632
- if (!existsSync10(dir))
2633
- mkdirSync7(dir, { recursive: true });
2993
+ function ensureDir4(dir) {
2994
+ if (!existsSync13(dir))
2995
+ mkdirSync8(dir, { recursive: true });
2634
2996
  return dir;
2635
2997
  }
2636
2998
  function readJsonFile2(file) {
2637
2999
  try {
2638
- return JSON.parse(readFileSync7(file, "utf8"));
3000
+ return JSON.parse(readFileSync8(file, "utf8"));
2639
3001
  } catch {
2640
3002
  return null;
2641
3003
  }
2642
3004
  }
2643
3005
  function writeJsonFile2(file, value) {
2644
- ensureDir3(path8.dirname(file));
3006
+ ensureDir4(path8.dirname(file));
2645
3007
  writeFileSync6(file, `${JSON.stringify(value, null, 2)}
2646
3008
  `);
2647
3009
  }
@@ -2655,7 +3017,7 @@ function detectRepoRoot2(start2) {
2655
3017
  let dir = path8.resolve(start2);
2656
3018
  const root = path8.parse(dir).root;
2657
3019
  while (dir !== root) {
2658
- if (existsSync10(path8.join(dir, ".git")))
3020
+ if (existsSync13(path8.join(dir, ".git")))
2659
3021
  return dir;
2660
3022
  dir = path8.dirname(dir);
2661
3023
  }
@@ -2688,7 +3050,7 @@ function detectInstallHost2(repoRoot) {
2688
3050
  function getInstalledVersion(metaUrl) {
2689
3051
  const packageRoot = getPackageRoot(metaUrl);
2690
3052
  try {
2691
- const pkg = JSON.parse(readFileSync7(path8.join(packageRoot, "package.json"), "utf8"));
3053
+ const pkg = JSON.parse(readFileSync8(path8.join(packageRoot, "package.json"), "utf8"));
2692
3054
  return pkg.version ?? "unknown";
2693
3055
  } catch {
2694
3056
  return "unknown";
@@ -2789,6 +3151,7 @@ loadEnv({ quiet: true });
2789
3151
  loadEnv({ path: ".env.runtime", quiet: true });
2790
3152
  var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
2791
3153
  var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
3154
+ var walletNudgeShown = false;
2792
3155
  function parseArgs(argv) {
2793
3156
  const raw = argv.slice(2);
2794
3157
  const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : "help";
@@ -2935,6 +3298,14 @@ function formatSavedDuration(ms) {
2935
3298
  return `${(ms / 1000).toFixed(1)}s`;
2936
3299
  return `${ms}ms`;
2937
3300
  }
3301
+ function formatCostUsd(uc) {
3302
+ const usd = uc / 1e6;
3303
+ if (usd >= 1)
3304
+ return `$${usd.toFixed(2)}`;
3305
+ if (usd >= 0.01)
3306
+ return `$${usd.toFixed(3)}`;
3307
+ return `$${usd.toFixed(4)}`;
3308
+ }
2938
3309
  function emitImpactSummary(result) {
2939
3310
  const impact = result.impact;
2940
3311
  if (!impact)
@@ -2943,14 +3314,17 @@ function emitImpactSummary(result) {
2943
3314
  const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
2944
3315
  const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
2945
3316
  const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
3317
+ const costSavedUc = typeof impact.cost_saved_uc === "number" ? impact.cost_saved_uc : 0;
2946
3318
  const browserAvoided = impact.browser_avoided === true;
2947
- if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
3319
+ if (timeSavedMs <= 0 && tokensSaved <= 0 && costSavedUc <= 0 && !browserAvoided)
2948
3320
  return;
2949
3321
  const parts = [];
2950
3322
  if (timeSavedMs > 0)
2951
3323
  parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
2952
3324
  if (tokensSaved > 0)
2953
3325
  parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
3326
+ if (costSavedUc > 0)
3327
+ parts.push(`${formatCostUsd(costSavedUc)} saved`);
2954
3328
  if (browserAvoided)
2955
3329
  parts.push("browser avoided");
2956
3330
  info(parts.join(" \u2022 "));
@@ -3095,9 +3469,24 @@ async function cmdResolve(flags) {
3095
3469
  }
3096
3470
  result = slimTrace(result);
3097
3471
  emitImpactSummary(result);
3472
+ {
3473
+ const entry = impactFromResult("resolve", result, { intent, domain });
3474
+ if (entry)
3475
+ appendImpact(entry);
3476
+ }
3098
3477
  emitNextActionSummary(result);
3099
3478
  const skill = result.skill;
3100
3479
  const trace = result.trace;
3480
+ if (trace?.success && !walletNudgeShown) {
3481
+ try {
3482
+ const { checkWalletConfigured: checkWalletConfigured3 } = await Promise.resolve().then(() => (init_wallet2(), exports_wallet));
3483
+ const wallet = checkWalletConfigured3();
3484
+ if (!wallet.configured) {
3485
+ info("You're indexing routes but have no payout wallet. Run: npx @crossmint/lobster-cli setup");
3486
+ walletNudgeShown = true;
3487
+ }
3488
+ } catch (_e) {}
3489
+ }
3101
3490
  if (skill?.skill_id && trace) {
3102
3491
  result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
3103
3492
  }
@@ -3255,6 +3644,14 @@ async function cmdExecute(flags) {
3255
3644
  }
3256
3645
  result = slimTrace(result);
3257
3646
  emitImpactSummary(result);
3647
+ {
3648
+ const entry = impactFromResult("execute", result, {
3649
+ skill_id: skillId,
3650
+ endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : undefined
3651
+ });
3652
+ if (entry)
3653
+ appendImpact(entry);
3654
+ }
3258
3655
  emitNextActionSummary(result);
3259
3656
  const pathFlag = flags.path;
3260
3657
  const extractFlag = flags.extract;
@@ -3343,6 +3740,25 @@ async function cmdFeedback(flags) {
3343
3740
  body.diagnostics = JSON.parse(flags.diagnostics);
3344
3741
  output(await api2("POST", "/v1/feedback", body), !!flags.pretty);
3345
3742
  }
3743
+ async function cmdAnnotate(flags) {
3744
+ const skillId = flags.skill;
3745
+ const endpointId = flags.endpoint;
3746
+ if (!skillId || !endpointId)
3747
+ die("--skill and --endpoint are required");
3748
+ const body = {};
3749
+ if (flags.text) {
3750
+ body.annotations = [{ text: flags.text }];
3751
+ }
3752
+ if (flags.constraint) {
3753
+ const parts = flags.constraint.split(":");
3754
+ if (parts.length >= 3) {
3755
+ body.constraints = [{ param: parts[0], rule: parts[1], message: parts.slice(2).join(":") }];
3756
+ }
3757
+ }
3758
+ if (!body.annotations && !body.constraints)
3759
+ die("--text or --constraint required");
3760
+ output(await api2("POST", `/v1/skills/${skillId}/endpoints/${endpointId}/annotate`, body), !!flags.pretty);
3761
+ }
3346
3762
  async function cmdReview(flags) {
3347
3763
  const skillId = flags.skill;
3348
3764
  if (!skillId)
@@ -3481,6 +3897,16 @@ async function cmdSetup(flags) {
3481
3897
  info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
3482
3898
  }
3483
3899
  }
3900
+ if (report.wallet.configured) {
3901
+ info(`Wallet configured (${report.wallet.provider}): ${report.wallet.wallet_address ?? "linked"}`);
3902
+ } else if (report.wallet.lobster_installed) {
3903
+ info("Wallet not paired \u2014 your indexed routes won't earn payouts.");
3904
+ info("Run: npx @crossmint/lobster-cli setup");
3905
+ } else {
3906
+ info("No wallet configured \u2014 you're indexing routes for free.");
3907
+ info("Set up a wallet so you earn when agents use your routes:");
3908
+ info(" npx @crossmint/lobster-cli setup");
3909
+ }
3484
3910
  await recordInstallTelemetryEvent("setup", {
3485
3911
  hostType,
3486
3912
  status: report.browser_engine.action === "failed" ? "failed" : "installed",
@@ -3547,6 +3973,7 @@ var CLI_REFERENCE = {
3547
3973
  { name: "resolve", usage: '--intent "..." [--domain "..."] [--url "..."] [opts]', desc: "Search cached indexed/published routes and optionally execute the top trusted endpoint" },
3548
3974
  { name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
3549
3975
  { name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
3976
+ { name: "annotate", usage: "--skill ID --endpoint ID --text 'tip' [--constraint 'param:rule:message']", desc: "Contribute best practices or constraints for an endpoint" },
3550
3977
  { name: "review", usage: "--skill ID --endpoints '[...]'", desc: "Push reviewed descriptions/schema metadata back to a captured skill before publish" },
3551
3978
  { name: "index", usage: "--skill ID", desc: "Recompute local graph/contracts/export from cached skill state only" },
3552
3979
  { name: "publish", usage: "--skill ID [--confirm-publish] [--endpoints '[...]']", desc: "Re-index locally, inspect publish-review metadata, then publish/share from cached skill state" },
@@ -3574,7 +4001,8 @@ var CLI_REFERENCE = {
3574
4001
  { name: "back", usage: "[--session id]", desc: "Navigate back" },
3575
4002
  { name: "forward", usage: "[--session id]", desc: "Navigate forward" },
3576
4003
  { 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" }
4004
+ { name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" },
4005
+ { name: "stats", usage: "[--json] [--pretty]", desc: "Show lifetime time/tokens/cost saved and marketplace earnings/spending" }
3578
4006
  ],
3579
4007
  globalFlags: [
3580
4008
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -3615,6 +4043,131 @@ var CLI_REFERENCE = {
3615
4043
  `unbrowse publish --skill abc --endpoints '[{"endpoint_id":"def","description":"Search court judgments by keywords","action_kind":"search","resource_kind":"judgment"}]'`
3616
4044
  ]
3617
4045
  };
4046
+ function formatTotalDuration(ms) {
4047
+ if (ms >= 3600000)
4048
+ return `${(ms / 3600000).toFixed(1)}h`;
4049
+ if (ms >= 60000)
4050
+ return `${(ms / 60000).toFixed(1)}m`;
4051
+ if (ms >= 1000)
4052
+ return `${(ms / 1000).toFixed(1)}s`;
4053
+ return `${ms}ms`;
4054
+ }
4055
+ async function cmdStats(flags) {
4056
+ const pretty = !!flags.pretty;
4057
+ const jsonOnly = !!flags.json;
4058
+ const local = readImpactSummary();
4059
+ const agentId = getAgentId();
4060
+ let profile = null;
4061
+ let earnings = null;
4062
+ let spending = null;
4063
+ const remoteErrors = {};
4064
+ if (agentId) {
4065
+ const results = await Promise.allSettled([
4066
+ getMyProfile(),
4067
+ getCreatorEarnings(agentId),
4068
+ getTransactionHistory(agentId)
4069
+ ]);
4070
+ if (results[0].status === "fulfilled")
4071
+ profile = results[0].value;
4072
+ else
4073
+ remoteErrors.profile = results[0].reason?.message ?? String(results[0].reason);
4074
+ if (results[1].status === "fulfilled")
4075
+ earnings = results[1].value;
4076
+ else
4077
+ remoteErrors.earnings = results[1].reason?.message ?? String(results[1].reason);
4078
+ if (results[2].status === "fulfilled")
4079
+ spending = results[2].value;
4080
+ else
4081
+ remoteErrors.spending = results[2].reason?.message ?? String(results[2].reason);
4082
+ } else {
4083
+ remoteErrors.profile = "No agent_id in local config. Run `unbrowse setup` to register.";
4084
+ }
4085
+ const earnedUsd = earnings?.ledger?.total_earned_usd ?? 0;
4086
+ const spentUsd = spending?.ledger?.total_spent_usd ?? 0;
4087
+ const netUsd = earnedUsd - spentUsd;
4088
+ const savedUsd = local.total_cost_saved_uc / 1e6;
4089
+ const payload = {
4090
+ agent_id: agentId,
4091
+ profile,
4092
+ impact: {
4093
+ total_runs: local.total_runs,
4094
+ successful_runs: local.successful_runs,
4095
+ browser_avoided_runs: local.browser_avoided_runs,
4096
+ total_time_saved_ms: local.total_time_saved_ms,
4097
+ total_time_saved_human: formatTotalDuration(local.total_time_saved_ms),
4098
+ total_tokens_saved: local.total_tokens_saved,
4099
+ total_cost_saved_usd: Number(savedUsd.toFixed(6)),
4100
+ avg_time_saved_pct: local.avg_time_saved_pct,
4101
+ avg_tokens_saved_pct: local.avg_tokens_saved_pct,
4102
+ by_source: local.by_source,
4103
+ first_entry_at: local.first_entry_at,
4104
+ last_entry_at: local.last_entry_at,
4105
+ log_path: getImpactLogPath()
4106
+ },
4107
+ earnings: {
4108
+ total_earned_usd: earnedUsd,
4109
+ total_earned_uc: earnings?.ledger?.total_earned_uc ?? 0,
4110
+ transaction_count: earnings?.ledger?.transaction_count ?? 0,
4111
+ last_transaction_at: earnings?.ledger?.last_transaction_at ?? null
4112
+ },
4113
+ spending: {
4114
+ total_spent_usd: spentUsd,
4115
+ total_spent_uc: spending?.ledger?.total_spent_uc ?? 0,
4116
+ transaction_count: spending?.ledger?.transaction_count ?? 0,
4117
+ last_transaction_at: spending?.ledger?.last_transaction_at ?? null
4118
+ },
4119
+ net_usd: netUsd,
4120
+ ...Object.keys(remoteErrors).length > 0 ? { remote_errors: remoteErrors } : {}
4121
+ };
4122
+ if (jsonOnly) {
4123
+ output(payload, pretty);
4124
+ return;
4125
+ }
4126
+ const lines = [];
4127
+ lines.push("Unbrowse stats");
4128
+ lines.push(` agent_id: ${agentId ?? "(not registered \u2014 run `unbrowse setup`)"}`);
4129
+ if (profile?.name)
4130
+ lines.push(` name: ${profile.name}`);
4131
+ lines.push("");
4132
+ lines.push("Impact (local, this machine):");
4133
+ if (local.total_runs === 0) {
4134
+ lines.push(" No resolve/execute runs recorded yet.");
4135
+ lines.push(` Log file: ${getImpactLogPath()}`);
4136
+ } else {
4137
+ lines.push(` Runs: ${local.total_runs} (${local.successful_runs} successful, ${local.browser_avoided_runs} browser-avoided)`);
4138
+ if (local.total_time_saved_ms > 0) {
4139
+ lines.push(` Time saved: ${formatTotalDuration(local.total_time_saved_ms)} (avg ${local.avg_time_saved_pct}% faster)`);
4140
+ }
4141
+ if (local.total_tokens_saved > 0) {
4142
+ lines.push(` Tokens saved: ${local.total_tokens_saved.toLocaleString("en-US")} (avg ${local.avg_tokens_saved_pct}% less context)`);
4143
+ }
4144
+ if (savedUsd > 0) {
4145
+ lines.push(` Cost saved: ${formatCostUsd(local.total_cost_saved_uc)}`);
4146
+ }
4147
+ if (Object.keys(local.by_source).length > 0) {
4148
+ const topSources = Object.entries(local.by_source).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k}=${v}`).join(", ");
4149
+ lines.push(` By source: ${topSources}`);
4150
+ }
4151
+ }
4152
+ lines.push("");
4153
+ lines.push("Money (backend ledger):");
4154
+ if (!agentId) {
4155
+ lines.push(" (not registered \u2014 earnings/spending unavailable)");
4156
+ } else if (remoteErrors.earnings || remoteErrors.spending) {
4157
+ if (remoteErrors.earnings)
4158
+ lines.push(` earnings: error \u2014 ${remoteErrors.earnings}`);
4159
+ if (remoteErrors.spending)
4160
+ lines.push(` spending: error \u2014 ${remoteErrors.spending}`);
4161
+ } else {
4162
+ lines.push(` Earned: $${earnedUsd.toFixed(4)} (${earnings?.ledger?.transaction_count ?? 0} payouts)`);
4163
+ lines.push(` Spent: $${spentUsd.toFixed(4)} (${spending?.ledger?.transaction_count ?? 0} payments)`);
4164
+ lines.push(` Net: ${netUsd >= 0 ? "+" : ""}$${netUsd.toFixed(4)}`);
4165
+ }
4166
+ lines.push("");
4167
+ info(lines.join(`
4168
+ `));
4169
+ output(payload, pretty);
4170
+ }
3618
4171
  function printHelp() {
3619
4172
  const r = CLI_REFERENCE;
3620
4173
  const lines = ["unbrowse \u2014 shell-safe CLI for the local API", ""];
@@ -4036,6 +4589,8 @@ async function main() {
4036
4589
  return cmdUpgrade(flags);
4037
4590
  if (command === "connect-chrome")
4038
4591
  return cmdConnectChrome();
4592
+ if (command === "stats")
4593
+ return cmdStats(flags);
4039
4594
  const KNOWN_COMMANDS = new Set([
4040
4595
  "health",
4041
4596
  "mcp",
@@ -4045,6 +4600,7 @@ async function main() {
4045
4600
  "exec",
4046
4601
  "feedback",
4047
4602
  "fb",
4603
+ "annotate",
4048
4604
  "review",
4049
4605
  "index",
4050
4606
  "publish",
@@ -4079,7 +4635,8 @@ async function main() {
4079
4635
  "forward",
4080
4636
  "sync",
4081
4637
  "close",
4082
- "connect-chrome"
4638
+ "connect-chrome",
4639
+ "stats"
4083
4640
  ]);
4084
4641
  if (!KNOWN_COMMANDS.has(command)) {
4085
4642
  const pack = findSitePack(command);
@@ -4115,6 +4672,8 @@ async function main() {
4115
4672
  case "feedback":
4116
4673
  case "fb":
4117
4674
  return cmdFeedback(flags);
4675
+ case "annotate":
4676
+ return cmdAnnotate(flags);
4118
4677
  case "review":
4119
4678
  return cmdReview(flags);
4120
4679
  case "index":
@@ -4175,6 +4734,8 @@ async function main() {
4175
4734
  return cmdClose(flags);
4176
4735
  case "connect-chrome":
4177
4736
  return cmdConnectChrome();
4737
+ case "stats":
4738
+ return cmdStats(flags);
4178
4739
  default:
4179
4740
  info(`Unknown command: ${command}`);
4180
4741
  printHelp();