unbrowse 2.9.1 → 2.10.2

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;
@@ -4645,6 +4722,8 @@ __export(exports_client2, {
4645
4722
  validateManifest: () => validateManifest,
4646
4723
  updateEndpointScore: () => updateEndpointScore,
4647
4724
  updateEndpointSchema: () => updateEndpointSchema,
4725
+ syncAgentWallet: () => syncAgentWallet2,
4726
+ setSkillSplitConfig: () => setSkillSplitConfig,
4648
4727
  setSkillPrice: () => setSkillPrice,
4649
4728
  searchIntentResolve: () => searchIntentResolve,
4650
4729
  searchIntentInDomain: () => searchIntentInDomain,
@@ -4656,6 +4735,7 @@ __export(exports_client2, {
4656
4735
  recordFeedback: () => recordFeedback,
4657
4736
  recordExecution: () => recordExecution,
4658
4737
  recordDiagnostics: () => recordDiagnostics,
4738
+ recordAnalyticsSession: () => recordAnalyticsSession,
4659
4739
  publishSkill: () => publishSkill,
4660
4740
  publishGraphEdges: () => publishGraphEdges,
4661
4741
  normalizeAgentEmail: () => normalizeAgentEmail2,
@@ -4668,7 +4748,7 @@ __export(exports_client2, {
4668
4748
  getSkillChunk: () => getSkillChunk2,
4669
4749
  getSkill: () => getSkill,
4670
4750
  getRecentLocalSkill: () => getRecentLocalSkill,
4671
- getMyProfile: () => getMyProfile,
4751
+ getMyProfile: () => getMyProfile2,
4672
4752
  getEndpointSchema: () => getEndpointSchema,
4673
4753
  getCreatorEarnings: () => getCreatorEarnings,
4674
4754
  getApiKey: () => getApiKey2,
@@ -4686,7 +4766,7 @@ __export(exports_client2, {
4686
4766
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
4687
4767
  import { join as join3 } from "path";
4688
4768
  import { homedir as homedir2, hostname as hostname2 } from "os";
4689
- import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
4769
+ import { randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
4690
4770
  import { createInterface as createInterface2 } from "readline";
4691
4771
  function decodeBase64Json2(value) {
4692
4772
  try {
@@ -4758,6 +4838,20 @@ function resolveAgentName2(preferredEmail, fallbackName) {
4758
4838
  const normalized = normalizeAgentEmail2(preferredEmail ?? "");
4759
4839
  return isValidAgentEmail2(normalized) ? normalized : fallbackName;
4760
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
+ }
4761
4855
  function getApiKey2() {
4762
4856
  if (LOCAL_ONLY2)
4763
4857
  return "local-only";
@@ -4773,7 +4867,7 @@ function getApiKey2() {
4773
4867
  function hashApiKey(key) {
4774
4868
  if (!key || key === "local-only")
4775
4869
  return "";
4776
- return createHash2("sha256").update(key).digest("hex");
4870
+ return createHash3("sha256").update(key).digest("hex");
4777
4871
  }
4778
4872
  function getAgentId() {
4779
4873
  const config = loadConfig2();
@@ -4836,7 +4930,7 @@ async function findUsableApiKey2() {
4836
4930
  }
4837
4931
  return null;
4838
4932
  }
4839
- async function api2(method, path4, body, opts) {
4933
+ async function apiRequest2(method, path4, body, opts) {
4840
4934
  const key = opts?.noAuth ? "" : getApiKey2();
4841
4935
  const controller = new AbortController;
4842
4936
  const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? API_TIMEOUT_MS2);
@@ -4882,6 +4976,10 @@ async function api2(method, path4, body, opts) {
4882
4976
  const msg = errData.details?.length ? `${errData.error}: ${errData.details.join("; ")}` : errData.error ?? `API HTTP ${res.status}`;
4883
4977
  throw new Error(msg);
4884
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);
4885
4983
  return data;
4886
4984
  }
4887
4985
  async function promptTosAcceptance2(summary, tosUrl) {
@@ -4979,6 +5077,13 @@ async function ensureRegistered2(options) {
4979
5077
  console.log("[unbrowse] Restored saved registration.");
4980
5078
  }
4981
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 {}
4982
5087
  return;
4983
5088
  }
4984
5089
  let tosInfo;
@@ -4998,7 +5103,8 @@ async function ensureRegistered2(options) {
4998
5103
  const name = options?.promptForEmail ? await promptAgentEmail2(fallbackName) : resolveAgentName2(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
4999
5104
  console.log(`Registering as "${name}"...`);
5000
5105
  try {
5001
- 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 });
5002
5108
  process.env.UNBROWSE_API_KEY = api_key;
5003
5109
  saveConfig2({
5004
5110
  api_key,
@@ -5006,7 +5112,8 @@ async function ensureRegistered2(options) {
5006
5112
  agent_name: name,
5007
5113
  registered_at: new Date().toISOString(),
5008
5114
  tos_accepted_version: tosInfo.version,
5009
- tos_accepted_at: new Date().toISOString()
5115
+ tos_accepted_at: new Date().toISOString(),
5116
+ ...wallet
5010
5117
  });
5011
5118
  console.log(`Registered as ${name}. API key saved to ~/.unbrowse/config.json`);
5012
5119
  } catch (err) {
@@ -5162,7 +5269,27 @@ async function publishSkill(draft) {
5162
5269
  }
5163
5270
  if (LOCAL_ONLY2)
5164
5271
  throw new Error("local-only mode");
5165
- 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 };
5166
5293
  }
5167
5294
  async function deprecateSkill(skillId) {
5168
5295
  if (LOCAL_ONLY2)
@@ -5204,12 +5331,15 @@ async function searchIntentResolve(intent, domain, domainK = 5, globalK = 10) {
5204
5331
  if (LOCAL_ONLY2)
5205
5332
  return { domain_results: [], global_results: [], skipped_global: false };
5206
5333
  try {
5207
- return await api2("POST", "/v1/search/resolve", {
5334
+ const { data, headers } = await apiRequest2("POST", "/v1/search/resolve", {
5208
5335
  intent,
5209
5336
  domain,
5210
5337
  domain_k: domainK,
5211
5338
  global_k: globalK
5212
5339
  });
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;
5213
5343
  } catch (err) {
5214
5344
  if (isX402Error(err))
5215
5345
  throw err;
@@ -5288,6 +5418,11 @@ async function recordOrchestrationPerf(timing) {
5288
5418
  const phaseTotals = Object.fromEntries(attributeLifecycle(events));
5289
5419
  await api2("POST", "/v1/stats/perf", { ...timing, phase_totals_ms: phaseTotals });
5290
5420
  }
5421
+ async function recordAnalyticsSession(session) {
5422
+ if (LOCAL_ONLY2)
5423
+ return;
5424
+ await api2("POST", "/v1/analytics/sessions", session);
5425
+ }
5291
5426
  async function validateManifest(manifest) {
5292
5427
  if (LOCAL_ONLY2)
5293
5428
  return { valid: true, hardErrors: [], softWarnings: [] };
@@ -5342,8 +5477,8 @@ async function verifyMarketplaceDiscovery(skillId, intent, maxWaitMs = 60000) {
5342
5477
  }
5343
5478
  return { found: false, latency_ms: Date.now() - start2 };
5344
5479
  }
5345
- async function registerAgent(name) {
5346
- return api2("POST", "/v1/agents/register", { name });
5480
+ async function registerAgent(name, wallet = getLocalWalletContext2()) {
5481
+ return api2("POST", "/v1/agents/register", { name, ...wallet });
5347
5482
  }
5348
5483
  async function getAgent(agentId) {
5349
5484
  try {
@@ -5352,9 +5487,18 @@ async function getAgent(agentId) {
5352
5487
  return null;
5353
5488
  }
5354
5489
  }
5355
- async function getMyProfile() {
5490
+ async function getMyProfile2() {
5356
5491
  return api2("GET", "/v1/agents/me", undefined);
5357
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
+ }
5358
5502
  async function getTransactionHistory(agentId) {
5359
5503
  return api2("GET", `/v1/transactions/consumer/${agentId}`);
5360
5504
  }
@@ -5364,8 +5508,12 @@ async function getCreatorEarnings(agentId) {
5364
5508
  async function setSkillPrice(skillId, priceUsd) {
5365
5509
  return api2("PATCH", `/v1/skills/${skillId}`, { base_price_usd: priceUsd });
5366
5510
  }
5511
+ async function setSkillSplitConfig(skillId, splitConfig) {
5512
+ return api2("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
5513
+ }
5367
5514
  var API_URL2, PROFILE_NAME2, recentLocalSkills2, LOCAL_ONLY2, EMAIL_RE2, API_TIMEOUT_MS2, PUBLISH_TIMEOUT_MS2;
5368
5515
  var init_client2 = __esm(() => {
5516
+ init_cascade();
5369
5517
  API_URL2 = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
5370
5518
  PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? "");
5371
5519
  recentLocalSkills2 = new Map;
@@ -5449,7 +5597,7 @@ var init_marketplace = __esm(() => {
5449
5597
  });
5450
5598
 
5451
5599
  // ../../src/version.ts
5452
- import { createHash as createHash3 } from "crypto";
5600
+ import { createHash as createHash4 } from "crypto";
5453
5601
  import { readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
5454
5602
  import { dirname, join as join4 } from "path";
5455
5603
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -5469,7 +5617,7 @@ function computeCodeHash() {
5469
5617
  try {
5470
5618
  const srcDir = join4(MODULE_DIR, ".");
5471
5619
  const files = collectTsFiles(srcDir).sort();
5472
- const hash = createHash3("sha256");
5620
+ const hash = createHash4("sha256");
5473
5621
  for (const file of files) {
5474
5622
  hash.update(file.slice(srcDir.length));
5475
5623
  hash.update(readFileSync3(file, "utf-8"));
@@ -5491,7 +5639,7 @@ var init_version = __esm(() => {
5491
5639
  });
5492
5640
 
5493
5641
  // ../../src/telemetry.ts
5494
- import { createHash as createHash4 } from "node:crypto";
5642
+ import { createHash as createHash5 } from "node:crypto";
5495
5643
  import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
5496
5644
  import { join as join5 } from "node:path";
5497
5645
  function getTraceDir() {
@@ -5508,7 +5656,7 @@ function safeBindingNames(bindings) {
5508
5656
  }
5509
5657
  function hashResponseBody(result) {
5510
5658
  const str = typeof result === "string" ? result : JSON.stringify(result ?? "");
5511
- return createHash4("sha256").update(str).digest("hex").slice(0, 32);
5659
+ return createHash5("sha256").update(str).digest("hex").slice(0, 32);
5512
5660
  }
5513
5661
  function classifyFailure(error) {
5514
5662
  if (!error)
@@ -6321,6 +6469,10 @@ function filterExpired(cookies) {
6321
6469
  });
6322
6470
  }
6323
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) {
6324
6476
  const regDomain = getRegistrableDomain(domain);
6325
6477
  const keysToTry = [`auth:${regDomain}`];
6326
6478
  if (domain !== regDomain)
@@ -6331,11 +6483,9 @@ async function getStoredAuth(domain) {
6331
6483
  continue;
6332
6484
  try {
6333
6485
  const parsed = JSON.parse(stored);
6334
- const cookies = parsed.cookies;
6335
- if (!cookies || cookies.length === 0)
6336
- continue;
6486
+ const cookies = parsed.cookies ?? [];
6337
6487
  const valid = filterExpired(cookies);
6338
- if (valid.length === 0) {
6488
+ if (cookies.length > 0 && valid.length === 0 && Object.keys(parsed.headers ?? {}).length === 0) {
6339
6489
  log("auth", `all ${cookies.length} cookies for ${domain} (key: ${key}) are expired — deleting`);
6340
6490
  await deleteCredential(key);
6341
6491
  continue;
@@ -6343,7 +6493,12 @@ async function getStoredAuth(domain) {
6343
6493
  if (valid.length < cookies.length) {
6344
6494
  log("auth", `filtered ${cookies.length - valid.length} expired cookies for ${domain}`);
6345
6495
  }
6346
- 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
+ };
6347
6502
  } catch {
6348
6503
  continue;
6349
6504
  }
@@ -11424,7 +11579,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
11424
11579
  } catch {}
11425
11580
  }
11426
11581
  recordExecution(skill.skill_id, endpoint.endpoint_id, trace, skill).catch(() => {});
11427
- 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) {
11428
11583
  const consumerConfig = (() => {
11429
11584
  try {
11430
11585
  return JSON.parse(__require("fs").readFileSync(__require("os").homedir() + "/.unbrowse/config.json", "utf-8"));
@@ -12734,7 +12889,7 @@ var init_prefetch = __esm(async () => {
12734
12889
  });
12735
12890
 
12736
12891
  // ../../src/orchestrator/first-pass-action.ts
12737
- import { createHash as createHash5 } from "node:crypto";
12892
+ import { createHash as createHash6 } from "node:crypto";
12738
12893
  function classifyIntent(intent) {
12739
12894
  if (/\b(search|find|discover|look\s+for|browse)\b/i.test(intent))
12740
12895
  return "search";
@@ -12781,7 +12936,7 @@ function filterJsonApiEntries(entries) {
12781
12936
  function synthesizeSkillFromIntercepted(interceptedEntries, domain, intent) {
12782
12937
  if (interceptedEntries.length === 0)
12783
12938
  return;
12784
- 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);
12785
12940
  const endpoints = interceptedEntries.map((entry, i) => {
12786
12941
  const parsed = new URL(entry.request.url);
12787
12942
  return {
@@ -13005,7 +13160,7 @@ function checkWalletConfigured() {
13005
13160
  import { nanoid as nanoid7 } from "nanoid";
13006
13161
  import { existsSync as existsSync9, writeFileSync as writeFileSync7, readFileSync as readFileSync6, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "node:fs";
13007
13162
  import { dirname as dirname2, join as join9 } from "node:path";
13008
- import { createHash as createHash6 } from "node:crypto";
13163
+ import { createHash as createHash7 } from "node:crypto";
13009
13164
  function summarizeSchema(schema, maxDepth = 3) {
13010
13165
  function walk(s, depth) {
13011
13166
  if (depth <= 0)
@@ -13120,7 +13275,7 @@ function scopedResolveCacheKeys(scope, key) {
13120
13275
  return scope === "global" ? [scopedCacheKey("global", key)] : [scopedCacheKey(scope, key), scopedCacheKey("global", key)];
13121
13276
  }
13122
13277
  function snapshotPathForCacheKey(cacheKey) {
13123
- const digest = createHash6("sha1").update(cacheKey).digest("hex");
13278
+ const digest = createHash7("sha1").update(cacheKey).digest("hex");
13124
13279
  return join9(SKILL_SNAPSHOT_DIR, `${digest}.json`);
13125
13280
  }
13126
13281
  function writeSkillSnapshot(cacheKey, skill) {
@@ -14224,6 +14379,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14224
14379
  response_bytes: 0,
14225
14380
  time_saved_pct: 0,
14226
14381
  tokens_saved_pct: 0,
14382
+ actual_total_ms: 0,
14227
14383
  trace_version: TRACE_VERSION
14228
14384
  };
14229
14385
  const decisionTrace = {
@@ -14259,6 +14415,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14259
14415
  }
14260
14416
  function finalize(source, result2, skillId, skill, trace2) {
14261
14417
  timing.total_ms = Date.now() - t0;
14418
+ timing.actual_total_ms = timing.total_ms;
14262
14419
  timing.source = source;
14263
14420
  timing.skill_id = skillId;
14264
14421
  const resultStr = typeof result2 === "string" ? result2 : JSON.stringify(result2 ?? "");
@@ -14267,11 +14424,20 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14267
14424
  const cost = skill?.discovery_cost;
14268
14425
  const baselineTokens = cost?.capture_tokens ?? DEFAULT_CAPTURE_TOKENS;
14269
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;
14270
14432
  if (source === "marketplace" || source === "route-cache" || source === "first-pass") {
14271
14433
  timing.tokens_saved = Math.max(0, baselineTokens - responseTokens);
14272
14434
  timing.tokens_saved_pct = baselineTokens > 0 ? Math.round(timing.tokens_saved / baselineTokens * 100) : 0;
14273
14435
  timing.time_saved_pct = baselineMs > 0 ? Math.round(Math.max(0, baselineMs - timing.total_ms) / baselineMs * 100) : 0;
14274
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
+ }
14275
14441
  if (trace2) {
14276
14442
  trace2.tokens_used = responseTokens;
14277
14443
  trace2.tokens_saved = timing.tokens_saved;
@@ -15175,6 +15341,9 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15175
15341
  skipped_global: false
15176
15342
  };
15177
15343
  }
15344
+ if (typeof searchResponse.actual_cost_uc === "number" && searchResponse.actual_cost_uc > 0) {
15345
+ timing.paid_search_uc = searchResponse.actual_cost_uc;
15346
+ }
15178
15347
  const { domain_results: domainResults, global_results: globalResults } = searchResponse;
15179
15348
  timing.search_ms = Date.now() - ts0;
15180
15349
  console.log(`[marketplace] search: ${domainResults.length} domain + ${globalResults.length} global results (${timing.search_ms}ms)`);
@@ -16499,11 +16668,29 @@ var init_verification = __esm(() => {
16499
16668
  var exports_routes = {};
16500
16669
  __export(exports_routes, {
16501
16670
  registerRoutes: () => registerRoutes,
16502
- registerBrowseSession: () => registerBrowseSession
16671
+ registerBrowseSession: () => registerBrowseSession,
16672
+ buildAnalyticsSessionPayload: () => buildAnalyticsSessionPayload
16503
16673
  });
16504
16674
  import { nanoid as nanoid8 } from "nanoid";
16505
16675
  import { writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
16506
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
+ }
16507
16694
  function harEntriesToRawRequests(entries) {
16508
16695
  return entries.filter((e) => e.request && e.response).map((e) => ({
16509
16696
  url: e.request.url,
@@ -16605,11 +16792,11 @@ async function fetchStats() {
16605
16792
  const npmRange = (pkg) => fetch(`https://api.npmjs.org/downloads/range/last-month/${pkg}`).then((r) => r.json());
16606
16793
  const externalCalls = [
16607
16794
  npmPoint("unbrowse", "last-month"),
16608
- npmPoint("@getfoundry/unbrowse-openclaw", "last-month"),
16795
+ npmPoint("unbrowse-openclaw", "last-month"),
16609
16796
  npmPoint("unbrowse", "1970-01-01:2099-12-31"),
16610
- npmPoint("@getfoundry/unbrowse-openclaw", "1970-01-01:2099-12-31"),
16797
+ npmPoint("unbrowse-openclaw", "1970-01-01:2099-12-31"),
16611
16798
  npmRange("unbrowse"),
16612
- npmRange("@getfoundry/unbrowse-openclaw"),
16799
+ npmRange("unbrowse-openclaw"),
16613
16800
  fetch("https://api.github.com/repos/anthropic-ai/unbrowse", {
16614
16801
  headers: { "User-Agent": "unbrowse-stats" }
16615
16802
  }).then((r) => r.json())
@@ -16718,6 +16905,10 @@ async function registerRoutes(app) {
16718
16905
  if (innerResult?.available_endpoints && !res.available_endpoints) {
16719
16906
  res.available_endpoints = innerResult.available_endpoints;
16720
16907
  }
16908
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(result, {
16909
+ browser_mode: "replaced",
16910
+ discovery_queries: 1
16911
+ })).catch(() => {});
16721
16912
  return reply.send(result);
16722
16913
  } catch (err) {
16723
16914
  return reply.code(500).send({ error: err.message });
@@ -16927,6 +17118,10 @@ async function registerRoutes(app) {
16927
17118
  if (freshResult.trace?.skill_id && freshResult.trace?.endpoint_id) {
16928
17119
  recordExecution(freshResult.trace.skill_id, freshResult.trace.endpoint_id, freshResult.trace, skill).catch(() => {});
16929
17120
  }
17121
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(freshResult, {
17122
+ browser_mode: "manual",
17123
+ discovery_queries: 1
17124
+ })).catch(() => {});
16930
17125
  return reply.send({
16931
17126
  ...freshResult,
16932
17127
  _recovery: {
@@ -16937,6 +17132,12 @@ async function registerRoutes(app) {
16937
17132
  });
16938
17133
  } catch {}
16939
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(() => {});
16940
17141
  return reply.send(execResult);
16941
17142
  } catch (err) {
16942
17143
  return reply.code(500).send({ error: err.message });
@@ -17443,10 +17644,10 @@ var init_routes = __esm(async () => {
17443
17644
  init_session_logs();
17444
17645
  await __promiseAll([
17445
17646
  init_indexer(),
17647
+ init_vault(),
17446
17648
  init_orchestrator(),
17447
17649
  init_orchestrator(),
17448
17650
  init_execution(),
17449
- init_vault(),
17450
17651
  init_auth(),
17451
17652
  init_indexer()
17452
17653
  ]);
@@ -17531,10 +17732,11 @@ var init_server = __esm(async () => {
17531
17732
  import { config as loadEnv } from "dotenv";
17532
17733
 
17533
17734
  // ../../src/client/index.ts
17735
+ init_cascade();
17534
17736
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
17535
17737
  import { join } from "path";
17536
17738
  import { homedir, hostname } from "os";
17537
- import { randomBytes, createHash } from "crypto";
17739
+ import { randomBytes, createHash as createHash2 } from "crypto";
17538
17740
  import { createInterface } from "readline";
17539
17741
  var API_URL = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
17540
17742
  var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
@@ -17596,6 +17798,20 @@ function resolveAgentName(preferredEmail, fallbackName) {
17596
17798
  const normalized = normalizeAgentEmail(preferredEmail ?? "");
17597
17799
  return isValidAgentEmail(normalized) ? normalized : fallbackName;
17598
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
+ }
17599
17815
  function getApiKey() {
17600
17816
  if (LOCAL_ONLY)
17601
17817
  return "local-only";
@@ -17667,7 +17883,7 @@ async function findUsableApiKey() {
17667
17883
  }
17668
17884
  return null;
17669
17885
  }
17670
- async function api(method, path, body, opts) {
17886
+ async function apiRequest(method, path, body, opts) {
17671
17887
  const key = opts?.noAuth ? "" : getApiKey();
17672
17888
  const controller = new AbortController;
17673
17889
  const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? API_TIMEOUT_MS);
@@ -17713,6 +17929,10 @@ async function api(method, path, body, opts) {
17713
17929
  const msg = errData.details?.length ? `${errData.error}: ${errData.details.join("; ")}` : errData.error ?? `API HTTP ${res.status}`;
17714
17930
  throw new Error(msg);
17715
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);
17716
17936
  return data;
17717
17937
  }
17718
17938
  async function promptTosAcceptance(summary, tosUrl) {
@@ -17810,6 +18030,13 @@ async function ensureRegistered(options) {
17810
18030
  console.log("[unbrowse] Restored saved registration.");
17811
18031
  }
17812
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 {}
17813
18040
  return;
17814
18041
  }
17815
18042
  let tosInfo;
@@ -17829,7 +18056,8 @@ async function ensureRegistered(options) {
17829
18056
  const name = options?.promptForEmail ? await promptAgentEmail(fallbackName) : resolveAgentName(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
17830
18057
  console.log(`Registering as "${name}"...`);
17831
18058
  try {
17832
- 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 });
17833
18061
  process.env.UNBROWSE_API_KEY = api_key;
17834
18062
  saveConfig({
17835
18063
  api_key,
@@ -17837,7 +18065,8 @@ async function ensureRegistered(options) {
17837
18065
  agent_name: name,
17838
18066
  registered_at: new Date().toISOString(),
17839
18067
  tos_accepted_version: tosInfo.version,
17840
- tos_accepted_at: new Date().toISOString()
18068
+ tos_accepted_at: new Date().toISOString(),
18069
+ ...wallet
17841
18070
  });
17842
18071
  console.log(`Registered as ${name}. API key saved to ~/.unbrowse/config.json`);
17843
18072
  } catch (err) {
@@ -17846,6 +18075,18 @@ async function ensureRegistered(options) {
17846
18075
  process.exit(1);
17847
18076
  }
17848
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
+ }
17849
18090
 
17850
18091
  // ../../src/cli/shortcuts.ts
17851
18092
  var linkedin = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unbrowse",
3
- "version": "2.9.1",
3
+ "version": "2.10.2",
4
4
  "description": "Reverse-engineer any website into reusable API skills. Zero-dep single binary with embedded browser engine.",
5
5
  "type": "module",
6
6
  "bin": {