unbrowse 3.0.2 → 3.1.0-experiments.5e7a7bb
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 +629 -101
- package/dist/mcp.js +710 -73
- package/package.json +1 -1
- package/runtime-src/api/browse-index.ts +26 -4
- package/runtime-src/api/routes.ts +43 -9
- package/runtime-src/browser/index.ts +2 -1
- package/runtime-src/build-info.generated.ts +5 -5
- package/runtime-src/capture/index.ts +113 -0
- package/runtime-src/cli.ts +190 -2
- package/runtime-src/client/index.ts +28 -12
- package/runtime-src/execution/index.ts +43 -21
- package/runtime-src/execution/token-resolver.ts +122 -0
- package/runtime-src/graph/index.ts +14 -6
- package/runtime-src/impact-log.ts +227 -0
- package/runtime-src/kuri/client.ts +5 -1
- package/runtime-src/marketplace/index.ts +9 -1
- package/runtime-src/mcp.ts +247 -34
- package/runtime-src/orchestrator/browser-agent.ts +2 -1
- package/runtime-src/orchestrator/index.ts +7 -3
- package/runtime-src/payments/lobster-pay.ts +182 -0
- package/runtime-src/reverse-engineer/token-sources.ts +357 -0
- package/runtime-src/types/skill.ts +19 -0
- 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 +10 -6
- package/vendor/kuri/win-x64/kuri.exe +0 -0
package/dist/cli.js
CHANGED
|
@@ -17,12 +17,21 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
});
|
|
18
18
|
return to;
|
|
19
19
|
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
20
29
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
21
30
|
var __promiseAll = (args) => Promise.all(args);
|
|
22
31
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
23
32
|
|
|
24
33
|
// ../../src/build-info.generated.ts
|
|
25
|
-
var BUILD_RELEASE_VERSION = "3.0.
|
|
34
|
+
var BUILD_RELEASE_VERSION = "3.1.0-experiments.5e7a7bb", BUILD_GIT_SHA = "5e7a7bb949c1", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuNWU3YTdiYiIsImdpdF9zaGEiOiI1ZTdhN2JiOTQ5YzEiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDVlN2E3YmI5NDljMSIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDVUMTQ6NTY6MjkuNjY2WiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "OuZD9NeemoStAyT3-MgMS3V3eeatbRMKkVY_J4_6nsM", BUILD_DEFAULT_BACKEND_URL = "https://unbrowse-backend-experiments.lewis-6d8.workers.dev";
|
|
26
35
|
|
|
27
36
|
// ../../src/version.ts
|
|
28
37
|
import { createHash } from "crypto";
|
|
@@ -284,8 +293,114 @@ var init_telemetry_attribution = __esm(() => {
|
|
|
284
293
|
];
|
|
285
294
|
});
|
|
286
295
|
|
|
296
|
+
// ../../src/payments/lobster-pay.ts
|
|
297
|
+
var exports_lobster_pay = {};
|
|
298
|
+
__export(exports_lobster_pay, {
|
|
299
|
+
payAndRetry: () => payAndRetry,
|
|
300
|
+
lobsterX402Fetch: () => lobsterX402Fetch,
|
|
301
|
+
isLobsterAvailable: () => isLobsterAvailable
|
|
302
|
+
});
|
|
303
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
304
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
305
|
+
import { homedir as homedir2 } from "node:os";
|
|
306
|
+
import { join as join3 } from "node:path";
|
|
307
|
+
function getLobsterCommand() {
|
|
308
|
+
try {
|
|
309
|
+
execFileSync("lobstercash", ["--version"], { stdio: "ignore", timeout: 3000 });
|
|
310
|
+
return { cmd: "lobstercash", prefix: [] };
|
|
311
|
+
} catch (_e) {}
|
|
312
|
+
try {
|
|
313
|
+
const npmPrefix = execFileSync("npm", ["config", "get", "prefix"], { encoding: "utf8", timeout: 5000 }).trim();
|
|
314
|
+
const lobsterPath = join3(npmPrefix, "bin", "lobstercash");
|
|
315
|
+
if (existsSync3(lobsterPath)) {
|
|
316
|
+
execFileSync(lobsterPath, ["--version"], { stdio: "ignore", timeout: 3000 });
|
|
317
|
+
return { cmd: lobsterPath, prefix: [] };
|
|
318
|
+
}
|
|
319
|
+
} catch (_e) {}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
function lobsterCmd() {
|
|
323
|
+
if (cachedCommand === undefined)
|
|
324
|
+
cachedCommand = getLobsterCommand();
|
|
325
|
+
return cachedCommand;
|
|
326
|
+
}
|
|
327
|
+
function isLobsterAvailable() {
|
|
328
|
+
const agentsPath = join3(process.env.HOME || homedir2(), ".lobster", "agents.json");
|
|
329
|
+
return existsSync3(agentsPath);
|
|
330
|
+
}
|
|
331
|
+
function lobsterX402Fetch(url, options) {
|
|
332
|
+
return new Promise((resolve) => {
|
|
333
|
+
const resolved = lobsterCmd();
|
|
334
|
+
if (!resolved) {
|
|
335
|
+
resolve({ success: false, body: "", error: "lobstercash CLI not in PATH" });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const { cmd, prefix } = resolved;
|
|
339
|
+
const args = [...prefix, "x402", "fetch", url, "--debug"];
|
|
340
|
+
if (options?.jsonBody) {
|
|
341
|
+
args.push("--json", options.jsonBody);
|
|
342
|
+
}
|
|
343
|
+
if (options?.headers) {
|
|
344
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
345
|
+
args.push("--header", `${key}:${value}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const timeout = options?.timeoutMs ?? LOBSTER_PAY_TIMEOUT_MS;
|
|
349
|
+
args.push("--timeout", String(timeout));
|
|
350
|
+
execFile(cmd, args, { timeout: timeout + 5000, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
351
|
+
if (err) {
|
|
352
|
+
const msg = stderr?.trim() || err.message;
|
|
353
|
+
console.warn(`[lobster-pay] x402 fetch failed: ${msg}`);
|
|
354
|
+
resolve({ success: false, body: "", error: msg });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (stderr) {
|
|
358
|
+
for (const line of stderr.split(`
|
|
359
|
+
`).filter(Boolean)) {
|
|
360
|
+
console.log(`[lobster-pay] ${line}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const statusMatch = stdout.match(/^Status:\s*(\d+)/m);
|
|
364
|
+
const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : undefined;
|
|
365
|
+
if (statusCode && statusCode >= 400) {
|
|
366
|
+
resolve({ success: false, body: stdout, statusCode, error: `HTTP ${statusCode}` });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
resolve({ success: true, body: stdout, statusCode });
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
async function payAndRetry(fullUrl, options) {
|
|
374
|
+
if (!isLobsterAvailable()) {
|
|
375
|
+
console.log("[lobster-pay] lobster.cash not configured — skipping payment");
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
console.log(`[lobster-pay] attempting x402 payment for ${fullUrl}`);
|
|
379
|
+
const result = await lobsterX402Fetch(fullUrl, {
|
|
380
|
+
jsonBody: options?.body ? JSON.stringify(options.body) : undefined,
|
|
381
|
+
headers: options?.headers
|
|
382
|
+
});
|
|
383
|
+
if (!result.success) {
|
|
384
|
+
console.warn(`[lobster-pay] payment failed: ${result.error}`);
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
const raw = result.body;
|
|
389
|
+
const jsonStart = Math.min(...[raw.indexOf("{"), raw.indexOf("[")].filter((i) => i >= 0));
|
|
390
|
+
const jsonStr = jsonStart >= 0 ? raw.slice(jsonStart) : raw;
|
|
391
|
+
const data = JSON.parse(jsonStr);
|
|
392
|
+
console.log("[lobster-pay] payment successful — got paid response");
|
|
393
|
+
return { data, paid: true };
|
|
394
|
+
} catch (_e) {
|
|
395
|
+
console.warn("[lobster-pay] paid response was not valid JSON");
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
var LOBSTER_PAY_TIMEOUT_MS = 30000, cachedCommand = undefined;
|
|
400
|
+
var init_lobster_pay = () => {};
|
|
401
|
+
|
|
287
402
|
// ../../src/runtime/paths.ts
|
|
288
|
-
import { existsSync as
|
|
403
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, realpathSync } from "node:fs";
|
|
289
404
|
import os from "node:os";
|
|
290
405
|
import path from "node:path";
|
|
291
406
|
import { createRequire as createRequire2 } from "node:module";
|
|
@@ -299,7 +414,7 @@ function getPackageRoot(metaUrl) {
|
|
|
299
414
|
let dir = getModuleDir(metaUrl);
|
|
300
415
|
const root = path.parse(dir).root;
|
|
301
416
|
while (dir !== root) {
|
|
302
|
-
if (
|
|
417
|
+
if (existsSync6(path.join(dir, "package.json")))
|
|
303
418
|
return dir;
|
|
304
419
|
dir = path.dirname(dir);
|
|
305
420
|
}
|
|
@@ -318,7 +433,7 @@ function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
|
|
|
318
433
|
const req = createRequire2(metaUrl);
|
|
319
434
|
const tsxPkg = req.resolve("tsx/package.json");
|
|
320
435
|
const tsxLoader = path.join(path.dirname(tsxPkg), "dist", "loader.mjs");
|
|
321
|
-
if (
|
|
436
|
+
if (existsSync6(tsxLoader))
|
|
322
437
|
return ["--import", pathToFileURL(tsxLoader).href, entrypoint];
|
|
323
438
|
} catch {}
|
|
324
439
|
return ["--import", "tsx", entrypoint];
|
|
@@ -326,16 +441,16 @@ function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
|
|
|
326
441
|
function getUnbrowseHome() {
|
|
327
442
|
return path.join(os.homedir(), ".unbrowse");
|
|
328
443
|
}
|
|
329
|
-
function
|
|
330
|
-
if (!
|
|
331
|
-
|
|
444
|
+
function ensureDir2(dir) {
|
|
445
|
+
if (!existsSync6(dir))
|
|
446
|
+
mkdirSync3(dir, { recursive: true });
|
|
332
447
|
return dir;
|
|
333
448
|
}
|
|
334
449
|
function getLogsDir() {
|
|
335
|
-
return
|
|
450
|
+
return ensureDir2(path.join(getUnbrowseHome(), "logs"));
|
|
336
451
|
}
|
|
337
452
|
function getRunDir() {
|
|
338
|
-
return
|
|
453
|
+
return ensureDir2(process.env.UNBROWSE_RUN_DIR || path.join(getUnbrowseHome(), "run"));
|
|
339
454
|
}
|
|
340
455
|
function sanitizeSegment(value) {
|
|
341
456
|
return value.replace(/[^a-zA-Z0-9.-]+/g, "_");
|
|
@@ -470,8 +585,8 @@ var init_logger = __esm(() => {
|
|
|
470
585
|
});
|
|
471
586
|
|
|
472
587
|
// ../../src/kuri/client.ts
|
|
473
|
-
import { execFileSync, spawn as spawn2 } from "node:child_process";
|
|
474
|
-
import { existsSync as
|
|
588
|
+
import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
|
|
589
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
475
590
|
import path5 from "node:path";
|
|
476
591
|
function createBrokerState(port = KURI_DEFAULT_PORT) {
|
|
477
592
|
return {
|
|
@@ -496,12 +611,14 @@ function currentBundledKuriTarget() {
|
|
|
496
611
|
return "linux-arm64";
|
|
497
612
|
if (process.platform === "linux" && process.arch === "x64")
|
|
498
613
|
return "linux-x64";
|
|
614
|
+
if (process.platform === "win32" && process.arch === "x64")
|
|
615
|
+
return "win-x64";
|
|
499
616
|
return null;
|
|
500
617
|
}
|
|
501
618
|
function resolveBinaryOnPath(name) {
|
|
502
619
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
503
620
|
try {
|
|
504
|
-
const output =
|
|
621
|
+
const output = execFileSync2(checker, [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
505
622
|
const match = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
506
623
|
return match || null;
|
|
507
624
|
} catch {
|
|
@@ -544,7 +661,7 @@ function findKuriBinary() {
|
|
|
544
661
|
if (process.env.KURI_BIN)
|
|
545
662
|
return process.env.KURI_BIN;
|
|
546
663
|
const candidates = getKuriBinaryCandidates();
|
|
547
|
-
return candidates.find((candidate) =>
|
|
664
|
+
return candidates.find((candidate) => existsSync8(candidate)) ?? candidates[0] ?? kuriBinaryName();
|
|
548
665
|
}
|
|
549
666
|
var KURI_DEFAULT_PORT = 7700, defaultBrokerState, brokerClients;
|
|
550
667
|
var init_client2 = __esm(() => {
|
|
@@ -567,7 +684,7 @@ var init_browser_access = () => {};
|
|
|
567
684
|
|
|
568
685
|
// ../../src/capture/index.ts
|
|
569
686
|
import { nanoid as nanoid2 } from "nanoid";
|
|
570
|
-
var activeTabRegistry, interceptorInjectedTabs;
|
|
687
|
+
var activeTabRegistry, interceptorInjectedTabs, cdpDocStartTabs, cdpCapturedHeaders;
|
|
571
688
|
var init_capture = __esm(() => {
|
|
572
689
|
init_client2();
|
|
573
690
|
init_domain();
|
|
@@ -575,6 +692,8 @@ var init_capture = __esm(() => {
|
|
|
575
692
|
init_browser_access();
|
|
576
693
|
activeTabRegistry = new Set;
|
|
577
694
|
interceptorInjectedTabs = new Set;
|
|
695
|
+
cdpDocStartTabs = new Set;
|
|
696
|
+
cdpCapturedHeaders = new Map;
|
|
578
697
|
});
|
|
579
698
|
|
|
580
699
|
// ../../src/transform/index.ts
|
|
@@ -584,11 +703,11 @@ var init_transform = __esm(() => {
|
|
|
584
703
|
});
|
|
585
704
|
|
|
586
705
|
// ../../src/debug-trace.ts
|
|
587
|
-
import { join as
|
|
706
|
+
import { join as join6 } from "node:path";
|
|
588
707
|
import { nanoid as nanoid3 } from "nanoid";
|
|
589
708
|
var TRACE_DIR;
|
|
590
709
|
var init_debug_trace = __esm(() => {
|
|
591
|
-
TRACE_DIR = process.env.TRACES_DIR ??
|
|
710
|
+
TRACE_DIR = process.env.TRACES_DIR ?? join6(process.cwd(), "traces");
|
|
592
711
|
});
|
|
593
712
|
|
|
594
713
|
// ../../src/publish/sanitize.ts
|
|
@@ -678,8 +797,8 @@ var init_bundle_scanner = __esm(() => {
|
|
|
678
797
|
});
|
|
679
798
|
|
|
680
799
|
// ../../src/vault/index.ts
|
|
681
|
-
import { join as
|
|
682
|
-
import { homedir as
|
|
800
|
+
import { join as join7 } from "path";
|
|
801
|
+
import { homedir as homedir5 } from "os";
|
|
683
802
|
function normalizeKeytarModule(mod) {
|
|
684
803
|
let candidate = mod;
|
|
685
804
|
for (let depth = 0;depth < 3; depth++) {
|
|
@@ -701,9 +820,9 @@ var init_vault = __esm(async () => {
|
|
|
701
820
|
try {
|
|
702
821
|
keytar = normalizeKeytarModule(await import("keytar"));
|
|
703
822
|
} catch {}
|
|
704
|
-
VAULT_DIR =
|
|
705
|
-
VAULT_FILE =
|
|
706
|
-
KEY_FILE =
|
|
823
|
+
VAULT_DIR = join7(homedir5(), ".unbrowse", "vault");
|
|
824
|
+
VAULT_FILE = join7(VAULT_DIR, "credentials.enc");
|
|
825
|
+
KEY_FILE = join7(VAULT_DIR, ".key");
|
|
707
826
|
vaultLock = Promise.resolve();
|
|
708
827
|
});
|
|
709
828
|
|
|
@@ -854,7 +973,7 @@ var init_schema_review = __esm(() => {
|
|
|
854
973
|
});
|
|
855
974
|
|
|
856
975
|
// ../../src/indexer/index.ts
|
|
857
|
-
import { join as
|
|
976
|
+
import { join as join8 } from "node:path";
|
|
858
977
|
var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
|
|
859
978
|
var init_indexer = __esm(async () => {
|
|
860
979
|
init_graph();
|
|
@@ -869,7 +988,7 @@ var init_indexer = __esm(async () => {
|
|
|
869
988
|
init_graph();
|
|
870
989
|
init_schema_review();
|
|
871
990
|
await init_orchestrator();
|
|
872
|
-
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
991
|
+
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
873
992
|
indexInFlight = new Map;
|
|
874
993
|
pendingIndexJobs = new Map;
|
|
875
994
|
});
|
|
@@ -1046,8 +1165,8 @@ var init_routing_telemetry = __esm(() => {
|
|
|
1046
1165
|
});
|
|
1047
1166
|
// ../../src/orchestrator/index.ts
|
|
1048
1167
|
import { nanoid as nanoid9 } from "nanoid";
|
|
1049
|
-
import { existsSync as
|
|
1050
|
-
import { dirname as
|
|
1168
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "node:fs";
|
|
1169
|
+
import { dirname as dirname3, join as join9 } from "node:path";
|
|
1051
1170
|
var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
|
|
1052
1171
|
var init_orchestrator = __esm(async () => {
|
|
1053
1172
|
init_client();
|
|
@@ -1077,13 +1196,13 @@ var init_orchestrator = __esm(async () => {
|
|
|
1077
1196
|
captureInFlight = new Map;
|
|
1078
1197
|
captureDomainLocks = new Map;
|
|
1079
1198
|
skillRouteCache = new Map;
|
|
1080
|
-
ROUTE_CACHE_FILE =
|
|
1081
|
-
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
1199
|
+
ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
|
|
1200
|
+
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1082
1201
|
domainSkillCache = new Map;
|
|
1083
|
-
DOMAIN_CACHE_FILE =
|
|
1202
|
+
DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
1084
1203
|
try {
|
|
1085
|
-
if (
|
|
1086
|
-
const data = JSON.parse(
|
|
1204
|
+
if (existsSync9(DOMAIN_CACHE_FILE)) {
|
|
1205
|
+
const data = JSON.parse(readFileSync6(DOMAIN_CACHE_FILE, "utf-8"));
|
|
1087
1206
|
for (const [k, v] of Object.entries(data)) {
|
|
1088
1207
|
const entry = v;
|
|
1089
1208
|
if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
|
|
@@ -1098,17 +1217,17 @@ var init_orchestrator = __esm(async () => {
|
|
|
1098
1217
|
return;
|
|
1099
1218
|
_routeCacheDirty = false;
|
|
1100
1219
|
try {
|
|
1101
|
-
const dir =
|
|
1102
|
-
if (!
|
|
1103
|
-
|
|
1220
|
+
const dir = dirname3(ROUTE_CACHE_FILE);
|
|
1221
|
+
if (!existsSync9(dir))
|
|
1222
|
+
mkdirSync5(dir, { recursive: true });
|
|
1104
1223
|
const entries = Object.fromEntries(skillRouteCache);
|
|
1105
1224
|
writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
|
|
1106
1225
|
} catch {}
|
|
1107
1226
|
}, 5000);
|
|
1108
1227
|
routeCacheFlushTimer.unref?.();
|
|
1109
1228
|
try {
|
|
1110
|
-
if (
|
|
1111
|
-
const data = JSON.parse(
|
|
1229
|
+
if (existsSync9(ROUTE_CACHE_FILE)) {
|
|
1230
|
+
const data = JSON.parse(readFileSync6(ROUTE_CACHE_FILE, "utf-8"));
|
|
1112
1231
|
for (const [k, v] of Object.entries(data)) {
|
|
1113
1232
|
const entry = v;
|
|
1114
1233
|
if (Date.now() - entry.ts < 24 * 60 * 60000) {
|
|
@@ -1198,6 +1317,60 @@ var init_orchestrator = __esm(async () => {
|
|
|
1198
1317
|
]);
|
|
1199
1318
|
});
|
|
1200
1319
|
|
|
1320
|
+
// ../../src/payments/wallet.ts
|
|
1321
|
+
var exports_wallet = {};
|
|
1322
|
+
__export(exports_wallet, {
|
|
1323
|
+
getWalletContext: () => getWalletContext2,
|
|
1324
|
+
checkWalletConfigured: () => checkWalletConfigured2
|
|
1325
|
+
});
|
|
1326
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
|
|
1327
|
+
import { homedir as homedir6 } from "node:os";
|
|
1328
|
+
import { join as join11 } from "node:path";
|
|
1329
|
+
function asNonEmptyString2(value) {
|
|
1330
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1331
|
+
}
|
|
1332
|
+
function getLobsterWalletFromLocalConfig2() {
|
|
1333
|
+
const agentsPath = join11(process.env.HOME || homedir6(), ".lobster", "agents.json");
|
|
1334
|
+
if (!existsSync13(agentsPath))
|
|
1335
|
+
return;
|
|
1336
|
+
try {
|
|
1337
|
+
const raw = JSON.parse(readFileSync9(agentsPath, "utf8"));
|
|
1338
|
+
const activeAgentId = asNonEmptyString2(raw.activeAgentId);
|
|
1339
|
+
const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString2(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
|
|
1340
|
+
return asNonEmptyString2(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString2(activeAgent?.walletAddress) ?? asNonEmptyString2(activeAgent?.wallet_address);
|
|
1341
|
+
} catch {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function getWalletContext2() {
|
|
1346
|
+
const lobsterWallet = asNonEmptyString2(process.env.LOBSTER_WALLET_ADDRESS);
|
|
1347
|
+
if (lobsterWallet) {
|
|
1348
|
+
return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
|
|
1349
|
+
}
|
|
1350
|
+
const genericWallet = asNonEmptyString2(process.env.AGENT_WALLET_ADDRESS);
|
|
1351
|
+
if (genericWallet) {
|
|
1352
|
+
return {
|
|
1353
|
+
wallet_address: genericWallet,
|
|
1354
|
+
wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
const localLobsterWallet = getLobsterWalletFromLocalConfig2();
|
|
1358
|
+
if (localLobsterWallet) {
|
|
1359
|
+
return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
|
|
1360
|
+
}
|
|
1361
|
+
return {};
|
|
1362
|
+
}
|
|
1363
|
+
function checkWalletConfigured2() {
|
|
1364
|
+
const wallet = getWalletContext2();
|
|
1365
|
+
if (!wallet.wallet_address)
|
|
1366
|
+
return { configured: false };
|
|
1367
|
+
return {
|
|
1368
|
+
configured: true,
|
|
1369
|
+
provider: wallet.wallet_provider ?? "unknown"
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
var init_wallet2 = () => {};
|
|
1373
|
+
|
|
1201
1374
|
// ../../src/cli.ts
|
|
1202
1375
|
import { config as loadEnv } from "dotenv";
|
|
1203
1376
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -1207,9 +1380,9 @@ init_version();
|
|
|
1207
1380
|
init_cascade();
|
|
1208
1381
|
init_wallet();
|
|
1209
1382
|
init_telemetry_attribution();
|
|
1210
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as
|
|
1211
|
-
import { join as
|
|
1212
|
-
import { homedir as
|
|
1383
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
|
|
1384
|
+
import { join as join4 } from "path";
|
|
1385
|
+
import { homedir as homedir3, hostname } from "os";
|
|
1213
1386
|
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
1214
1387
|
import { createInterface } from "readline";
|
|
1215
1388
|
var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
@@ -1244,13 +1417,13 @@ function decodeBase64Json(value) {
|
|
|
1244
1417
|
function getConfigDir() {
|
|
1245
1418
|
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
1246
1419
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
1247
|
-
return PROFILE_NAME ?
|
|
1420
|
+
return PROFILE_NAME ? join4(homedir3(), ".unbrowse", "profiles", PROFILE_NAME) : join4(homedir3(), ".unbrowse");
|
|
1248
1421
|
}
|
|
1249
1422
|
function getConfigPath() {
|
|
1250
|
-
return
|
|
1423
|
+
return join4(getConfigDir(), "config.json");
|
|
1251
1424
|
}
|
|
1252
1425
|
function getInstallTelemetryPath() {
|
|
1253
|
-
return
|
|
1426
|
+
return join4(getConfigDir(), "install-state.json");
|
|
1254
1427
|
}
|
|
1255
1428
|
function getLandingToken() {
|
|
1256
1429
|
const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
|
|
@@ -1265,7 +1438,7 @@ function getActiveProfile() {
|
|
|
1265
1438
|
function loadConfig() {
|
|
1266
1439
|
try {
|
|
1267
1440
|
const configPath = getConfigPath();
|
|
1268
|
-
if (
|
|
1441
|
+
if (existsSync4(configPath)) {
|
|
1269
1442
|
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1270
1443
|
}
|
|
1271
1444
|
} catch {}
|
|
@@ -1274,14 +1447,14 @@ function loadConfig() {
|
|
|
1274
1447
|
function saveConfig(config) {
|
|
1275
1448
|
const configDir = getConfigDir();
|
|
1276
1449
|
const configPath = getConfigPath();
|
|
1277
|
-
if (!
|
|
1450
|
+
if (!existsSync4(configDir))
|
|
1278
1451
|
mkdirSync(configDir, { recursive: true });
|
|
1279
1452
|
writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
1280
1453
|
}
|
|
1281
1454
|
function loadInstallTelemetryState() {
|
|
1282
1455
|
try {
|
|
1283
1456
|
const statePath = getInstallTelemetryPath();
|
|
1284
|
-
if (
|
|
1457
|
+
if (existsSync4(statePath)) {
|
|
1285
1458
|
return JSON.parse(readFileSync3(statePath, "utf-8"));
|
|
1286
1459
|
}
|
|
1287
1460
|
} catch {}
|
|
@@ -1290,7 +1463,7 @@ function loadInstallTelemetryState() {
|
|
|
1290
1463
|
function saveInstallTelemetryState(state) {
|
|
1291
1464
|
const configDir = getConfigDir();
|
|
1292
1465
|
const statePath = getInstallTelemetryPath();
|
|
1293
|
-
if (!
|
|
1466
|
+
if (!existsSync4(configDir))
|
|
1294
1467
|
mkdirSync(configDir, { recursive: true });
|
|
1295
1468
|
writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1296
1469
|
}
|
|
@@ -1448,6 +1621,10 @@ function getApiKey() {
|
|
|
1448
1621
|
}
|
|
1449
1622
|
return "";
|
|
1450
1623
|
}
|
|
1624
|
+
function getAgentId() {
|
|
1625
|
+
const config = loadConfig();
|
|
1626
|
+
return config?.agent_id ?? null;
|
|
1627
|
+
}
|
|
1451
1628
|
var API_TIMEOUT_MS = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
|
|
1452
1629
|
var PUBLISH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
|
|
1453
1630
|
async function validateApiKey(key) {
|
|
@@ -1547,6 +1724,26 @@ async function apiRequest(method, path, body, opts) {
|
|
|
1547
1724
|
const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
|
|
1548
1725
|
const legacyPaymentTerms = res.headers.get("X-Payment-Required");
|
|
1549
1726
|
const terms = paymentRequired ? decodeBase64Json(paymentRequired) : legacyPaymentTerms ? JSON.parse(legacyPaymentTerms) : data.terms;
|
|
1727
|
+
try {
|
|
1728
|
+
const { isLobsterAvailable: isLobsterAvailable2, payAndRetry: payAndRetry2 } = await Promise.resolve().then(() => (init_lobster_pay(), exports_lobster_pay));
|
|
1729
|
+
if (isLobsterAvailable2()) {
|
|
1730
|
+
const fullUrl = `${API_URL}${path}`;
|
|
1731
|
+
const paidResult = await payAndRetry2(fullUrl, {
|
|
1732
|
+
body,
|
|
1733
|
+
headers: {
|
|
1734
|
+
"Content-Type": "application/json",
|
|
1735
|
+
"Accept-Encoding": "gzip, deflate",
|
|
1736
|
+
...releaseAttestationHeaders,
|
|
1737
|
+
...key ? { Authorization: `Bearer ${key}` } : {}
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
if (paidResult) {
|
|
1741
|
+
return { data: paidResult.data, headers: new Headers };
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
} catch (payErr) {
|
|
1745
|
+
console.warn(`[x402] lobster pay-and-retry failed: ${payErr.message}`);
|
|
1746
|
+
}
|
|
1550
1747
|
const err = new Error(`Payment required: ${data.error ?? "This skill requires payment"}`);
|
|
1551
1748
|
err.x402 = true;
|
|
1552
1749
|
err.terms = terms;
|
|
@@ -1565,19 +1762,10 @@ async function api(method, path, body, opts) {
|
|
|
1565
1762
|
return data;
|
|
1566
1763
|
}
|
|
1567
1764
|
function parseInstallAttribution() {
|
|
1568
|
-
const result = {};
|
|
1569
|
-
const b64 = process.env.UNBROWSE_ATTRIBUTION_B64;
|
|
1570
|
-
if (b64) {
|
|
1571
|
-
try {
|
|
1572
|
-
const decoded = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
1573
|
-
if (decoded && typeof decoded === "object")
|
|
1574
|
-
result.install_attribution = decoded;
|
|
1575
|
-
} catch {}
|
|
1576
|
-
}
|
|
1577
1765
|
const token = process.env.UNBROWSE_LANDING_TOKEN;
|
|
1578
1766
|
if (token && token.length < 2048)
|
|
1579
|
-
|
|
1580
|
-
return
|
|
1767
|
+
return { landing_token: token };
|
|
1768
|
+
return {};
|
|
1581
1769
|
}
|
|
1582
1770
|
async function promptTosAcceptance(summary, tosUrl) {
|
|
1583
1771
|
if (process.env.UNBROWSE_NON_INTERACTIVE === "1") {
|
|
@@ -1748,6 +1936,170 @@ async function syncAgentWallet(wallet = getLocalWalletContext()) {
|
|
|
1748
1936
|
return;
|
|
1749
1937
|
saveConfig({ ...config, ...wallet });
|
|
1750
1938
|
}
|
|
1939
|
+
async function getTransactionHistory(agentId) {
|
|
1940
|
+
return api("GET", `/v1/transactions/consumer/${agentId}`);
|
|
1941
|
+
}
|
|
1942
|
+
async function getCreatorEarnings(agentId) {
|
|
1943
|
+
return api("GET", `/v1/transactions/creator/${agentId}`);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// ../../src/impact-log.ts
|
|
1947
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync } from "node:fs";
|
|
1948
|
+
import { homedir as homedir4 } from "node:os";
|
|
1949
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
1950
|
+
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
1951
|
+
var MAX_ROTATIONS = 3;
|
|
1952
|
+
function getLogDir() {
|
|
1953
|
+
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
1954
|
+
return process.env.UNBROWSE_CONFIG_DIR;
|
|
1955
|
+
const profile = process.env.UNBROWSE_PROFILE?.trim();
|
|
1956
|
+
return profile ? join5(homedir4(), ".unbrowse", "profiles", profile) : join5(homedir4(), ".unbrowse");
|
|
1957
|
+
}
|
|
1958
|
+
function getImpactLogPath() {
|
|
1959
|
+
return join5(getLogDir(), "impact-log.jsonl");
|
|
1960
|
+
}
|
|
1961
|
+
function ensureDir(path) {
|
|
1962
|
+
const dir = dirname2(path);
|
|
1963
|
+
if (!existsSync5(dir))
|
|
1964
|
+
mkdirSync2(dir, { recursive: true });
|
|
1965
|
+
}
|
|
1966
|
+
function rotateIfNeeded(path) {
|
|
1967
|
+
try {
|
|
1968
|
+
if (!existsSync5(path))
|
|
1969
|
+
return;
|
|
1970
|
+
const size = statSync(path).size;
|
|
1971
|
+
if (size < MAX_LOG_BYTES)
|
|
1972
|
+
return;
|
|
1973
|
+
for (let i = MAX_ROTATIONS;i >= 1; i--) {
|
|
1974
|
+
const older = `${path}.${i}`;
|
|
1975
|
+
if (!existsSync5(older))
|
|
1976
|
+
continue;
|
|
1977
|
+
if (i === MAX_ROTATIONS) {
|
|
1978
|
+
try {
|
|
1979
|
+
unlinkSync(older);
|
|
1980
|
+
} catch {}
|
|
1981
|
+
} else {
|
|
1982
|
+
try {
|
|
1983
|
+
renameSync(older, `${path}.${i + 1}`);
|
|
1984
|
+
} catch {}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
renameSync(path, `${path}.1`);
|
|
1988
|
+
} catch {}
|
|
1989
|
+
}
|
|
1990
|
+
function appendImpact(entry) {
|
|
1991
|
+
try {
|
|
1992
|
+
const hasSignal = (entry.time_saved_ms ?? 0) > 0 || (entry.tokens_saved ?? 0) > 0 || (entry.cost_saved_uc ?? 0) > 0 || entry.browser_avoided === true;
|
|
1993
|
+
if (!hasSignal)
|
|
1994
|
+
return;
|
|
1995
|
+
const path = getImpactLogPath();
|
|
1996
|
+
ensureDir(path);
|
|
1997
|
+
rotateIfNeeded(path);
|
|
1998
|
+
appendFileSync(path, JSON.stringify(entry) + `
|
|
1999
|
+
`, "utf8");
|
|
2000
|
+
} catch {}
|
|
2001
|
+
}
|
|
2002
|
+
function impactFromResult(command, result, extras = {}) {
|
|
2003
|
+
if (!result || typeof result !== "object")
|
|
2004
|
+
return null;
|
|
2005
|
+
const r = result;
|
|
2006
|
+
const impact = r.impact ?? null;
|
|
2007
|
+
if (!impact || typeof impact !== "object")
|
|
2008
|
+
return null;
|
|
2009
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
2010
|
+
return {
|
|
2011
|
+
ts: new Date().toISOString(),
|
|
2012
|
+
command,
|
|
2013
|
+
source: typeof impact.source === "string" ? impact.source : undefined,
|
|
2014
|
+
domain: extras.domain,
|
|
2015
|
+
intent: extras.intent,
|
|
2016
|
+
skill_id: extras.skill_id ?? (typeof r.skill_id === "string" ? r.skill_id : undefined),
|
|
2017
|
+
endpoint_id: extras.endpoint_id ?? (typeof r.endpoint_id === "string" ? r.endpoint_id : undefined),
|
|
2018
|
+
time_saved_ms: num(impact.time_saved_ms),
|
|
2019
|
+
time_saved_pct: num(impact.time_saved_pct),
|
|
2020
|
+
tokens_saved: num(impact.tokens_saved),
|
|
2021
|
+
tokens_saved_pct: num(impact.tokens_saved_pct),
|
|
2022
|
+
cost_saved_uc: num(impact.cost_saved_uc),
|
|
2023
|
+
browser_avoided: impact.browser_avoided === true,
|
|
2024
|
+
success: r.error == null
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
function readImpactSummary() {
|
|
2028
|
+
const path = getImpactLogPath();
|
|
2029
|
+
const summary = {
|
|
2030
|
+
total_runs: 0,
|
|
2031
|
+
successful_runs: 0,
|
|
2032
|
+
browser_avoided_runs: 0,
|
|
2033
|
+
total_time_saved_ms: 0,
|
|
2034
|
+
total_tokens_saved: 0,
|
|
2035
|
+
total_cost_saved_uc: 0,
|
|
2036
|
+
avg_time_saved_pct: 0,
|
|
2037
|
+
avg_tokens_saved_pct: 0,
|
|
2038
|
+
by_source: {},
|
|
2039
|
+
first_entry_at: null,
|
|
2040
|
+
last_entry_at: null
|
|
2041
|
+
};
|
|
2042
|
+
const files = [];
|
|
2043
|
+
for (let i = MAX_ROTATIONS;i >= 1; i--) {
|
|
2044
|
+
const rotated = `${path}.${i}`;
|
|
2045
|
+
if (existsSync5(rotated))
|
|
2046
|
+
files.push(rotated);
|
|
2047
|
+
}
|
|
2048
|
+
if (existsSync5(path))
|
|
2049
|
+
files.push(path);
|
|
2050
|
+
if (files.length === 0)
|
|
2051
|
+
return summary;
|
|
2052
|
+
let timePctSum = 0;
|
|
2053
|
+
let timePctCount = 0;
|
|
2054
|
+
let tokenPctSum = 0;
|
|
2055
|
+
let tokenPctCount = 0;
|
|
2056
|
+
for (const file of files) {
|
|
2057
|
+
let raw;
|
|
2058
|
+
try {
|
|
2059
|
+
raw = readFileSync4(file, "utf8");
|
|
2060
|
+
} catch {
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
for (const line of raw.split(`
|
|
2064
|
+
`)) {
|
|
2065
|
+
const trimmed = line.trim();
|
|
2066
|
+
if (!trimmed)
|
|
2067
|
+
continue;
|
|
2068
|
+
let e;
|
|
2069
|
+
try {
|
|
2070
|
+
e = JSON.parse(trimmed);
|
|
2071
|
+
} catch {
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
summary.total_runs += 1;
|
|
2075
|
+
if (e.success !== false)
|
|
2076
|
+
summary.successful_runs += 1;
|
|
2077
|
+
if (e.browser_avoided)
|
|
2078
|
+
summary.browser_avoided_runs += 1;
|
|
2079
|
+
summary.total_time_saved_ms += e.time_saved_ms ?? 0;
|
|
2080
|
+
summary.total_tokens_saved += e.tokens_saved ?? 0;
|
|
2081
|
+
summary.total_cost_saved_uc += e.cost_saved_uc ?? 0;
|
|
2082
|
+
if (typeof e.time_saved_pct === "number") {
|
|
2083
|
+
timePctSum += e.time_saved_pct;
|
|
2084
|
+
timePctCount += 1;
|
|
2085
|
+
}
|
|
2086
|
+
if (typeof e.tokens_saved_pct === "number") {
|
|
2087
|
+
tokenPctSum += e.tokens_saved_pct;
|
|
2088
|
+
tokenPctCount += 1;
|
|
2089
|
+
}
|
|
2090
|
+
if (e.source) {
|
|
2091
|
+
summary.by_source[e.source] = (summary.by_source[e.source] ?? 0) + 1;
|
|
2092
|
+
}
|
|
2093
|
+
if (!summary.first_entry_at || e.ts < summary.first_entry_at)
|
|
2094
|
+
summary.first_entry_at = e.ts;
|
|
2095
|
+
if (!summary.last_entry_at || e.ts > summary.last_entry_at)
|
|
2096
|
+
summary.last_entry_at = e.ts;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
summary.avg_time_saved_pct = timePctCount > 0 ? Math.round(timePctSum / timePctCount) : 0;
|
|
2100
|
+
summary.avg_tokens_saved_pct = tokenPctCount > 0 ? Math.round(tokenPctSum / tokenPctCount) : 0;
|
|
2101
|
+
return summary;
|
|
2102
|
+
}
|
|
1751
2103
|
|
|
1752
2104
|
// ../../src/cli/shortcuts.ts
|
|
1753
2105
|
var linkedin = {
|
|
@@ -1948,7 +2300,7 @@ function buildDepsMetadata(pack, taskName) {
|
|
|
1948
2300
|
init_paths();
|
|
1949
2301
|
init_supervisor();
|
|
1950
2302
|
init_version();
|
|
1951
|
-
import { openSync, readFileSync as
|
|
2303
|
+
import { openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1952
2304
|
import path2 from "node:path";
|
|
1953
2305
|
import { spawn } from "node:child_process";
|
|
1954
2306
|
function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
|
|
@@ -1986,14 +2338,14 @@ function isPidAlive(pid) {
|
|
|
1986
2338
|
}
|
|
1987
2339
|
function readPidState(pidFile) {
|
|
1988
2340
|
try {
|
|
1989
|
-
return JSON.parse(
|
|
2341
|
+
return JSON.parse(readFileSync5(pidFile, "utf-8"));
|
|
1990
2342
|
} catch {
|
|
1991
2343
|
return null;
|
|
1992
2344
|
}
|
|
1993
2345
|
}
|
|
1994
2346
|
function clearStalePidFile(pidFile) {
|
|
1995
2347
|
try {
|
|
1996
|
-
|
|
2348
|
+
unlinkSync2(pidFile);
|
|
1997
2349
|
} catch {}
|
|
1998
2350
|
}
|
|
1999
2351
|
function deriveListenEnv(baseUrl) {
|
|
@@ -2027,7 +2379,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
|
|
|
2027
2379
|
const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
|
|
2028
2380
|
const spawnSpec = getServerSpawnSpec(metaUrl, entrypoint);
|
|
2029
2381
|
const logFile = getServerAutostartLogFile();
|
|
2030
|
-
|
|
2382
|
+
ensureDir2(path2.dirname(logFile));
|
|
2031
2383
|
const logFd = openSync(logFile, "a");
|
|
2032
2384
|
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
2033
2385
|
cwd: spawnSpec.cwd,
|
|
@@ -2159,7 +2511,7 @@ async function restartServer(baseUrl, metaUrl) {
|
|
|
2159
2511
|
}
|
|
2160
2512
|
|
|
2161
2513
|
// ../../src/runtime/paths.ts
|
|
2162
|
-
import { existsSync as
|
|
2514
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
|
|
2163
2515
|
import path3 from "node:path";
|
|
2164
2516
|
import { createRequire as createRequire3 } from "node:module";
|
|
2165
2517
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
@@ -2179,7 +2531,7 @@ function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
|
|
|
2179
2531
|
const req = createRequire3(metaUrl);
|
|
2180
2532
|
const tsxPkg = req.resolve("tsx/package.json");
|
|
2181
2533
|
const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
|
|
2182
|
-
if (
|
|
2534
|
+
if (existsSync7(tsxLoader))
|
|
2183
2535
|
return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
|
|
2184
2536
|
} catch {}
|
|
2185
2537
|
return ["--import", "tsx", entrypoint];
|
|
@@ -2209,8 +2561,8 @@ init_publish();
|
|
|
2209
2561
|
init_settings();
|
|
2210
2562
|
init_graph();
|
|
2211
2563
|
init_schema_review();
|
|
2212
|
-
import { join as
|
|
2213
|
-
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
2564
|
+
import { join as join10 } from "node:path";
|
|
2565
|
+
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
2214
2566
|
var indexInFlight2 = new Map;
|
|
2215
2567
|
var pendingIndexJobs2 = new Map;
|
|
2216
2568
|
async function drainPendingIndexJobs() {
|
|
@@ -2247,14 +2599,14 @@ init_paths();
|
|
|
2247
2599
|
init_client2();
|
|
2248
2600
|
init_logger();
|
|
2249
2601
|
init_wallet();
|
|
2250
|
-
import { execFileSync as
|
|
2251
|
-
import { existsSync as
|
|
2602
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2603
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2252
2604
|
import os4 from "node:os";
|
|
2253
2605
|
import path7 from "node:path";
|
|
2254
2606
|
|
|
2255
2607
|
// ../../src/runtime/update-hints.ts
|
|
2256
2608
|
init_paths();
|
|
2257
|
-
import { existsSync as
|
|
2609
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2258
2610
|
import os3 from "node:os";
|
|
2259
2611
|
import path6 from "node:path";
|
|
2260
2612
|
var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -2267,20 +2619,20 @@ function getConfigDir2() {
|
|
|
2267
2619
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
2268
2620
|
return path6.join(getHomeDir(), ".unbrowse");
|
|
2269
2621
|
}
|
|
2270
|
-
function
|
|
2271
|
-
if (!
|
|
2272
|
-
|
|
2622
|
+
function ensureDir3(dir) {
|
|
2623
|
+
if (!existsSync10(dir))
|
|
2624
|
+
mkdirSync6(dir, { recursive: true });
|
|
2273
2625
|
return dir;
|
|
2274
2626
|
}
|
|
2275
2627
|
function readJsonFile(file) {
|
|
2276
2628
|
try {
|
|
2277
|
-
return JSON.parse(
|
|
2629
|
+
return JSON.parse(readFileSync7(file, "utf8"));
|
|
2278
2630
|
} catch {
|
|
2279
2631
|
return null;
|
|
2280
2632
|
}
|
|
2281
2633
|
}
|
|
2282
2634
|
function writeJsonFile(file, value) {
|
|
2283
|
-
|
|
2635
|
+
ensureDir3(path6.dirname(file));
|
|
2284
2636
|
writeFileSync4(file, `${JSON.stringify(value, null, 2)}
|
|
2285
2637
|
`);
|
|
2286
2638
|
}
|
|
@@ -2291,7 +2643,7 @@ function detectRepoRoot(start2) {
|
|
|
2291
2643
|
let dir = path6.resolve(start2);
|
|
2292
2644
|
const root = path6.parse(dir).root;
|
|
2293
2645
|
while (dir !== root) {
|
|
2294
|
-
if (
|
|
2646
|
+
if (existsSync10(path6.join(dir, ".git")))
|
|
2295
2647
|
return dir;
|
|
2296
2648
|
dir = path6.dirname(dir);
|
|
2297
2649
|
}
|
|
@@ -2370,13 +2722,13 @@ codex_hooks = true
|
|
|
2370
2722
|
}
|
|
2371
2723
|
function writeCodexHook(metaUrl) {
|
|
2372
2724
|
const configPath = getCodexConfigPath();
|
|
2373
|
-
if (!
|
|
2725
|
+
if (!existsSync10(path6.dirname(configPath))) {
|
|
2374
2726
|
return { host: "codex", action: "not-detected", config_file: configPath };
|
|
2375
2727
|
}
|
|
2376
2728
|
try {
|
|
2377
2729
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
2378
|
-
const fileExistsBefore =
|
|
2379
|
-
let content = fileExistsBefore ?
|
|
2730
|
+
const fileExistsBefore = existsSync10(configPath);
|
|
2731
|
+
let content = fileExistsBefore ? readFileSync7(configPath, "utf8") : "";
|
|
2380
2732
|
const previous = content;
|
|
2381
2733
|
content = ensureCodexHooksFeature(content);
|
|
2382
2734
|
if (!content.includes("unbrowse-update-hint.mjs")) {
|
|
@@ -2410,13 +2762,13 @@ command = ${JSON.stringify(command)}
|
|
|
2410
2762
|
}
|
|
2411
2763
|
function writeClaudeHook(metaUrl) {
|
|
2412
2764
|
const settingsPath = getClaudeSettingsPath();
|
|
2413
|
-
if (!
|
|
2765
|
+
if (!existsSync10(path6.dirname(settingsPath))) {
|
|
2414
2766
|
return { host: "claude", action: "not-detected", config_file: settingsPath };
|
|
2415
2767
|
}
|
|
2416
2768
|
try {
|
|
2417
2769
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
2418
2770
|
const command = `node "${hookScript}"`;
|
|
2419
|
-
const fileExistsBefore =
|
|
2771
|
+
const fileExistsBefore = existsSync10(settingsPath);
|
|
2420
2772
|
const settings = readJsonFile(settingsPath) ?? {};
|
|
2421
2773
|
settings.hooks ??= {};
|
|
2422
2774
|
settings.hooks.SessionStart ??= [];
|
|
@@ -2454,7 +2806,7 @@ function configureUpdateHintHooks(metaUrl, install) {
|
|
|
2454
2806
|
function hasBinary(name) {
|
|
2455
2807
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
2456
2808
|
try {
|
|
2457
|
-
|
|
2809
|
+
execFileSync3(checker, [name], { stdio: "ignore" });
|
|
2458
2810
|
return true;
|
|
2459
2811
|
} catch {
|
|
2460
2812
|
return false;
|
|
@@ -2481,7 +2833,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
|
|
|
2481
2833
|
return path7.join(cwd, ".opencode", "commands");
|
|
2482
2834
|
}
|
|
2483
2835
|
function detectOpenCode(cwd) {
|
|
2484
|
-
return hasBinary("opencode") ||
|
|
2836
|
+
return hasBinary("opencode") || existsSync11(path7.join(resolveConfigHome(), "opencode")) || existsSync11(path7.join(cwd, ".opencode"));
|
|
2485
2837
|
}
|
|
2486
2838
|
function renderOpenCodeCommand() {
|
|
2487
2839
|
return `---
|
|
@@ -2509,12 +2861,12 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
2509
2861
|
if (scope === "auto" && !detected) {
|
|
2510
2862
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
2511
2863
|
}
|
|
2512
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" :
|
|
2864
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync11(path7.join(cwd, ".opencode")) ? "project" : "global";
|
|
2513
2865
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
2514
|
-
const commandFile = path7.join(
|
|
2866
|
+
const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
|
|
2515
2867
|
const content = renderOpenCodeCommand();
|
|
2516
|
-
const action2 =
|
|
2517
|
-
|
|
2868
|
+
const action2 = existsSync11(commandFile) ? "updated" : "installed";
|
|
2869
|
+
mkdirSync7(path7.dirname(commandFile), { recursive: true });
|
|
2518
2870
|
writeFileSync5(commandFile, content);
|
|
2519
2871
|
return {
|
|
2520
2872
|
detected: detected || scope !== "auto",
|
|
@@ -2525,10 +2877,10 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
2525
2877
|
}
|
|
2526
2878
|
async function ensureBrowserEngineInstalled() {
|
|
2527
2879
|
const binary = findKuriBinary();
|
|
2528
|
-
if (
|
|
2880
|
+
if (existsSync11(binary)) {
|
|
2529
2881
|
return { installed: true, action: "already-installed" };
|
|
2530
2882
|
}
|
|
2531
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) =>
|
|
2883
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync11(path7.join(candidate, "build.zig")));
|
|
2532
2884
|
if (!sourceDir) {
|
|
2533
2885
|
return {
|
|
2534
2886
|
installed: false,
|
|
@@ -2544,13 +2896,13 @@ async function ensureBrowserEngineInstalled() {
|
|
|
2544
2896
|
};
|
|
2545
2897
|
}
|
|
2546
2898
|
try {
|
|
2547
|
-
|
|
2899
|
+
execFileSync3("zig", ["build", "-Doptimize=ReleaseFast"], {
|
|
2548
2900
|
cwd: sourceDir,
|
|
2549
2901
|
stdio: "inherit",
|
|
2550
2902
|
timeout: 300000
|
|
2551
2903
|
});
|
|
2552
2904
|
const builtBinary = findKuriBinary();
|
|
2553
|
-
if (
|
|
2905
|
+
if (existsSync11(builtBinary)) {
|
|
2554
2906
|
return {
|
|
2555
2907
|
installed: true,
|
|
2556
2908
|
action: "installed",
|
|
@@ -2575,11 +2927,11 @@ async function runSetup(options) {
|
|
|
2575
2927
|
const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
|
|
2576
2928
|
const walletCheck = checkWalletConfigured();
|
|
2577
2929
|
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
2578
|
-
const lobsterInstalled = hasBinary("lobstercash") ||
|
|
2930
|
+
const lobsterInstalled = hasBinary("lobstercash") || existsSync11(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
2579
2931
|
if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
|
|
2580
2932
|
console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
|
|
2581
2933
|
try {
|
|
2582
|
-
|
|
2934
|
+
execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
2583
2935
|
stdio: "inherit",
|
|
2584
2936
|
timeout: 60000
|
|
2585
2937
|
});
|
|
@@ -2615,7 +2967,7 @@ async function runSetup(options) {
|
|
|
2615
2967
|
|
|
2616
2968
|
// ../../src/runtime/update-hints.ts
|
|
2617
2969
|
init_paths();
|
|
2618
|
-
import { existsSync as
|
|
2970
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2619
2971
|
import os5 from "node:os";
|
|
2620
2972
|
import path8 from "node:path";
|
|
2621
2973
|
var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
|
|
@@ -2628,20 +2980,20 @@ function getConfigDir3() {
|
|
|
2628
2980
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
2629
2981
|
return path8.join(getHomeDir2(), ".unbrowse");
|
|
2630
2982
|
}
|
|
2631
|
-
function
|
|
2632
|
-
if (!
|
|
2633
|
-
|
|
2983
|
+
function ensureDir4(dir) {
|
|
2984
|
+
if (!existsSync12(dir))
|
|
2985
|
+
mkdirSync8(dir, { recursive: true });
|
|
2634
2986
|
return dir;
|
|
2635
2987
|
}
|
|
2636
2988
|
function readJsonFile2(file) {
|
|
2637
2989
|
try {
|
|
2638
|
-
return JSON.parse(
|
|
2990
|
+
return JSON.parse(readFileSync8(file, "utf8"));
|
|
2639
2991
|
} catch {
|
|
2640
2992
|
return null;
|
|
2641
2993
|
}
|
|
2642
2994
|
}
|
|
2643
2995
|
function writeJsonFile2(file, value) {
|
|
2644
|
-
|
|
2996
|
+
ensureDir4(path8.dirname(file));
|
|
2645
2997
|
writeFileSync6(file, `${JSON.stringify(value, null, 2)}
|
|
2646
2998
|
`);
|
|
2647
2999
|
}
|
|
@@ -2655,7 +3007,7 @@ function detectRepoRoot2(start2) {
|
|
|
2655
3007
|
let dir = path8.resolve(start2);
|
|
2656
3008
|
const root = path8.parse(dir).root;
|
|
2657
3009
|
while (dir !== root) {
|
|
2658
|
-
if (
|
|
3010
|
+
if (existsSync12(path8.join(dir, ".git")))
|
|
2659
3011
|
return dir;
|
|
2660
3012
|
dir = path8.dirname(dir);
|
|
2661
3013
|
}
|
|
@@ -2688,7 +3040,7 @@ function detectInstallHost2(repoRoot) {
|
|
|
2688
3040
|
function getInstalledVersion(metaUrl) {
|
|
2689
3041
|
const packageRoot = getPackageRoot(metaUrl);
|
|
2690
3042
|
try {
|
|
2691
|
-
const pkg = JSON.parse(
|
|
3043
|
+
const pkg = JSON.parse(readFileSync8(path8.join(packageRoot, "package.json"), "utf8"));
|
|
2692
3044
|
return pkg.version ?? "unknown";
|
|
2693
3045
|
} catch {
|
|
2694
3046
|
return "unknown";
|
|
@@ -2789,6 +3141,7 @@ loadEnv({ quiet: true });
|
|
|
2789
3141
|
loadEnv({ path: ".env.runtime", quiet: true });
|
|
2790
3142
|
var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
|
|
2791
3143
|
var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
|
|
3144
|
+
var walletNudgeShown = false;
|
|
2792
3145
|
function parseArgs(argv) {
|
|
2793
3146
|
const raw = argv.slice(2);
|
|
2794
3147
|
const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : "help";
|
|
@@ -2935,6 +3288,14 @@ function formatSavedDuration(ms) {
|
|
|
2935
3288
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
2936
3289
|
return `${ms}ms`;
|
|
2937
3290
|
}
|
|
3291
|
+
function formatCostUsd(uc) {
|
|
3292
|
+
const usd = uc / 1e6;
|
|
3293
|
+
if (usd >= 1)
|
|
3294
|
+
return `$${usd.toFixed(2)}`;
|
|
3295
|
+
if (usd >= 0.01)
|
|
3296
|
+
return `$${usd.toFixed(3)}`;
|
|
3297
|
+
return `$${usd.toFixed(4)}`;
|
|
3298
|
+
}
|
|
2938
3299
|
function emitImpactSummary(result) {
|
|
2939
3300
|
const impact = result.impact;
|
|
2940
3301
|
if (!impact)
|
|
@@ -2943,14 +3304,17 @@ function emitImpactSummary(result) {
|
|
|
2943
3304
|
const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
|
|
2944
3305
|
const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
|
|
2945
3306
|
const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
|
|
3307
|
+
const costSavedUc = typeof impact.cost_saved_uc === "number" ? impact.cost_saved_uc : 0;
|
|
2946
3308
|
const browserAvoided = impact.browser_avoided === true;
|
|
2947
|
-
if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
|
|
3309
|
+
if (timeSavedMs <= 0 && tokensSaved <= 0 && costSavedUc <= 0 && !browserAvoided)
|
|
2948
3310
|
return;
|
|
2949
3311
|
const parts = [];
|
|
2950
3312
|
if (timeSavedMs > 0)
|
|
2951
3313
|
parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
|
|
2952
3314
|
if (tokensSaved > 0)
|
|
2953
3315
|
parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
|
|
3316
|
+
if (costSavedUc > 0)
|
|
3317
|
+
parts.push(`${formatCostUsd(costSavedUc)} saved`);
|
|
2954
3318
|
if (browserAvoided)
|
|
2955
3319
|
parts.push("browser avoided");
|
|
2956
3320
|
info(parts.join(" \u2022 "));
|
|
@@ -3095,9 +3459,24 @@ async function cmdResolve(flags) {
|
|
|
3095
3459
|
}
|
|
3096
3460
|
result = slimTrace(result);
|
|
3097
3461
|
emitImpactSummary(result);
|
|
3462
|
+
{
|
|
3463
|
+
const entry = impactFromResult("resolve", result, { intent, domain });
|
|
3464
|
+
if (entry)
|
|
3465
|
+
appendImpact(entry);
|
|
3466
|
+
}
|
|
3098
3467
|
emitNextActionSummary(result);
|
|
3099
3468
|
const skill = result.skill;
|
|
3100
3469
|
const trace = result.trace;
|
|
3470
|
+
if (trace?.success && !walletNudgeShown) {
|
|
3471
|
+
try {
|
|
3472
|
+
const { checkWalletConfigured: checkWalletConfigured3 } = await Promise.resolve().then(() => (init_wallet2(), exports_wallet));
|
|
3473
|
+
const wallet = checkWalletConfigured3();
|
|
3474
|
+
if (!wallet.configured) {
|
|
3475
|
+
info("You're indexing routes but have no payout wallet. Run: npx @crossmint/lobster-cli setup");
|
|
3476
|
+
walletNudgeShown = true;
|
|
3477
|
+
}
|
|
3478
|
+
} catch (_e) {}
|
|
3479
|
+
}
|
|
3101
3480
|
if (skill?.skill_id && trace) {
|
|
3102
3481
|
result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
|
|
3103
3482
|
}
|
|
@@ -3255,6 +3634,14 @@ async function cmdExecute(flags) {
|
|
|
3255
3634
|
}
|
|
3256
3635
|
result = slimTrace(result);
|
|
3257
3636
|
emitImpactSummary(result);
|
|
3637
|
+
{
|
|
3638
|
+
const entry = impactFromResult("execute", result, {
|
|
3639
|
+
skill_id: skillId,
|
|
3640
|
+
endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : undefined
|
|
3641
|
+
});
|
|
3642
|
+
if (entry)
|
|
3643
|
+
appendImpact(entry);
|
|
3644
|
+
}
|
|
3258
3645
|
emitNextActionSummary(result);
|
|
3259
3646
|
const pathFlag = flags.path;
|
|
3260
3647
|
const extractFlag = flags.extract;
|
|
@@ -3481,6 +3868,16 @@ async function cmdSetup(flags) {
|
|
|
3481
3868
|
info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
|
|
3482
3869
|
}
|
|
3483
3870
|
}
|
|
3871
|
+
if (report.wallet.configured) {
|
|
3872
|
+
info(`Wallet configured (${report.wallet.provider}): ${report.wallet.wallet_address ?? "linked"}`);
|
|
3873
|
+
} else if (report.wallet.lobster_installed) {
|
|
3874
|
+
info("Wallet not paired \u2014 your indexed routes won't earn payouts.");
|
|
3875
|
+
info("Run: npx @crossmint/lobster-cli setup");
|
|
3876
|
+
} else {
|
|
3877
|
+
info("No wallet configured \u2014 you're indexing routes for free.");
|
|
3878
|
+
info("Set up a wallet so you earn when agents use your routes:");
|
|
3879
|
+
info(" npx @crossmint/lobster-cli setup");
|
|
3880
|
+
}
|
|
3484
3881
|
await recordInstallTelemetryEvent("setup", {
|
|
3485
3882
|
hostType,
|
|
3486
3883
|
status: report.browser_engine.action === "failed" ? "failed" : "installed",
|
|
@@ -3574,7 +3971,8 @@ var CLI_REFERENCE = {
|
|
|
3574
3971
|
{ name: "back", usage: "[--session id]", desc: "Navigate back" },
|
|
3575
3972
|
{ name: "forward", usage: "[--session id]", desc: "Navigate forward" },
|
|
3576
3973
|
{ name: "sync", usage: "[--session id]", desc: "Checkpoint current capture, keep tab open, queue background index + publish, then inspect via skill/publish review" },
|
|
3577
|
-
{ name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" }
|
|
3974
|
+
{ name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" },
|
|
3975
|
+
{ name: "stats", usage: "[--json] [--pretty]", desc: "Show lifetime time/tokens/cost saved and marketplace earnings/spending" }
|
|
3578
3976
|
],
|
|
3579
3977
|
globalFlags: [
|
|
3580
3978
|
{ flag: "--pretty", desc: "Indented JSON output" },
|
|
@@ -3615,6 +4013,131 @@ var CLI_REFERENCE = {
|
|
|
3615
4013
|
`unbrowse publish --skill abc --endpoints '[{"endpoint_id":"def","description":"Search court judgments by keywords","action_kind":"search","resource_kind":"judgment"}]'`
|
|
3616
4014
|
]
|
|
3617
4015
|
};
|
|
4016
|
+
function formatTotalDuration(ms) {
|
|
4017
|
+
if (ms >= 3600000)
|
|
4018
|
+
return `${(ms / 3600000).toFixed(1)}h`;
|
|
4019
|
+
if (ms >= 60000)
|
|
4020
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
4021
|
+
if (ms >= 1000)
|
|
4022
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
4023
|
+
return `${ms}ms`;
|
|
4024
|
+
}
|
|
4025
|
+
async function cmdStats(flags) {
|
|
4026
|
+
const pretty = !!flags.pretty;
|
|
4027
|
+
const jsonOnly = !!flags.json;
|
|
4028
|
+
const local = readImpactSummary();
|
|
4029
|
+
const agentId = getAgentId();
|
|
4030
|
+
let profile = null;
|
|
4031
|
+
let earnings = null;
|
|
4032
|
+
let spending = null;
|
|
4033
|
+
const remoteErrors = {};
|
|
4034
|
+
if (agentId) {
|
|
4035
|
+
const results = await Promise.allSettled([
|
|
4036
|
+
getMyProfile(),
|
|
4037
|
+
getCreatorEarnings(agentId),
|
|
4038
|
+
getTransactionHistory(agentId)
|
|
4039
|
+
]);
|
|
4040
|
+
if (results[0].status === "fulfilled")
|
|
4041
|
+
profile = results[0].value;
|
|
4042
|
+
else
|
|
4043
|
+
remoteErrors.profile = results[0].reason?.message ?? String(results[0].reason);
|
|
4044
|
+
if (results[1].status === "fulfilled")
|
|
4045
|
+
earnings = results[1].value;
|
|
4046
|
+
else
|
|
4047
|
+
remoteErrors.earnings = results[1].reason?.message ?? String(results[1].reason);
|
|
4048
|
+
if (results[2].status === "fulfilled")
|
|
4049
|
+
spending = results[2].value;
|
|
4050
|
+
else
|
|
4051
|
+
remoteErrors.spending = results[2].reason?.message ?? String(results[2].reason);
|
|
4052
|
+
} else {
|
|
4053
|
+
remoteErrors.profile = "No agent_id in local config. Run `unbrowse setup` to register.";
|
|
4054
|
+
}
|
|
4055
|
+
const earnedUsd = earnings?.ledger?.total_earned_usd ?? 0;
|
|
4056
|
+
const spentUsd = spending?.ledger?.total_spent_usd ?? 0;
|
|
4057
|
+
const netUsd = earnedUsd - spentUsd;
|
|
4058
|
+
const savedUsd = local.total_cost_saved_uc / 1e6;
|
|
4059
|
+
const payload = {
|
|
4060
|
+
agent_id: agentId,
|
|
4061
|
+
profile,
|
|
4062
|
+
impact: {
|
|
4063
|
+
total_runs: local.total_runs,
|
|
4064
|
+
successful_runs: local.successful_runs,
|
|
4065
|
+
browser_avoided_runs: local.browser_avoided_runs,
|
|
4066
|
+
total_time_saved_ms: local.total_time_saved_ms,
|
|
4067
|
+
total_time_saved_human: formatTotalDuration(local.total_time_saved_ms),
|
|
4068
|
+
total_tokens_saved: local.total_tokens_saved,
|
|
4069
|
+
total_cost_saved_usd: Number(savedUsd.toFixed(6)),
|
|
4070
|
+
avg_time_saved_pct: local.avg_time_saved_pct,
|
|
4071
|
+
avg_tokens_saved_pct: local.avg_tokens_saved_pct,
|
|
4072
|
+
by_source: local.by_source,
|
|
4073
|
+
first_entry_at: local.first_entry_at,
|
|
4074
|
+
last_entry_at: local.last_entry_at,
|
|
4075
|
+
log_path: getImpactLogPath()
|
|
4076
|
+
},
|
|
4077
|
+
earnings: {
|
|
4078
|
+
total_earned_usd: earnedUsd,
|
|
4079
|
+
total_earned_uc: earnings?.ledger?.total_earned_uc ?? 0,
|
|
4080
|
+
transaction_count: earnings?.ledger?.transaction_count ?? 0,
|
|
4081
|
+
last_transaction_at: earnings?.ledger?.last_transaction_at ?? null
|
|
4082
|
+
},
|
|
4083
|
+
spending: {
|
|
4084
|
+
total_spent_usd: spentUsd,
|
|
4085
|
+
total_spent_uc: spending?.ledger?.total_spent_uc ?? 0,
|
|
4086
|
+
transaction_count: spending?.ledger?.transaction_count ?? 0,
|
|
4087
|
+
last_transaction_at: spending?.ledger?.last_transaction_at ?? null
|
|
4088
|
+
},
|
|
4089
|
+
net_usd: netUsd,
|
|
4090
|
+
...Object.keys(remoteErrors).length > 0 ? { remote_errors: remoteErrors } : {}
|
|
4091
|
+
};
|
|
4092
|
+
if (jsonOnly) {
|
|
4093
|
+
output(payload, pretty);
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
const lines = [];
|
|
4097
|
+
lines.push("Unbrowse stats");
|
|
4098
|
+
lines.push(` agent_id: ${agentId ?? "(not registered \u2014 run `unbrowse setup`)"}`);
|
|
4099
|
+
if (profile?.name)
|
|
4100
|
+
lines.push(` name: ${profile.name}`);
|
|
4101
|
+
lines.push("");
|
|
4102
|
+
lines.push("Impact (local, this machine):");
|
|
4103
|
+
if (local.total_runs === 0) {
|
|
4104
|
+
lines.push(" No resolve/execute runs recorded yet.");
|
|
4105
|
+
lines.push(` Log file: ${getImpactLogPath()}`);
|
|
4106
|
+
} else {
|
|
4107
|
+
lines.push(` Runs: ${local.total_runs} (${local.successful_runs} successful, ${local.browser_avoided_runs} browser-avoided)`);
|
|
4108
|
+
if (local.total_time_saved_ms > 0) {
|
|
4109
|
+
lines.push(` Time saved: ${formatTotalDuration(local.total_time_saved_ms)} (avg ${local.avg_time_saved_pct}% faster)`);
|
|
4110
|
+
}
|
|
4111
|
+
if (local.total_tokens_saved > 0) {
|
|
4112
|
+
lines.push(` Tokens saved: ${local.total_tokens_saved.toLocaleString("en-US")} (avg ${local.avg_tokens_saved_pct}% less context)`);
|
|
4113
|
+
}
|
|
4114
|
+
if (savedUsd > 0) {
|
|
4115
|
+
lines.push(` Cost saved: ${formatCostUsd(local.total_cost_saved_uc)}`);
|
|
4116
|
+
}
|
|
4117
|
+
if (Object.keys(local.by_source).length > 0) {
|
|
4118
|
+
const topSources = Object.entries(local.by_source).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
4119
|
+
lines.push(` By source: ${topSources}`);
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
lines.push("");
|
|
4123
|
+
lines.push("Money (backend ledger):");
|
|
4124
|
+
if (!agentId) {
|
|
4125
|
+
lines.push(" (not registered \u2014 earnings/spending unavailable)");
|
|
4126
|
+
} else if (remoteErrors.earnings || remoteErrors.spending) {
|
|
4127
|
+
if (remoteErrors.earnings)
|
|
4128
|
+
lines.push(` earnings: error \u2014 ${remoteErrors.earnings}`);
|
|
4129
|
+
if (remoteErrors.spending)
|
|
4130
|
+
lines.push(` spending: error \u2014 ${remoteErrors.spending}`);
|
|
4131
|
+
} else {
|
|
4132
|
+
lines.push(` Earned: $${earnedUsd.toFixed(4)} (${earnings?.ledger?.transaction_count ?? 0} payouts)`);
|
|
4133
|
+
lines.push(` Spent: $${spentUsd.toFixed(4)} (${spending?.ledger?.transaction_count ?? 0} payments)`);
|
|
4134
|
+
lines.push(` Net: ${netUsd >= 0 ? "+" : ""}$${netUsd.toFixed(4)}`);
|
|
4135
|
+
}
|
|
4136
|
+
lines.push("");
|
|
4137
|
+
info(lines.join(`
|
|
4138
|
+
`));
|
|
4139
|
+
output(payload, pretty);
|
|
4140
|
+
}
|
|
3618
4141
|
function printHelp() {
|
|
3619
4142
|
const r = CLI_REFERENCE;
|
|
3620
4143
|
const lines = ["unbrowse \u2014 shell-safe CLI for the local API", ""];
|
|
@@ -4036,6 +4559,8 @@ async function main() {
|
|
|
4036
4559
|
return cmdUpgrade(flags);
|
|
4037
4560
|
if (command === "connect-chrome")
|
|
4038
4561
|
return cmdConnectChrome();
|
|
4562
|
+
if (command === "stats")
|
|
4563
|
+
return cmdStats(flags);
|
|
4039
4564
|
const KNOWN_COMMANDS = new Set([
|
|
4040
4565
|
"health",
|
|
4041
4566
|
"mcp",
|
|
@@ -4079,7 +4604,8 @@ async function main() {
|
|
|
4079
4604
|
"forward",
|
|
4080
4605
|
"sync",
|
|
4081
4606
|
"close",
|
|
4082
|
-
"connect-chrome"
|
|
4607
|
+
"connect-chrome",
|
|
4608
|
+
"stats"
|
|
4083
4609
|
]);
|
|
4084
4610
|
if (!KNOWN_COMMANDS.has(command)) {
|
|
4085
4611
|
const pack = findSitePack(command);
|
|
@@ -4175,6 +4701,8 @@ async function main() {
|
|
|
4175
4701
|
return cmdClose(flags);
|
|
4176
4702
|
case "connect-chrome":
|
|
4177
4703
|
return cmdConnectChrome();
|
|
4704
|
+
case "stats":
|
|
4705
|
+
return cmdStats(flags);
|
|
4178
4706
|
default:
|
|
4179
4707
|
info(`Unknown command: ${command}`);
|
|
4180
4708
|
printHelp();
|