unbrowse 6.2.6 → 6.4.0

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
@@ -31,7 +31,7 @@ var __promiseAll = (args) => Promise.all(args);
31
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
32
32
 
33
33
  // ../../src/build-info.generated.ts
34
- var BUILD_RELEASE_VERSION = "6.2.6", BUILD_GIT_SHA = "8d412404ebd4", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi4yLjYiLCJnaXRfc2hhIjoiOGQ0MTI0MDRlYmQ0IiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA4ZDQxMjQwNGViZDQiLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTAxVDAzOjI4OjE4Ljk0NloifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "Otgiy3edVGT28Wb-c-n31uyBsZGKK_p7wnGc0YPoClM", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
34
+ var BUILD_RELEASE_VERSION = "6.4.0", BUILD_GIT_SHA = "5445eb4fd89e", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi40LjAiLCJnaXRfc2hhIjoiNTQ0NWViNGZkODllIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA1NDQ1ZWI0ZmQ4OWUiLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTAxVDA5OjQ5OjE0LjcyMFoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "3l8L_1jJ1G9weeuFgVBrYwcglXLwH_AnfmsnXqdPigg", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
35
35
 
36
36
  // ../../src/version.ts
37
37
  import { createHash } from "crypto";
@@ -515,8 +515,11 @@ var init_client = __esm(() => {
515
515
 
516
516
  // ../../src/marketplace/index.ts
517
517
  import { nanoid } from "nanoid";
518
+ var TTL_MS, marketplaceCache;
518
519
  var init_marketplace = __esm(() => {
519
520
  init_client();
521
+ TTL_MS = 5 * 60 * 1000;
522
+ marketplaceCache = new Map;
520
523
  });
521
524
 
522
525
  // ../../src/domain.ts
@@ -1559,6 +1562,11 @@ var RETRYABLE_STATUSES;
1559
1562
  var init_retry = __esm(() => {
1560
1563
  RETRYABLE_STATUSES = new Set([500, 502, 503, 504, 429]);
1561
1564
  });
1565
+
1566
+ // ../../src/execution/probe.ts
1567
+ var init_probe = __esm(() => {
1568
+ init_logger();
1569
+ });
1562
1570
  // ../../src/extraction/index.ts
1563
1571
  import * as cheerio from "cheerio";
1564
1572
  var STRIP_TAGS, CHROME_TAGS;
@@ -1628,6 +1636,102 @@ var init_schema_review = __esm(() => {
1628
1636
  init_sanitize();
1629
1637
  });
1630
1638
 
1639
+ // ../../src/config/contribution.ts
1640
+ import fs2 from "node:fs";
1641
+ import path6 from "node:path";
1642
+ import os3 from "node:os";
1643
+ function configPath() {
1644
+ return process.env.UNBROWSE_CONFIG_PATH || path6.join(os3.homedir(), ".unbrowse", "config.json");
1645
+ }
1646
+ function freshDefault() {
1647
+ return {
1648
+ contribution: { ...DEFAULT.contribution },
1649
+ rev_share: { ...DEFAULT.rev_share },
1650
+ notice_shown_count: 0
1651
+ };
1652
+ }
1653
+ function getContributionConfig() {
1654
+ if (cached)
1655
+ return cached;
1656
+ const p = configPath();
1657
+ let fileExisted = false;
1658
+ let raw = null;
1659
+ try {
1660
+ const content = fs2.readFileSync(p, "utf-8");
1661
+ fileExisted = true;
1662
+ if (content.trim().length > 0) {
1663
+ raw = JSON.parse(content);
1664
+ }
1665
+ } catch {}
1666
+ if (!raw) {
1667
+ cached = freshDefault();
1668
+ return cached;
1669
+ }
1670
+ const contributionRaw = raw.contribution ?? null;
1671
+ const revShareRaw = raw.rev_share ?? null;
1672
+ const isExistingUserMigration = fileExisted && !contributionRaw;
1673
+ cached = {
1674
+ contribution: {
1675
+ share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT.contribution.share_pointers),
1676
+ set_via: contributionRaw?.set_via ?? DEFAULT.contribution.set_via,
1677
+ ...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
1678
+ },
1679
+ rev_share: {
1680
+ opted_in: !!(revShareRaw?.opted_in ?? DEFAULT.rev_share.opted_in),
1681
+ ...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
1682
+ },
1683
+ notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
1684
+ };
1685
+ if (isExistingUserMigration) {
1686
+ try {
1687
+ const merged = { ...raw, ...cached };
1688
+ fs2.mkdirSync(path6.dirname(p), { recursive: true });
1689
+ fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
1690
+ } catch {}
1691
+ }
1692
+ return cached;
1693
+ }
1694
+ function setContributionConfig(updates) {
1695
+ const current = getContributionConfig();
1696
+ const next = {
1697
+ contribution: {
1698
+ ...current.contribution,
1699
+ ...updates.contribution ?? {},
1700
+ set_at: new Date().toISOString()
1701
+ },
1702
+ rev_share: {
1703
+ ...current.rev_share,
1704
+ ...updates.rev_share ?? {}
1705
+ },
1706
+ notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
1707
+ };
1708
+ const p = configPath();
1709
+ let existing = {};
1710
+ try {
1711
+ const content = fs2.readFileSync(p, "utf-8");
1712
+ if (content.trim())
1713
+ existing = JSON.parse(content);
1714
+ } catch {}
1715
+ const merged = { ...existing, ...next };
1716
+ fs2.mkdirSync(path6.dirname(p), { recursive: true });
1717
+ fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
1718
+ cached = next;
1719
+ }
1720
+ function decrementNoticeCounter() {
1721
+ const cfg = getContributionConfig();
1722
+ const next = Math.max(0, (cfg.notice_shown_count ?? 0) - 1);
1723
+ setContributionConfig({ notice_shown_count: next });
1724
+ return next;
1725
+ }
1726
+ var DEFAULT, cached = null;
1727
+ var init_contribution = __esm(() => {
1728
+ DEFAULT = {
1729
+ contribution: { share_pointers: false, set_via: "default" },
1730
+ rev_share: { opted_in: false },
1731
+ notice_shown_count: 0
1732
+ };
1733
+ });
1734
+
1631
1735
  // ../../src/indexer/index.ts
1632
1736
  import { join as join9 } from "node:path";
1633
1737
  var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
@@ -1643,6 +1747,7 @@ var init_indexer = __esm(async () => {
1643
1747
  init_settings();
1644
1748
  init_graph();
1645
1749
  init_schema_review();
1750
+ init_contribution();
1646
1751
  await init_orchestrator();
1647
1752
  SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
1648
1753
  indexInFlight = new Map;
@@ -1659,9 +1764,9 @@ var init_payments = __esm(() => {
1659
1764
  });
1660
1765
 
1661
1766
  // ../../src/execution/robots.ts
1662
- var TTL_MS, cache;
1767
+ var TTL_MS2, cache;
1663
1768
  var init_robots = __esm(() => {
1664
- TTL_MS = 24 * 60 * 60 * 1000;
1769
+ TTL_MS2 = 24 * 60 * 60 * 1000;
1665
1770
  cache = new Map;
1666
1771
  });
1667
1772
 
@@ -1733,6 +1838,7 @@ var init_execution = __esm(async () => {
1733
1838
  init_client();
1734
1839
  init_client();
1735
1840
  init_retry();
1841
+ init_probe();
1736
1842
  init_domain();
1737
1843
  init_extraction();
1738
1844
  init_graph();
@@ -1865,6 +1971,13 @@ import { nanoid as nanoid8 } from "nanoid";
1865
1971
  var init_routing_telemetry = __esm(() => {
1866
1972
  init_telemetry();
1867
1973
  });
1974
+
1975
+ // ../../src/orchestrator/resolve-race.ts
1976
+ var init_resolve_race = __esm(async () => {
1977
+ init_probe();
1978
+ init_marketplace();
1979
+ await init_execution();
1980
+ });
1868
1981
  // ../../src/orchestrator/index.ts
1869
1982
  import { nanoid as nanoid9 } from "nanoid";
1870
1983
  import { existsSync as existsSync12, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "node:fs";
@@ -1901,7 +2014,8 @@ var init_orchestrator = __esm(async () => {
1901
2014
  init_execution(),
1902
2015
  init_dag_advisor(),
1903
2016
  init_prefetch(),
1904
- init_runtime()
2017
+ init_runtime(),
2018
+ init_resolve_race()
1905
2019
  ]);
1906
2020
  LIVE_CAPTURE_TIMEOUT_MS = Number(process.env.UNBROWSE_LIVE_CAPTURE_TIMEOUT_MS ?? "120000");
1907
2021
  capturedDomainCache = new Map;
@@ -2285,9 +2399,9 @@ function getChromiumKeychainServiceName2(opts) {
2285
2399
  }
2286
2400
  function getChromiumDecryptionKey2(opts) {
2287
2401
  const service = getChromiumKeychainServiceName2(opts);
2288
- const cached = _chromiumKeyCache2.get(service);
2289
- if (cached)
2290
- return cached;
2402
+ const cached2 = _chromiumKeyCache2.get(service);
2403
+ if (cached2)
2404
+ return cached2;
2291
2405
  if (platform2() !== "darwin")
2292
2406
  return null;
2293
2407
  try {
@@ -3831,6 +3945,7 @@ init_publish();
3831
3945
  init_settings();
3832
3946
  init_graph();
3833
3947
  init_schema_review();
3948
+ init_contribution();
3834
3949
  import { join as join11 } from "node:path";
3835
3950
  var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join11(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
3836
3951
  var indexInFlight2 = new Map;
@@ -3871,23 +3986,23 @@ init_logger();
3871
3986
  init_wallet();
3872
3987
  import { execFileSync as execFileSync4 } from "node:child_process";
3873
3988
  import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "node:fs";
3874
- import os4 from "node:os";
3875
- import path7 from "node:path";
3989
+ import os5 from "node:os";
3990
+ import path8 from "node:path";
3876
3991
 
3877
3992
  // ../../src/runtime/update-hints.ts
3878
3993
  init_paths();
3879
3994
  import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
3880
- import os3 from "node:os";
3881
- import path6 from "node:path";
3995
+ import os4 from "node:os";
3996
+ import path7 from "node:path";
3882
3997
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
3883
3998
  var CODEX_MARKER = "# Unbrowse update hints — managed by unbrowse setup";
3884
3999
  function getHomeDir() {
3885
- return process.env.HOME || os3.homedir();
4000
+ return process.env.HOME || os4.homedir();
3886
4001
  }
3887
4002
  function getConfigDir2() {
3888
4003
  if (process.env.UNBROWSE_CONFIG_DIR)
3889
4004
  return process.env.UNBROWSE_CONFIG_DIR;
3890
- return path6.join(getHomeDir(), ".unbrowse");
4005
+ return path7.join(getHomeDir(), ".unbrowse");
3891
4006
  }
3892
4007
  function ensureDir3(dir) {
3893
4008
  if (!existsSync13(dir))
@@ -3902,20 +4017,20 @@ function readJsonFile(file) {
3902
4017
  }
3903
4018
  }
3904
4019
  function writeJsonFile(file, value) {
3905
- ensureDir3(path6.dirname(file));
4020
+ ensureDir3(path7.dirname(file));
3906
4021
  writeFileSync5(file, `${JSON.stringify(value, null, 2)}
3907
4022
  `);
3908
4023
  }
3909
4024
  function getInstallSourcePath() {
3910
- return path6.join(getConfigDir2(), "install-source.json");
4025
+ return path7.join(getConfigDir2(), "install-source.json");
3911
4026
  }
3912
4027
  function detectRepoRoot(start2) {
3913
- let dir = path6.resolve(start2);
3914
- const root = path6.parse(dir).root;
4028
+ let dir = path7.resolve(start2);
4029
+ const root = path7.parse(dir).root;
3915
4030
  while (dir !== root) {
3916
- if (existsSync13(path6.join(dir, ".git")))
4031
+ if (existsSync13(path7.join(dir, ".git")))
3917
4032
  return dir;
3918
- dir = path6.dirname(dir);
4033
+ dir = path7.dirname(dir);
3919
4034
  }
3920
4035
  return;
3921
4036
  }
@@ -3924,7 +4039,7 @@ function detectInstallMethod(packageRoot) {
3924
4039
  return "repo-clone";
3925
4040
  if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
3926
4041
  return "npm-global";
3927
- if (packageRoot.includes(`${path6.sep}node_modules${path6.sep}`))
4042
+ if (packageRoot.includes(`${path7.sep}node_modules${path7.sep}`))
3928
4043
  return "npm-global";
3929
4044
  return detectRepoRoot(packageRoot) ? "repo-clone" : "unknown";
3930
4045
  }
@@ -3934,12 +4049,12 @@ function detectInstallHost(repoRoot) {
3934
4049
  return explicit;
3935
4050
  if (!repoRoot)
3936
4051
  return "unknown";
3937
- const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
3938
- if (repoRoot === path6.join(codexHome, "skills", "unbrowse"))
4052
+ const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
4053
+ if (repoRoot === path7.join(codexHome, "skills", "unbrowse"))
3939
4054
  return "codex";
3940
- if (repoRoot === path6.join(getHomeDir(), ".claude", "skills", "unbrowse"))
4055
+ if (repoRoot === path7.join(getHomeDir(), ".claude", "skills", "unbrowse"))
3941
4056
  return "claude";
3942
- if (repoRoot === path6.join(getHomeDir(), "unbrowse"))
4057
+ if (repoRoot === path7.join(getHomeDir(), "unbrowse"))
3943
4058
  return "off";
3944
4059
  return "unknown";
3945
4060
  }
@@ -3967,14 +4082,14 @@ function commandIncludesHook(command, marker) {
3967
4082
  return typeof command === "string" && command.includes(marker);
3968
4083
  }
3969
4084
  function getCodexConfigPath() {
3970
- const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
3971
- return path6.join(codexHome, "config.toml");
4085
+ const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
4086
+ return path7.join(codexHome, "config.toml");
3972
4087
  }
3973
4088
  function getClaudeSettingsPath() {
3974
- return path6.join(getHomeDir(), ".claude", "settings.json");
4089
+ return path7.join(getHomeDir(), ".claude", "settings.json");
3975
4090
  }
3976
4091
  function getHookScriptPath(metaUrl) {
3977
- return path6.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
4092
+ return path7.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
3978
4093
  }
3979
4094
  function ensureCodexHooksFeature(content) {
3980
4095
  if (/\bcodex_hooks\s*=\s*true\b/.test(content))
@@ -3991,14 +4106,14 @@ codex_hooks = true
3991
4106
  `;
3992
4107
  }
3993
4108
  function writeCodexHook(metaUrl) {
3994
- const configPath = getCodexConfigPath();
3995
- if (!existsSync13(path6.dirname(configPath))) {
3996
- return { host: "codex", action: "not-detected", config_file: configPath };
4109
+ const configPath2 = getCodexConfigPath();
4110
+ if (!existsSync13(path7.dirname(configPath2))) {
4111
+ return { host: "codex", action: "not-detected", config_file: configPath2 };
3997
4112
  }
3998
4113
  try {
3999
4114
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
4000
- const fileExistsBefore = existsSync13(configPath);
4001
- let content = fileExistsBefore ? readFileSync8(configPath, "utf8") : "";
4115
+ const fileExistsBefore = existsSync13(configPath2);
4116
+ let content = fileExistsBefore ? readFileSync8(configPath2, "utf8") : "";
4002
4117
  const previous = content;
4003
4118
  content = ensureCodexHooksFeature(content);
4004
4119
  if (!content.includes("unbrowse-update-hint.mjs")) {
@@ -4013,26 +4128,26 @@ command = ${JSON.stringify(command)}
4013
4128
  `;
4014
4129
  }
4015
4130
  if (content !== previous) {
4016
- writeFileSync5(configPath, content, "utf8");
4131
+ writeFileSync5(configPath2, content, "utf8");
4017
4132
  return {
4018
4133
  host: "codex",
4019
4134
  action: fileExistsBefore ? "updated" : "installed",
4020
- config_file: configPath
4135
+ config_file: configPath2
4021
4136
  };
4022
4137
  }
4023
- return { host: "codex", action: "already-installed", config_file: configPath };
4138
+ return { host: "codex", action: "already-installed", config_file: configPath2 };
4024
4139
  } catch (error) {
4025
4140
  return {
4026
4141
  host: "codex",
4027
4142
  action: "failed",
4028
- config_file: configPath,
4143
+ config_file: configPath2,
4029
4144
  message: error instanceof Error ? error.message : String(error)
4030
4145
  };
4031
4146
  }
4032
4147
  }
4033
4148
  function writeClaudeHook(metaUrl) {
4034
4149
  const settingsPath = getClaudeSettingsPath();
4035
- if (!existsSync13(path6.dirname(settingsPath))) {
4150
+ if (!existsSync13(path7.dirname(settingsPath))) {
4036
4151
  return { host: "claude", action: "not-detected", config_file: settingsPath };
4037
4152
  }
4038
4153
  try {
@@ -4092,18 +4207,18 @@ function detectPackageManagers() {
4092
4207
  }
4093
4208
  function resolveConfigHome() {
4094
4209
  if (process.platform === "win32") {
4095
- return process.env.APPDATA || path7.join(os4.homedir(), "AppData", "Roaming");
4210
+ return process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming");
4096
4211
  }
4097
- return process.env.XDG_CONFIG_HOME || path7.join(os4.homedir(), ".config");
4212
+ return process.env.XDG_CONFIG_HOME || path8.join(os5.homedir(), ".config");
4098
4213
  }
4099
4214
  function getOpenCodeGlobalCommandsDir() {
4100
- return path7.join(resolveConfigHome(), "opencode", "commands");
4215
+ return path8.join(resolveConfigHome(), "opencode", "commands");
4101
4216
  }
4102
4217
  function getOpenCodeProjectCommandsDir(cwd) {
4103
- return path7.join(cwd, ".opencode", "commands");
4218
+ return path8.join(cwd, ".opencode", "commands");
4104
4219
  }
4105
4220
  function detectOpenCode(cwd) {
4106
- return hasBinary("opencode") || existsSync14(path7.join(resolveConfigHome(), "opencode")) || existsSync14(path7.join(cwd, ".opencode"));
4221
+ return hasBinary("opencode") || existsSync14(path8.join(resolveConfigHome(), "opencode")) || existsSync14(path8.join(cwd, ".opencode"));
4107
4222
  }
4108
4223
  function renderOpenCodeCommand() {
4109
4224
  return `---
@@ -4131,12 +4246,12 @@ function writeOpenCodeCommand(scope, cwd) {
4131
4246
  if (scope === "auto" && !detected) {
4132
4247
  return { detected: false, action: "not-detected", scope: "off" };
4133
4248
  }
4134
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path7.join(cwd, ".opencode")) ? "project" : "global";
4249
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path8.join(cwd, ".opencode")) ? "project" : "global";
4135
4250
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
4136
- const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
4251
+ const commandFile = path8.join(ensureDir2(commandsDir), "unbrowse.md");
4137
4252
  const content = renderOpenCodeCommand();
4138
4253
  const action2 = existsSync14(commandFile) ? "updated" : "installed";
4139
- mkdirSync8(path7.dirname(commandFile), { recursive: true });
4254
+ mkdirSync8(path8.dirname(commandFile), { recursive: true });
4140
4255
  writeFileSync6(commandFile, content);
4141
4256
  return {
4142
4257
  detected: detected || scope !== "auto",
@@ -4150,7 +4265,7 @@ async function ensureBrowserEngineInstalled() {
4150
4265
  if (existsSync14(binary)) {
4151
4266
  return { installed: true, action: "already-installed" };
4152
4267
  }
4153
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path7.join(candidate, "build.zig")));
4268
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path8.join(candidate, "build.zig")));
4154
4269
  if (!sourceDir) {
4155
4270
  return {
4156
4271
  installed: false,
@@ -4197,7 +4312,7 @@ async function runSetup(options) {
4197
4312
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
4198
4313
  const walletCheck = checkWalletConfigured();
4199
4314
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
4200
- let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
4315
+ let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os5.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
4201
4316
  if (!skipWalletSetup && !walletCheck.configured) {
4202
4317
  if (!lobsterInstalled) {
4203
4318
  console.log("[unbrowse] Setting up Crossmint wallet (required for earning + payments)...");
@@ -4237,7 +4352,7 @@ async function runSetup(options) {
4237
4352
  return {
4238
4353
  os: {
4239
4354
  platform: process.platform,
4240
- release: os4.release(),
4355
+ release: os5.release(),
4241
4356
  arch: process.arch
4242
4357
  },
4243
4358
  host_environment: hostEnv,
@@ -4252,17 +4367,17 @@ async function runSetup(options) {
4252
4367
  // ../../src/runtime/update-hints.ts
4253
4368
  init_paths();
4254
4369
  import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
4255
- import os5 from "node:os";
4256
- import path8 from "node:path";
4370
+ import os6 from "node:os";
4371
+ import path9 from "node:path";
4257
4372
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
4258
4373
  var DEFAULT_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
4259
4374
  function getHomeDir2() {
4260
- return process.env.HOME || os5.homedir();
4375
+ return process.env.HOME || os6.homedir();
4261
4376
  }
4262
4377
  function getConfigDir3() {
4263
4378
  if (process.env.UNBROWSE_CONFIG_DIR)
4264
4379
  return process.env.UNBROWSE_CONFIG_DIR;
4265
- return path8.join(getHomeDir2(), ".unbrowse");
4380
+ return path9.join(getHomeDir2(), ".unbrowse");
4266
4381
  }
4267
4382
  function ensureDir4(dir) {
4268
4383
  if (!existsSync15(dir))
@@ -4277,23 +4392,23 @@ function readJsonFile2(file) {
4277
4392
  }
4278
4393
  }
4279
4394
  function writeJsonFile2(file, value) {
4280
- ensureDir4(path8.dirname(file));
4395
+ ensureDir4(path9.dirname(file));
4281
4396
  writeFileSync7(file, `${JSON.stringify(value, null, 2)}
4282
4397
  `);
4283
4398
  }
4284
4399
  function getInstallSourcePath2() {
4285
- return path8.join(getConfigDir3(), "install-source.json");
4400
+ return path9.join(getConfigDir3(), "install-source.json");
4286
4401
  }
4287
4402
  function getUpdateCheckStatePath() {
4288
- return path8.join(getConfigDir3(), "update-check.json");
4403
+ return path9.join(getConfigDir3(), "update-check.json");
4289
4404
  }
4290
4405
  function detectRepoRoot2(start2) {
4291
- let dir = path8.resolve(start2);
4292
- const root = path8.parse(dir).root;
4406
+ let dir = path9.resolve(start2);
4407
+ const root = path9.parse(dir).root;
4293
4408
  while (dir !== root) {
4294
- if (existsSync15(path8.join(dir, ".git")))
4409
+ if (existsSync15(path9.join(dir, ".git")))
4295
4410
  return dir;
4296
- dir = path8.dirname(dir);
4411
+ dir = path9.dirname(dir);
4297
4412
  }
4298
4413
  return;
4299
4414
  }
@@ -4302,7 +4417,7 @@ function detectInstallMethod2(packageRoot) {
4302
4417
  return "repo-clone";
4303
4418
  if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
4304
4419
  return "npm-global";
4305
- if (packageRoot.includes(`${path8.sep}node_modules${path8.sep}`))
4420
+ if (packageRoot.includes(`${path9.sep}node_modules${path9.sep}`))
4306
4421
  return "npm-global";
4307
4422
  return detectRepoRoot2(packageRoot) ? "repo-clone" : "unknown";
4308
4423
  }
@@ -4312,19 +4427,19 @@ function detectInstallHost2(repoRoot) {
4312
4427
  return explicit;
4313
4428
  if (!repoRoot)
4314
4429
  return "unknown";
4315
- const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir2(), ".codex");
4316
- if (repoRoot === path8.join(codexHome, "skills", "unbrowse"))
4430
+ const codexHome = process.env.CODEX_HOME || path9.join(getHomeDir2(), ".codex");
4431
+ if (repoRoot === path9.join(codexHome, "skills", "unbrowse"))
4317
4432
  return "codex";
4318
- if (repoRoot === path8.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
4433
+ if (repoRoot === path9.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
4319
4434
  return "claude";
4320
- if (repoRoot === path8.join(getHomeDir2(), "unbrowse"))
4435
+ if (repoRoot === path9.join(getHomeDir2(), "unbrowse"))
4321
4436
  return "off";
4322
4437
  return "unknown";
4323
4438
  }
4324
4439
  function getInstalledVersion(metaUrl) {
4325
4440
  const packageRoot = getPackageRoot(metaUrl);
4326
4441
  try {
4327
- const pkg = JSON.parse(readFileSync9(path8.join(packageRoot, "package.json"), "utf8"));
4442
+ const pkg = JSON.parse(readFileSync9(path9.join(packageRoot, "package.json"), "utf8"));
4328
4443
  return pkg.version ?? "unknown";
4329
4444
  } catch {
4330
4445
  return "unknown";
@@ -4420,6 +4535,81 @@ function recordUpdateHint(latestVersion) {
4420
4535
  });
4421
4536
  }
4422
4537
 
4538
+ // ../../src/cli-setup.ts
4539
+ init_contribution();
4540
+ import { createInterface as createInterface2 } from "node:readline";
4541
+ async function defaultReadChoice(allowed, dflt) {
4542
+ const isInteractive = !!process.stdin.isTTY && !!process.stdout.isTTY && process.env.UNBROWSE_NON_INTERACTIVE !== "1";
4543
+ if (!isInteractive)
4544
+ return dflt;
4545
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
4546
+ try {
4547
+ for (;; ) {
4548
+ const answer = await new Promise((resolve) => {
4549
+ rl.question(`Choice [${dflt}]: `, resolve);
4550
+ });
4551
+ const trimmed = answer.trim();
4552
+ if (!trimmed)
4553
+ return dflt;
4554
+ const n = parseInt(trimmed, 10);
4555
+ if (allowed.includes(n))
4556
+ return n;
4557
+ console.log(`Please enter ${allowed.join(", ")} (or press Enter for ${dflt}).`);
4558
+ }
4559
+ } finally {
4560
+ rl.close();
4561
+ }
4562
+ }
4563
+ async function promptContributionMode(opts = {}) {
4564
+ const log2 = opts.log ?? ((msg) => console.log(msg));
4565
+ const cfg = getContributionConfig();
4566
+ if (!opts.force && cfg.contribution.set_via && cfg.contribution.set_via !== "default") {
4567
+ return null;
4568
+ }
4569
+ log2("");
4570
+ log2("How do you want unbrowse to handle the routes you discover?");
4571
+ log2("");
4572
+ log2(" [1] Private (default) — cache locally, never publish");
4573
+ log2(" [2] Share — contribute pointers to the marketplace");
4574
+ log2(" [3] Share + earn — contribute and add a wallet for x402 rev-share");
4575
+ log2("");
4576
+ const reader = opts.readChoice ?? defaultReadChoice;
4577
+ const choice = await reader([1, 2, 3], 1);
4578
+ const set_via = opts.force ? "mode-command" : "setup-prompt";
4579
+ if (choice === 1) {
4580
+ setContributionConfig({
4581
+ contribution: { share_pointers: false, set_via },
4582
+ rev_share: { opted_in: false }
4583
+ });
4584
+ log2("Private mode set. Run `unbrowse mode` anytime to change.");
4585
+ } else if (choice === 2) {
4586
+ setContributionConfig({
4587
+ contribution: { share_pointers: true, set_via },
4588
+ rev_share: { opted_in: false }
4589
+ });
4590
+ log2("Sharing pointers enabled. Add a wallet via `unbrowse mode` to opt into rev-share.");
4591
+ } else {
4592
+ setContributionConfig({
4593
+ contribution: { share_pointers: true, set_via },
4594
+ rev_share: { opted_in: true }
4595
+ });
4596
+ log2("Sharing + rev-share enabled. Run `unbrowse wallet` to add your wallet for payouts.");
4597
+ }
4598
+ return choice;
4599
+ }
4600
+ function maybeShowContributionNotice() {
4601
+ const cfg = getContributionConfig();
4602
+ if (cfg.contribution.set_via !== "default")
4603
+ return false;
4604
+ const remaining = cfg.notice_shown_count ?? 0;
4605
+ if (remaining <= 0)
4606
+ return false;
4607
+ process.stderr.write("[unbrowse] contribution mode is now `private` by default — captures stay on your machine.\n" + `[unbrowse] run \`unbrowse mode\` to opt into sharing + rev-share. (notice will repeat ${remaining - 1} more time${remaining - 1 === 1 ? "" : "s"})
4608
+ `);
4609
+ decrementNoticeCounter();
4610
+ return true;
4611
+ }
4612
+
4423
4613
  // ../../src/cli.ts
4424
4614
  loadEnv({ quiet: true });
4425
4615
  loadEnv({ path: ".env.runtime", quiet: true });
@@ -4465,8 +4655,8 @@ function parseArgs(argv) {
4465
4655
  }
4466
4656
  return { command, args: positional, flags, params };
4467
4657
  }
4468
- async function api2(method, path9, body) {
4469
- let target = `${BASE_URL}${path9}`;
4658
+ async function api2(method, path10, body) {
4659
+ let target = `${BASE_URL}${path10}`;
4470
4660
  let requestBody = body;
4471
4661
  if (method === "GET" && body && typeof body === "object") {
4472
4662
  const params = new URLSearchParams;
@@ -4566,6 +4756,8 @@ function slimTrace(obj) {
4566
4756
  out.provider = obj.provider;
4567
4757
  if ("result" in obj)
4568
4758
  out.result = obj.result;
4759
+ if (Array.isArray(obj.decision_trace))
4760
+ out.decision_trace = obj.decision_trace;
4569
4761
  if (obj.available_endpoints)
4570
4762
  out.available_endpoints = obj.available_endpoints;
4571
4763
  if (obj.impact)
@@ -4706,6 +4898,7 @@ async function cmdResolve(flags) {
4706
4898
  const intent = flags.intent;
4707
4899
  if (!intent)
4708
4900
  die("--intent is required");
4901
+ maybeShowContributionNotice();
4709
4902
  const hostType = detectTelemetryHostType();
4710
4903
  await ensureCliInstallTracked(hostType);
4711
4904
  await recordFunnelTelemetryEvent("cli_invoked", {
@@ -4767,6 +4960,14 @@ async function cmdResolve(flags) {
4767
4960
  body.force_capture = true;
4768
4961
  if (flags["skip-robots"])
4769
4962
  body.skip_robots_check = true;
4963
+ const budgetFlag = flags.budget;
4964
+ if (typeof budgetFlag === "string") {
4965
+ const parsed = parseInt(budgetFlag, 10);
4966
+ if (Number.isFinite(parsed) && parsed > 0)
4967
+ body.budget_ms = parsed;
4968
+ } else if (typeof budgetFlag === "number" && Number.isFinite(budgetFlag) && budgetFlag > 0) {
4969
+ body.budget_ms = budgetFlag;
4970
+ }
4770
4971
  body.projection = { raw: true };
4771
4972
  const startedAt = Date.now();
4772
4973
  async function resolveOnce(message = "Still working. Searching cached routes...") {
@@ -4853,8 +5054,8 @@ async function cmdResolve(flags) {
4853
5054
  throw error;
4854
5055
  }
4855
5056
  }
4856
- function drillPath(data, path9) {
4857
- const segments = path9.split(/\./).flatMap((s) => {
5057
+ function drillPath(data, path10) {
5058
+ const segments = path10.split(/\./).flatMap((s) => {
4858
5059
  const m = s.match(/^(.+)\[\]$/);
4859
5060
  return m ? [m[1], "[]"] : [s];
4860
5061
  });
@@ -4881,9 +5082,9 @@ function drillPath(data, path9) {
4881
5082
  }
4882
5083
  return values;
4883
5084
  }
4884
- function resolveDotPath(obj, path9) {
5085
+ function resolveDotPath(obj, path10) {
4885
5086
  let cur = obj;
4886
- for (const key of path9.split(".")) {
5087
+ for (const key of path10.split(".")) {
4887
5088
  if (cur == null || typeof cur !== "object")
4888
5089
  return;
4889
5090
  cur = cur[key];
@@ -4900,8 +5101,8 @@ function applyExtract(items, extractSpec) {
4900
5101
  return items.map((item) => {
4901
5102
  const row = {};
4902
5103
  let hasValue = false;
4903
- for (const { alias, path: path9 } of fields) {
4904
- const val = resolveDotPath(item, path9);
5104
+ for (const { alias, path: path10 } of fields) {
5105
+ const val = resolveDotPath(item, path10);
4905
5106
  row[alias] = val ?? null;
4906
5107
  if (val != null)
4907
5108
  hasValue = true;
@@ -4932,6 +5133,7 @@ async function cmdExecute(flags) {
4932
5133
  const skillId = flags.skill;
4933
5134
  if (!skillId)
4934
5135
  die("--skill is required");
5136
+ maybeShowContributionNotice();
4935
5137
  const hostType = detectTelemetryHostType();
4936
5138
  await ensureCliInstallTracked(hostType);
4937
5139
  await recordFunnelTelemetryEvent("cli_invoked", {
@@ -5373,6 +5575,35 @@ async function cmdSetup(flags) {
5373
5575
  info(' unbrowse resolve --intent "list posts" --url "https://jsonplaceholder.typicode.com"');
5374
5576
  }
5375
5577
  }
5578
+ async function cmdMode(_flags) {
5579
+ await promptContributionMode({ force: true });
5580
+ }
5581
+ async function runPostSetupContributionPrompt() {
5582
+ try {
5583
+ await promptContributionMode({ force: false });
5584
+ } catch {}
5585
+ }
5586
+ async function cmdCapture(flags) {
5587
+ const url = flags.url;
5588
+ const intent = flags.intent || "capture";
5589
+ if (!url)
5590
+ die("--url is required");
5591
+ maybeShowContributionNotice();
5592
+ const t0 = Date.now();
5593
+ const result = await api2("POST", "/v1/capture", { url, intent });
5594
+ const endpoints = Array.isArray(result.endpoints) ? result.endpoints : Array.isArray(result.available_endpoints) ? result.available_endpoints : [];
5595
+ const skill = result.skill ?? null;
5596
+ const skillId = result.skill_id ?? skill?.skill_id ?? (typeof result.learned_skill_id === "string" ? result.learned_skill_id : undefined);
5597
+ const envelope = {
5598
+ skill_id: skillId,
5599
+ endpoints_discovered: typeof result.endpoints_discovered === "number" ? result.endpoints_discovered : endpoints.length,
5600
+ marketplace_published: !!result.marketplace_published,
5601
+ ms: typeof result.ms === "number" ? result.ms : Date.now() - t0,
5602
+ next_step: endpoints.length > 0 ? `unbrowse resolve --intent "${intent.replace(/"/g, "\\\"")}" --url "${url.replace(/"/g, "\\\"")}"` : "no endpoints discovered; site may need authentication or different intent",
5603
+ ...result.error ? { error: result.error } : {}
5604
+ };
5605
+ output(envelope, !!flags.pretty);
5606
+ }
5376
5607
  var CLI_REFERENCE = {
5377
5608
  commands: [
5378
5609
  { name: "health", usage: "", desc: "Server health check" },
@@ -5417,7 +5648,9 @@ var CLI_REFERENCE = {
5417
5648
  { name: "earnings", usage: "[--json]", desc: "Show your credit balance, earnings from indexing, and spending" },
5418
5649
  { name: "corpus-test", usage: "--url <url> [--id <id>] [--retries N]", desc: "Capture a single URL with retry logic; keeps best result across N attempts" },
5419
5650
  { name: "corpus-run", usage: "--corpus <file> --out <file> [--retries N]", desc: "Run corpus-test over all cases in a corpus JSON file and write a comparable snapshot" },
5420
- { name: "register", usage: "[--no-prompt]", desc: "Optional: register an API key to publish skills, check earnings, and access backend analytics" }
5651
+ { name: "register", usage: "[--no-prompt]", desc: "Optional: register an API key to publish skills, check earnings, and access backend analytics" },
5652
+ { name: "mode", usage: "", desc: "Re-prompt for contribution mode (private / share / share + earn)" },
5653
+ { name: "capture", usage: "--url <url> --intent <intent>", desc: "Live-browser capture for a single URL \u2014 discovers + indexes API endpoints. Marketplace publish gated by `unbrowse mode`." }
5421
5654
  ],
5422
5655
  globalFlags: [
5423
5656
  { flag: "--pretty", desc: "Indented JSON output" },
@@ -5874,6 +6107,7 @@ async function cmdGo(args, flags) {
5874
6107
  const url = args[0] ?? flags.url;
5875
6108
  if (!url)
5876
6109
  die("Usage: unbrowse go <url>");
6110
+ maybeShowContributionNotice();
5877
6111
  output(await api2("POST", "/v1/browse/go", {
5878
6112
  url,
5879
6113
  ...typeof flags.session === "string" ? { session_id: flags.session } : {}
@@ -6300,6 +6534,11 @@ async function main() {
6300
6534
  }
6301
6535
  if (command === "setup") {
6302
6536
  await cmdSetup(flags);
6537
+ await runPostSetupContributionPrompt();
6538
+ return;
6539
+ }
6540
+ if (command === "mode") {
6541
+ await cmdMode(flags);
6303
6542
  return;
6304
6543
  }
6305
6544
  if (command === "mcp")
@@ -6378,7 +6617,9 @@ async function main() {
6378
6617
  "corpus-run",
6379
6618
  "sessions-scan",
6380
6619
  "cache-clear",
6381
- "register"
6620
+ "register",
6621
+ "mode",
6622
+ "capture"
6382
6623
  ]);
6383
6624
  if (!KNOWN_COMMANDS.has(command)) {
6384
6625
  const pack = findSitePack(command);
@@ -6492,6 +6733,10 @@ async function main() {
6492
6733
  return cmdSessionsScan(flags);
6493
6734
  case "register":
6494
6735
  return cmdRegister(flags);
6736
+ case "mode":
6737
+ return cmdMode(flags);
6738
+ case "capture":
6739
+ return cmdCapture(flags);
6495
6740
  default:
6496
6741
  info(`Unknown command: ${command}`);
6497
6742
  printHelp();