unbrowse 2.9.0 → 2.10.1

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
@@ -30,6 +30,83 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
30
  var __promiseAll = (args) => Promise.all(args);
31
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
32
32
 
33
+ // ../../src/payments/cascade.ts
34
+ import { createHash } from "crypto";
35
+ import bs58 from "bs58";
36
+ function payableContributors(skill) {
37
+ return (skill.contributors ?? []).filter((c) => !!c.wallet_address?.trim());
38
+ }
39
+ function cascadeLabel(skillId) {
40
+ const digest = createHash("sha256").update(skillId).digest("hex");
41
+ return `ubr-${digest.slice(0, 23)}`;
42
+ }
43
+ function decodeSecretKey(raw) {
44
+ const trimmed = raw.trim();
45
+ if (!trimmed)
46
+ throw new Error("empty signer secret");
47
+ if (trimmed.startsWith("[")) {
48
+ return Uint8Array.from(JSON.parse(trimmed));
49
+ }
50
+ if (/^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed)) {
51
+ return Uint8Array.from(bs58.decode(trimmed));
52
+ }
53
+ return Uint8Array.from(Buffer.from(trimmed, "base64"));
54
+ }
55
+ function recipientsForSkill(skill, platformWallet) {
56
+ return [
57
+ { address: platformWallet, share: 10 },
58
+ ...payableContributors(skill).map((c) => ({
59
+ address: c.wallet_address.trim(),
60
+ share: c.share
61
+ }))
62
+ ];
63
+ }
64
+ async function ensureCascadeSplitForSkill(skill, deps = {}) {
65
+ const env = deps.env ?? process.env;
66
+ if (skill.split_config?.trim()) {
67
+ return { split_config: skill.split_config.trim(), source: "existing" };
68
+ }
69
+ const contributors = payableContributors(skill);
70
+ if (contributors.length <= 1)
71
+ return {};
72
+ const explicitSplitConfig = env.UNBROWSE_CASCADE_SPLIT_ADDRESS?.trim() || env.UNBROWSE_CASCADE_SPLIT_CONFIG?.trim();
73
+ if (explicitSplitConfig) {
74
+ return { split_config: explicitSplitConfig, source: "env" };
75
+ }
76
+ const platformWallet = env.UNBROWSE_CASCADE_PLATFORM_WALLET?.trim() || env.PAYMENT_RECIPIENT?.trim();
77
+ const secretKey = env.UNBROWSE_CASCADE_SIGNER_SECRET_KEY?.trim();
78
+ const rpcUrl = env.UNBROWSE_CASCADE_RPC_URL?.trim();
79
+ const rpcWsUrl = env.UNBROWSE_CASCADE_RPC_WS_URL?.trim();
80
+ if (!platformWallet || !secretKey || !rpcUrl || !rpcWsUrl) {
81
+ return {
82
+ warning: "cascade_split_not_configured"
83
+ };
84
+ }
85
+ const loadSdk = deps.loadSdk ?? (async () => await import("@cascade-fyi/splits-sdk"));
86
+ const loadKit = deps.loadKit ?? (async () => await import("@solana/kit"));
87
+ const [sdk, kit] = await Promise.all([loadSdk(), loadKit()]);
88
+ const signer = await kit.createKeyPairSignerFromBytes(decodeSecretKey(secretKey));
89
+ const splits = sdk.createSplitsClient({
90
+ rpc: kit.createSolanaRpc(rpcUrl),
91
+ rpcSubscriptions: kit.createSolanaRpcSubscriptions(rpcWsUrl),
92
+ signer
93
+ });
94
+ const result = await splits.ensureSplit({
95
+ recipients: recipientsForSkill(skill, platformWallet),
96
+ uniqueId: sdk.labelToSeed(cascadeLabel(skill.skill_id))
97
+ });
98
+ if (result.status === "created" || result.status === "updated" || result.status === "no_change") {
99
+ return {
100
+ split_config: result.splitConfig,
101
+ source: "sdk"
102
+ };
103
+ }
104
+ return {
105
+ warning: result.message || `cascade_split_${result.status}`
106
+ };
107
+ }
108
+ var init_cascade = () => {};
109
+
33
110
  // ../../src/runtime/lifecycle.ts
34
111
  function attributeLifecycle(events) {
35
112
  const totals = new Map;
@@ -501,11 +578,75 @@ async function getCookies(tabId) {
501
578
  const raw = await kuriGet("/cookies", { tab_id: tabId });
502
579
  return raw?.result?.cookies ?? [];
503
580
  }
581
+ async function setCookieViaCDP(wsUrl, cookie) {
582
+ return new Promise((resolve) => {
583
+ const timer = setTimeout(() => {
584
+ resolve(false);
585
+ }, 3000);
586
+ try {
587
+ const ws = new (__require("ws"))(wsUrl);
588
+ ws.on("open", () => {
589
+ ws.send(JSON.stringify({
590
+ id: 1,
591
+ method: "Network.setCookie",
592
+ params: {
593
+ ...cookie,
594
+ url: `https://${cookie.domain.replace(/^\./, "")}/`
595
+ }
596
+ }));
597
+ });
598
+ ws.on("message", (data) => {
599
+ clearTimeout(timer);
600
+ try {
601
+ const msg = JSON.parse(data.toString());
602
+ if (msg.id === 1) {
603
+ ws.close();
604
+ resolve(msg.result?.success ?? false);
605
+ }
606
+ } catch {
607
+ ws.close();
608
+ resolve(false);
609
+ }
610
+ });
611
+ ws.on("error", () => {
612
+ clearTimeout(timer);
613
+ resolve(false);
614
+ });
615
+ } catch {
616
+ clearTimeout(timer);
617
+ resolve(false);
618
+ }
619
+ });
620
+ }
504
621
  async function setCookie(tabId, cookie) {
622
+ const value = cookie.value.replace(/^"|"$/g, "");
623
+ if (cookie.secure || cookie.httpOnly) {
624
+ try {
625
+ const res = await fetch("http://127.0.0.1:9222/json", { signal: AbortSignal.timeout(1000) }).catch(() => null);
626
+ if (res?.ok) {
627
+ const pages = await res.json();
628
+ const page = pages.find((p) => p.id === tabId);
629
+ if (page?.webSocketDebuggerUrl) {
630
+ const success = await setCookieViaCDP(page.webSocketDebuggerUrl, {
631
+ name: cookie.name,
632
+ value,
633
+ domain: cookie.domain,
634
+ path: cookie.path || "/",
635
+ secure: cookie.secure ?? false,
636
+ httpOnly: cookie.httpOnly ?? false,
637
+ sameSite: cookie.sameSite || "Lax",
638
+ ...cookie.expires && cookie.expires > 0 ? { expires: cookie.expires } : {}
639
+ });
640
+ if (success)
641
+ return;
642
+ }
643
+ }
644
+ } catch {}
645
+ }
505
646
  await kuriGet("/cookies", {
506
647
  tab_id: tabId,
507
648
  name: cookie.name,
508
- value: cookie.value,
649
+ value,
509
650
  domain: cookie.domain,
510
651
  ...cookie.path ? { path: cookie.path } : {}
511
652
  });
@@ -4581,6 +4722,8 @@ __export(exports_client2, {
4581
4722
  validateManifest: () => validateManifest,
4582
4723
  updateEndpointScore: () => updateEndpointScore,
4583
4724
  updateEndpointSchema: () => updateEndpointSchema,
4725
+ syncAgentWallet: () => syncAgentWallet2,
4726
+ setSkillSplitConfig: () => setSkillSplitConfig,
4584
4727
  setSkillPrice: () => setSkillPrice,
4585
4728
  searchIntentResolve: () => searchIntentResolve,
4586
4729
  searchIntentInDomain: () => searchIntentInDomain,
@@ -4592,10 +4735,12 @@ __export(exports_client2, {
4592
4735
  recordFeedback: () => recordFeedback,
4593
4736
  recordExecution: () => recordExecution,
4594
4737
  recordDiagnostics: () => recordDiagnostics,
4738
+ recordAnalyticsSession: () => recordAnalyticsSession,
4595
4739
  publishSkill: () => publishSkill,
4596
4740
  publishGraphEdges: () => publishGraphEdges,
4597
4741
  normalizeAgentEmail: () => normalizeAgentEmail2,
4598
4742
  listSkills: () => listSkills,
4743
+ isX402Error: () => isX402Error,
4599
4744
  isValidAgentEmail: () => isValidAgentEmail2,
4600
4745
  isLocalOnlyMode: () => isLocalOnlyMode,
4601
4746
  hashApiKey: () => hashApiKey,
@@ -4603,7 +4748,7 @@ __export(exports_client2, {
4603
4748
  getSkillChunk: () => getSkillChunk2,
4604
4749
  getSkill: () => getSkill,
4605
4750
  getRecentLocalSkill: () => getRecentLocalSkill,
4606
- getMyProfile: () => getMyProfile,
4751
+ getMyProfile: () => getMyProfile2,
4607
4752
  getEndpointSchema: () => getEndpointSchema,
4608
4753
  getCreatorEarnings: () => getCreatorEarnings,
4609
4754
  getApiKey: () => getApiKey2,
@@ -4621,7 +4766,7 @@ __export(exports_client2, {
4621
4766
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
4622
4767
  import { join as join3 } from "path";
4623
4768
  import { homedir as homedir2, hostname as hostname2 } from "os";
4624
- import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
4769
+ import { randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
4625
4770
  import { createInterface as createInterface2 } from "readline";
4626
4771
  function decodeBase64Json2(value) {
4627
4772
  try {
@@ -4638,6 +4783,9 @@ function decodeBase64Json2(value) {
4638
4783
  return;
4639
4784
  }
4640
4785
  }
4786
+ function isX402Error(err) {
4787
+ return !!err && typeof err === "object" && err.x402 === true;
4788
+ }
4641
4789
  function scopedSkillKey(skillId, scopeId) {
4642
4790
  return scopeId ? `${scopeId}:${skillId}` : skillId;
4643
4791
  }
@@ -4690,6 +4838,20 @@ function resolveAgentName2(preferredEmail, fallbackName) {
4690
4838
  const normalized = normalizeAgentEmail2(preferredEmail ?? "");
4691
4839
  return isValidAgentEmail2(normalized) ? normalized : fallbackName;
4692
4840
  }
4841
+ function getLocalWalletContext2() {
4842
+ const lobsterWallet = process.env.LOBSTER_WALLET_ADDRESS?.trim();
4843
+ if (lobsterWallet) {
4844
+ return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
4845
+ }
4846
+ const genericWallet = process.env.AGENT_WALLET_ADDRESS?.trim();
4847
+ if (genericWallet) {
4848
+ return {
4849
+ wallet_address: genericWallet,
4850
+ wallet_provider: process.env.AGENT_WALLET_PROVIDER?.trim() || undefined
4851
+ };
4852
+ }
4853
+ return {};
4854
+ }
4693
4855
  function getApiKey2() {
4694
4856
  if (LOCAL_ONLY2)
4695
4857
  return "local-only";
@@ -4705,7 +4867,7 @@ function getApiKey2() {
4705
4867
  function hashApiKey(key) {
4706
4868
  if (!key || key === "local-only")
4707
4869
  return "";
4708
- return createHash2("sha256").update(key).digest("hex");
4870
+ return createHash3("sha256").update(key).digest("hex");
4709
4871
  }
4710
4872
  function getAgentId() {
4711
4873
  const config = loadConfig2();
@@ -4768,7 +4930,7 @@ async function findUsableApiKey2() {
4768
4930
  }
4769
4931
  return null;
4770
4932
  }
4771
- async function api2(method, path4, body, opts) {
4933
+ async function apiRequest2(method, path4, body, opts) {
4772
4934
  const key = opts?.noAuth ? "" : getApiKey2();
4773
4935
  const controller = new AbortController;
4774
4936
  const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? API_TIMEOUT_MS2);
@@ -4814,6 +4976,10 @@ async function api2(method, path4, body, opts) {
4814
4976
  const msg = errData.details?.length ? `${errData.error}: ${errData.details.join("; ")}` : errData.error ?? `API HTTP ${res.status}`;
4815
4977
  throw new Error(msg);
4816
4978
  }
4979
+ return { data, headers: res.headers };
4980
+ }
4981
+ async function api2(method, path4, body, opts) {
4982
+ const { data } = await apiRequest2(method, path4, body, opts);
4817
4983
  return data;
4818
4984
  }
4819
4985
  async function promptTosAcceptance2(summary, tosUrl) {
@@ -4911,6 +5077,13 @@ async function ensureRegistered2(options) {
4911
5077
  console.log("[unbrowse] Restored saved registration.");
4912
5078
  }
4913
5079
  await checkTosStatus2();
5080
+ try {
5081
+ const profile = await getMyProfile2();
5082
+ const wallet = getLocalWalletContext2();
5083
+ if (wallet.wallet_address && profile.wallet_address !== wallet.wallet_address) {
5084
+ await syncAgentWallet2(wallet);
5085
+ }
5086
+ } catch {}
4914
5087
  return;
4915
5088
  }
4916
5089
  let tosInfo;
@@ -4930,7 +5103,8 @@ async function ensureRegistered2(options) {
4930
5103
  const name = options?.promptForEmail ? await promptAgentEmail2(fallbackName) : resolveAgentName2(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
4931
5104
  console.log(`Registering as "${name}"...`);
4932
5105
  try {
4933
- const { agent_id, api_key } = await api2("POST", "/v1/agents/register", { name, tos_version: tosInfo.version });
5106
+ const wallet = getLocalWalletContext2();
5107
+ const { agent_id, api_key } = await api2("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet });
4934
5108
  process.env.UNBROWSE_API_KEY = api_key;
4935
5109
  saveConfig2({
4936
5110
  api_key,
@@ -4938,7 +5112,8 @@ async function ensureRegistered2(options) {
4938
5112
  agent_name: name,
4939
5113
  registered_at: new Date().toISOString(),
4940
5114
  tos_accepted_version: tosInfo.version,
4941
- tos_accepted_at: new Date().toISOString()
5115
+ tos_accepted_at: new Date().toISOString(),
5116
+ ...wallet
4942
5117
  });
4943
5118
  console.log(`Registered as ${name}. API key saved to ~/.unbrowse/config.json`);
4944
5119
  } catch (err) {
@@ -5094,7 +5269,27 @@ async function publishSkill(draft) {
5094
5269
  }
5095
5270
  if (LOCAL_ONLY2)
5096
5271
  throw new Error("local-only mode");
5097
- return api2("POST", "/v1/skills", draft, { timeoutMs: PUBLISH_TIMEOUT_MS2 });
5272
+ const wallet = getLocalWalletContext2();
5273
+ const published = await api2("POST", "/v1/skills", {
5274
+ ...draft,
5275
+ ...wallet.wallet_address ? wallet : {}
5276
+ }, { timeoutMs: PUBLISH_TIMEOUT_MS2 });
5277
+ const cascade = await ensureCascadeSplitForSkill(published).catch((err) => ({
5278
+ warning: `cascade_split_failed:${err.message}`
5279
+ }));
5280
+ const warnings = [...published.warnings ?? []];
5281
+ if (cascade.warning)
5282
+ warnings.push(cascade.warning);
5283
+ if (cascade.split_config && cascade.split_config !== published.split_config) {
5284
+ const updated = await api2("PATCH", `/v1/skills/${published.skill_id}`, {
5285
+ split_config: cascade.split_config
5286
+ });
5287
+ return {
5288
+ ...updated,
5289
+ warnings
5290
+ };
5291
+ }
5292
+ return { ...published, warnings };
5098
5293
  }
5099
5294
  async function deprecateSkill(skillId) {
5100
5295
  if (LOCAL_ONLY2)
@@ -5136,16 +5331,29 @@ async function searchIntentResolve(intent, domain, domainK = 5, globalK = 10) {
5136
5331
  if (LOCAL_ONLY2)
5137
5332
  return { domain_results: [], global_results: [], skipped_global: false };
5138
5333
  try {
5139
- return await api2("POST", "/v1/search/resolve", {
5334
+ const { data, headers } = await apiRequest2("POST", "/v1/search/resolve", {
5140
5335
  intent,
5141
5336
  domain,
5142
5337
  domain_k: domainK,
5143
5338
  global_k: globalK
5144
5339
  });
5145
- } catch {
5340
+ const actualCostHeader = headers.get("X-Unbrowse-Cost-Uc");
5341
+ const actualCostUc = actualCostHeader && /^\d+$/.test(actualCostHeader) ? Number(actualCostHeader) : undefined;
5342
+ return actualCostUc != null ? { ...data, actual_cost_uc: actualCostUc } : data;
5343
+ } catch (err) {
5344
+ if (isX402Error(err))
5345
+ throw err;
5146
5346
  const [domain_results, global_results] = await Promise.all([
5147
- domain ? searchIntentInDomain(intent, domain, domainK).catch(() => []) : Promise.resolve([]),
5148
- searchIntent(intent, globalK).catch(() => [])
5347
+ domain ? searchIntentInDomain(intent, domain, domainK).catch((fallbackErr) => {
5348
+ if (isX402Error(fallbackErr))
5349
+ throw fallbackErr;
5350
+ return [];
5351
+ }) : Promise.resolve([]),
5352
+ searchIntent(intent, globalK).catch((fallbackErr) => {
5353
+ if (isX402Error(fallbackErr))
5354
+ throw fallbackErr;
5355
+ return [];
5356
+ })
5149
5357
  ]);
5150
5358
  return { domain_results, global_results, skipped_global: false };
5151
5359
  }
@@ -5210,6 +5418,11 @@ async function recordOrchestrationPerf(timing) {
5210
5418
  const phaseTotals = Object.fromEntries(attributeLifecycle(events));
5211
5419
  await api2("POST", "/v1/stats/perf", { ...timing, phase_totals_ms: phaseTotals });
5212
5420
  }
5421
+ async function recordAnalyticsSession(session) {
5422
+ if (LOCAL_ONLY2)
5423
+ return;
5424
+ await api2("POST", "/v1/analytics/sessions", session);
5425
+ }
5213
5426
  async function validateManifest(manifest) {
5214
5427
  if (LOCAL_ONLY2)
5215
5428
  return { valid: true, hardErrors: [], softWarnings: [] };
@@ -5264,8 +5477,8 @@ async function verifyMarketplaceDiscovery(skillId, intent, maxWaitMs = 60000) {
5264
5477
  }
5265
5478
  return { found: false, latency_ms: Date.now() - start2 };
5266
5479
  }
5267
- async function registerAgent(name) {
5268
- return api2("POST", "/v1/agents/register", { name });
5480
+ async function registerAgent(name, wallet = getLocalWalletContext2()) {
5481
+ return api2("POST", "/v1/agents/register", { name, ...wallet });
5269
5482
  }
5270
5483
  async function getAgent(agentId) {
5271
5484
  try {
@@ -5274,9 +5487,18 @@ async function getAgent(agentId) {
5274
5487
  return null;
5275
5488
  }
5276
5489
  }
5277
- async function getMyProfile() {
5490
+ async function getMyProfile2() {
5278
5491
  return api2("GET", "/v1/agents/me", undefined);
5279
5492
  }
5493
+ async function syncAgentWallet2(wallet = getLocalWalletContext2()) {
5494
+ if (!wallet.wallet_address)
5495
+ return;
5496
+ await api2("POST", "/v1/agents/wallet", wallet);
5497
+ const config = loadConfig2();
5498
+ if (!config)
5499
+ return;
5500
+ saveConfig2({ ...config, ...wallet });
5501
+ }
5280
5502
  async function getTransactionHistory(agentId) {
5281
5503
  return api2("GET", `/v1/transactions/consumer/${agentId}`);
5282
5504
  }
@@ -5286,8 +5508,12 @@ async function getCreatorEarnings(agentId) {
5286
5508
  async function setSkillPrice(skillId, priceUsd) {
5287
5509
  return api2("PATCH", `/v1/skills/${skillId}`, { base_price_usd: priceUsd });
5288
5510
  }
5511
+ async function setSkillSplitConfig(skillId, splitConfig) {
5512
+ return api2("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
5513
+ }
5289
5514
  var API_URL2, PROFILE_NAME2, recentLocalSkills2, LOCAL_ONLY2, EMAIL_RE2, API_TIMEOUT_MS2, PUBLISH_TIMEOUT_MS2;
5290
5515
  var init_client2 = __esm(() => {
5516
+ init_cascade();
5291
5517
  API_URL2 = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
5292
5518
  PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? "");
5293
5519
  recentLocalSkills2 = new Map;
@@ -5371,7 +5597,7 @@ var init_marketplace = __esm(() => {
5371
5597
  });
5372
5598
 
5373
5599
  // ../../src/version.ts
5374
- import { createHash as createHash3 } from "crypto";
5600
+ import { createHash as createHash4 } from "crypto";
5375
5601
  import { readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
5376
5602
  import { dirname, join as join4 } from "path";
5377
5603
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -5391,7 +5617,7 @@ function computeCodeHash() {
5391
5617
  try {
5392
5618
  const srcDir = join4(MODULE_DIR, ".");
5393
5619
  const files = collectTsFiles(srcDir).sort();
5394
- const hash = createHash3("sha256");
5620
+ const hash = createHash4("sha256");
5395
5621
  for (const file of files) {
5396
5622
  hash.update(file.slice(srcDir.length));
5397
5623
  hash.update(readFileSync3(file, "utf-8"));
@@ -5413,7 +5639,7 @@ var init_version = __esm(() => {
5413
5639
  });
5414
5640
 
5415
5641
  // ../../src/telemetry.ts
5416
- import { createHash as createHash4 } from "node:crypto";
5642
+ import { createHash as createHash5 } from "node:crypto";
5417
5643
  import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
5418
5644
  import { join as join5 } from "node:path";
5419
5645
  function getTraceDir() {
@@ -5430,7 +5656,7 @@ function safeBindingNames(bindings) {
5430
5656
  }
5431
5657
  function hashResponseBody(result) {
5432
5658
  const str = typeof result === "string" ? result : JSON.stringify(result ?? "");
5433
- return createHash4("sha256").update(str).digest("hex").slice(0, 32);
5659
+ return createHash5("sha256").update(str).digest("hex").slice(0, 32);
5434
5660
  }
5435
5661
  function classifyFailure(error) {
5436
5662
  if (!error)
@@ -6243,6 +6469,10 @@ function filterExpired(cookies) {
6243
6469
  });
6244
6470
  }
6245
6471
  async function getStoredAuth(domain) {
6472
+ const bundle = await getStoredAuthBundle(domain);
6473
+ return bundle?.cookies?.length ? bundle.cookies : null;
6474
+ }
6475
+ async function getStoredAuthBundle(domain) {
6246
6476
  const regDomain = getRegistrableDomain(domain);
6247
6477
  const keysToTry = [`auth:${regDomain}`];
6248
6478
  if (domain !== regDomain)
@@ -6253,11 +6483,9 @@ async function getStoredAuth(domain) {
6253
6483
  continue;
6254
6484
  try {
6255
6485
  const parsed = JSON.parse(stored);
6256
- const cookies = parsed.cookies;
6257
- if (!cookies || cookies.length === 0)
6258
- continue;
6486
+ const cookies = parsed.cookies ?? [];
6259
6487
  const valid = filterExpired(cookies);
6260
- if (valid.length === 0) {
6488
+ if (cookies.length > 0 && valid.length === 0 && Object.keys(parsed.headers ?? {}).length === 0) {
6261
6489
  log("auth", `all ${cookies.length} cookies for ${domain} (key: ${key}) are expired — deleting`);
6262
6490
  await deleteCredential(key);
6263
6491
  continue;
@@ -6265,7 +6493,12 @@ async function getStoredAuth(domain) {
6265
6493
  if (valid.length < cookies.length) {
6266
6494
  log("auth", `filtered ${cookies.length - valid.length} expired cookies for ${domain}`);
6267
6495
  }
6268
- return valid;
6496
+ return {
6497
+ cookies: valid,
6498
+ headers: parsed.headers ?? {},
6499
+ source_keys: parsed.source_keys ?? [],
6500
+ source_meta: parsed.source_meta ?? null
6501
+ };
6269
6502
  } catch {
6270
6503
  continue;
6271
6504
  }
@@ -11346,7 +11579,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
11346
11579
  } catch {}
11347
11580
  }
11348
11581
  recordExecution(skill.skill_id, endpoint.endpoint_id, trace, skill).catch(() => {});
11349
- if (trace.success && skill.indexer_id && skill.base_price_usd && skill.base_price_usd > 0) {
11582
+ if (trace.success && options?.payment_verified === true && skill.base_price_usd && skill.base_price_usd > 0) {
11350
11583
  const consumerConfig = (() => {
11351
11584
  try {
11352
11585
  return JSON.parse(__require("fs").readFileSync(__require("os").homedir() + "/.unbrowse/config.json", "utf-8"));
@@ -12656,7 +12889,7 @@ var init_prefetch = __esm(async () => {
12656
12889
  });
12657
12890
 
12658
12891
  // ../../src/orchestrator/first-pass-action.ts
12659
- import { createHash as createHash5 } from "node:crypto";
12892
+ import { createHash as createHash6 } from "node:crypto";
12660
12893
  function classifyIntent(intent) {
12661
12894
  if (/\b(search|find|discover|look\s+for|browse)\b/i.test(intent))
12662
12895
  return "search";
@@ -12703,7 +12936,7 @@ function filterJsonApiEntries(entries) {
12703
12936
  function synthesizeSkillFromIntercepted(interceptedEntries, domain, intent) {
12704
12937
  if (interceptedEntries.length === 0)
12705
12938
  return;
12706
- const hash = createHash5("sha1").update(interceptedEntries.map((e) => e.request.url).join("|")).digest("hex").slice(0, 8);
12939
+ const hash = createHash6("sha1").update(interceptedEntries.map((e) => e.request.url).join("|")).digest("hex").slice(0, 8);
12707
12940
  const endpoints = interceptedEntries.map((entry, i) => {
12708
12941
  const parsed = new URL(entry.request.url);
12709
12942
  return {
@@ -12927,7 +13160,7 @@ function checkWalletConfigured() {
12927
13160
  import { nanoid as nanoid7 } from "nanoid";
12928
13161
  import { existsSync as existsSync9, writeFileSync as writeFileSync7, readFileSync as readFileSync6, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "node:fs";
12929
13162
  import { dirname as dirname2, join as join9 } from "node:path";
12930
- import { createHash as createHash6 } from "node:crypto";
13163
+ import { createHash as createHash7 } from "node:crypto";
12931
13164
  function summarizeSchema(schema, maxDepth = 3) {
12932
13165
  function walk(s, depth) {
12933
13166
  if (depth <= 0)
@@ -13042,7 +13275,7 @@ function scopedResolveCacheKeys(scope, key) {
13042
13275
  return scope === "global" ? [scopedCacheKey("global", key)] : [scopedCacheKey(scope, key), scopedCacheKey("global", key)];
13043
13276
  }
13044
13277
  function snapshotPathForCacheKey(cacheKey) {
13045
- const digest = createHash6("sha1").update(cacheKey).digest("hex");
13278
+ const digest = createHash7("sha1").update(cacheKey).digest("hex");
13046
13279
  return join9(SKILL_SNAPSHOT_DIR, `${digest}.json`);
13047
13280
  }
13048
13281
  function writeSkillSnapshot(cacheKey, skill) {
@@ -14146,6 +14379,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14146
14379
  response_bytes: 0,
14147
14380
  time_saved_pct: 0,
14148
14381
  tokens_saved_pct: 0,
14382
+ actual_total_ms: 0,
14149
14383
  trace_version: TRACE_VERSION
14150
14384
  };
14151
14385
  const decisionTrace = {
@@ -14181,6 +14415,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14181
14415
  }
14182
14416
  function finalize(source, result2, skillId, skill, trace2) {
14183
14417
  timing.total_ms = Date.now() - t0;
14418
+ timing.actual_total_ms = timing.total_ms;
14184
14419
  timing.source = source;
14185
14420
  timing.skill_id = skillId;
14186
14421
  const resultStr = typeof result2 === "string" ? result2 : JSON.stringify(result2 ?? "");
@@ -14189,11 +14424,20 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14189
14424
  const cost = skill?.discovery_cost;
14190
14425
  const baselineTokens = cost?.capture_tokens ?? DEFAULT_CAPTURE_TOKENS;
14191
14426
  const baselineMs = cost?.capture_ms ?? DEFAULT_CAPTURE_MS;
14427
+ const paidSearchUc = timing.paid_search_uc ?? 0;
14428
+ const paidExecutionUc = timing.paid_execution_uc ?? 0;
14429
+ const totalActualCostUc = paidSearchUc + paidExecutionUc;
14430
+ if (totalActualCostUc > 0)
14431
+ timing.actual_cost_uc = totalActualCostUc;
14192
14432
  if (source === "marketplace" || source === "route-cache" || source === "first-pass") {
14193
14433
  timing.tokens_saved = Math.max(0, baselineTokens - responseTokens);
14194
14434
  timing.tokens_saved_pct = baselineTokens > 0 ? Math.round(timing.tokens_saved / baselineTokens * 100) : 0;
14195
14435
  timing.time_saved_pct = baselineMs > 0 ? Math.round(Math.max(0, baselineMs - timing.total_ms) / baselineMs * 100) : 0;
14196
14436
  }
14437
+ if (cost?.capture_ms != null) {
14438
+ timing.baseline_total_ms = cost.capture_ms;
14439
+ timing.time_saved_ms = Math.max(0, cost.capture_ms - timing.total_ms);
14440
+ }
14197
14441
  if (trace2) {
14198
14442
  trace2.tokens_used = responseTokens;
14199
14443
  trace2.tokens_saved = timing.tokens_saved;
@@ -15053,17 +15297,54 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15053
15297
  const MARKETPLACE_TIMEOUT_MS = context?.url ? 5000 : 30000;
15054
15298
  if (!forceCapture) {
15055
15299
  const ts0 = Date.now();
15056
- const { domain_results: domainResults, global_results: globalResults } = await Promise.race([
15057
- searchIntentResolve(queryIntent, requestedDomain ?? undefined, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K),
15058
- new Promise((resolve) => setTimeout(() => {
15059
- console.log(`[marketplace] timeout after ${MARKETPLACE_TIMEOUT_MS}ms falling through to browser`);
15060
- resolve({ domain_results: [], global_results: [], skipped_global: true });
15061
- }, MARKETPLACE_TIMEOUT_MS))
15062
- ]).catch(() => ({
15063
- domain_results: [],
15064
- global_results: [],
15065
- skipped_global: false
15066
- }));
15300
+ let searchResponse;
15301
+ try {
15302
+ searchResponse = await Promise.race([
15303
+ searchIntentResolve(queryIntent, requestedDomain ?? undefined, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K),
15304
+ new Promise((resolve) => setTimeout(() => {
15305
+ console.log(`[marketplace] timeout after ${MARKETPLACE_TIMEOUT_MS}ms — falling through to browser`);
15306
+ resolve({ domain_results: [], global_results: [], skipped_global: true });
15307
+ }, MARKETPLACE_TIMEOUT_MS))
15308
+ ]);
15309
+ } catch (err) {
15310
+ if (isX402Error(err)) {
15311
+ const trace2 = {
15312
+ trace_id: nanoid7(),
15313
+ skill_id: "marketplace-search",
15314
+ endpoint_id: "search",
15315
+ started_at: new Date().toISOString(),
15316
+ completed_at: new Date().toISOString(),
15317
+ success: false,
15318
+ status_code: 402,
15319
+ error: "payment_required"
15320
+ };
15321
+ return {
15322
+ result: {
15323
+ error: "payment_required",
15324
+ payment_status: "payment_required",
15325
+ wallet_provider: "lobster.cash",
15326
+ message: "Marketplace search requires payment before shared graph results are returned.",
15327
+ next_step: "Pay the Tier 3 search fee, or re-run with force capture for free local discovery.",
15328
+ indexing_fallback_available: true,
15329
+ tier: "tier3",
15330
+ terms: err.terms
15331
+ },
15332
+ trace: trace2,
15333
+ source: "marketplace",
15334
+ skill: undefined,
15335
+ timing: finalize("marketplace", null, undefined, undefined, trace2)
15336
+ };
15337
+ }
15338
+ searchResponse = {
15339
+ domain_results: [],
15340
+ global_results: [],
15341
+ skipped_global: false
15342
+ };
15343
+ }
15344
+ if (typeof searchResponse.actual_cost_uc === "number" && searchResponse.actual_cost_uc > 0) {
15345
+ timing.paid_search_uc = searchResponse.actual_cost_uc;
15346
+ }
15347
+ const { domain_results: domainResults, global_results: globalResults } = searchResponse;
15067
15348
  timing.search_ms = Date.now() - ts0;
15068
15349
  console.log(`[marketplace] search: ${domainResults.length} domain + ${globalResults.length} global results (${timing.search_ms}ms)`);
15069
15350
  const seen = new Set;
@@ -16387,11 +16668,29 @@ var init_verification = __esm(() => {
16387
16668
  var exports_routes = {};
16388
16669
  __export(exports_routes, {
16389
16670
  registerRoutes: () => registerRoutes,
16390
- registerBrowseSession: () => registerBrowseSession
16671
+ registerBrowseSession: () => registerBrowseSession,
16672
+ buildAnalyticsSessionPayload: () => buildAnalyticsSessionPayload
16391
16673
  });
16392
16674
  import { nanoid as nanoid8 } from "nanoid";
16393
16675
  import { writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
16394
16676
  import { join as join12 } from "path";
16677
+ function buildAnalyticsSessionPayload(result, opts) {
16678
+ const source = result.timing?.source ?? result.source;
16679
+ const apiCalls = result.trace.endpoint_id ? 1 : 0;
16680
+ const cachedSkillCalls = opts.cached_skill_calls ?? (apiCalls > 0 && source !== "live-capture" && source !== "first-pass" ? 1 : 0);
16681
+ const freshIndexCalls = opts.fresh_index_calls ?? (apiCalls > 0 && (source === "live-capture" || source === "first-pass") ? 1 : 0);
16682
+ return {
16683
+ session_id: result.trace.trace_id,
16684
+ started_at: result.trace.started_at,
16685
+ completed_at: result.trace.completed_at,
16686
+ trace_version: result.trace.trace_version ?? TRACE_VERSION,
16687
+ api_calls: apiCalls,
16688
+ discovery_queries: opts.discovery_queries,
16689
+ cached_skill_calls: cachedSkillCalls,
16690
+ fresh_index_calls: freshIndexCalls,
16691
+ browser_mode: opts.browser_mode ?? "unknown"
16692
+ };
16693
+ }
16395
16694
  function harEntriesToRawRequests(entries) {
16396
16695
  return entries.filter((e) => e.request && e.response).map((e) => ({
16397
16696
  url: e.request.url,
@@ -16493,11 +16792,11 @@ async function fetchStats() {
16493
16792
  const npmRange = (pkg) => fetch(`https://api.npmjs.org/downloads/range/last-month/${pkg}`).then((r) => r.json());
16494
16793
  const externalCalls = [
16495
16794
  npmPoint("unbrowse", "last-month"),
16496
- npmPoint("@getfoundry/unbrowse-openclaw", "last-month"),
16795
+ npmPoint("unbrowse-openclaw", "last-month"),
16497
16796
  npmPoint("unbrowse", "1970-01-01:2099-12-31"),
16498
- npmPoint("@getfoundry/unbrowse-openclaw", "1970-01-01:2099-12-31"),
16797
+ npmPoint("unbrowse-openclaw", "1970-01-01:2099-12-31"),
16499
16798
  npmRange("unbrowse"),
16500
- npmRange("@getfoundry/unbrowse-openclaw"),
16799
+ npmRange("unbrowse-openclaw"),
16501
16800
  fetch("https://api.github.com/repos/anthropic-ai/unbrowse", {
16502
16801
  headers: { "User-Agent": "unbrowse-stats" }
16503
16802
  }).then((r) => r.json())
@@ -16606,6 +16905,10 @@ async function registerRoutes(app) {
16606
16905
  if (innerResult?.available_endpoints && !res.available_endpoints) {
16607
16906
  res.available_endpoints = innerResult.available_endpoints;
16608
16907
  }
16908
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(result, {
16909
+ browser_mode: "replaced",
16910
+ discovery_queries: 1
16911
+ })).catch(() => {});
16609
16912
  return reply.send(result);
16610
16913
  } catch (err) {
16611
16914
  return reply.code(500).send({ error: err.message });
@@ -16815,6 +17118,10 @@ async function registerRoutes(app) {
16815
17118
  if (freshResult.trace?.skill_id && freshResult.trace?.endpoint_id) {
16816
17119
  recordExecution(freshResult.trace.skill_id, freshResult.trace.endpoint_id, freshResult.trace, skill).catch(() => {});
16817
17120
  }
17121
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(freshResult, {
17122
+ browser_mode: "manual",
17123
+ discovery_queries: 1
17124
+ })).catch(() => {});
16818
17125
  return reply.send({
16819
17126
  ...freshResult,
16820
17127
  _recovery: {
@@ -16825,6 +17132,12 @@ async function registerRoutes(app) {
16825
17132
  });
16826
17133
  } catch {}
16827
17134
  }
17135
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(execResult, {
17136
+ browser_mode: "manual",
17137
+ discovery_queries: 0,
17138
+ cached_skill_calls: execResult.trace.endpoint_id ? 1 : 0,
17139
+ fresh_index_calls: 0
17140
+ })).catch(() => {});
16828
17141
  return reply.send(execResult);
16829
17142
  } catch (err) {
16830
17143
  return reply.code(500).send({ error: err.message });
@@ -16995,6 +17308,7 @@ async function registerRoutes(app) {
16995
17308
  if (session.domain && session.domain !== newDomain) {
16996
17309
  await authProfileSave(session.tabId, session.domain).catch(() => {});
16997
17310
  }
17311
+ let cookiesInjected = 0;
16998
17312
  if (newDomain && newDomain !== session.domain) {
16999
17313
  await authProfileLoad(session.tabId, newDomain).catch(() => {});
17000
17314
  try {
@@ -17003,6 +17317,7 @@ async function registerRoutes(app) {
17003
17317
  for (const c of browserCookies) {
17004
17318
  await setCookie(session.tabId, c).catch(() => {});
17005
17319
  }
17320
+ cookiesInjected = browserCookies.length;
17006
17321
  }
17007
17322
  } catch {}
17008
17323
  }
@@ -17015,7 +17330,7 @@ async function registerRoutes(app) {
17015
17330
  session.url = typeof finalUrl === "string" && finalUrl.startsWith("http") ? finalUrl : url;
17016
17331
  session.domain = profileName(session.url);
17017
17332
  await injectInterceptor(session.tabId);
17018
- return reply.send({ ok: true, url: session.url, tab_id: session.tabId, auth_profile: session.domain });
17333
+ return reply.send({ ok: true, url: session.url, tab_id: session.tabId, auth_profile: session.domain, ...cookiesInjected > 0 ? { cookies_injected: cookiesInjected } : {} });
17019
17334
  });
17020
17335
  app.post("/v1/browse/snap", async (req, reply) => {
17021
17336
  const { filter } = req.body ?? {};
@@ -17329,10 +17644,10 @@ var init_routes = __esm(async () => {
17329
17644
  init_session_logs();
17330
17645
  await __promiseAll([
17331
17646
  init_indexer(),
17647
+ init_vault(),
17332
17648
  init_orchestrator(),
17333
17649
  init_orchestrator(),
17334
17650
  init_execution(),
17335
- init_vault(),
17336
17651
  init_auth(),
17337
17652
  init_indexer()
17338
17653
  ]);
@@ -17417,10 +17732,11 @@ var init_server = __esm(async () => {
17417
17732
  import { config as loadEnv } from "dotenv";
17418
17733
 
17419
17734
  // ../../src/client/index.ts
17735
+ init_cascade();
17420
17736
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
17421
17737
  import { join } from "path";
17422
17738
  import { homedir, hostname } from "os";
17423
- import { randomBytes, createHash } from "crypto";
17739
+ import { randomBytes, createHash as createHash2 } from "crypto";
17424
17740
  import { createInterface } from "readline";
17425
17741
  var API_URL = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
17426
17742
  var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
@@ -17482,6 +17798,20 @@ function resolveAgentName(preferredEmail, fallbackName) {
17482
17798
  const normalized = normalizeAgentEmail(preferredEmail ?? "");
17483
17799
  return isValidAgentEmail(normalized) ? normalized : fallbackName;
17484
17800
  }
17801
+ function getLocalWalletContext() {
17802
+ const lobsterWallet = process.env.LOBSTER_WALLET_ADDRESS?.trim();
17803
+ if (lobsterWallet) {
17804
+ return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
17805
+ }
17806
+ const genericWallet = process.env.AGENT_WALLET_ADDRESS?.trim();
17807
+ if (genericWallet) {
17808
+ return {
17809
+ wallet_address: genericWallet,
17810
+ wallet_provider: process.env.AGENT_WALLET_PROVIDER?.trim() || undefined
17811
+ };
17812
+ }
17813
+ return {};
17814
+ }
17485
17815
  function getApiKey() {
17486
17816
  if (LOCAL_ONLY)
17487
17817
  return "local-only";
@@ -17553,7 +17883,7 @@ async function findUsableApiKey() {
17553
17883
  }
17554
17884
  return null;
17555
17885
  }
17556
- async function api(method, path, body, opts) {
17886
+ async function apiRequest(method, path, body, opts) {
17557
17887
  const key = opts?.noAuth ? "" : getApiKey();
17558
17888
  const controller = new AbortController;
17559
17889
  const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? API_TIMEOUT_MS);
@@ -17599,6 +17929,10 @@ async function api(method, path, body, opts) {
17599
17929
  const msg = errData.details?.length ? `${errData.error}: ${errData.details.join("; ")}` : errData.error ?? `API HTTP ${res.status}`;
17600
17930
  throw new Error(msg);
17601
17931
  }
17932
+ return { data, headers: res.headers };
17933
+ }
17934
+ async function api(method, path, body, opts) {
17935
+ const { data } = await apiRequest(method, path, body, opts);
17602
17936
  return data;
17603
17937
  }
17604
17938
  async function promptTosAcceptance(summary, tosUrl) {
@@ -17696,6 +18030,13 @@ async function ensureRegistered(options) {
17696
18030
  console.log("[unbrowse] Restored saved registration.");
17697
18031
  }
17698
18032
  await checkTosStatus();
18033
+ try {
18034
+ const profile = await getMyProfile();
18035
+ const wallet = getLocalWalletContext();
18036
+ if (wallet.wallet_address && profile.wallet_address !== wallet.wallet_address) {
18037
+ await syncAgentWallet(wallet);
18038
+ }
18039
+ } catch {}
17699
18040
  return;
17700
18041
  }
17701
18042
  let tosInfo;
@@ -17715,7 +18056,8 @@ async function ensureRegistered(options) {
17715
18056
  const name = options?.promptForEmail ? await promptAgentEmail(fallbackName) : resolveAgentName(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
17716
18057
  console.log(`Registering as "${name}"...`);
17717
18058
  try {
17718
- const { agent_id, api_key } = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version });
18059
+ const wallet = getLocalWalletContext();
18060
+ const { agent_id, api_key } = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet });
17719
18061
  process.env.UNBROWSE_API_KEY = api_key;
17720
18062
  saveConfig({
17721
18063
  api_key,
@@ -17723,7 +18065,8 @@ async function ensureRegistered(options) {
17723
18065
  agent_name: name,
17724
18066
  registered_at: new Date().toISOString(),
17725
18067
  tos_accepted_version: tosInfo.version,
17726
- tos_accepted_at: new Date().toISOString()
18068
+ tos_accepted_at: new Date().toISOString(),
18069
+ ...wallet
17727
18070
  });
17728
18071
  console.log(`Registered as ${name}. API key saved to ~/.unbrowse/config.json`);
17729
18072
  } catch (err) {
@@ -17732,6 +18075,18 @@ async function ensureRegistered(options) {
17732
18075
  process.exit(1);
17733
18076
  }
17734
18077
  }
18078
+ async function getMyProfile() {
18079
+ return api("GET", "/v1/agents/me", undefined);
18080
+ }
18081
+ async function syncAgentWallet(wallet = getLocalWalletContext()) {
18082
+ if (!wallet.wallet_address)
18083
+ return;
18084
+ await api("POST", "/v1/agents/wallet", wallet);
18085
+ const config = loadConfig();
18086
+ if (!config)
18087
+ return;
18088
+ saveConfig({ ...config, ...wallet });
18089
+ }
17735
18090
 
17736
18091
  // ../../src/cli/shortcuts.ts
17737
18092
  var linkedin = {