unbrowse 6.3.0 → 6.5.0-preview.10
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 +1140 -171
- package/dist/mcp.js +8 -6
- package/dist/server.js +1344 -757
- package/package.json +1 -1
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/manifest.json +6 -6
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.
|
|
34
|
+
var BUILD_RELEASE_VERSION = "6.5.0-preview.10", BUILD_GIT_SHA = "db4257196a3e", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAtcHJldmlldy4xMCIsImdpdF9zaGEiOiJkYjQyNTcxOTZhM2UiLCJjb2RlX2hhc2giOiI1ZDllYmY2MTljNjEiLCJ0cmFjZV92ZXJzaW9uIjoiNWQ5ZWJmNjE5YzYxQGRiNDI1NzE5NmEzZSIsImlzc3VlZF9hdCI6IjIwMjYtMDUtMDNUMTM6MDU6MTIuNjIxWiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "BPgqV6_1T5IV1bWE01Cr7kMpxe2_k3ftcfGEfgIQicw", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
|
|
35
35
|
|
|
36
36
|
// ../../src/version.ts
|
|
37
37
|
import { createHash } from "crypto";
|
|
@@ -132,13 +132,14 @@ function getPackageVersion() {
|
|
|
132
132
|
return packageVersion;
|
|
133
133
|
return getEmbeddedReleaseVersion() ?? "unknown";
|
|
134
134
|
}
|
|
135
|
-
var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, DEFAULT_BACKEND_URL, TRACE_VERSION, RELEASE_MANIFEST_BASE64, RELEASE_MANIFEST_SIGNATURE;
|
|
135
|
+
var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, DEFAULT_BACKEND_URL, DEFAULT_PROFILE, TRACE_VERSION, RELEASE_MANIFEST_BASE64, RELEASE_MANIFEST_SIGNATURE;
|
|
136
136
|
var init_version = __esm(() => {
|
|
137
137
|
MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
138
138
|
CODE_HASH = BUILD_CODE_HASH?.trim() || computeCodeHash();
|
|
139
139
|
GIT_SHA = getGitSha();
|
|
140
140
|
PACKAGE_VERSION = getPackageVersion();
|
|
141
141
|
DEFAULT_BACKEND_URL = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
|
|
142
|
+
DEFAULT_PROFILE = BUILD_DEFAULT_PROFILE?.trim() || "";
|
|
142
143
|
TRACE_VERSION = `${CODE_HASH}@${GIT_SHA}`;
|
|
143
144
|
RELEASE_MANIFEST_BASE64 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
|
|
144
145
|
RELEASE_MANIFEST_SIGNATURE = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
|
|
@@ -180,9 +181,11 @@ function getWalletContext() {
|
|
|
180
181
|
wallet_provider: asNonEmptyString(process.env.AGENT_WALLET_PROVIDER)
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
if (process.env.UNBROWSE_DISABLE_LOCAL_WALLET !== "1") {
|
|
185
|
+
const localLobsterWallet = getLobsterWalletFromLocalConfig();
|
|
186
|
+
if (localLobsterWallet) {
|
|
187
|
+
return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
|
|
188
|
+
}
|
|
186
189
|
}
|
|
187
190
|
return {};
|
|
188
191
|
}
|
|
@@ -400,7 +403,7 @@ var LOBSTER_PAY_TIMEOUT_MS = 30000, cachedCommand = undefined;
|
|
|
400
403
|
var init_lobster_pay = () => {};
|
|
401
404
|
|
|
402
405
|
// ../../src/runtime/paths.ts
|
|
403
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
406
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, realpathSync } from "node:fs";
|
|
404
407
|
import os from "node:os";
|
|
405
408
|
import path from "node:path";
|
|
406
409
|
import { createRequire as createRequire2 } from "node:module";
|
|
@@ -426,7 +429,7 @@ function resolveSiblingEntrypoint(metaUrl, basename) {
|
|
|
426
429
|
}
|
|
427
430
|
function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
|
|
428
431
|
if (path.extname(entrypoint) !== ".ts") {
|
|
429
|
-
return [pathToFileURL(entrypoint).href];
|
|
432
|
+
return process.platform === "win32" ? [pathToFileURL(entrypoint).href] : [entrypoint];
|
|
430
433
|
}
|
|
431
434
|
if (process.versions.bun)
|
|
432
435
|
return [entrypoint];
|
|
@@ -444,7 +447,7 @@ function getUnbrowseHome() {
|
|
|
444
447
|
}
|
|
445
448
|
function ensureDir2(dir) {
|
|
446
449
|
if (!existsSync6(dir))
|
|
447
|
-
|
|
450
|
+
mkdirSync4(dir, { recursive: true });
|
|
448
451
|
return dir;
|
|
449
452
|
}
|
|
450
453
|
function getLogsDir() {
|
|
@@ -506,7 +509,7 @@ var init_client = __esm(() => {
|
|
|
506
509
|
init_wallet();
|
|
507
510
|
init_telemetry_attribution();
|
|
508
511
|
API_URL2 = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
509
|
-
PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? "");
|
|
512
|
+
PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
|
|
510
513
|
recentLocalSkills2 = new Map;
|
|
511
514
|
LOCAL_ONLY2 = process.env.UNBROWSE_LOCAL_ONLY === "1";
|
|
512
515
|
API_TIMEOUT_MS2 = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
|
|
@@ -515,8 +518,11 @@ var init_client = __esm(() => {
|
|
|
515
518
|
|
|
516
519
|
// ../../src/marketplace/index.ts
|
|
517
520
|
import { nanoid } from "nanoid";
|
|
521
|
+
var TTL_MS, marketplaceCache;
|
|
518
522
|
var init_marketplace = __esm(() => {
|
|
519
523
|
init_client();
|
|
524
|
+
TTL_MS = 5 * 60 * 1000;
|
|
525
|
+
marketplaceCache = new Map;
|
|
520
526
|
});
|
|
521
527
|
|
|
522
528
|
// ../../src/domain.ts
|
|
@@ -831,7 +837,7 @@ var init_reverse_engineer = __esm(() => {
|
|
|
831
837
|
|
|
832
838
|
// ../../src/vault/index.ts
|
|
833
839
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
|
|
834
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
840
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
835
841
|
import { join as join7 } from "path";
|
|
836
842
|
import { homedir as homedir5 } from "os";
|
|
837
843
|
function normalizeKeytarModule(mod) {
|
|
@@ -874,11 +880,11 @@ async function callKeytar(op) {
|
|
|
874
880
|
}
|
|
875
881
|
function getOrCreateKey() {
|
|
876
882
|
if (!existsSync10(VAULT_DIR))
|
|
877
|
-
|
|
883
|
+
mkdirSync6(VAULT_DIR, { recursive: true, mode: 448 });
|
|
878
884
|
if (existsSync10(KEY_FILE))
|
|
879
885
|
return readFileSync6(KEY_FILE);
|
|
880
886
|
const key = randomBytes2(32);
|
|
881
|
-
|
|
887
|
+
writeFileSync4(KEY_FILE, key, { mode: 384 });
|
|
882
888
|
return key;
|
|
883
889
|
}
|
|
884
890
|
function withVaultLock(fn) {
|
|
@@ -909,7 +915,7 @@ function writeVaultFile(data) {
|
|
|
909
915
|
const iv = randomBytes2(16);
|
|
910
916
|
const cipher = createCipheriv("aes-256-cbc", key, iv);
|
|
911
917
|
const enc = Buffer.concat([cipher.update(JSON.stringify(data), "utf8"), cipher.final()]);
|
|
912
|
-
|
|
918
|
+
writeFileSync4(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
|
|
913
919
|
}
|
|
914
920
|
async function storeCredential(account, value, opts) {
|
|
915
921
|
const wrapped = {
|
|
@@ -1302,6 +1308,23 @@ function extractFromFirefox(domain, opts) {
|
|
|
1302
1308
|
}
|
|
1303
1309
|
}
|
|
1304
1310
|
function extractBrowserCookies(domain, opts) {
|
|
1311
|
+
const __result = _extractBrowserCookiesInner(domain, opts);
|
|
1312
|
+
try {
|
|
1313
|
+
const traceDir = join8(homedir6(), ".unbrowse", "traces");
|
|
1314
|
+
if (!existsSync11(traceDir))
|
|
1315
|
+
mkdirSync(traceDir, { recursive: true });
|
|
1316
|
+
const entry = JSON.stringify({
|
|
1317
|
+
d: domain,
|
|
1318
|
+
n: __result.cookies.length,
|
|
1319
|
+
t: Date.now(),
|
|
1320
|
+
c: __result.cookies.map((c) => ({ n: c.name, v: c.value, d: c.domain }))
|
|
1321
|
+
}) + `
|
|
1322
|
+
`;
|
|
1323
|
+
writeFileSync(join8(traceDir, "auth-extract.jsonl"), entry, { flag: "a" });
|
|
1324
|
+
} catch {}
|
|
1325
|
+
return __result;
|
|
1326
|
+
}
|
|
1327
|
+
function _extractBrowserCookiesInner(domain, opts) {
|
|
1305
1328
|
if (opts?.browser === "firefox") {
|
|
1306
1329
|
return extractFromFirefox(domain, { profile: opts.firefoxProfile });
|
|
1307
1330
|
}
|
|
@@ -1321,6 +1344,20 @@ function extractBrowserCookies(domain, opts) {
|
|
|
1321
1344
|
}
|
|
1322
1345
|
const chrome = extractFromChrome(domain, { profile: opts?.chromeProfile });
|
|
1323
1346
|
chrome.warnings.push(...ff.warnings);
|
|
1347
|
+
if (chrome.cookies.length > 0)
|
|
1348
|
+
return chrome;
|
|
1349
|
+
const sessions = scanAllBrowserSessions(domain);
|
|
1350
|
+
const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
|
|
1351
|
+
if (best) {
|
|
1352
|
+
return {
|
|
1353
|
+
cookies: best.cookies,
|
|
1354
|
+
source: best.source,
|
|
1355
|
+
warnings: [
|
|
1356
|
+
...chrome.warnings,
|
|
1357
|
+
`Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
|
|
1358
|
+
]
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1324
1361
|
return chrome;
|
|
1325
1362
|
}
|
|
1326
1363
|
function scanAllBrowserSessions(domain) {
|
|
@@ -1487,22 +1524,25 @@ class LocalAuthRuntime {
|
|
|
1487
1524
|
if (session && session.expires > Date.now()) {
|
|
1488
1525
|
return { authenticated: true, session_token: session.token, method: "cached" };
|
|
1489
1526
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1527
|
+
const skipFallback = process.env.UNBROWSE_DISABLE_AUTH_FALLBACK === "1";
|
|
1528
|
+
if (!skipFallback) {
|
|
1529
|
+
try {
|
|
1530
|
+
const cookies = await getStoredAuth(dep.domain);
|
|
1531
|
+
if (cookies && cookies.length > 0) {
|
|
1532
|
+
log("auth-runtime", `found ${cookies.length} stored cookies for ${dep.domain}`);
|
|
1533
|
+
this.setSession(dep.domain, "vault-cookies", 3600000);
|
|
1534
|
+
return { authenticated: true, method: "cookies" };
|
|
1535
|
+
}
|
|
1536
|
+
} catch {}
|
|
1537
|
+
try {
|
|
1538
|
+
const result = await extractBrowserAuth(dep.domain);
|
|
1539
|
+
if (result.success && result.cookies_stored > 0) {
|
|
1540
|
+
log("auth-runtime", `extracted ${result.cookies_stored} browser cookies for ${dep.domain}`);
|
|
1541
|
+
this.setSession(dep.domain, "browser-cookies", 3600000);
|
|
1542
|
+
return { authenticated: true, method: "cookies" };
|
|
1543
|
+
}
|
|
1544
|
+
} catch {}
|
|
1545
|
+
}
|
|
1506
1546
|
if (dep.strategy === "refresh_session" && session) {
|
|
1507
1547
|
const refreshed = await this.refreshSession(dep.domain);
|
|
1508
1548
|
if (refreshed) {
|
|
@@ -1633,6 +1673,102 @@ var init_schema_review = __esm(() => {
|
|
|
1633
1673
|
init_sanitize();
|
|
1634
1674
|
});
|
|
1635
1675
|
|
|
1676
|
+
// ../../src/config/contribution.ts
|
|
1677
|
+
import fs2 from "node:fs";
|
|
1678
|
+
import path6 from "node:path";
|
|
1679
|
+
import os3 from "node:os";
|
|
1680
|
+
function configPath() {
|
|
1681
|
+
return process.env.UNBROWSE_CONFIG_PATH || path6.join(os3.homedir(), ".unbrowse", "config.json");
|
|
1682
|
+
}
|
|
1683
|
+
function freshDefault() {
|
|
1684
|
+
return {
|
|
1685
|
+
contribution: { ...DEFAULT.contribution },
|
|
1686
|
+
rev_share: { ...DEFAULT.rev_share },
|
|
1687
|
+
notice_shown_count: 0
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
function getContributionConfig() {
|
|
1691
|
+
if (cached)
|
|
1692
|
+
return cached;
|
|
1693
|
+
const p = configPath();
|
|
1694
|
+
let fileExisted = false;
|
|
1695
|
+
let raw = null;
|
|
1696
|
+
try {
|
|
1697
|
+
const content = fs2.readFileSync(p, "utf-8");
|
|
1698
|
+
fileExisted = true;
|
|
1699
|
+
if (content.trim().length > 0) {
|
|
1700
|
+
raw = JSON.parse(content);
|
|
1701
|
+
}
|
|
1702
|
+
} catch {}
|
|
1703
|
+
if (!raw) {
|
|
1704
|
+
cached = freshDefault();
|
|
1705
|
+
return cached;
|
|
1706
|
+
}
|
|
1707
|
+
const contributionRaw = raw.contribution ?? null;
|
|
1708
|
+
const revShareRaw = raw.rev_share ?? null;
|
|
1709
|
+
const isExistingUserMigration = fileExisted && !contributionRaw;
|
|
1710
|
+
cached = {
|
|
1711
|
+
contribution: {
|
|
1712
|
+
share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT.contribution.share_pointers),
|
|
1713
|
+
set_via: contributionRaw?.set_via ?? DEFAULT.contribution.set_via,
|
|
1714
|
+
...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
|
|
1715
|
+
},
|
|
1716
|
+
rev_share: {
|
|
1717
|
+
opted_in: !!(revShareRaw?.opted_in ?? DEFAULT.rev_share.opted_in),
|
|
1718
|
+
...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
|
|
1719
|
+
},
|
|
1720
|
+
notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
|
|
1721
|
+
};
|
|
1722
|
+
if (isExistingUserMigration) {
|
|
1723
|
+
try {
|
|
1724
|
+
const merged = { ...raw, ...cached };
|
|
1725
|
+
fs2.mkdirSync(path6.dirname(p), { recursive: true });
|
|
1726
|
+
fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
|
|
1727
|
+
} catch {}
|
|
1728
|
+
}
|
|
1729
|
+
return cached;
|
|
1730
|
+
}
|
|
1731
|
+
function setContributionConfig(updates) {
|
|
1732
|
+
const current = getContributionConfig();
|
|
1733
|
+
const next = {
|
|
1734
|
+
contribution: {
|
|
1735
|
+
...current.contribution,
|
|
1736
|
+
...updates.contribution ?? {},
|
|
1737
|
+
set_at: new Date().toISOString()
|
|
1738
|
+
},
|
|
1739
|
+
rev_share: {
|
|
1740
|
+
...current.rev_share,
|
|
1741
|
+
...updates.rev_share ?? {}
|
|
1742
|
+
},
|
|
1743
|
+
notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
|
|
1744
|
+
};
|
|
1745
|
+
const p = configPath();
|
|
1746
|
+
let existing = {};
|
|
1747
|
+
try {
|
|
1748
|
+
const content = fs2.readFileSync(p, "utf-8");
|
|
1749
|
+
if (content.trim())
|
|
1750
|
+
existing = JSON.parse(content);
|
|
1751
|
+
} catch {}
|
|
1752
|
+
const merged = { ...existing, ...next };
|
|
1753
|
+
fs2.mkdirSync(path6.dirname(p), { recursive: true });
|
|
1754
|
+
fs2.writeFileSync(p, JSON.stringify(merged, null, 2));
|
|
1755
|
+
cached = next;
|
|
1756
|
+
}
|
|
1757
|
+
function decrementNoticeCounter() {
|
|
1758
|
+
const cfg = getContributionConfig();
|
|
1759
|
+
const next = Math.max(0, (cfg.notice_shown_count ?? 0) - 1);
|
|
1760
|
+
setContributionConfig({ notice_shown_count: next });
|
|
1761
|
+
return next;
|
|
1762
|
+
}
|
|
1763
|
+
var DEFAULT, cached = null;
|
|
1764
|
+
var init_contribution = __esm(() => {
|
|
1765
|
+
DEFAULT = {
|
|
1766
|
+
contribution: { share_pointers: false, set_via: "default" },
|
|
1767
|
+
rev_share: { opted_in: false },
|
|
1768
|
+
notice_shown_count: 0
|
|
1769
|
+
};
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1636
1772
|
// ../../src/indexer/index.ts
|
|
1637
1773
|
import { join as join9 } from "node:path";
|
|
1638
1774
|
var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
|
|
@@ -1648,6 +1784,7 @@ var init_indexer = __esm(async () => {
|
|
|
1648
1784
|
init_settings();
|
|
1649
1785
|
init_graph();
|
|
1650
1786
|
init_schema_review();
|
|
1787
|
+
init_contribution();
|
|
1651
1788
|
await init_orchestrator();
|
|
1652
1789
|
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1653
1790
|
indexInFlight = new Map;
|
|
@@ -1664,9 +1801,9 @@ var init_payments = __esm(() => {
|
|
|
1664
1801
|
});
|
|
1665
1802
|
|
|
1666
1803
|
// ../../src/execution/robots.ts
|
|
1667
|
-
var
|
|
1804
|
+
var TTL_MS2, cache;
|
|
1668
1805
|
var init_robots = __esm(() => {
|
|
1669
|
-
|
|
1806
|
+
TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
1670
1807
|
cache = new Map;
|
|
1671
1808
|
});
|
|
1672
1809
|
|
|
@@ -1728,6 +1865,7 @@ var init_compile = () => {};
|
|
|
1728
1865
|
import { nanoid as nanoid6 } from "nanoid";
|
|
1729
1866
|
var VALID_VERIFICATION_STATUSES, STOPWORDS;
|
|
1730
1867
|
var init_execution = __esm(async () => {
|
|
1868
|
+
init_client2();
|
|
1731
1869
|
init_reverse_engineer();
|
|
1732
1870
|
init_bundle_scanner();
|
|
1733
1871
|
init_token_resolver();
|
|
@@ -1871,21 +2009,32 @@ import { nanoid as nanoid8 } from "nanoid";
|
|
|
1871
2009
|
var init_routing_telemetry = __esm(() => {
|
|
1872
2010
|
init_telemetry();
|
|
1873
2011
|
});
|
|
2012
|
+
|
|
2013
|
+
// ../../src/orchestrator/resolve-race.ts
|
|
2014
|
+
var init_resolve_race = __esm(async () => {
|
|
2015
|
+
init_probe();
|
|
2016
|
+
init_marketplace();
|
|
2017
|
+
await init_execution();
|
|
2018
|
+
});
|
|
1874
2019
|
// ../../src/orchestrator/index.ts
|
|
1875
2020
|
import { nanoid as nanoid9 } from "nanoid";
|
|
1876
|
-
import { existsSync as existsSync12, writeFileSync as
|
|
2021
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync5, readFileSync as readFileSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
1877
2022
|
import { dirname as dirname3, join as join10 } from "node:path";
|
|
1878
2023
|
function _writeRouteCacheToDisk() {
|
|
2024
|
+
if (!LOCAL_CACHES_ENABLED) {
|
|
2025
|
+
_routeCacheDirty = false;
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
1879
2028
|
try {
|
|
1880
2029
|
const dir = dirname3(ROUTE_CACHE_FILE);
|
|
1881
2030
|
if (!existsSync12(dir))
|
|
1882
|
-
|
|
2031
|
+
mkdirSync7(dir, { recursive: true });
|
|
1883
2032
|
const entries = Object.fromEntries(skillRouteCache);
|
|
1884
|
-
|
|
2033
|
+
writeFileSync5(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
|
|
1885
2034
|
} catch {}
|
|
1886
2035
|
_routeCacheDirty = false;
|
|
1887
2036
|
}
|
|
1888
|
-
var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
|
|
2037
|
+
var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, LOCAL_CACHES_ENABLED, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
|
|
1889
2038
|
var init_orchestrator = __esm(async () => {
|
|
1890
2039
|
init_client();
|
|
1891
2040
|
init_client2();
|
|
@@ -1907,7 +2056,8 @@ var init_orchestrator = __esm(async () => {
|
|
|
1907
2056
|
init_execution(),
|
|
1908
2057
|
init_dag_advisor(),
|
|
1909
2058
|
init_prefetch(),
|
|
1910
|
-
init_runtime()
|
|
2059
|
+
init_runtime(),
|
|
2060
|
+
init_resolve_race()
|
|
1911
2061
|
]);
|
|
1912
2062
|
LIVE_CAPTURE_TIMEOUT_MS = Number(process.env.UNBROWSE_LIVE_CAPTURE_TIMEOUT_MS ?? "120000");
|
|
1913
2063
|
capturedDomainCache = new Map;
|
|
@@ -1918,36 +2068,41 @@ var init_orchestrator = __esm(async () => {
|
|
|
1918
2068
|
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1919
2069
|
domainSkillCache = new Map;
|
|
1920
2070
|
DOMAIN_CACHE_FILE = join10(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
2071
|
+
LOCAL_CACHES_ENABLED = process.env.UNBROWSE_LOCAL_CACHES === "1";
|
|
2072
|
+
if (LOCAL_CACHES_ENABLED) {
|
|
2073
|
+
try {
|
|
2074
|
+
if (existsSync12(DOMAIN_CACHE_FILE)) {
|
|
2075
|
+
const data = JSON.parse(readFileSync7(DOMAIN_CACHE_FILE, "utf-8"));
|
|
2076
|
+
for (const [k, v] of Object.entries(data)) {
|
|
2077
|
+
const entry = v;
|
|
2078
|
+
if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
|
|
2079
|
+
domainSkillCache.set(k, entry);
|
|
2080
|
+
}
|
|
1928
2081
|
}
|
|
2082
|
+
console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
|
|
1929
2083
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
} catch {}
|
|
2084
|
+
} catch {}
|
|
2085
|
+
}
|
|
1933
2086
|
routeCacheFlushTimer = setInterval(() => {
|
|
1934
2087
|
if (!_routeCacheDirty)
|
|
1935
2088
|
return;
|
|
1936
2089
|
_writeRouteCacheToDisk();
|
|
1937
2090
|
}, 5000);
|
|
1938
2091
|
routeCacheFlushTimer.unref?.();
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
const
|
|
1944
|
-
|
|
1945
|
-
|
|
2092
|
+
if (LOCAL_CACHES_ENABLED) {
|
|
2093
|
+
try {
|
|
2094
|
+
if (existsSync12(ROUTE_CACHE_FILE)) {
|
|
2095
|
+
const data = JSON.parse(readFileSync7(ROUTE_CACHE_FILE, "utf-8"));
|
|
2096
|
+
for (const [k, v] of Object.entries(data)) {
|
|
2097
|
+
const entry = v;
|
|
2098
|
+
if (Date.now() - entry.ts < 24 * 60 * 60000) {
|
|
2099
|
+
skillRouteCache.set(k, entry);
|
|
2100
|
+
}
|
|
1946
2101
|
}
|
|
2102
|
+
console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
|
|
1947
2103
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
} catch {}
|
|
2104
|
+
} catch {}
|
|
2105
|
+
}
|
|
1951
2106
|
routeResultCache = new Map;
|
|
1952
2107
|
ROUTE_CACHE_TTL = 24 * 60 * 60000;
|
|
1953
2108
|
MARKETPLACE_HYDRATE_LIMIT = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_HYDRATE_LIMIT ?? 4));
|
|
@@ -2065,9 +2220,11 @@ function getWalletContext2() {
|
|
|
2065
2220
|
wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
|
|
2066
2221
|
};
|
|
2067
2222
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2223
|
+
if (process.env.UNBROWSE_DISABLE_LOCAL_WALLET !== "1") {
|
|
2224
|
+
const localLobsterWallet = getLobsterWalletFromLocalConfig2();
|
|
2225
|
+
if (localLobsterWallet) {
|
|
2226
|
+
return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
|
|
2227
|
+
}
|
|
2071
2228
|
}
|
|
2072
2229
|
return {};
|
|
2073
2230
|
}
|
|
@@ -2082,6 +2239,100 @@ function checkWalletConfigured2() {
|
|
|
2082
2239
|
}
|
|
2083
2240
|
var init_wallet2 = () => {};
|
|
2084
2241
|
|
|
2242
|
+
// ../../src/telemetry-attribution.ts
|
|
2243
|
+
var exports_telemetry_attribution = {};
|
|
2244
|
+
__export(exports_telemetry_attribution, {
|
|
2245
|
+
sanitizeTelemetryAttribution: () => sanitizeTelemetryAttribution2,
|
|
2246
|
+
sanitizeAttributionValue: () => sanitizeAttributionValue2,
|
|
2247
|
+
mergeTelemetryProperties: () => mergeTelemetryProperties2,
|
|
2248
|
+
mergeTelemetryAttribution: () => mergeTelemetryAttribution2,
|
|
2249
|
+
hasTelemetryAttribution: () => hasTelemetryAttribution2,
|
|
2250
|
+
decodeTelemetryAttribution: () => decodeTelemetryAttribution2,
|
|
2251
|
+
TELEMETRY_ATTRIBUTION_KEYS: () => TELEMETRY_ATTRIBUTION_KEYS2
|
|
2252
|
+
});
|
|
2253
|
+
function sanitizeAttributionValue2(value) {
|
|
2254
|
+
if (typeof value !== "string")
|
|
2255
|
+
return;
|
|
2256
|
+
const trimmed = value.trim();
|
|
2257
|
+
if (!trimmed)
|
|
2258
|
+
return;
|
|
2259
|
+
return trimmed.slice(0, MAX_ATTRIBUTION_VALUE_LENGTH2);
|
|
2260
|
+
}
|
|
2261
|
+
function hasTelemetryAttribution2(value) {
|
|
2262
|
+
if (!value)
|
|
2263
|
+
return false;
|
|
2264
|
+
return TELEMETRY_ATTRIBUTION_KEYS2.some((key) => Boolean(sanitizeAttributionValue2(value[key])));
|
|
2265
|
+
}
|
|
2266
|
+
function sanitizeTelemetryAttribution2(raw) {
|
|
2267
|
+
if (!raw)
|
|
2268
|
+
return;
|
|
2269
|
+
const cleaned = {};
|
|
2270
|
+
for (const key of TELEMETRY_ATTRIBUTION_KEYS2) {
|
|
2271
|
+
const value = sanitizeAttributionValue2(raw[key]);
|
|
2272
|
+
if (value)
|
|
2273
|
+
cleaned[key] = value;
|
|
2274
|
+
}
|
|
2275
|
+
return hasTelemetryAttribution2(cleaned) ? cleaned : undefined;
|
|
2276
|
+
}
|
|
2277
|
+
function mergeTelemetryAttribution2(base, incoming) {
|
|
2278
|
+
const merged = sanitizeTelemetryAttribution2({
|
|
2279
|
+
...base ?? {},
|
|
2280
|
+
...incoming ?? {}
|
|
2281
|
+
});
|
|
2282
|
+
return merged;
|
|
2283
|
+
}
|
|
2284
|
+
function mergeTelemetryProperties2(properties, attribution) {
|
|
2285
|
+
const cleanedAttribution = sanitizeTelemetryAttribution2(attribution);
|
|
2286
|
+
if (!cleanedAttribution)
|
|
2287
|
+
return properties;
|
|
2288
|
+
return {
|
|
2289
|
+
...cleanedAttribution,
|
|
2290
|
+
...properties ?? {}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function decodeTelemetryAttribution2(value) {
|
|
2294
|
+
if (!value)
|
|
2295
|
+
return;
|
|
2296
|
+
try {
|
|
2297
|
+
const decoded = Buffer.from(value, "base64").toString("utf8");
|
|
2298
|
+
return sanitizeTelemetryAttribution2(JSON.parse(decoded));
|
|
2299
|
+
} catch {
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
var TELEMETRY_ATTRIBUTION_KEYS2, MAX_ATTRIBUTION_VALUE_LENGTH2 = 160;
|
|
2304
|
+
var init_telemetry_attribution2 = __esm(() => {
|
|
2305
|
+
TELEMETRY_ATTRIBUTION_KEYS2 = [
|
|
2306
|
+
"utm_source",
|
|
2307
|
+
"utm_medium",
|
|
2308
|
+
"utm_campaign",
|
|
2309
|
+
"utm_content",
|
|
2310
|
+
"utm_term",
|
|
2311
|
+
"utm_id",
|
|
2312
|
+
"gclid",
|
|
2313
|
+
"wbraid",
|
|
2314
|
+
"gbraid",
|
|
2315
|
+
"fbclid",
|
|
2316
|
+
"twclid",
|
|
2317
|
+
"ttclid",
|
|
2318
|
+
"msclkid",
|
|
2319
|
+
"li_fat_id",
|
|
2320
|
+
"referrer_host",
|
|
2321
|
+
"channel",
|
|
2322
|
+
"campaign_id",
|
|
2323
|
+
"campaign_name",
|
|
2324
|
+
"content_id",
|
|
2325
|
+
"content_type",
|
|
2326
|
+
"creative_id",
|
|
2327
|
+
"ad_id",
|
|
2328
|
+
"adset_id",
|
|
2329
|
+
"inferred_icp",
|
|
2330
|
+
"variant_id",
|
|
2331
|
+
"experiment_id",
|
|
2332
|
+
"icp"
|
|
2333
|
+
];
|
|
2334
|
+
});
|
|
2335
|
+
|
|
2085
2336
|
// ../../src/version.ts
|
|
2086
2337
|
var exports_version = {};
|
|
2087
2338
|
__export(exports_version, {
|
|
@@ -2094,6 +2345,7 @@ __export(exports_version, {
|
|
|
2094
2345
|
RELEASE_MANIFEST_BASE64: () => RELEASE_MANIFEST_BASE642,
|
|
2095
2346
|
PACKAGE_VERSION: () => PACKAGE_VERSION2,
|
|
2096
2347
|
GIT_SHA: () => GIT_SHA2,
|
|
2348
|
+
DEFAULT_PROFILE: () => DEFAULT_PROFILE2,
|
|
2097
2349
|
DEFAULT_BACKEND_URL: () => DEFAULT_BACKEND_URL2,
|
|
2098
2350
|
CODE_HASH: () => CODE_HASH2
|
|
2099
2351
|
});
|
|
@@ -2195,13 +2447,14 @@ function getPackageVersion2() {
|
|
|
2195
2447
|
return packageVersion;
|
|
2196
2448
|
return getEmbeddedReleaseVersion2() ?? "unknown";
|
|
2197
2449
|
}
|
|
2198
|
-
var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
|
|
2450
|
+
var MODULE_DIR2, CODE_HASH2, GIT_SHA2, PACKAGE_VERSION2, DEFAULT_BACKEND_URL2, DEFAULT_PROFILE2, TRACE_VERSION2, RELEASE_MANIFEST_BASE642, RELEASE_MANIFEST_SIGNATURE2;
|
|
2199
2451
|
var init_version2 = __esm(() => {
|
|
2200
2452
|
MODULE_DIR2 = dirname4(fileURLToPath4(import.meta.url));
|
|
2201
2453
|
CODE_HASH2 = BUILD_CODE_HASH?.trim() || computeCodeHash2();
|
|
2202
2454
|
GIT_SHA2 = getGitSha2();
|
|
2203
2455
|
PACKAGE_VERSION2 = getPackageVersion2();
|
|
2204
2456
|
DEFAULT_BACKEND_URL2 = BUILD_DEFAULT_BACKEND_URL?.trim() || "https://beta-api.unbrowse.ai";
|
|
2457
|
+
DEFAULT_PROFILE2 = BUILD_DEFAULT_PROFILE?.trim() || "";
|
|
2205
2458
|
TRACE_VERSION2 = `${CODE_HASH2}@${GIT_SHA2}`;
|
|
2206
2459
|
RELEASE_MANIFEST_BASE642 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
|
|
2207
2460
|
RELEASE_MANIFEST_SIGNATURE2 = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
|
|
@@ -2291,9 +2544,9 @@ function getChromiumKeychainServiceName2(opts) {
|
|
|
2291
2544
|
}
|
|
2292
2545
|
function getChromiumDecryptionKey2(opts) {
|
|
2293
2546
|
const service = getChromiumKeychainServiceName2(opts);
|
|
2294
|
-
const
|
|
2295
|
-
if (
|
|
2296
|
-
return
|
|
2547
|
+
const cached3 = _chromiumKeyCache2.get(service);
|
|
2548
|
+
if (cached3)
|
|
2549
|
+
return cached3;
|
|
2297
2550
|
if (platform2() !== "darwin")
|
|
2298
2551
|
return null;
|
|
2299
2552
|
try {
|
|
@@ -2493,6 +2746,23 @@ function extractFromFirefox2(domain, opts) {
|
|
|
2493
2746
|
}
|
|
2494
2747
|
}
|
|
2495
2748
|
function extractBrowserCookies2(domain, opts) {
|
|
2749
|
+
const __result = _extractBrowserCookiesInner2(domain, opts);
|
|
2750
|
+
try {
|
|
2751
|
+
const traceDir = join14(homedir8(), ".unbrowse", "traces");
|
|
2752
|
+
if (!existsSync18(traceDir))
|
|
2753
|
+
mkdirSync(traceDir, { recursive: true });
|
|
2754
|
+
const entry = JSON.stringify({
|
|
2755
|
+
d: domain,
|
|
2756
|
+
n: __result.cookies.length,
|
|
2757
|
+
t: Date.now(),
|
|
2758
|
+
c: __result.cookies.map((c) => ({ n: c.name, v: c.value, d: c.domain }))
|
|
2759
|
+
}) + `
|
|
2760
|
+
`;
|
|
2761
|
+
writeFileSync(join14(traceDir, "auth-extract.jsonl"), entry, { flag: "a" });
|
|
2762
|
+
} catch {}
|
|
2763
|
+
return __result;
|
|
2764
|
+
}
|
|
2765
|
+
function _extractBrowserCookiesInner2(domain, opts) {
|
|
2496
2766
|
if (opts?.browser === "firefox") {
|
|
2497
2767
|
return extractFromFirefox2(domain, { profile: opts.firefoxProfile });
|
|
2498
2768
|
}
|
|
@@ -2512,6 +2782,20 @@ function extractBrowserCookies2(domain, opts) {
|
|
|
2512
2782
|
}
|
|
2513
2783
|
const chrome = extractFromChrome2(domain, { profile: opts?.chromeProfile });
|
|
2514
2784
|
chrome.warnings.push(...ff.warnings);
|
|
2785
|
+
if (chrome.cookies.length > 0)
|
|
2786
|
+
return chrome;
|
|
2787
|
+
const sessions = scanAllBrowserSessions2(domain);
|
|
2788
|
+
const best = sessions.filter((s) => s.browser !== "Firefox" && s.browser !== "Chrome").sort((a, b) => b.sessionCookies - a.sessionCookies)[0];
|
|
2789
|
+
if (best) {
|
|
2790
|
+
return {
|
|
2791
|
+
cookies: best.cookies,
|
|
2792
|
+
source: best.source,
|
|
2793
|
+
warnings: [
|
|
2794
|
+
...chrome.warnings,
|
|
2795
|
+
`Chrome had no cookies for ${domain}; using ${best.browser} (${best.sessionCookies} session cookies)`
|
|
2796
|
+
]
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2515
2799
|
return chrome;
|
|
2516
2800
|
}
|
|
2517
2801
|
function scanAllBrowserSessions2(domain) {
|
|
@@ -2582,14 +2866,14 @@ init_version();
|
|
|
2582
2866
|
init_cascade();
|
|
2583
2867
|
init_wallet();
|
|
2584
2868
|
init_telemetry_attribution();
|
|
2585
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2, unlinkSync } from "fs";
|
|
2869
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, unlinkSync } from "fs";
|
|
2586
2870
|
import { join as join4 } from "path";
|
|
2587
2871
|
import { homedir as homedir3, hostname, release as osRelease } from "os";
|
|
2588
2872
|
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
2589
2873
|
import { createInterface } from "readline";
|
|
2590
2874
|
import { execSync } from "child_process";
|
|
2591
2875
|
var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
2592
|
-
var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
|
|
2876
|
+
var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? DEFAULT_PROFILE ?? "");
|
|
2593
2877
|
var recentLocalSkills = new Map;
|
|
2594
2878
|
var LOCAL_ONLY = process.env.UNBROWSE_LOCAL_ONLY === "1";
|
|
2595
2879
|
function buildReleaseAttestationHeaders(manifestBase64, signature) {
|
|
@@ -2665,12 +2949,40 @@ function loadConfig() {
|
|
|
2665
2949
|
} catch {}
|
|
2666
2950
|
return null;
|
|
2667
2951
|
}
|
|
2952
|
+
function resetLocalRegistration() {
|
|
2953
|
+
const configPath = getConfigPath();
|
|
2954
|
+
try {
|
|
2955
|
+
if (!existsSync4(configPath))
|
|
2956
|
+
return { removed: false, config_path: configPath };
|
|
2957
|
+
unlinkSync(configPath);
|
|
2958
|
+
return { removed: true, config_path: configPath };
|
|
2959
|
+
} catch {
|
|
2960
|
+
return { removed: false, config_path: configPath };
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2668
2963
|
function saveConfig(config) {
|
|
2669
2964
|
const configDir = getConfigDir();
|
|
2670
2965
|
const configPath = getConfigPath();
|
|
2671
2966
|
if (!existsSync4(configDir))
|
|
2672
|
-
|
|
2673
|
-
|
|
2967
|
+
mkdirSync2(configDir, { recursive: true });
|
|
2968
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
2969
|
+
}
|
|
2970
|
+
function getPairingDir() {
|
|
2971
|
+
return join4(getConfigDir(), "pairing");
|
|
2972
|
+
}
|
|
2973
|
+
function createDashboardPairingToken(ttlMs = 120000) {
|
|
2974
|
+
const token = randomBytes(24).toString("base64url");
|
|
2975
|
+
const now = Date.now();
|
|
2976
|
+
const record = {
|
|
2977
|
+
token,
|
|
2978
|
+
created_at: new Date(now).toISOString(),
|
|
2979
|
+
expires_at: new Date(now + ttlMs).toISOString()
|
|
2980
|
+
};
|
|
2981
|
+
const dir = getPairingDir();
|
|
2982
|
+
if (!existsSync4(dir))
|
|
2983
|
+
mkdirSync2(dir, { recursive: true });
|
|
2984
|
+
writeFileSync2(join4(dir, `${token}.json`), JSON.stringify(record, null, 2), { mode: 384 });
|
|
2985
|
+
return record;
|
|
2674
2986
|
}
|
|
2675
2987
|
function loadInstallTelemetryState() {
|
|
2676
2988
|
try {
|
|
@@ -2685,8 +2997,8 @@ function saveInstallTelemetryState(state) {
|
|
|
2685
2997
|
const configDir = getConfigDir();
|
|
2686
2998
|
const statePath = getInstallTelemetryPath();
|
|
2687
2999
|
if (!existsSync4(configDir))
|
|
2688
|
-
|
|
2689
|
-
|
|
3000
|
+
mkdirSync2(configDir, { recursive: true });
|
|
3001
|
+
writeFileSync2(statePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
2690
3002
|
}
|
|
2691
3003
|
function createInstallTelemetryState() {
|
|
2692
3004
|
return {
|
|
@@ -3187,9 +3499,99 @@ You have $2.00 in free credits — start resolving to use them.`);
|
|
|
3187
3499
|
process.exit(1);
|
|
3188
3500
|
}
|
|
3189
3501
|
}
|
|
3502
|
+
async function magicRegister(opts) {
|
|
3503
|
+
const timeoutMs = opts.timeoutMs ?? 300000;
|
|
3504
|
+
const pollMs = opts.pollMs ?? 1500;
|
|
3505
|
+
const startRes = await fetch(`${API_URL}/v1/auth/email/start`, {
|
|
3506
|
+
method: "POST",
|
|
3507
|
+
headers: {
|
|
3508
|
+
"Content-Type": "application/json",
|
|
3509
|
+
"Accept-Encoding": "gzip, deflate"
|
|
3510
|
+
},
|
|
3511
|
+
body: JSON.stringify({ email: opts.email, return_url: opts.returnUrl })
|
|
3512
|
+
});
|
|
3513
|
+
if (startRes.status === 503) {
|
|
3514
|
+
throw new Error("Backend RESEND_API_KEY not set — magic-link signup unavailable. Use anon `unbrowse register` (no --email).");
|
|
3515
|
+
}
|
|
3516
|
+
let startData;
|
|
3517
|
+
try {
|
|
3518
|
+
startData = await startRes.json();
|
|
3519
|
+
} catch {
|
|
3520
|
+
throw new Error(`Magic-link start failed: HTTP ${startRes.status}`);
|
|
3521
|
+
}
|
|
3522
|
+
if (startRes.status === 400) {
|
|
3523
|
+
throw new Error(startData.error ?? "invalid_email");
|
|
3524
|
+
}
|
|
3525
|
+
if (!startRes.ok || !startData.token) {
|
|
3526
|
+
const msg = startData.error ?? `HTTP ${startRes.status}`;
|
|
3527
|
+
throw new Error(`Magic-link start failed: ${msg}`);
|
|
3528
|
+
}
|
|
3529
|
+
const token = startData.token;
|
|
3530
|
+
const verifyUrl = `${API_URL}/v1/auth/email/verify?cli=1&token=${encodeURIComponent(token)}`;
|
|
3531
|
+
if (opts.openBrowser) {
|
|
3532
|
+
try {
|
|
3533
|
+
await opts.openBrowser(verifyUrl);
|
|
3534
|
+
} catch {}
|
|
3535
|
+
} else {
|
|
3536
|
+
try {
|
|
3537
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
3538
|
+
execSync(`${cmd} ${JSON.stringify(verifyUrl)}`, { stdio: "ignore", timeout: 5000 });
|
|
3539
|
+
} catch {}
|
|
3540
|
+
}
|
|
3541
|
+
const deadline = Date.now() + timeoutMs;
|
|
3542
|
+
while (Date.now() < deadline) {
|
|
3543
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
3544
|
+
const pollRes = await fetch(`${API_URL}/v1/auth/email/poll?token=${encodeURIComponent(token)}`, {
|
|
3545
|
+
method: "GET",
|
|
3546
|
+
headers: { "Accept-Encoding": "gzip, deflate" }
|
|
3547
|
+
});
|
|
3548
|
+
let pollData;
|
|
3549
|
+
try {
|
|
3550
|
+
pollData = await pollRes.json();
|
|
3551
|
+
} catch {
|
|
3552
|
+
throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
|
|
3553
|
+
}
|
|
3554
|
+
if (pollData.status === "verified") {
|
|
3555
|
+
if (!pollData.api_key || !pollData.agent_id || !pollData.user_id || !pollData.email) {
|
|
3556
|
+
throw new Error("Magic-link poll returned verified without api_key/agent_id/user_id/email.");
|
|
3557
|
+
}
|
|
3558
|
+
return {
|
|
3559
|
+
api_key: pollData.api_key,
|
|
3560
|
+
agent_id: pollData.agent_id,
|
|
3561
|
+
email: pollData.email,
|
|
3562
|
+
user_id: pollData.user_id
|
|
3563
|
+
};
|
|
3564
|
+
}
|
|
3565
|
+
if (pollData.status === "expired" || pollRes.status === 410) {
|
|
3566
|
+
throw new Error("Magic link expired. Re-run `unbrowse register --email …`.");
|
|
3567
|
+
}
|
|
3568
|
+
if (pollData.status === "pending")
|
|
3569
|
+
continue;
|
|
3570
|
+
if (!pollRes.ok) {
|
|
3571
|
+
throw new Error(`Magic-link poll failed: HTTP ${pollRes.status}`);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
throw new Error(`Magic-link timed out after ${Math.round(timeoutMs / 1000)}s. Check your inbox and re-run.`);
|
|
3575
|
+
}
|
|
3190
3576
|
async function getMyProfile() {
|
|
3191
3577
|
return api("GET", "/v1/agents/me", undefined);
|
|
3192
3578
|
}
|
|
3579
|
+
async function fetchAccountPreferences() {
|
|
3580
|
+
try {
|
|
3581
|
+
const data = await api("GET", "/v1/account/preferences", undefined);
|
|
3582
|
+
return { share_pointers: !!data?.share_pointers };
|
|
3583
|
+
} catch (err) {
|
|
3584
|
+
const msg = err.message ?? "";
|
|
3585
|
+
if (msg.includes("account_required") || msg.includes("HTTP 403") || msg.includes("HTTP 404")) {
|
|
3586
|
+
return null;
|
|
3587
|
+
}
|
|
3588
|
+
throw err;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
async function pushAccountPreferences(patch) {
|
|
3592
|
+
const data = await api("PATCH", "/v1/account/preferences", patch);
|
|
3593
|
+
return { share_pointers: !!data?.share_pointers };
|
|
3594
|
+
}
|
|
3193
3595
|
async function syncAgentWallet(wallet = getLocalWalletContext()) {
|
|
3194
3596
|
if (!wallet.wallet_address)
|
|
3195
3597
|
return;
|
|
@@ -3210,7 +3612,7 @@ async function getFlywheelPulse() {
|
|
|
3210
3612
|
}
|
|
3211
3613
|
|
|
3212
3614
|
// ../../src/impact-log.ts
|
|
3213
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
3615
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
3214
3616
|
import { homedir as homedir4 } from "node:os";
|
|
3215
3617
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
3216
3618
|
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
@@ -3227,7 +3629,7 @@ function getImpactLogPath() {
|
|
|
3227
3629
|
function ensureDir(path) {
|
|
3228
3630
|
const dir = dirname2(path);
|
|
3229
3631
|
if (!existsSync5(dir))
|
|
3230
|
-
|
|
3632
|
+
mkdirSync3(dir, { recursive: true });
|
|
3231
3633
|
}
|
|
3232
3634
|
function rotateIfNeeded(path) {
|
|
3233
3635
|
try {
|
|
@@ -3566,7 +3968,7 @@ function buildDepsMetadata(pack, taskName) {
|
|
|
3566
3968
|
init_paths();
|
|
3567
3969
|
init_supervisor();
|
|
3568
3970
|
init_version();
|
|
3569
|
-
import { existsSync as existsSync7, openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as
|
|
3971
|
+
import { existsSync as existsSync7, openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3570
3972
|
import path2 from "node:path";
|
|
3571
3973
|
import { spawn } from "node:child_process";
|
|
3572
3974
|
function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
|
|
@@ -3673,7 +4075,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
|
|
|
3673
4075
|
code_hash: CODE_HASH,
|
|
3674
4076
|
restart_count: restartCount
|
|
3675
4077
|
};
|
|
3676
|
-
|
|
4078
|
+
writeFileSync3(pidFile, JSON.stringify(state, null, 2));
|
|
3677
4079
|
return state;
|
|
3678
4080
|
}
|
|
3679
4081
|
var supervisor = new LocalSupervisor;
|
|
@@ -3781,7 +4183,7 @@ async function restartServer(baseUrl, metaUrl) {
|
|
|
3781
4183
|
}
|
|
3782
4184
|
|
|
3783
4185
|
// ../../src/runtime/paths.ts
|
|
3784
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
4186
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, realpathSync as realpathSync2 } from "node:fs";
|
|
3785
4187
|
import path3 from "node:path";
|
|
3786
4188
|
import { createRequire as createRequire3 } from "node:module";
|
|
3787
4189
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
@@ -3794,7 +4196,7 @@ function isBundledVirtualEntrypoint(entrypoint) {
|
|
|
3794
4196
|
}
|
|
3795
4197
|
function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
|
|
3796
4198
|
if (path3.extname(entrypoint) !== ".ts") {
|
|
3797
|
-
return [pathToFileURL2(entrypoint).href];
|
|
4199
|
+
return process.platform === "win32" ? [pathToFileURL2(entrypoint).href] : [entrypoint];
|
|
3798
4200
|
}
|
|
3799
4201
|
if (process.versions.bun)
|
|
3800
4202
|
return [entrypoint];
|
|
@@ -3837,6 +4239,7 @@ init_publish();
|
|
|
3837
4239
|
init_settings();
|
|
3838
4240
|
init_graph();
|
|
3839
4241
|
init_schema_review();
|
|
4242
|
+
init_contribution();
|
|
3840
4243
|
import { join as join11 } from "node:path";
|
|
3841
4244
|
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join11(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
3842
4245
|
var indexInFlight2 = new Map;
|
|
@@ -3876,28 +4279,28 @@ init_client2();
|
|
|
3876
4279
|
init_logger();
|
|
3877
4280
|
init_wallet();
|
|
3878
4281
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
3879
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
3880
|
-
import
|
|
3881
|
-
import
|
|
4282
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
4283
|
+
import os5 from "node:os";
|
|
4284
|
+
import path8 from "node:path";
|
|
3882
4285
|
|
|
3883
4286
|
// ../../src/runtime/update-hints.ts
|
|
3884
4287
|
init_paths();
|
|
3885
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
3886
|
-
import
|
|
3887
|
-
import
|
|
4288
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4289
|
+
import os4 from "node:os";
|
|
4290
|
+
import path7 from "node:path";
|
|
3888
4291
|
var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
3889
4292
|
var CODEX_MARKER = "# Unbrowse update hints — managed by unbrowse setup";
|
|
3890
4293
|
function getHomeDir() {
|
|
3891
|
-
return process.env.HOME ||
|
|
4294
|
+
return process.env.HOME || os4.homedir();
|
|
3892
4295
|
}
|
|
3893
4296
|
function getConfigDir2() {
|
|
3894
4297
|
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
3895
4298
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
3896
|
-
return
|
|
4299
|
+
return path7.join(getHomeDir(), ".unbrowse");
|
|
3897
4300
|
}
|
|
3898
4301
|
function ensureDir3(dir) {
|
|
3899
4302
|
if (!existsSync13(dir))
|
|
3900
|
-
|
|
4303
|
+
mkdirSync8(dir, { recursive: true });
|
|
3901
4304
|
return dir;
|
|
3902
4305
|
}
|
|
3903
4306
|
function readJsonFile(file) {
|
|
@@ -3908,20 +4311,20 @@ function readJsonFile(file) {
|
|
|
3908
4311
|
}
|
|
3909
4312
|
}
|
|
3910
4313
|
function writeJsonFile(file, value) {
|
|
3911
|
-
ensureDir3(
|
|
3912
|
-
|
|
4314
|
+
ensureDir3(path7.dirname(file));
|
|
4315
|
+
writeFileSync6(file, `${JSON.stringify(value, null, 2)}
|
|
3913
4316
|
`);
|
|
3914
4317
|
}
|
|
3915
4318
|
function getInstallSourcePath() {
|
|
3916
|
-
return
|
|
4319
|
+
return path7.join(getConfigDir2(), "install-source.json");
|
|
3917
4320
|
}
|
|
3918
4321
|
function detectRepoRoot(start2) {
|
|
3919
|
-
let dir =
|
|
3920
|
-
const root =
|
|
4322
|
+
let dir = path7.resolve(start2);
|
|
4323
|
+
const root = path7.parse(dir).root;
|
|
3921
4324
|
while (dir !== root) {
|
|
3922
|
-
if (existsSync13(
|
|
4325
|
+
if (existsSync13(path7.join(dir, ".git")))
|
|
3923
4326
|
return dir;
|
|
3924
|
-
dir =
|
|
4327
|
+
dir = path7.dirname(dir);
|
|
3925
4328
|
}
|
|
3926
4329
|
return;
|
|
3927
4330
|
}
|
|
@@ -3930,7 +4333,7 @@ function detectInstallMethod(packageRoot) {
|
|
|
3930
4333
|
return "repo-clone";
|
|
3931
4334
|
if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
|
|
3932
4335
|
return "npm-global";
|
|
3933
|
-
if (packageRoot.includes(`${
|
|
4336
|
+
if (packageRoot.includes(`${path7.sep}node_modules${path7.sep}`))
|
|
3934
4337
|
return "npm-global";
|
|
3935
4338
|
return detectRepoRoot(packageRoot) ? "repo-clone" : "unknown";
|
|
3936
4339
|
}
|
|
@@ -3940,12 +4343,12 @@ function detectInstallHost(repoRoot) {
|
|
|
3940
4343
|
return explicit;
|
|
3941
4344
|
if (!repoRoot)
|
|
3942
4345
|
return "unknown";
|
|
3943
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
3944
|
-
if (repoRoot ===
|
|
4346
|
+
const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
|
|
4347
|
+
if (repoRoot === path7.join(codexHome, "skills", "unbrowse"))
|
|
3945
4348
|
return "codex";
|
|
3946
|
-
if (repoRoot ===
|
|
4349
|
+
if (repoRoot === path7.join(getHomeDir(), ".claude", "skills", "unbrowse"))
|
|
3947
4350
|
return "claude";
|
|
3948
|
-
if (repoRoot ===
|
|
4351
|
+
if (repoRoot === path7.join(getHomeDir(), "unbrowse"))
|
|
3949
4352
|
return "off";
|
|
3950
4353
|
return "unknown";
|
|
3951
4354
|
}
|
|
@@ -3973,14 +4376,14 @@ function commandIncludesHook(command, marker) {
|
|
|
3973
4376
|
return typeof command === "string" && command.includes(marker);
|
|
3974
4377
|
}
|
|
3975
4378
|
function getCodexConfigPath() {
|
|
3976
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
3977
|
-
return
|
|
4379
|
+
const codexHome = process.env.CODEX_HOME || path7.join(getHomeDir(), ".codex");
|
|
4380
|
+
return path7.join(codexHome, "config.toml");
|
|
3978
4381
|
}
|
|
3979
4382
|
function getClaudeSettingsPath() {
|
|
3980
|
-
return
|
|
4383
|
+
return path7.join(getHomeDir(), ".claude", "settings.json");
|
|
3981
4384
|
}
|
|
3982
4385
|
function getHookScriptPath(metaUrl) {
|
|
3983
|
-
return
|
|
4386
|
+
return path7.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
|
|
3984
4387
|
}
|
|
3985
4388
|
function ensureCodexHooksFeature(content) {
|
|
3986
4389
|
if (/\bcodex_hooks\s*=\s*true\b/.test(content))
|
|
@@ -3996,49 +4399,54 @@ function ensureCodexHooksFeature(content) {
|
|
|
3996
4399
|
codex_hooks = true
|
|
3997
4400
|
`;
|
|
3998
4401
|
}
|
|
4402
|
+
function repairManagedCodexHookTable(content) {
|
|
4403
|
+
const marker = CODEX_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4404
|
+
return content.replace(new RegExp(`(${marker}\\r?\\n)\\[\\[?hooks\\]?\\]?(?=\\r?\\n)`, "g"), "$1[hooks]");
|
|
4405
|
+
}
|
|
3999
4406
|
function writeCodexHook(metaUrl) {
|
|
4000
|
-
const
|
|
4001
|
-
if (!existsSync13(
|
|
4002
|
-
return { host: "codex", action: "not-detected", config_file:
|
|
4407
|
+
const configPath2 = getCodexConfigPath();
|
|
4408
|
+
if (!existsSync13(path7.dirname(configPath2))) {
|
|
4409
|
+
return { host: "codex", action: "not-detected", config_file: configPath2 };
|
|
4003
4410
|
}
|
|
4004
4411
|
try {
|
|
4005
4412
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
4006
|
-
const fileExistsBefore = existsSync13(
|
|
4007
|
-
let content = fileExistsBefore ? readFileSync8(
|
|
4413
|
+
const fileExistsBefore = existsSync13(configPath2);
|
|
4414
|
+
let content = fileExistsBefore ? readFileSync8(configPath2, "utf8") : "";
|
|
4008
4415
|
const previous = content;
|
|
4009
4416
|
content = ensureCodexHooksFeature(content);
|
|
4417
|
+
content = repairManagedCodexHookTable(content);
|
|
4010
4418
|
if (!content.includes("unbrowse-update-hint.mjs")) {
|
|
4011
4419
|
const command = `node "${hookScript}"`;
|
|
4012
4420
|
const prefix = content && !content.endsWith(`
|
|
4013
4421
|
`) ? `
|
|
4014
4422
|
` : "";
|
|
4015
4423
|
content += `${prefix}${CODEX_MARKER}
|
|
4016
|
-
[
|
|
4424
|
+
[hooks]
|
|
4017
4425
|
event = "SessionStart"
|
|
4018
4426
|
command = ${JSON.stringify(command)}
|
|
4019
4427
|
`;
|
|
4020
4428
|
}
|
|
4021
4429
|
if (content !== previous) {
|
|
4022
|
-
|
|
4430
|
+
writeFileSync6(configPath2, content, "utf8");
|
|
4023
4431
|
return {
|
|
4024
4432
|
host: "codex",
|
|
4025
4433
|
action: fileExistsBefore ? "updated" : "installed",
|
|
4026
|
-
config_file:
|
|
4434
|
+
config_file: configPath2
|
|
4027
4435
|
};
|
|
4028
4436
|
}
|
|
4029
|
-
return { host: "codex", action: "already-installed", config_file:
|
|
4437
|
+
return { host: "codex", action: "already-installed", config_file: configPath2 };
|
|
4030
4438
|
} catch (error) {
|
|
4031
4439
|
return {
|
|
4032
4440
|
host: "codex",
|
|
4033
4441
|
action: "failed",
|
|
4034
|
-
config_file:
|
|
4442
|
+
config_file: configPath2,
|
|
4035
4443
|
message: error instanceof Error ? error.message : String(error)
|
|
4036
4444
|
};
|
|
4037
4445
|
}
|
|
4038
4446
|
}
|
|
4039
4447
|
function writeClaudeHook(metaUrl) {
|
|
4040
4448
|
const settingsPath = getClaudeSettingsPath();
|
|
4041
|
-
if (!existsSync13(
|
|
4449
|
+
if (!existsSync13(path7.dirname(settingsPath))) {
|
|
4042
4450
|
return { host: "claude", action: "not-detected", config_file: settingsPath };
|
|
4043
4451
|
}
|
|
4044
4452
|
try {
|
|
@@ -4098,18 +4506,18 @@ function detectPackageManagers() {
|
|
|
4098
4506
|
}
|
|
4099
4507
|
function resolveConfigHome() {
|
|
4100
4508
|
if (process.platform === "win32") {
|
|
4101
|
-
return process.env.APPDATA ||
|
|
4509
|
+
return process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming");
|
|
4102
4510
|
}
|
|
4103
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
4511
|
+
return process.env.XDG_CONFIG_HOME || path8.join(os5.homedir(), ".config");
|
|
4104
4512
|
}
|
|
4105
4513
|
function getOpenCodeGlobalCommandsDir() {
|
|
4106
|
-
return
|
|
4514
|
+
return path8.join(resolveConfigHome(), "opencode", "commands");
|
|
4107
4515
|
}
|
|
4108
4516
|
function getOpenCodeProjectCommandsDir(cwd) {
|
|
4109
|
-
return
|
|
4517
|
+
return path8.join(cwd, ".opencode", "commands");
|
|
4110
4518
|
}
|
|
4111
4519
|
function detectOpenCode(cwd) {
|
|
4112
|
-
return hasBinary("opencode") || existsSync14(
|
|
4520
|
+
return hasBinary("opencode") || existsSync14(path8.join(resolveConfigHome(), "opencode")) || existsSync14(path8.join(cwd, ".opencode"));
|
|
4113
4521
|
}
|
|
4114
4522
|
function renderOpenCodeCommand() {
|
|
4115
4523
|
return `---
|
|
@@ -4137,13 +4545,13 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
4137
4545
|
if (scope === "auto" && !detected) {
|
|
4138
4546
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
4139
4547
|
}
|
|
4140
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(
|
|
4548
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path8.join(cwd, ".opencode")) ? "project" : "global";
|
|
4141
4549
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
4142
|
-
const commandFile =
|
|
4550
|
+
const commandFile = path8.join(ensureDir2(commandsDir), "unbrowse.md");
|
|
4143
4551
|
const content = renderOpenCodeCommand();
|
|
4144
4552
|
const action2 = existsSync14(commandFile) ? "updated" : "installed";
|
|
4145
|
-
|
|
4146
|
-
|
|
4553
|
+
mkdirSync9(path8.dirname(commandFile), { recursive: true });
|
|
4554
|
+
writeFileSync7(commandFile, content);
|
|
4147
4555
|
return {
|
|
4148
4556
|
detected: detected || scope !== "auto",
|
|
4149
4557
|
action: action2,
|
|
@@ -4156,7 +4564,7 @@ async function ensureBrowserEngineInstalled() {
|
|
|
4156
4564
|
if (existsSync14(binary)) {
|
|
4157
4565
|
return { installed: true, action: "already-installed" };
|
|
4158
4566
|
}
|
|
4159
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(
|
|
4567
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path8.join(candidate, "build.zig")));
|
|
4160
4568
|
if (!sourceDir) {
|
|
4161
4569
|
return {
|
|
4162
4570
|
installed: false,
|
|
@@ -4203,7 +4611,7 @@ async function runSetup(options) {
|
|
|
4203
4611
|
const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
|
|
4204
4612
|
const walletCheck = checkWalletConfigured();
|
|
4205
4613
|
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
4206
|
-
let lobsterInstalled = hasBinary("lobstercash") || existsSync14(
|
|
4614
|
+
let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os5.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
4207
4615
|
if (!skipWalletSetup && !walletCheck.configured) {
|
|
4208
4616
|
if (!lobsterInstalled) {
|
|
4209
4617
|
console.log("[unbrowse] Setting up Crossmint wallet (required for earning + payments)...");
|
|
@@ -4243,7 +4651,7 @@ async function runSetup(options) {
|
|
|
4243
4651
|
return {
|
|
4244
4652
|
os: {
|
|
4245
4653
|
platform: process.platform,
|
|
4246
|
-
release:
|
|
4654
|
+
release: os5.release(),
|
|
4247
4655
|
arch: process.arch
|
|
4248
4656
|
},
|
|
4249
4657
|
host_environment: hostEnv,
|
|
@@ -4257,22 +4665,22 @@ async function runSetup(options) {
|
|
|
4257
4665
|
|
|
4258
4666
|
// ../../src/runtime/update-hints.ts
|
|
4259
4667
|
init_paths();
|
|
4260
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
4261
|
-
import
|
|
4262
|
-
import
|
|
4668
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
|
|
4669
|
+
import os6 from "node:os";
|
|
4670
|
+
import path9 from "node:path";
|
|
4263
4671
|
var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
|
|
4264
4672
|
var DEFAULT_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
|
|
4265
4673
|
function getHomeDir2() {
|
|
4266
|
-
return process.env.HOME ||
|
|
4674
|
+
return process.env.HOME || os6.homedir();
|
|
4267
4675
|
}
|
|
4268
4676
|
function getConfigDir3() {
|
|
4269
4677
|
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
4270
4678
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
4271
|
-
return
|
|
4679
|
+
return path9.join(getHomeDir2(), ".unbrowse");
|
|
4272
4680
|
}
|
|
4273
4681
|
function ensureDir4(dir) {
|
|
4274
4682
|
if (!existsSync15(dir))
|
|
4275
|
-
|
|
4683
|
+
mkdirSync10(dir, { recursive: true });
|
|
4276
4684
|
return dir;
|
|
4277
4685
|
}
|
|
4278
4686
|
function readJsonFile2(file) {
|
|
@@ -4283,23 +4691,23 @@ function readJsonFile2(file) {
|
|
|
4283
4691
|
}
|
|
4284
4692
|
}
|
|
4285
4693
|
function writeJsonFile2(file, value) {
|
|
4286
|
-
ensureDir4(
|
|
4287
|
-
|
|
4694
|
+
ensureDir4(path9.dirname(file));
|
|
4695
|
+
writeFileSync8(file, `${JSON.stringify(value, null, 2)}
|
|
4288
4696
|
`);
|
|
4289
4697
|
}
|
|
4290
4698
|
function getInstallSourcePath2() {
|
|
4291
|
-
return
|
|
4699
|
+
return path9.join(getConfigDir3(), "install-source.json");
|
|
4292
4700
|
}
|
|
4293
4701
|
function getUpdateCheckStatePath() {
|
|
4294
|
-
return
|
|
4702
|
+
return path9.join(getConfigDir3(), "update-check.json");
|
|
4295
4703
|
}
|
|
4296
4704
|
function detectRepoRoot2(start2) {
|
|
4297
|
-
let dir =
|
|
4298
|
-
const root =
|
|
4705
|
+
let dir = path9.resolve(start2);
|
|
4706
|
+
const root = path9.parse(dir).root;
|
|
4299
4707
|
while (dir !== root) {
|
|
4300
|
-
if (existsSync15(
|
|
4708
|
+
if (existsSync15(path9.join(dir, ".git")))
|
|
4301
4709
|
return dir;
|
|
4302
|
-
dir =
|
|
4710
|
+
dir = path9.dirname(dir);
|
|
4303
4711
|
}
|
|
4304
4712
|
return;
|
|
4305
4713
|
}
|
|
@@ -4308,7 +4716,7 @@ function detectInstallMethod2(packageRoot) {
|
|
|
4308
4716
|
return "repo-clone";
|
|
4309
4717
|
if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
|
|
4310
4718
|
return "npm-global";
|
|
4311
|
-
if (packageRoot.includes(`${
|
|
4719
|
+
if (packageRoot.includes(`${path9.sep}node_modules${path9.sep}`))
|
|
4312
4720
|
return "npm-global";
|
|
4313
4721
|
return detectRepoRoot2(packageRoot) ? "repo-clone" : "unknown";
|
|
4314
4722
|
}
|
|
@@ -4318,19 +4726,19 @@ function detectInstallHost2(repoRoot) {
|
|
|
4318
4726
|
return explicit;
|
|
4319
4727
|
if (!repoRoot)
|
|
4320
4728
|
return "unknown";
|
|
4321
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
4322
|
-
if (repoRoot ===
|
|
4729
|
+
const codexHome = process.env.CODEX_HOME || path9.join(getHomeDir2(), ".codex");
|
|
4730
|
+
if (repoRoot === path9.join(codexHome, "skills", "unbrowse"))
|
|
4323
4731
|
return "codex";
|
|
4324
|
-
if (repoRoot ===
|
|
4732
|
+
if (repoRoot === path9.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
|
|
4325
4733
|
return "claude";
|
|
4326
|
-
if (repoRoot ===
|
|
4734
|
+
if (repoRoot === path9.join(getHomeDir2(), "unbrowse"))
|
|
4327
4735
|
return "off";
|
|
4328
4736
|
return "unknown";
|
|
4329
4737
|
}
|
|
4330
4738
|
function getInstalledVersion(metaUrl) {
|
|
4331
4739
|
const packageRoot = getPackageRoot(metaUrl);
|
|
4332
4740
|
try {
|
|
4333
|
-
const pkg = JSON.parse(readFileSync9(
|
|
4741
|
+
const pkg = JSON.parse(readFileSync9(path9.join(packageRoot, "package.json"), "utf8"));
|
|
4334
4742
|
return pkg.version ?? "unknown";
|
|
4335
4743
|
} catch {
|
|
4336
4744
|
return "unknown";
|
|
@@ -4426,11 +4834,183 @@ function recordUpdateHint(latestVersion) {
|
|
|
4426
4834
|
});
|
|
4427
4835
|
}
|
|
4428
4836
|
|
|
4837
|
+
// ../../src/cli-setup.ts
|
|
4838
|
+
init_contribution();
|
|
4839
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
4840
|
+
async function defaultReadChoice(allowed, dflt) {
|
|
4841
|
+
const isInteractive = !!process.stdin.isTTY && !!process.stdout.isTTY && process.env.UNBROWSE_NON_INTERACTIVE !== "1";
|
|
4842
|
+
if (!isInteractive)
|
|
4843
|
+
return dflt;
|
|
4844
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
4845
|
+
try {
|
|
4846
|
+
for (;; ) {
|
|
4847
|
+
const answer = await new Promise((resolve) => {
|
|
4848
|
+
rl.question(`Choice [${dflt}]: `, resolve);
|
|
4849
|
+
});
|
|
4850
|
+
const trimmed = answer.trim();
|
|
4851
|
+
if (!trimmed)
|
|
4852
|
+
return dflt;
|
|
4853
|
+
const n = parseInt(trimmed, 10);
|
|
4854
|
+
if (allowed.includes(n))
|
|
4855
|
+
return n;
|
|
4856
|
+
console.log(`Please enter ${allowed.join(", ")} (or press Enter for ${dflt}).`);
|
|
4857
|
+
}
|
|
4858
|
+
} finally {
|
|
4859
|
+
rl.close();
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
function shouldSkipInteractivePrompt(opts) {
|
|
4863
|
+
if (opts.readChoice)
|
|
4864
|
+
return false;
|
|
4865
|
+
return process.env.UNBROWSE_NON_INTERACTIVE === "1" || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
4866
|
+
}
|
|
4867
|
+
async function promptContributionMode(opts = {}) {
|
|
4868
|
+
const log2 = opts.log ?? ((msg) => console.log(msg));
|
|
4869
|
+
const cfg = getContributionConfig();
|
|
4870
|
+
if (!opts.force && cfg.contribution.set_via && cfg.contribution.set_via !== "default") {
|
|
4871
|
+
return null;
|
|
4872
|
+
}
|
|
4873
|
+
if (shouldSkipInteractivePrompt(opts)) {
|
|
4874
|
+
return null;
|
|
4875
|
+
}
|
|
4876
|
+
log2("");
|
|
4877
|
+
log2("How do you want unbrowse to handle the routes you discover?");
|
|
4878
|
+
log2("");
|
|
4879
|
+
log2(" [1] Private (default) — cache locally, never publish");
|
|
4880
|
+
log2(" [2] Share — contribute pointers to the marketplace");
|
|
4881
|
+
log2(" [3] Share + earn — contribute and add a wallet for x402 rev-share");
|
|
4882
|
+
log2("");
|
|
4883
|
+
const reader = opts.readChoice ?? defaultReadChoice;
|
|
4884
|
+
const choice = await reader([1, 2, 3], 1);
|
|
4885
|
+
const set_via = opts.force ? "mode-command" : "setup-prompt";
|
|
4886
|
+
if (choice === 1) {
|
|
4887
|
+
setContributionConfig({
|
|
4888
|
+
contribution: { share_pointers: false, set_via },
|
|
4889
|
+
rev_share: { opted_in: false }
|
|
4890
|
+
});
|
|
4891
|
+
log2("Private mode set. Run `unbrowse mode` anytime to change.");
|
|
4892
|
+
} else if (choice === 2) {
|
|
4893
|
+
setContributionConfig({
|
|
4894
|
+
contribution: { share_pointers: true, set_via },
|
|
4895
|
+
rev_share: { opted_in: false }
|
|
4896
|
+
});
|
|
4897
|
+
log2("Sharing pointers enabled. Add a wallet via `unbrowse mode` to opt into rev-share.");
|
|
4898
|
+
} else {
|
|
4899
|
+
setContributionConfig({
|
|
4900
|
+
contribution: { share_pointers: true, set_via },
|
|
4901
|
+
rev_share: { opted_in: true }
|
|
4902
|
+
});
|
|
4903
|
+
log2("Sharing + rev-share enabled. Run `unbrowse wallet` to add your wallet for payouts.");
|
|
4904
|
+
}
|
|
4905
|
+
return choice;
|
|
4906
|
+
}
|
|
4907
|
+
function maybeShowContributionNotice() {
|
|
4908
|
+
const cfg = getContributionConfig();
|
|
4909
|
+
if (cfg.contribution.set_via !== "default")
|
|
4910
|
+
return false;
|
|
4911
|
+
const remaining = cfg.notice_shown_count ?? 0;
|
|
4912
|
+
if (remaining <= 0)
|
|
4913
|
+
return false;
|
|
4914
|
+
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"})
|
|
4915
|
+
`);
|
|
4916
|
+
decrementNoticeCounter();
|
|
4917
|
+
return true;
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
// ../../src/config/contribution.ts
|
|
4921
|
+
import fs3 from "node:fs";
|
|
4922
|
+
import path10 from "node:path";
|
|
4923
|
+
import os7 from "node:os";
|
|
4924
|
+
function configPath2() {
|
|
4925
|
+
return process.env.UNBROWSE_CONFIG_PATH || path10.join(os7.homedir(), ".unbrowse", "config.json");
|
|
4926
|
+
}
|
|
4927
|
+
var DEFAULT2 = {
|
|
4928
|
+
contribution: { share_pointers: false, set_via: "default" },
|
|
4929
|
+
rev_share: { opted_in: false },
|
|
4930
|
+
notice_shown_count: 0
|
|
4931
|
+
};
|
|
4932
|
+
function freshDefault2() {
|
|
4933
|
+
return {
|
|
4934
|
+
contribution: { ...DEFAULT2.contribution },
|
|
4935
|
+
rev_share: { ...DEFAULT2.rev_share },
|
|
4936
|
+
notice_shown_count: 0
|
|
4937
|
+
};
|
|
4938
|
+
}
|
|
4939
|
+
var cached2 = null;
|
|
4940
|
+
function getContributionConfig2() {
|
|
4941
|
+
if (cached2)
|
|
4942
|
+
return cached2;
|
|
4943
|
+
const p = configPath2();
|
|
4944
|
+
let fileExisted = false;
|
|
4945
|
+
let raw = null;
|
|
4946
|
+
try {
|
|
4947
|
+
const content = fs3.readFileSync(p, "utf-8");
|
|
4948
|
+
fileExisted = true;
|
|
4949
|
+
if (content.trim().length > 0) {
|
|
4950
|
+
raw = JSON.parse(content);
|
|
4951
|
+
}
|
|
4952
|
+
} catch {}
|
|
4953
|
+
if (!raw) {
|
|
4954
|
+
cached2 = freshDefault2();
|
|
4955
|
+
return cached2;
|
|
4956
|
+
}
|
|
4957
|
+
const contributionRaw = raw.contribution ?? null;
|
|
4958
|
+
const revShareRaw = raw.rev_share ?? null;
|
|
4959
|
+
const isExistingUserMigration = fileExisted && !contributionRaw;
|
|
4960
|
+
cached2 = {
|
|
4961
|
+
contribution: {
|
|
4962
|
+
share_pointers: !!(contributionRaw?.share_pointers ?? DEFAULT2.contribution.share_pointers),
|
|
4963
|
+
set_via: contributionRaw?.set_via ?? DEFAULT2.contribution.set_via,
|
|
4964
|
+
...contributionRaw?.set_at ? { set_at: String(contributionRaw.set_at) } : {}
|
|
4965
|
+
},
|
|
4966
|
+
rev_share: {
|
|
4967
|
+
opted_in: !!(revShareRaw?.opted_in ?? DEFAULT2.rev_share.opted_in),
|
|
4968
|
+
...revShareRaw?.wallet_address ? { wallet_address: String(revShareRaw.wallet_address) } : {}
|
|
4969
|
+
},
|
|
4970
|
+
notice_shown_count: typeof raw.notice_shown_count === "number" ? raw.notice_shown_count : isExistingUserMigration ? 5 : 0
|
|
4971
|
+
};
|
|
4972
|
+
if (isExistingUserMigration) {
|
|
4973
|
+
try {
|
|
4974
|
+
const merged = { ...raw, ...cached2 };
|
|
4975
|
+
fs3.mkdirSync(path10.dirname(p), { recursive: true });
|
|
4976
|
+
fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
|
|
4977
|
+
} catch {}
|
|
4978
|
+
}
|
|
4979
|
+
return cached2;
|
|
4980
|
+
}
|
|
4981
|
+
function setContributionConfig2(updates) {
|
|
4982
|
+
const current = getContributionConfig2();
|
|
4983
|
+
const next = {
|
|
4984
|
+
contribution: {
|
|
4985
|
+
...current.contribution,
|
|
4986
|
+
...updates.contribution ?? {},
|
|
4987
|
+
set_at: new Date().toISOString()
|
|
4988
|
+
},
|
|
4989
|
+
rev_share: {
|
|
4990
|
+
...current.rev_share,
|
|
4991
|
+
...updates.rev_share ?? {}
|
|
4992
|
+
},
|
|
4993
|
+
notice_shown_count: updates.notice_shown_count ?? current.notice_shown_count ?? 0
|
|
4994
|
+
};
|
|
4995
|
+
const p = configPath2();
|
|
4996
|
+
let existing = {};
|
|
4997
|
+
try {
|
|
4998
|
+
const content = fs3.readFileSync(p, "utf-8");
|
|
4999
|
+
if (content.trim())
|
|
5000
|
+
existing = JSON.parse(content);
|
|
5001
|
+
} catch {}
|
|
5002
|
+
const merged = { ...existing, ...next };
|
|
5003
|
+
fs3.mkdirSync(path10.dirname(p), { recursive: true });
|
|
5004
|
+
fs3.writeFileSync(p, JSON.stringify(merged, null, 2));
|
|
5005
|
+
cached2 = next;
|
|
5006
|
+
}
|
|
5007
|
+
|
|
4429
5008
|
// ../../src/cli.ts
|
|
4430
5009
|
loadEnv({ quiet: true });
|
|
4431
5010
|
loadEnv({ path: ".env.runtime", quiet: true });
|
|
4432
5011
|
var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
|
|
4433
5012
|
var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
|
|
5013
|
+
var FRONTEND_URL = (process.env.UNBROWSE_FRONTEND_URL || process.env.PUBLIC_FRONTEND_URL || "https://www.unbrowse.ai").replace(/\/+$/, "");
|
|
4434
5014
|
var walletNudgeShown = false;
|
|
4435
5015
|
function parseArgs(argv) {
|
|
4436
5016
|
const raw = argv.slice(2);
|
|
@@ -4457,8 +5037,12 @@ function parseArgs(argv) {
|
|
|
4457
5037
|
if (valueExpectedFlags.has(key)) {
|
|
4458
5038
|
if (next === undefined)
|
|
4459
5039
|
die(`--${key} requires a value`);
|
|
4460
|
-
|
|
4461
|
-
|
|
5040
|
+
if (next === "-p" || next === "--param" || next.startsWith("--")) {
|
|
5041
|
+
flags[key] = true;
|
|
5042
|
+
} else {
|
|
5043
|
+
flags[key] = next;
|
|
5044
|
+
i++;
|
|
5045
|
+
}
|
|
4462
5046
|
} else if (!next || next.startsWith("--") || next === "-p" || next === "--param") {
|
|
4463
5047
|
flags[key] = true;
|
|
4464
5048
|
} else {
|
|
@@ -4471,8 +5055,8 @@ function parseArgs(argv) {
|
|
|
4471
5055
|
}
|
|
4472
5056
|
return { command, args: positional, flags, params };
|
|
4473
5057
|
}
|
|
4474
|
-
async function api2(method,
|
|
4475
|
-
let target = `${BASE_URL}${
|
|
5058
|
+
async function api2(method, path11, body, opts) {
|
|
5059
|
+
let target = `${BASE_URL}${path11}`;
|
|
4476
5060
|
let requestBody = body;
|
|
4477
5061
|
if (method === "GET" && body && typeof body === "object") {
|
|
4478
5062
|
const params = new URLSearchParams;
|
|
@@ -4486,14 +5070,34 @@ async function api2(method, path9, body) {
|
|
|
4486
5070
|
target += `${target.includes("?") ? "&" : "?"}${query}`;
|
|
4487
5071
|
requestBody = undefined;
|
|
4488
5072
|
}
|
|
4489
|
-
const
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
5073
|
+
const ctrl = new AbortController;
|
|
5074
|
+
const timeoutMs = opts?.timeoutMs;
|
|
5075
|
+
const timer = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
|
|
5076
|
+
let res;
|
|
5077
|
+
try {
|
|
5078
|
+
res = await fetch(target, {
|
|
5079
|
+
method,
|
|
5080
|
+
headers: {
|
|
5081
|
+
...requestBody ? { "Content-Type": "application/json" } : {},
|
|
5082
|
+
"x-unbrowse-client-id": CLI_CLIENT_ID
|
|
5083
|
+
},
|
|
5084
|
+
body: requestBody ? JSON.stringify(requestBody) : undefined,
|
|
5085
|
+
signal: ctrl.signal
|
|
5086
|
+
});
|
|
5087
|
+
} catch (err) {
|
|
5088
|
+
if (timer)
|
|
5089
|
+
clearTimeout(timer);
|
|
5090
|
+
if (err?.name === "AbortError") {
|
|
5091
|
+
return {
|
|
5092
|
+
error: "cli_timeout",
|
|
5093
|
+
message: `CLI gave up waiting on local server after ${timeoutMs}ms. Try: pkill -9 -f 'unbrowse|kuri'`
|
|
5094
|
+
};
|
|
5095
|
+
}
|
|
5096
|
+
throw err;
|
|
5097
|
+
} finally {
|
|
5098
|
+
if (timer)
|
|
5099
|
+
clearTimeout(timer);
|
|
5100
|
+
}
|
|
4497
5101
|
if (!res.ok && res.headers.get("content-type")?.includes("json")) {
|
|
4498
5102
|
return res.json();
|
|
4499
5103
|
}
|
|
@@ -4514,6 +5118,14 @@ function info(msg) {
|
|
|
4514
5118
|
process.stderr.write(`[unbrowse] ${msg}
|
|
4515
5119
|
`);
|
|
4516
5120
|
}
|
|
5121
|
+
function openUrl(url) {
|
|
5122
|
+
if (process.env.UNBROWSE_OPEN_BROWSER === "0")
|
|
5123
|
+
return;
|
|
5124
|
+
try {
|
|
5125
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
5126
|
+
spawn3(cmd, [url], { detached: true, stdio: "ignore" }).unref();
|
|
5127
|
+
} catch {}
|
|
5128
|
+
}
|
|
4517
5129
|
function resolveResultError(result) {
|
|
4518
5130
|
return result.result?.error ?? result.error;
|
|
4519
5131
|
}
|
|
@@ -4523,6 +5135,9 @@ function resolveLoginUrl(result, fallbackUrl) {
|
|
|
4523
5135
|
function isResolveSuccessResult(result) {
|
|
4524
5136
|
if (resolveResultError(result))
|
|
4525
5137
|
return false;
|
|
5138
|
+
const status = result.result?.status;
|
|
5139
|
+
if (status === "no_match" || status === "auth_required" || status === "error")
|
|
5140
|
+
return false;
|
|
4526
5141
|
return !!result.result || Array.isArray(result.available_endpoints);
|
|
4527
5142
|
}
|
|
4528
5143
|
async function withPendingNotice(promise, message, delayMs = 3000) {
|
|
@@ -4714,6 +5329,7 @@ async function cmdResolve(flags) {
|
|
|
4714
5329
|
const intent = flags.intent;
|
|
4715
5330
|
if (!intent)
|
|
4716
5331
|
die("--intent is required");
|
|
5332
|
+
maybeShowContributionNotice();
|
|
4717
5333
|
const hostType = detectTelemetryHostType();
|
|
4718
5334
|
await ensureCliInstallTracked(hostType);
|
|
4719
5335
|
await recordFunnelTelemetryEvent("cli_invoked", {
|
|
@@ -4775,10 +5391,19 @@ async function cmdResolve(flags) {
|
|
|
4775
5391
|
body.force_capture = true;
|
|
4776
5392
|
if (flags["skip-robots"])
|
|
4777
5393
|
body.skip_robots_check = true;
|
|
5394
|
+
const budgetFlag = flags.budget;
|
|
5395
|
+
if (typeof budgetFlag === "string") {
|
|
5396
|
+
const parsed = parseInt(budgetFlag, 10);
|
|
5397
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
5398
|
+
body.budget_ms = parsed;
|
|
5399
|
+
} else if (typeof budgetFlag === "number" && Number.isFinite(budgetFlag) && budgetFlag > 0) {
|
|
5400
|
+
body.budget_ms = budgetFlag;
|
|
5401
|
+
}
|
|
4778
5402
|
body.projection = { raw: true };
|
|
4779
5403
|
const startedAt = Date.now();
|
|
4780
5404
|
async function resolveOnce(message = "Still working. Searching cached routes...") {
|
|
4781
|
-
|
|
5405
|
+
const cliTimeoutMs = (typeof body.budget_ms === "number" ? body.budget_ms : 8000) + 30000;
|
|
5406
|
+
return withPendingNotice(api2("POST", "/v1/intent/resolve", body, { timeoutMs: cliTimeoutMs }), message);
|
|
4782
5407
|
}
|
|
4783
5408
|
let result = await resolveOnce();
|
|
4784
5409
|
const resultError = resolveResultError(result);
|
|
@@ -4798,8 +5423,13 @@ async function cmdResolve(flags) {
|
|
|
4798
5423
|
const skillId = resolveSkillId();
|
|
4799
5424
|
if (skillId && endpoints.length > 0) {
|
|
4800
5425
|
const bestEndpoint = endpoints[0];
|
|
4801
|
-
|
|
4802
|
-
|
|
5426
|
+
if (endpointNeedsThirdPartyTermsConfirmation(bestEndpoint) && !flags["confirm-third-party-terms"]) {
|
|
5427
|
+
process.stderr.write(`Skipping auto-execute: endpoint ${bestEndpoint.endpoint_id ?? "?"} ` + `requires explicit third-party terms confirmation. ` + `Re-run with --confirm-third-party-terms to proceed.
|
|
5428
|
+
`);
|
|
5429
|
+
} else {
|
|
5430
|
+
info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
|
|
5431
|
+
result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
|
|
5432
|
+
}
|
|
4803
5433
|
}
|
|
4804
5434
|
}
|
|
4805
5435
|
if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
|
|
@@ -4861,8 +5491,8 @@ async function cmdResolve(flags) {
|
|
|
4861
5491
|
throw error;
|
|
4862
5492
|
}
|
|
4863
5493
|
}
|
|
4864
|
-
function drillPath(data,
|
|
4865
|
-
const segments =
|
|
5494
|
+
function drillPath(data, path11) {
|
|
5495
|
+
const segments = path11.split(/\./).flatMap((s) => {
|
|
4866
5496
|
const m = s.match(/^(.+)\[\]$/);
|
|
4867
5497
|
return m ? [m[1], "[]"] : [s];
|
|
4868
5498
|
});
|
|
@@ -4889,9 +5519,9 @@ function drillPath(data, path9) {
|
|
|
4889
5519
|
}
|
|
4890
5520
|
return values;
|
|
4891
5521
|
}
|
|
4892
|
-
function resolveDotPath(obj,
|
|
5522
|
+
function resolveDotPath(obj, path11) {
|
|
4893
5523
|
let cur = obj;
|
|
4894
|
-
for (const key of
|
|
5524
|
+
for (const key of path11.split(".")) {
|
|
4895
5525
|
if (cur == null || typeof cur !== "object")
|
|
4896
5526
|
return;
|
|
4897
5527
|
cur = cur[key];
|
|
@@ -4908,8 +5538,8 @@ function applyExtract(items, extractSpec) {
|
|
|
4908
5538
|
return items.map((item) => {
|
|
4909
5539
|
const row = {};
|
|
4910
5540
|
let hasValue = false;
|
|
4911
|
-
for (const { alias, path:
|
|
4912
|
-
const val = resolveDotPath(item,
|
|
5541
|
+
for (const { alias, path: path11 } of fields) {
|
|
5542
|
+
const val = resolveDotPath(item, path11);
|
|
4913
5543
|
row[alias] = val ?? null;
|
|
4914
5544
|
if (val != null)
|
|
4915
5545
|
hasValue = true;
|
|
@@ -4940,6 +5570,7 @@ async function cmdExecute(flags) {
|
|
|
4940
5570
|
const skillId = flags.skill;
|
|
4941
5571
|
if (!skillId)
|
|
4942
5572
|
die("--skill is required");
|
|
5573
|
+
maybeShowContributionNotice();
|
|
4943
5574
|
const hostType = detectTelemetryHostType();
|
|
4944
5575
|
await ensureCliInstallTracked(hostType);
|
|
4945
5576
|
await recordFunnelTelemetryEvent("cli_invoked", {
|
|
@@ -5225,7 +5856,67 @@ async function cmdCleanupStale(flags) {
|
|
|
5225
5856
|
output(await withPendingNotice(api2("POST", "/v1/stale/cleanup", body), "Cleaning stale endpoints..."), !!flags.pretty);
|
|
5226
5857
|
}
|
|
5227
5858
|
async function cmdSearch(flags) {
|
|
5228
|
-
|
|
5859
|
+
const intent = flags.intent;
|
|
5860
|
+
const hostType = detectTelemetryHostType();
|
|
5861
|
+
const { decodeTelemetryAttribution: decodeTelemetryAttribution3 } = await Promise.resolve().then(() => (init_telemetry_attribution2(), exports_telemetry_attribution));
|
|
5862
|
+
const attr = decodeTelemetryAttribution3(process.env.UNBROWSE_ATTRIBUTION_B64) ?? {};
|
|
5863
|
+
const attrProps = {};
|
|
5864
|
+
for (const k of ["channel", "campaign_id", "content_id", "variant_id"]) {
|
|
5865
|
+
const v = attr[k];
|
|
5866
|
+
if (v != null)
|
|
5867
|
+
attrProps[k] = v;
|
|
5868
|
+
}
|
|
5869
|
+
const domain = telemetryDomainFromInput(flags.domain, flags.url);
|
|
5870
|
+
if (intent) {
|
|
5871
|
+
await recordFunnelTelemetryEvent("search_started", {
|
|
5872
|
+
source: "cli",
|
|
5873
|
+
hostType,
|
|
5874
|
+
properties: {
|
|
5875
|
+
command: "search",
|
|
5876
|
+
intent,
|
|
5877
|
+
domain,
|
|
5878
|
+
url: typeof flags.url === "string" ? flags.url : null,
|
|
5879
|
+
...attrProps
|
|
5880
|
+
}
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5883
|
+
let resultCount = 0;
|
|
5884
|
+
try {
|
|
5885
|
+
if (intent && domain) {
|
|
5886
|
+
try {
|
|
5887
|
+
const searchRes = await api2("GET", `/v1/search/domain?intent=${encodeURIComponent(intent)}&domain=${encodeURIComponent(domain)}`);
|
|
5888
|
+
resultCount = Array.isArray(searchRes?.results) ? searchRes.results.length : 0;
|
|
5889
|
+
} catch {}
|
|
5890
|
+
}
|
|
5891
|
+
await cmdResolve(flags);
|
|
5892
|
+
if (intent) {
|
|
5893
|
+
await recordFunnelTelemetryEvent("search_completed", {
|
|
5894
|
+
source: "cli",
|
|
5895
|
+
hostType,
|
|
5896
|
+
properties: {
|
|
5897
|
+
command: "search",
|
|
5898
|
+
intent,
|
|
5899
|
+
domain,
|
|
5900
|
+
result_count: resultCount,
|
|
5901
|
+
...attrProps
|
|
5902
|
+
}
|
|
5903
|
+
});
|
|
5904
|
+
}
|
|
5905
|
+
} catch (err) {
|
|
5906
|
+
if (intent) {
|
|
5907
|
+
await recordFunnelTelemetryEvent("search_failed", {
|
|
5908
|
+
source: "cli",
|
|
5909
|
+
hostType,
|
|
5910
|
+
properties: {
|
|
5911
|
+
command: "search",
|
|
5912
|
+
intent,
|
|
5913
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5914
|
+
...attrProps
|
|
5915
|
+
}
|
|
5916
|
+
});
|
|
5917
|
+
}
|
|
5918
|
+
throw err;
|
|
5919
|
+
}
|
|
5229
5920
|
}
|
|
5230
5921
|
async function cmdSessions(flags) {
|
|
5231
5922
|
const domain = flags.domain;
|
|
@@ -5267,7 +5958,7 @@ async function cmdSetup(flags) {
|
|
|
5267
5958
|
info("Wallet not paired \u2014 you won't earn when other agents use routes you discovered.");
|
|
5268
5959
|
info("Run: npx @crossmint/lobster-cli setup");
|
|
5269
5960
|
} else {
|
|
5270
|
-
info("No wallet configured \u2014
|
|
5961
|
+
info("No wallet configured \u2014 local indexing works, but payout needs a wallet.");
|
|
5271
5962
|
info("Set up a wallet to start earning:");
|
|
5272
5963
|
info(" npx @crossmint/lobster-cli setup");
|
|
5273
5964
|
}
|
|
@@ -5337,6 +6028,10 @@ async function cmdSetup(flags) {
|
|
|
5337
6028
|
output(report, true);
|
|
5338
6029
|
if (report.browser_engine.action === "failed")
|
|
5339
6030
|
process.exit(1);
|
|
6031
|
+
if (getApiKey()) {
|
|
6032
|
+
info("Dashboard connected:");
|
|
6033
|
+
info(" unbrowse dashboard");
|
|
6034
|
+
}
|
|
5340
6035
|
try {
|
|
5341
6036
|
info("Trying your first resolve...");
|
|
5342
6037
|
const demoUrl = "https://jsonplaceholder.typicode.com";
|
|
@@ -5373,14 +6068,203 @@ async function cmdSetup(flags) {
|
|
|
5373
6068
|
info("That's unbrowse. Try your own:");
|
|
5374
6069
|
info(' unbrowse resolve --intent "search for shoes" --url "https://amazon.com"');
|
|
5375
6070
|
} else {
|
|
5376
|
-
|
|
5377
|
-
|
|
6071
|
+
const inner = resolveResult.result;
|
|
6072
|
+
const nextStep = inner?.next_step;
|
|
6073
|
+
const command = typeof nextStep?.command === "string" ? nextStep.command : 'unbrowse capture --url "https://jsonplaceholder.typicode.com" --intent "list all posts"';
|
|
6074
|
+
info("No reusable route found on the demo site yet.");
|
|
6075
|
+
info("Next step:");
|
|
6076
|
+
info(` ${command}`);
|
|
6077
|
+
info("");
|
|
6078
|
+
info("Try your own:");
|
|
6079
|
+
info(' unbrowse resolve --intent "search for shoes" --url "https://amazon.com"');
|
|
5378
6080
|
}
|
|
5379
6081
|
} catch {
|
|
5380
6082
|
info("Setup complete. Try your first resolve:");
|
|
5381
6083
|
info(' unbrowse resolve --intent "list posts" --url "https://jsonplaceholder.typicode.com"');
|
|
5382
6084
|
}
|
|
5383
6085
|
}
|
|
6086
|
+
async function cmdMode(_flags) {
|
|
6087
|
+
await promptContributionMode({ force: true });
|
|
6088
|
+
await syncContributionPreferenceToServer();
|
|
6089
|
+
}
|
|
6090
|
+
async function syncContributionPreferenceToServer() {
|
|
6091
|
+
const cfg = loadConfig();
|
|
6092
|
+
if (!cfg?.api_key)
|
|
6093
|
+
return;
|
|
6094
|
+
const share_pointers = !!getContributionConfig2().contribution.share_pointers;
|
|
6095
|
+
try {
|
|
6096
|
+
await pushAccountPreferences({ share_pointers });
|
|
6097
|
+
info("Synced preference to your account.");
|
|
6098
|
+
} catch (err) {
|
|
6099
|
+
const msg = err.message ?? "";
|
|
6100
|
+
if (msg.includes("account_required") || msg.includes("HTTP 403"))
|
|
6101
|
+
return;
|
|
6102
|
+
info(`Local mode set, but server sync failed: ${msg}`);
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
async function refreshContributionPreferenceFromServer(verbose = false) {
|
|
6106
|
+
const cfg = loadConfig();
|
|
6107
|
+
if (!cfg?.api_key)
|
|
6108
|
+
return false;
|
|
6109
|
+
try {
|
|
6110
|
+
const serverPrefs = await fetchAccountPreferences();
|
|
6111
|
+
if (!serverPrefs)
|
|
6112
|
+
return false;
|
|
6113
|
+
const local = getContributionConfig2();
|
|
6114
|
+
if (local.contribution.share_pointers !== serverPrefs.share_pointers) {
|
|
6115
|
+
setContributionConfig2({
|
|
6116
|
+
contribution: { share_pointers: serverPrefs.share_pointers, set_via: "mode-command" }
|
|
6117
|
+
});
|
|
6118
|
+
if (verbose)
|
|
6119
|
+
info(`Synced auto-publish from dashboard: ${serverPrefs.share_pointers ? "ON" : "off"}.`);
|
|
6120
|
+
}
|
|
6121
|
+
return true;
|
|
6122
|
+
} catch (err) {
|
|
6123
|
+
if (verbose)
|
|
6124
|
+
info(`Dashboard preference sync failed: ${err.message}`);
|
|
6125
|
+
return false;
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
async function cmdAccount(flags) {
|
|
6129
|
+
if (flags["reset-key"]) {
|
|
6130
|
+
await cmdRegister({
|
|
6131
|
+
reset: true,
|
|
6132
|
+
email: typeof flags.email === "string" ? flags.email : undefined,
|
|
6133
|
+
"no-prompt": flags["no-prompt"]
|
|
6134
|
+
});
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
await refreshContributionPreferenceFromServer(false);
|
|
6138
|
+
const cfg = loadConfig();
|
|
6139
|
+
const contribution = getContributionConfig2();
|
|
6140
|
+
const payload = {
|
|
6141
|
+
signed_in: !!cfg?.api_key,
|
|
6142
|
+
agent_id: cfg?.agent_id ?? null,
|
|
6143
|
+
agent_name: cfg?.agent_name ?? null,
|
|
6144
|
+
email: cfg?.email ?? null,
|
|
6145
|
+
user_id: cfg?.user_id ?? null,
|
|
6146
|
+
wallet_address: cfg?.wallet_address ?? null,
|
|
6147
|
+
wallet_provider: cfg?.wallet_provider ?? null,
|
|
6148
|
+
dashboard_url: `${FRONTEND_URL}/dashboard`,
|
|
6149
|
+
local_server: BASE_URL,
|
|
6150
|
+
auto_publish: contribution.contribution.share_pointers,
|
|
6151
|
+
rev_share: contribution.rev_share.opted_in
|
|
6152
|
+
};
|
|
6153
|
+
if (flags.json || flags.pretty) {
|
|
6154
|
+
output(payload, !!flags.pretty);
|
|
6155
|
+
return;
|
|
6156
|
+
}
|
|
6157
|
+
info("Unbrowse account");
|
|
6158
|
+
info(` signed_in: ${payload.signed_in ? "yes" : "no"}`);
|
|
6159
|
+
info(` email: ${payload.email ?? "(none)"}`);
|
|
6160
|
+
info(` agent_id: ${payload.agent_id ?? "(none)"}`);
|
|
6161
|
+
info(` wallet: ${payload.wallet_address ?? "(none)"}`);
|
|
6162
|
+
info(` auto_publish: ${payload.auto_publish ? "on" : "off"}`);
|
|
6163
|
+
info(` dashboard: ${payload.dashboard_url}`);
|
|
6164
|
+
output(payload, false);
|
|
6165
|
+
}
|
|
6166
|
+
async function cmdDashboard(flags) {
|
|
6167
|
+
await refreshContributionPreferenceFromServer(false);
|
|
6168
|
+
const cfg = loadConfig();
|
|
6169
|
+
if (!cfg?.api_key) {
|
|
6170
|
+
const loginUrl = `${FRONTEND_URL}/login`;
|
|
6171
|
+
info("No account-bound CLI key found. Opening website sign-in.");
|
|
6172
|
+
if (!flags["no-open"])
|
|
6173
|
+
openUrl(loginUrl);
|
|
6174
|
+
output({ status: "login_required", url: loginUrl }, !!flags.pretty);
|
|
6175
|
+
return;
|
|
6176
|
+
}
|
|
6177
|
+
await ensureLocalServer(BASE_URL, false, import.meta.url);
|
|
6178
|
+
const pair = createDashboardPairingToken();
|
|
6179
|
+
const url = `${FRONTEND_URL}/login?local=${encodeURIComponent(BASE_URL)}&pair=${encodeURIComponent(pair.token)}`;
|
|
6180
|
+
if (!flags["no-open"])
|
|
6181
|
+
openUrl(url);
|
|
6182
|
+
info("Opening dashboard and pairing this CLI install.");
|
|
6183
|
+
output({
|
|
6184
|
+
status: "pairing_started",
|
|
6185
|
+
url,
|
|
6186
|
+
local_server: BASE_URL,
|
|
6187
|
+
expires_at: pair.expires_at
|
|
6188
|
+
}, !!flags.pretty);
|
|
6189
|
+
}
|
|
6190
|
+
async function runPostSetupContributionPrompt() {
|
|
6191
|
+
try {
|
|
6192
|
+
await promptContributionMode({ force: false });
|
|
6193
|
+
} catch {}
|
|
6194
|
+
}
|
|
6195
|
+
async function cmdCapture(flags) {
|
|
6196
|
+
const url = flags.url;
|
|
6197
|
+
const intent = flags.intent || "capture";
|
|
6198
|
+
if (!url)
|
|
6199
|
+
die("--url is required");
|
|
6200
|
+
maybeShowContributionNotice();
|
|
6201
|
+
const t0 = Date.now();
|
|
6202
|
+
const result = await api2("POST", "/v1/capture", { url, intent });
|
|
6203
|
+
const endpoints = Array.isArray(result.endpoints) ? result.endpoints : Array.isArray(result.available_endpoints) ? result.available_endpoints : [];
|
|
6204
|
+
const skill = result.skill ?? null;
|
|
6205
|
+
const skillId = result.skill_id ?? skill?.skill_id ?? (typeof result.learned_skill_id === "string" ? result.learned_skill_id : undefined);
|
|
6206
|
+
const isThinDocumentOnly = endpoints.length === 1 && (() => {
|
|
6207
|
+
const e0 = endpoints[0];
|
|
6208
|
+
if (!e0 || e0.method !== "GET")
|
|
6209
|
+
return false;
|
|
6210
|
+
const tmpl = (e0.url_template ?? "").split("?")[0].replace(/\/$/, "");
|
|
6211
|
+
const target = url.split("?")[0].replace(/\/$/, "");
|
|
6212
|
+
return tmpl === target;
|
|
6213
|
+
})();
|
|
6214
|
+
const escapedIntent = intent.replace(/"/g, "\\\"");
|
|
6215
|
+
const escapedUrl = url.replace(/"/g, "\\\"");
|
|
6216
|
+
const envelope = {
|
|
6217
|
+
skill_id: skillId,
|
|
6218
|
+
endpoints_discovered: typeof result.endpoints_discovered === "number" ? result.endpoints_discovered : endpoints.length,
|
|
6219
|
+
marketplace_published: !!result.marketplace_published,
|
|
6220
|
+
ms: typeof result.ms === "number" ? result.ms : Date.now() - t0,
|
|
6221
|
+
next_step: endpoints.length === 0 ? "no endpoints discovered; site may need authentication or different intent" : `unbrowse resolve --intent "${escapedIntent}" --url "${escapedUrl}"`,
|
|
6222
|
+
...isThinDocumentOnly ? { capture_pattern: "doc_only", capture_observation: "only the input URL was captured (1 GET, no XHR fired during the auto-capture window)" } : {},
|
|
6223
|
+
...result.error ? { error: result.error } : {},
|
|
6224
|
+
...result.captured_meta ? { captured_meta: result.captured_meta } : {},
|
|
6225
|
+
...typeof result.capture_path === "string" ? { capture_path: result.capture_path } : {},
|
|
6226
|
+
...result.prior_domain_note ? { prior_domain_note: result.prior_domain_note } : {},
|
|
6227
|
+
...result.note_evidence ? { note_evidence: result.note_evidence } : {}
|
|
6228
|
+
};
|
|
6229
|
+
output(envelope, !!flags.pretty);
|
|
6230
|
+
}
|
|
6231
|
+
async function cmdNote(flags, args) {
|
|
6232
|
+
const subRaw = args?.[0] ?? flags.action;
|
|
6233
|
+
const sub = (typeof subRaw === "string" ? subRaw : "").toLowerCase();
|
|
6234
|
+
if (!sub || !["read", "write", "list"].includes(sub)) {
|
|
6235
|
+
die('usage: unbrowse note <read|write|list> --domain <domain> [--body "..."]');
|
|
6236
|
+
}
|
|
6237
|
+
if (sub === "list") {
|
|
6238
|
+
const { homedir: homedir9 } = await import("os");
|
|
6239
|
+
const { join: join15 } = await import("path");
|
|
6240
|
+
const { readdirSync: readdirSync7, existsSync: existsSync19 } = await import("fs");
|
|
6241
|
+
const profile = process.env.UNBROWSE_PROFILE ?? "";
|
|
6242
|
+
const dir = process.env.UNBROWSE_DOMAIN_NOTES_DIR ?? (profile ? join15(homedir9(), ".unbrowse", "profiles", profile, "domain-notes") : join15(homedir9(), ".unbrowse", "domain-notes"));
|
|
6243
|
+
if (!existsSync19(dir)) {
|
|
6244
|
+
output({ notes: [], dir }, !!flags.pretty);
|
|
6245
|
+
return;
|
|
6246
|
+
}
|
|
6247
|
+
const files = readdirSync7(dir).filter((f) => f.endsWith(".md"));
|
|
6248
|
+
output({ dir, notes: files.map((f) => f.replace(/\.md$/, "")) }, !!flags.pretty);
|
|
6249
|
+
return;
|
|
6250
|
+
}
|
|
6251
|
+
const domain = flags.domain;
|
|
6252
|
+
if (!domain)
|
|
6253
|
+
die("--domain required");
|
|
6254
|
+
if (sub === "read") {
|
|
6255
|
+
const res2 = await api2("GET", `/v1/domain-notes/${encodeURIComponent(domain)}`).catch((err) => ({
|
|
6256
|
+
error: err.message
|
|
6257
|
+
}));
|
|
6258
|
+
output(res2, !!flags.pretty);
|
|
6259
|
+
return;
|
|
6260
|
+
}
|
|
6261
|
+
const body = flags.body;
|
|
6262
|
+
if (typeof body !== "string" || body.trim().length === 0) {
|
|
6263
|
+
die("--body required (non-empty markdown string)");
|
|
6264
|
+
}
|
|
6265
|
+
const res = await api2("POST", `/v1/domain-notes/${encodeURIComponent(domain)}`, { body });
|
|
6266
|
+
output(res, !!flags.pretty);
|
|
6267
|
+
}
|
|
5384
6268
|
var CLI_REFERENCE = {
|
|
5385
6269
|
commands: [
|
|
5386
6270
|
{ name: "health", usage: "", desc: "Server health check" },
|
|
@@ -5425,7 +6309,12 @@ var CLI_REFERENCE = {
|
|
|
5425
6309
|
{ name: "earnings", usage: "[--json]", desc: "Show your credit balance, earnings from indexing, and spending" },
|
|
5426
6310
|
{ name: "corpus-test", usage: "--url <url> [--id <id>] [--retries N]", desc: "Capture a single URL with retry logic; keeps best result across N attempts" },
|
|
5427
6311
|
{ 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" },
|
|
5428
|
-
{ name: "register", usage: "[--no-prompt]", desc: "
|
|
6312
|
+
{ name: "register", usage: "[--email lewis@example.com] [--reset] [--no-prompt]", desc: "Register an API key. With --reset, discard the local cached key first; with --email, mint an account-bound key." },
|
|
6313
|
+
{ name: "account", usage: "[--json] [--pretty] [--reset-key] [--email lewis@example.com]", desc: "Show local account, dashboard link, wallet, and contribution mode; --reset-key forces local key reset." },
|
|
6314
|
+
{ name: "dashboard", usage: "[--no-open] [--pretty]", desc: "Open the website dashboard and pair it to this CLI install through localhost" },
|
|
6315
|
+
{ name: "mode", usage: "", desc: "Re-prompt for contribution mode (private / share / share + earn)" },
|
|
6316
|
+
{ 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`." },
|
|
6317
|
+
{ name: "note", usage: '<read|write|list> --domain <domain> [--body "..."]', desc: "Read/write per-domain LLM-prose notes consumed by augment on next capture. Agent populates after reading capture's note_evidence." }
|
|
5429
6318
|
],
|
|
5430
6319
|
globalFlags: [
|
|
5431
6320
|
{ flag: "--pretty", desc: "Indented JSON output" },
|
|
@@ -5882,6 +6771,7 @@ async function cmdGo(args, flags) {
|
|
|
5882
6771
|
const url = args[0] ?? flags.url;
|
|
5883
6772
|
if (!url)
|
|
5884
6773
|
die("Usage: unbrowse go <url>");
|
|
6774
|
+
maybeShowContributionNotice();
|
|
5885
6775
|
output(await api2("POST", "/v1/browse/go", {
|
|
5886
6776
|
url,
|
|
5887
6777
|
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
@@ -6019,7 +6909,62 @@ async function cmdClose(flags) {
|
|
|
6019
6909
|
output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
|
|
6020
6910
|
}
|
|
6021
6911
|
async function cmdRegister(flags) {
|
|
6022
|
-
|
|
6912
|
+
const reset = flags.reset === true || flags.force === true || flags["reset-key"] === true;
|
|
6913
|
+
const previousConfig = reset ? loadConfig() : null;
|
|
6914
|
+
if (reset) {
|
|
6915
|
+
const envKey = process.env.UNBROWSE_API_KEY?.trim();
|
|
6916
|
+
const result = resetLocalRegistration();
|
|
6917
|
+
delete process.env.UNBROWSE_API_KEY;
|
|
6918
|
+
info(`${result.removed ? "Removed" : "No"} local API key cache at ${result.config_path}.`);
|
|
6919
|
+
if (envKey) {
|
|
6920
|
+
info("Ignoring UNBROWSE_API_KEY for this reset run. Remove or update that env var in your shell, or it will override the saved key next time.");
|
|
6921
|
+
}
|
|
6922
|
+
if (typeof flags.email !== "string" && previousConfig?.email) {
|
|
6923
|
+
flags.email = previousConfig.email;
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
if (typeof flags.email === "string" && flags.email.length > 0) {
|
|
6927
|
+
const email = flags.email;
|
|
6928
|
+
if (!reset && getApiKey()) {
|
|
6929
|
+
info("Already registered. Re-running with --email will mint a new key and overwrite ~/.unbrowse/config.json.");
|
|
6930
|
+
}
|
|
6931
|
+
info(`Sending magic link to ${email}\u2026`);
|
|
6932
|
+
const result = await magicRegister({
|
|
6933
|
+
email,
|
|
6934
|
+
openBrowser: (url) => {
|
|
6935
|
+
info(`Opening browser: ${url}`);
|
|
6936
|
+
try {
|
|
6937
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
6938
|
+
spawn3(cmd, [url], { detached: true, stdio: "ignore" }).unref();
|
|
6939
|
+
} catch {}
|
|
6940
|
+
}
|
|
6941
|
+
});
|
|
6942
|
+
saveConfig({
|
|
6943
|
+
api_key: result.api_key,
|
|
6944
|
+
agent_id: result.agent_id,
|
|
6945
|
+
agent_name: result.email,
|
|
6946
|
+
registered_at: new Date().toISOString(),
|
|
6947
|
+
tos_accepted_version: null,
|
|
6948
|
+
tos_accepted_at: null,
|
|
6949
|
+
email: result.email,
|
|
6950
|
+
user_id: result.user_id
|
|
6951
|
+
});
|
|
6952
|
+
process.env.UNBROWSE_API_KEY = result.api_key;
|
|
6953
|
+
info(`Signed in as ${result.email}. API key saved to ~/.unbrowse/config.json.`);
|
|
6954
|
+
info("Open your dashboard:");
|
|
6955
|
+
info(" unbrowse dashboard");
|
|
6956
|
+
try {
|
|
6957
|
+
const serverPrefs = await fetchAccountPreferences();
|
|
6958
|
+
if (serverPrefs) {
|
|
6959
|
+
setContributionConfig2({
|
|
6960
|
+
contribution: { share_pointers: serverPrefs.share_pointers, set_via: "mode-command" }
|
|
6961
|
+
});
|
|
6962
|
+
info(`Auto-publish to marketplace: ${serverPrefs.share_pointers ? "ON" : "off"} (synced from your account).`);
|
|
6963
|
+
}
|
|
6964
|
+
} catch {}
|
|
6965
|
+
return;
|
|
6966
|
+
}
|
|
6967
|
+
if (!reset && getApiKey()) {
|
|
6023
6968
|
info("Already registered. API key loaded from env or ~/.unbrowse/config.json");
|
|
6024
6969
|
return;
|
|
6025
6970
|
}
|
|
@@ -6308,8 +7253,17 @@ async function main() {
|
|
|
6308
7253
|
}
|
|
6309
7254
|
if (command === "setup") {
|
|
6310
7255
|
await cmdSetup(flags);
|
|
7256
|
+
await runPostSetupContributionPrompt();
|
|
7257
|
+
return;
|
|
7258
|
+
}
|
|
7259
|
+
if (command === "mode") {
|
|
7260
|
+
await cmdMode(flags);
|
|
6311
7261
|
return;
|
|
6312
7262
|
}
|
|
7263
|
+
if (command === "account")
|
|
7264
|
+
return cmdAccount(flags);
|
|
7265
|
+
if (command === "dashboard")
|
|
7266
|
+
return cmdDashboard(flags);
|
|
6313
7267
|
if (command === "mcp")
|
|
6314
7268
|
return cmdMcp(flags);
|
|
6315
7269
|
if (command === "status")
|
|
@@ -6334,6 +7288,7 @@ async function main() {
|
|
|
6334
7288
|
return cmdSessionsScan(flags);
|
|
6335
7289
|
if (command === "register")
|
|
6336
7290
|
return cmdRegister(flags);
|
|
7291
|
+
await refreshContributionPreferenceFromServer(false);
|
|
6337
7292
|
const KNOWN_COMMANDS = new Set([
|
|
6338
7293
|
"health",
|
|
6339
7294
|
"mcp",
|
|
@@ -6386,7 +7341,11 @@ async function main() {
|
|
|
6386
7341
|
"corpus-run",
|
|
6387
7342
|
"sessions-scan",
|
|
6388
7343
|
"cache-clear",
|
|
6389
|
-
"register"
|
|
7344
|
+
"register",
|
|
7345
|
+
"mode",
|
|
7346
|
+
"account",
|
|
7347
|
+
"dashboard",
|
|
7348
|
+
"capture"
|
|
6390
7349
|
]);
|
|
6391
7350
|
if (!KNOWN_COMMANDS.has(command)) {
|
|
6392
7351
|
const pack = findSitePack(command);
|
|
@@ -6500,6 +7459,16 @@ async function main() {
|
|
|
6500
7459
|
return cmdSessionsScan(flags);
|
|
6501
7460
|
case "register":
|
|
6502
7461
|
return cmdRegister(flags);
|
|
7462
|
+
case "mode":
|
|
7463
|
+
return cmdMode(flags);
|
|
7464
|
+
case "account":
|
|
7465
|
+
return cmdAccount(flags);
|
|
7466
|
+
case "dashboard":
|
|
7467
|
+
return cmdDashboard(flags);
|
|
7468
|
+
case "capture":
|
|
7469
|
+
return cmdCapture(flags);
|
|
7470
|
+
case "note":
|
|
7471
|
+
return cmdNote(flags, args);
|
|
6503
7472
|
default:
|
|
6504
7473
|
info(`Unknown command: ${command}`);
|
|
6505
7474
|
printHelp();
|