unbrowse 3.0.4 → 3.1.0-experiments.a2cf008
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 +627 -103
- package/dist/mcp.js +641 -43
- package/package.json +1 -1
- package/runtime-src/api/browse-index.ts +30 -4
- package/runtime-src/api/routes.ts +18 -8
- package/runtime-src/browser/index.ts +2 -2
- package/runtime-src/build-info.generated.ts +5 -5
- package/runtime-src/capture/index.ts +140 -5
- package/runtime-src/cli.ts +190 -2
- package/runtime-src/client/index.ts +25 -1
- package/runtime-src/execution/index.ts +48 -26
- package/runtime-src/execution/token-resolver.ts +135 -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 +166 -2
- package/runtime-src/orchestrator/browser-agent.ts +2 -2
- 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 +379 -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.a2cf008", BUILD_GIT_SHA = "a2cf008142ff", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuYTJjZjAwOCIsImdpdF9zaGEiOiJhMmNmMDA4MTQyZmYiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QGEyY2YwMDgxNDJmZiIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDVUMjM6MzQ6NDEuMzk1WiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "1vxiaCYILmk4RglXTLEsIOnEpX1T_x2iDsMRsp_SSis", 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
|
|
|
@@ -780,18 +899,6 @@ var init_extraction = __esm(() => {
|
|
|
780
899
|
CHROME_TAGS = new Set(["nav", "footer", "header"]);
|
|
781
900
|
});
|
|
782
901
|
|
|
783
|
-
// ../../src/graph/agent-augment.ts
|
|
784
|
-
var DEFAULT_MODEL, ENABLED, AUGMENT_TIMEOUT_MS, MAX_AUGMENT_ENDPOINTS, MAX_AUGMENT_PAYLOAD_CHARS, GENERIC_SEMANTIC_TYPES;
|
|
785
|
-
var init_agent_augment = __esm(() => {
|
|
786
|
-
init_graph();
|
|
787
|
-
DEFAULT_MODEL = process.env.UNBROWSE_AGENT_SEMANTIC_MODEL ?? process.env.UNBROWSE_AGENT_JUDGE_MODEL ?? "gpt-4.1-mini";
|
|
788
|
-
ENABLED = process.env.UNBROWSE_AGENT_SEMANTIC_AUGMENT !== "0";
|
|
789
|
-
AUGMENT_TIMEOUT_MS = Number(process.env.UNBROWSE_AGENT_SEMANTIC_TIMEOUT_MS ?? 8000);
|
|
790
|
-
MAX_AUGMENT_ENDPOINTS = Math.max(1, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_ENDPOINTS ?? 6));
|
|
791
|
-
MAX_AUGMENT_PAYLOAD_CHARS = Math.max(4000, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_PAYLOAD_CHARS ?? 24000));
|
|
792
|
-
GENERIC_SEMANTIC_TYPES = new Set(["identifier", "input", "resource", "entity", "item"]);
|
|
793
|
-
});
|
|
794
|
-
|
|
795
902
|
// ../../src/execution/search-forms.ts
|
|
796
903
|
var SEARCH_FIELD_NAMES, LOGIN_FIELD_NAMES, SUPPORTED_INPUT_TYPES;
|
|
797
904
|
var init_search_forms = __esm(() => {
|
|
@@ -854,7 +961,7 @@ var init_schema_review = __esm(() => {
|
|
|
854
961
|
});
|
|
855
962
|
|
|
856
963
|
// ../../src/indexer/index.ts
|
|
857
|
-
import { join as
|
|
964
|
+
import { join as join8 } from "node:path";
|
|
858
965
|
var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
|
|
859
966
|
var init_indexer = __esm(async () => {
|
|
860
967
|
init_graph();
|
|
@@ -869,7 +976,7 @@ var init_indexer = __esm(async () => {
|
|
|
869
976
|
init_graph();
|
|
870
977
|
init_schema_review();
|
|
871
978
|
await init_orchestrator();
|
|
872
|
-
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
979
|
+
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
873
980
|
indexInFlight = new Map;
|
|
874
981
|
pendingIndexJobs = new Map;
|
|
875
982
|
});
|
|
@@ -917,7 +1024,6 @@ var init_execution = __esm(async () => {
|
|
|
917
1024
|
init_domain();
|
|
918
1025
|
init_extraction();
|
|
919
1026
|
init_graph();
|
|
920
|
-
init_agent_augment();
|
|
921
1027
|
init_logger();
|
|
922
1028
|
init_version();
|
|
923
1029
|
init_search_forms();
|
|
@@ -1046,8 +1152,8 @@ var init_routing_telemetry = __esm(() => {
|
|
|
1046
1152
|
});
|
|
1047
1153
|
// ../../src/orchestrator/index.ts
|
|
1048
1154
|
import { nanoid as nanoid9 } from "nanoid";
|
|
1049
|
-
import { existsSync as
|
|
1050
|
-
import { dirname as
|
|
1155
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "node:fs";
|
|
1156
|
+
import { dirname as dirname3, join as join9 } from "node:path";
|
|
1051
1157
|
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
1158
|
var init_orchestrator = __esm(async () => {
|
|
1053
1159
|
init_client();
|
|
@@ -1077,13 +1183,13 @@ var init_orchestrator = __esm(async () => {
|
|
|
1077
1183
|
captureInFlight = new Map;
|
|
1078
1184
|
captureDomainLocks = new Map;
|
|
1079
1185
|
skillRouteCache = new Map;
|
|
1080
|
-
ROUTE_CACHE_FILE =
|
|
1081
|
-
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
1186
|
+
ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
|
|
1187
|
+
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1082
1188
|
domainSkillCache = new Map;
|
|
1083
|
-
DOMAIN_CACHE_FILE =
|
|
1189
|
+
DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
1084
1190
|
try {
|
|
1085
|
-
if (
|
|
1086
|
-
const data = JSON.parse(
|
|
1191
|
+
if (existsSync9(DOMAIN_CACHE_FILE)) {
|
|
1192
|
+
const data = JSON.parse(readFileSync6(DOMAIN_CACHE_FILE, "utf-8"));
|
|
1087
1193
|
for (const [k, v] of Object.entries(data)) {
|
|
1088
1194
|
const entry = v;
|
|
1089
1195
|
if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
|
|
@@ -1098,17 +1204,17 @@ var init_orchestrator = __esm(async () => {
|
|
|
1098
1204
|
return;
|
|
1099
1205
|
_routeCacheDirty = false;
|
|
1100
1206
|
try {
|
|
1101
|
-
const dir =
|
|
1102
|
-
if (!
|
|
1103
|
-
|
|
1207
|
+
const dir = dirname3(ROUTE_CACHE_FILE);
|
|
1208
|
+
if (!existsSync9(dir))
|
|
1209
|
+
mkdirSync5(dir, { recursive: true });
|
|
1104
1210
|
const entries = Object.fromEntries(skillRouteCache);
|
|
1105
1211
|
writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
|
|
1106
1212
|
} catch {}
|
|
1107
1213
|
}, 5000);
|
|
1108
1214
|
routeCacheFlushTimer.unref?.();
|
|
1109
1215
|
try {
|
|
1110
|
-
if (
|
|
1111
|
-
const data = JSON.parse(
|
|
1216
|
+
if (existsSync9(ROUTE_CACHE_FILE)) {
|
|
1217
|
+
const data = JSON.parse(readFileSync6(ROUTE_CACHE_FILE, "utf-8"));
|
|
1112
1218
|
for (const [k, v] of Object.entries(data)) {
|
|
1113
1219
|
const entry = v;
|
|
1114
1220
|
if (Date.now() - entry.ts < 24 * 60 * 60000) {
|
|
@@ -1198,6 +1304,60 @@ var init_orchestrator = __esm(async () => {
|
|
|
1198
1304
|
]);
|
|
1199
1305
|
});
|
|
1200
1306
|
|
|
1307
|
+
// ../../src/payments/wallet.ts
|
|
1308
|
+
var exports_wallet = {};
|
|
1309
|
+
__export(exports_wallet, {
|
|
1310
|
+
getWalletContext: () => getWalletContext2,
|
|
1311
|
+
checkWalletConfigured: () => checkWalletConfigured2
|
|
1312
|
+
});
|
|
1313
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
|
|
1314
|
+
import { homedir as homedir6 } from "node:os";
|
|
1315
|
+
import { join as join11 } from "node:path";
|
|
1316
|
+
function asNonEmptyString2(value) {
|
|
1317
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1318
|
+
}
|
|
1319
|
+
function getLobsterWalletFromLocalConfig2() {
|
|
1320
|
+
const agentsPath = join11(process.env.HOME || homedir6(), ".lobster", "agents.json");
|
|
1321
|
+
if (!existsSync13(agentsPath))
|
|
1322
|
+
return;
|
|
1323
|
+
try {
|
|
1324
|
+
const raw = JSON.parse(readFileSync9(agentsPath, "utf8"));
|
|
1325
|
+
const activeAgentId = asNonEmptyString2(raw.activeAgentId);
|
|
1326
|
+
const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString2(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
|
|
1327
|
+
return asNonEmptyString2(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString2(activeAgent?.walletAddress) ?? asNonEmptyString2(activeAgent?.wallet_address);
|
|
1328
|
+
} catch {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
function getWalletContext2() {
|
|
1333
|
+
const lobsterWallet = asNonEmptyString2(process.env.LOBSTER_WALLET_ADDRESS);
|
|
1334
|
+
if (lobsterWallet) {
|
|
1335
|
+
return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
|
|
1336
|
+
}
|
|
1337
|
+
const genericWallet = asNonEmptyString2(process.env.AGENT_WALLET_ADDRESS);
|
|
1338
|
+
if (genericWallet) {
|
|
1339
|
+
return {
|
|
1340
|
+
wallet_address: genericWallet,
|
|
1341
|
+
wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
const localLobsterWallet = getLobsterWalletFromLocalConfig2();
|
|
1345
|
+
if (localLobsterWallet) {
|
|
1346
|
+
return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
|
|
1347
|
+
}
|
|
1348
|
+
return {};
|
|
1349
|
+
}
|
|
1350
|
+
function checkWalletConfigured2() {
|
|
1351
|
+
const wallet = getWalletContext2();
|
|
1352
|
+
if (!wallet.wallet_address)
|
|
1353
|
+
return { configured: false };
|
|
1354
|
+
return {
|
|
1355
|
+
configured: true,
|
|
1356
|
+
provider: wallet.wallet_provider ?? "unknown"
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
var init_wallet2 = () => {};
|
|
1360
|
+
|
|
1201
1361
|
// ../../src/cli.ts
|
|
1202
1362
|
import { config as loadEnv } from "dotenv";
|
|
1203
1363
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -1207,9 +1367,9 @@ init_version();
|
|
|
1207
1367
|
init_cascade();
|
|
1208
1368
|
init_wallet();
|
|
1209
1369
|
init_telemetry_attribution();
|
|
1210
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as
|
|
1211
|
-
import { join as
|
|
1212
|
-
import { homedir as
|
|
1370
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
|
|
1371
|
+
import { join as join4 } from "path";
|
|
1372
|
+
import { homedir as homedir3, hostname } from "os";
|
|
1213
1373
|
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
1214
1374
|
import { createInterface } from "readline";
|
|
1215
1375
|
var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
@@ -1244,13 +1404,13 @@ function decodeBase64Json(value) {
|
|
|
1244
1404
|
function getConfigDir() {
|
|
1245
1405
|
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
1246
1406
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
1247
|
-
return PROFILE_NAME ?
|
|
1407
|
+
return PROFILE_NAME ? join4(homedir3(), ".unbrowse", "profiles", PROFILE_NAME) : join4(homedir3(), ".unbrowse");
|
|
1248
1408
|
}
|
|
1249
1409
|
function getConfigPath() {
|
|
1250
|
-
return
|
|
1410
|
+
return join4(getConfigDir(), "config.json");
|
|
1251
1411
|
}
|
|
1252
1412
|
function getInstallTelemetryPath() {
|
|
1253
|
-
return
|
|
1413
|
+
return join4(getConfigDir(), "install-state.json");
|
|
1254
1414
|
}
|
|
1255
1415
|
function getLandingToken() {
|
|
1256
1416
|
const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
|
|
@@ -1265,7 +1425,7 @@ function getActiveProfile() {
|
|
|
1265
1425
|
function loadConfig() {
|
|
1266
1426
|
try {
|
|
1267
1427
|
const configPath = getConfigPath();
|
|
1268
|
-
if (
|
|
1428
|
+
if (existsSync4(configPath)) {
|
|
1269
1429
|
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1270
1430
|
}
|
|
1271
1431
|
} catch {}
|
|
@@ -1274,14 +1434,14 @@ function loadConfig() {
|
|
|
1274
1434
|
function saveConfig(config) {
|
|
1275
1435
|
const configDir = getConfigDir();
|
|
1276
1436
|
const configPath = getConfigPath();
|
|
1277
|
-
if (!
|
|
1437
|
+
if (!existsSync4(configDir))
|
|
1278
1438
|
mkdirSync(configDir, { recursive: true });
|
|
1279
1439
|
writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
1280
1440
|
}
|
|
1281
1441
|
function loadInstallTelemetryState() {
|
|
1282
1442
|
try {
|
|
1283
1443
|
const statePath = getInstallTelemetryPath();
|
|
1284
|
-
if (
|
|
1444
|
+
if (existsSync4(statePath)) {
|
|
1285
1445
|
return JSON.parse(readFileSync3(statePath, "utf-8"));
|
|
1286
1446
|
}
|
|
1287
1447
|
} catch {}
|
|
@@ -1290,7 +1450,7 @@ function loadInstallTelemetryState() {
|
|
|
1290
1450
|
function saveInstallTelemetryState(state) {
|
|
1291
1451
|
const configDir = getConfigDir();
|
|
1292
1452
|
const statePath = getInstallTelemetryPath();
|
|
1293
|
-
if (!
|
|
1453
|
+
if (!existsSync4(configDir))
|
|
1294
1454
|
mkdirSync(configDir, { recursive: true });
|
|
1295
1455
|
writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1296
1456
|
}
|
|
@@ -1448,6 +1608,10 @@ function getApiKey() {
|
|
|
1448
1608
|
}
|
|
1449
1609
|
return "";
|
|
1450
1610
|
}
|
|
1611
|
+
function getAgentId() {
|
|
1612
|
+
const config = loadConfig();
|
|
1613
|
+
return config?.agent_id ?? null;
|
|
1614
|
+
}
|
|
1451
1615
|
var API_TIMEOUT_MS = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
|
|
1452
1616
|
var PUBLISH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
|
|
1453
1617
|
async function validateApiKey(key) {
|
|
@@ -1547,6 +1711,26 @@ async function apiRequest(method, path, body, opts) {
|
|
|
1547
1711
|
const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
|
|
1548
1712
|
const legacyPaymentTerms = res.headers.get("X-Payment-Required");
|
|
1549
1713
|
const terms = paymentRequired ? decodeBase64Json(paymentRequired) : legacyPaymentTerms ? JSON.parse(legacyPaymentTerms) : data.terms;
|
|
1714
|
+
try {
|
|
1715
|
+
const { isLobsterAvailable: isLobsterAvailable2, payAndRetry: payAndRetry2 } = await Promise.resolve().then(() => (init_lobster_pay(), exports_lobster_pay));
|
|
1716
|
+
if (isLobsterAvailable2()) {
|
|
1717
|
+
const fullUrl = `${API_URL}${path}`;
|
|
1718
|
+
const paidResult = await payAndRetry2(fullUrl, {
|
|
1719
|
+
body,
|
|
1720
|
+
headers: {
|
|
1721
|
+
"Content-Type": "application/json",
|
|
1722
|
+
"Accept-Encoding": "gzip, deflate",
|
|
1723
|
+
...releaseAttestationHeaders,
|
|
1724
|
+
...key ? { Authorization: `Bearer ${key}` } : {}
|
|
1725
|
+
}
|
|
1726
|
+
});
|
|
1727
|
+
if (paidResult) {
|
|
1728
|
+
return { data: paidResult.data, headers: new Headers };
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
} catch (payErr) {
|
|
1732
|
+
console.warn(`[x402] lobster pay-and-retry failed: ${payErr.message}`);
|
|
1733
|
+
}
|
|
1550
1734
|
const err = new Error(`Payment required: ${data.error ?? "This skill requires payment"}`);
|
|
1551
1735
|
err.x402 = true;
|
|
1552
1736
|
err.terms = terms;
|
|
@@ -1739,6 +1923,170 @@ async function syncAgentWallet(wallet = getLocalWalletContext()) {
|
|
|
1739
1923
|
return;
|
|
1740
1924
|
saveConfig({ ...config, ...wallet });
|
|
1741
1925
|
}
|
|
1926
|
+
async function getTransactionHistory(agentId) {
|
|
1927
|
+
return api("GET", `/v1/transactions/consumer/${agentId}`);
|
|
1928
|
+
}
|
|
1929
|
+
async function getCreatorEarnings(agentId) {
|
|
1930
|
+
return api("GET", `/v1/transactions/creator/${agentId}`);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// ../../src/impact-log.ts
|
|
1934
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync } from "node:fs";
|
|
1935
|
+
import { homedir as homedir4 } from "node:os";
|
|
1936
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
1937
|
+
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
1938
|
+
var MAX_ROTATIONS = 3;
|
|
1939
|
+
function getLogDir() {
|
|
1940
|
+
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
1941
|
+
return process.env.UNBROWSE_CONFIG_DIR;
|
|
1942
|
+
const profile = process.env.UNBROWSE_PROFILE?.trim();
|
|
1943
|
+
return profile ? join5(homedir4(), ".unbrowse", "profiles", profile) : join5(homedir4(), ".unbrowse");
|
|
1944
|
+
}
|
|
1945
|
+
function getImpactLogPath() {
|
|
1946
|
+
return join5(getLogDir(), "impact-log.jsonl");
|
|
1947
|
+
}
|
|
1948
|
+
function ensureDir(path) {
|
|
1949
|
+
const dir = dirname2(path);
|
|
1950
|
+
if (!existsSync5(dir))
|
|
1951
|
+
mkdirSync2(dir, { recursive: true });
|
|
1952
|
+
}
|
|
1953
|
+
function rotateIfNeeded(path) {
|
|
1954
|
+
try {
|
|
1955
|
+
if (!existsSync5(path))
|
|
1956
|
+
return;
|
|
1957
|
+
const size = statSync(path).size;
|
|
1958
|
+
if (size < MAX_LOG_BYTES)
|
|
1959
|
+
return;
|
|
1960
|
+
for (let i = MAX_ROTATIONS;i >= 1; i--) {
|
|
1961
|
+
const older = `${path}.${i}`;
|
|
1962
|
+
if (!existsSync5(older))
|
|
1963
|
+
continue;
|
|
1964
|
+
if (i === MAX_ROTATIONS) {
|
|
1965
|
+
try {
|
|
1966
|
+
unlinkSync(older);
|
|
1967
|
+
} catch {}
|
|
1968
|
+
} else {
|
|
1969
|
+
try {
|
|
1970
|
+
renameSync(older, `${path}.${i + 1}`);
|
|
1971
|
+
} catch {}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
renameSync(path, `${path}.1`);
|
|
1975
|
+
} catch {}
|
|
1976
|
+
}
|
|
1977
|
+
function appendImpact(entry) {
|
|
1978
|
+
try {
|
|
1979
|
+
const hasSignal = (entry.time_saved_ms ?? 0) > 0 || (entry.tokens_saved ?? 0) > 0 || (entry.cost_saved_uc ?? 0) > 0 || entry.browser_avoided === true;
|
|
1980
|
+
if (!hasSignal)
|
|
1981
|
+
return;
|
|
1982
|
+
const path = getImpactLogPath();
|
|
1983
|
+
ensureDir(path);
|
|
1984
|
+
rotateIfNeeded(path);
|
|
1985
|
+
appendFileSync(path, JSON.stringify(entry) + `
|
|
1986
|
+
`, "utf8");
|
|
1987
|
+
} catch {}
|
|
1988
|
+
}
|
|
1989
|
+
function impactFromResult(command, result, extras = {}) {
|
|
1990
|
+
if (!result || typeof result !== "object")
|
|
1991
|
+
return null;
|
|
1992
|
+
const r = result;
|
|
1993
|
+
const impact = r.impact ?? null;
|
|
1994
|
+
if (!impact || typeof impact !== "object")
|
|
1995
|
+
return null;
|
|
1996
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
1997
|
+
return {
|
|
1998
|
+
ts: new Date().toISOString(),
|
|
1999
|
+
command,
|
|
2000
|
+
source: typeof impact.source === "string" ? impact.source : undefined,
|
|
2001
|
+
domain: extras.domain,
|
|
2002
|
+
intent: extras.intent,
|
|
2003
|
+
skill_id: extras.skill_id ?? (typeof r.skill_id === "string" ? r.skill_id : undefined),
|
|
2004
|
+
endpoint_id: extras.endpoint_id ?? (typeof r.endpoint_id === "string" ? r.endpoint_id : undefined),
|
|
2005
|
+
time_saved_ms: num(impact.time_saved_ms),
|
|
2006
|
+
time_saved_pct: num(impact.time_saved_pct),
|
|
2007
|
+
tokens_saved: num(impact.tokens_saved),
|
|
2008
|
+
tokens_saved_pct: num(impact.tokens_saved_pct),
|
|
2009
|
+
cost_saved_uc: num(impact.cost_saved_uc),
|
|
2010
|
+
browser_avoided: impact.browser_avoided === true,
|
|
2011
|
+
success: r.error == null
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
function readImpactSummary() {
|
|
2015
|
+
const path = getImpactLogPath();
|
|
2016
|
+
const summary = {
|
|
2017
|
+
total_runs: 0,
|
|
2018
|
+
successful_runs: 0,
|
|
2019
|
+
browser_avoided_runs: 0,
|
|
2020
|
+
total_time_saved_ms: 0,
|
|
2021
|
+
total_tokens_saved: 0,
|
|
2022
|
+
total_cost_saved_uc: 0,
|
|
2023
|
+
avg_time_saved_pct: 0,
|
|
2024
|
+
avg_tokens_saved_pct: 0,
|
|
2025
|
+
by_source: {},
|
|
2026
|
+
first_entry_at: null,
|
|
2027
|
+
last_entry_at: null
|
|
2028
|
+
};
|
|
2029
|
+
const files = [];
|
|
2030
|
+
for (let i = MAX_ROTATIONS;i >= 1; i--) {
|
|
2031
|
+
const rotated = `${path}.${i}`;
|
|
2032
|
+
if (existsSync5(rotated))
|
|
2033
|
+
files.push(rotated);
|
|
2034
|
+
}
|
|
2035
|
+
if (existsSync5(path))
|
|
2036
|
+
files.push(path);
|
|
2037
|
+
if (files.length === 0)
|
|
2038
|
+
return summary;
|
|
2039
|
+
let timePctSum = 0;
|
|
2040
|
+
let timePctCount = 0;
|
|
2041
|
+
let tokenPctSum = 0;
|
|
2042
|
+
let tokenPctCount = 0;
|
|
2043
|
+
for (const file of files) {
|
|
2044
|
+
let raw;
|
|
2045
|
+
try {
|
|
2046
|
+
raw = readFileSync4(file, "utf8");
|
|
2047
|
+
} catch {
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
for (const line of raw.split(`
|
|
2051
|
+
`)) {
|
|
2052
|
+
const trimmed = line.trim();
|
|
2053
|
+
if (!trimmed)
|
|
2054
|
+
continue;
|
|
2055
|
+
let e;
|
|
2056
|
+
try {
|
|
2057
|
+
e = JSON.parse(trimmed);
|
|
2058
|
+
} catch {
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
summary.total_runs += 1;
|
|
2062
|
+
if (e.success !== false)
|
|
2063
|
+
summary.successful_runs += 1;
|
|
2064
|
+
if (e.browser_avoided)
|
|
2065
|
+
summary.browser_avoided_runs += 1;
|
|
2066
|
+
summary.total_time_saved_ms += e.time_saved_ms ?? 0;
|
|
2067
|
+
summary.total_tokens_saved += e.tokens_saved ?? 0;
|
|
2068
|
+
summary.total_cost_saved_uc += e.cost_saved_uc ?? 0;
|
|
2069
|
+
if (typeof e.time_saved_pct === "number") {
|
|
2070
|
+
timePctSum += e.time_saved_pct;
|
|
2071
|
+
timePctCount += 1;
|
|
2072
|
+
}
|
|
2073
|
+
if (typeof e.tokens_saved_pct === "number") {
|
|
2074
|
+
tokenPctSum += e.tokens_saved_pct;
|
|
2075
|
+
tokenPctCount += 1;
|
|
2076
|
+
}
|
|
2077
|
+
if (e.source) {
|
|
2078
|
+
summary.by_source[e.source] = (summary.by_source[e.source] ?? 0) + 1;
|
|
2079
|
+
}
|
|
2080
|
+
if (!summary.first_entry_at || e.ts < summary.first_entry_at)
|
|
2081
|
+
summary.first_entry_at = e.ts;
|
|
2082
|
+
if (!summary.last_entry_at || e.ts > summary.last_entry_at)
|
|
2083
|
+
summary.last_entry_at = e.ts;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
summary.avg_time_saved_pct = timePctCount > 0 ? Math.round(timePctSum / timePctCount) : 0;
|
|
2087
|
+
summary.avg_tokens_saved_pct = tokenPctCount > 0 ? Math.round(tokenPctSum / tokenPctCount) : 0;
|
|
2088
|
+
return summary;
|
|
2089
|
+
}
|
|
1742
2090
|
|
|
1743
2091
|
// ../../src/cli/shortcuts.ts
|
|
1744
2092
|
var linkedin = {
|
|
@@ -1939,7 +2287,7 @@ function buildDepsMetadata(pack, taskName) {
|
|
|
1939
2287
|
init_paths();
|
|
1940
2288
|
init_supervisor();
|
|
1941
2289
|
init_version();
|
|
1942
|
-
import { openSync, readFileSync as
|
|
2290
|
+
import { openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1943
2291
|
import path2 from "node:path";
|
|
1944
2292
|
import { spawn } from "node:child_process";
|
|
1945
2293
|
function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
|
|
@@ -1977,14 +2325,14 @@ function isPidAlive(pid) {
|
|
|
1977
2325
|
}
|
|
1978
2326
|
function readPidState(pidFile) {
|
|
1979
2327
|
try {
|
|
1980
|
-
return JSON.parse(
|
|
2328
|
+
return JSON.parse(readFileSync5(pidFile, "utf-8"));
|
|
1981
2329
|
} catch {
|
|
1982
2330
|
return null;
|
|
1983
2331
|
}
|
|
1984
2332
|
}
|
|
1985
2333
|
function clearStalePidFile(pidFile) {
|
|
1986
2334
|
try {
|
|
1987
|
-
|
|
2335
|
+
unlinkSync2(pidFile);
|
|
1988
2336
|
} catch {}
|
|
1989
2337
|
}
|
|
1990
2338
|
function deriveListenEnv(baseUrl) {
|
|
@@ -2018,7 +2366,7 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
|
|
|
2018
2366
|
const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
|
|
2019
2367
|
const spawnSpec = getServerSpawnSpec(metaUrl, entrypoint);
|
|
2020
2368
|
const logFile = getServerAutostartLogFile();
|
|
2021
|
-
|
|
2369
|
+
ensureDir2(path2.dirname(logFile));
|
|
2022
2370
|
const logFd = openSync(logFile, "a");
|
|
2023
2371
|
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
2024
2372
|
cwd: spawnSpec.cwd,
|
|
@@ -2150,7 +2498,7 @@ async function restartServer(baseUrl, metaUrl) {
|
|
|
2150
2498
|
}
|
|
2151
2499
|
|
|
2152
2500
|
// ../../src/runtime/paths.ts
|
|
2153
|
-
import { existsSync as
|
|
2501
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
|
|
2154
2502
|
import path3 from "node:path";
|
|
2155
2503
|
import { createRequire as createRequire3 } from "node:module";
|
|
2156
2504
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
@@ -2170,7 +2518,7 @@ function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
|
|
|
2170
2518
|
const req = createRequire3(metaUrl);
|
|
2171
2519
|
const tsxPkg = req.resolve("tsx/package.json");
|
|
2172
2520
|
const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
|
|
2173
|
-
if (
|
|
2521
|
+
if (existsSync7(tsxLoader))
|
|
2174
2522
|
return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
|
|
2175
2523
|
} catch {}
|
|
2176
2524
|
return ["--import", "tsx", entrypoint];
|
|
@@ -2200,8 +2548,8 @@ init_publish();
|
|
|
2200
2548
|
init_settings();
|
|
2201
2549
|
init_graph();
|
|
2202
2550
|
init_schema_review();
|
|
2203
|
-
import { join as
|
|
2204
|
-
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
2551
|
+
import { join as join10 } from "node:path";
|
|
2552
|
+
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
2205
2553
|
var indexInFlight2 = new Map;
|
|
2206
2554
|
var pendingIndexJobs2 = new Map;
|
|
2207
2555
|
async function drainPendingIndexJobs() {
|
|
@@ -2238,14 +2586,14 @@ init_paths();
|
|
|
2238
2586
|
init_client2();
|
|
2239
2587
|
init_logger();
|
|
2240
2588
|
init_wallet();
|
|
2241
|
-
import { execFileSync as
|
|
2242
|
-
import { existsSync as
|
|
2589
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2590
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2243
2591
|
import os4 from "node:os";
|
|
2244
2592
|
import path7 from "node:path";
|
|
2245
2593
|
|
|
2246
2594
|
// ../../src/runtime/update-hints.ts
|
|
2247
2595
|
init_paths();
|
|
2248
|
-
import { existsSync as
|
|
2596
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2249
2597
|
import os3 from "node:os";
|
|
2250
2598
|
import path6 from "node:path";
|
|
2251
2599
|
var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -2258,20 +2606,20 @@ function getConfigDir2() {
|
|
|
2258
2606
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
2259
2607
|
return path6.join(getHomeDir(), ".unbrowse");
|
|
2260
2608
|
}
|
|
2261
|
-
function
|
|
2262
|
-
if (!
|
|
2263
|
-
|
|
2609
|
+
function ensureDir3(dir) {
|
|
2610
|
+
if (!existsSync10(dir))
|
|
2611
|
+
mkdirSync6(dir, { recursive: true });
|
|
2264
2612
|
return dir;
|
|
2265
2613
|
}
|
|
2266
2614
|
function readJsonFile(file) {
|
|
2267
2615
|
try {
|
|
2268
|
-
return JSON.parse(
|
|
2616
|
+
return JSON.parse(readFileSync7(file, "utf8"));
|
|
2269
2617
|
} catch {
|
|
2270
2618
|
return null;
|
|
2271
2619
|
}
|
|
2272
2620
|
}
|
|
2273
2621
|
function writeJsonFile(file, value) {
|
|
2274
|
-
|
|
2622
|
+
ensureDir3(path6.dirname(file));
|
|
2275
2623
|
writeFileSync4(file, `${JSON.stringify(value, null, 2)}
|
|
2276
2624
|
`);
|
|
2277
2625
|
}
|
|
@@ -2282,7 +2630,7 @@ function detectRepoRoot(start2) {
|
|
|
2282
2630
|
let dir = path6.resolve(start2);
|
|
2283
2631
|
const root = path6.parse(dir).root;
|
|
2284
2632
|
while (dir !== root) {
|
|
2285
|
-
if (
|
|
2633
|
+
if (existsSync10(path6.join(dir, ".git")))
|
|
2286
2634
|
return dir;
|
|
2287
2635
|
dir = path6.dirname(dir);
|
|
2288
2636
|
}
|
|
@@ -2361,13 +2709,13 @@ codex_hooks = true
|
|
|
2361
2709
|
}
|
|
2362
2710
|
function writeCodexHook(metaUrl) {
|
|
2363
2711
|
const configPath = getCodexConfigPath();
|
|
2364
|
-
if (!
|
|
2712
|
+
if (!existsSync10(path6.dirname(configPath))) {
|
|
2365
2713
|
return { host: "codex", action: "not-detected", config_file: configPath };
|
|
2366
2714
|
}
|
|
2367
2715
|
try {
|
|
2368
2716
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
2369
|
-
const fileExistsBefore =
|
|
2370
|
-
let content = fileExistsBefore ?
|
|
2717
|
+
const fileExistsBefore = existsSync10(configPath);
|
|
2718
|
+
let content = fileExistsBefore ? readFileSync7(configPath, "utf8") : "";
|
|
2371
2719
|
const previous = content;
|
|
2372
2720
|
content = ensureCodexHooksFeature(content);
|
|
2373
2721
|
if (!content.includes("unbrowse-update-hint.mjs")) {
|
|
@@ -2401,13 +2749,13 @@ command = ${JSON.stringify(command)}
|
|
|
2401
2749
|
}
|
|
2402
2750
|
function writeClaudeHook(metaUrl) {
|
|
2403
2751
|
const settingsPath = getClaudeSettingsPath();
|
|
2404
|
-
if (!
|
|
2752
|
+
if (!existsSync10(path6.dirname(settingsPath))) {
|
|
2405
2753
|
return { host: "claude", action: "not-detected", config_file: settingsPath };
|
|
2406
2754
|
}
|
|
2407
2755
|
try {
|
|
2408
2756
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
2409
2757
|
const command = `node "${hookScript}"`;
|
|
2410
|
-
const fileExistsBefore =
|
|
2758
|
+
const fileExistsBefore = existsSync10(settingsPath);
|
|
2411
2759
|
const settings = readJsonFile(settingsPath) ?? {};
|
|
2412
2760
|
settings.hooks ??= {};
|
|
2413
2761
|
settings.hooks.SessionStart ??= [];
|
|
@@ -2445,7 +2793,7 @@ function configureUpdateHintHooks(metaUrl, install) {
|
|
|
2445
2793
|
function hasBinary(name) {
|
|
2446
2794
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
2447
2795
|
try {
|
|
2448
|
-
|
|
2796
|
+
execFileSync3(checker, [name], { stdio: "ignore" });
|
|
2449
2797
|
return true;
|
|
2450
2798
|
} catch {
|
|
2451
2799
|
return false;
|
|
@@ -2472,7 +2820,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
|
|
|
2472
2820
|
return path7.join(cwd, ".opencode", "commands");
|
|
2473
2821
|
}
|
|
2474
2822
|
function detectOpenCode(cwd) {
|
|
2475
|
-
return hasBinary("opencode") ||
|
|
2823
|
+
return hasBinary("opencode") || existsSync11(path7.join(resolveConfigHome(), "opencode")) || existsSync11(path7.join(cwd, ".opencode"));
|
|
2476
2824
|
}
|
|
2477
2825
|
function renderOpenCodeCommand() {
|
|
2478
2826
|
return `---
|
|
@@ -2500,12 +2848,12 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
2500
2848
|
if (scope === "auto" && !detected) {
|
|
2501
2849
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
2502
2850
|
}
|
|
2503
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" :
|
|
2851
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync11(path7.join(cwd, ".opencode")) ? "project" : "global";
|
|
2504
2852
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
2505
|
-
const commandFile = path7.join(
|
|
2853
|
+
const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
|
|
2506
2854
|
const content = renderOpenCodeCommand();
|
|
2507
|
-
const action2 =
|
|
2508
|
-
|
|
2855
|
+
const action2 = existsSync11(commandFile) ? "updated" : "installed";
|
|
2856
|
+
mkdirSync7(path7.dirname(commandFile), { recursive: true });
|
|
2509
2857
|
writeFileSync5(commandFile, content);
|
|
2510
2858
|
return {
|
|
2511
2859
|
detected: detected || scope !== "auto",
|
|
@@ -2516,10 +2864,10 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
2516
2864
|
}
|
|
2517
2865
|
async function ensureBrowserEngineInstalled() {
|
|
2518
2866
|
const binary = findKuriBinary();
|
|
2519
|
-
if (
|
|
2867
|
+
if (existsSync11(binary)) {
|
|
2520
2868
|
return { installed: true, action: "already-installed" };
|
|
2521
2869
|
}
|
|
2522
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) =>
|
|
2870
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync11(path7.join(candidate, "build.zig")));
|
|
2523
2871
|
if (!sourceDir) {
|
|
2524
2872
|
return {
|
|
2525
2873
|
installed: false,
|
|
@@ -2535,13 +2883,13 @@ async function ensureBrowserEngineInstalled() {
|
|
|
2535
2883
|
};
|
|
2536
2884
|
}
|
|
2537
2885
|
try {
|
|
2538
|
-
|
|
2886
|
+
execFileSync3("zig", ["build", "-Doptimize=ReleaseFast"], {
|
|
2539
2887
|
cwd: sourceDir,
|
|
2540
2888
|
stdio: "inherit",
|
|
2541
2889
|
timeout: 300000
|
|
2542
2890
|
});
|
|
2543
2891
|
const builtBinary = findKuriBinary();
|
|
2544
|
-
if (
|
|
2892
|
+
if (existsSync11(builtBinary)) {
|
|
2545
2893
|
return {
|
|
2546
2894
|
installed: true,
|
|
2547
2895
|
action: "installed",
|
|
@@ -2566,11 +2914,11 @@ async function runSetup(options) {
|
|
|
2566
2914
|
const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
|
|
2567
2915
|
const walletCheck = checkWalletConfigured();
|
|
2568
2916
|
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
2569
|
-
const lobsterInstalled = hasBinary("lobstercash") ||
|
|
2917
|
+
const lobsterInstalled = hasBinary("lobstercash") || existsSync11(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
2570
2918
|
if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
|
|
2571
2919
|
console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
|
|
2572
2920
|
try {
|
|
2573
|
-
|
|
2921
|
+
execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
2574
2922
|
stdio: "inherit",
|
|
2575
2923
|
timeout: 60000
|
|
2576
2924
|
});
|
|
@@ -2606,7 +2954,7 @@ async function runSetup(options) {
|
|
|
2606
2954
|
|
|
2607
2955
|
// ../../src/runtime/update-hints.ts
|
|
2608
2956
|
init_paths();
|
|
2609
|
-
import { existsSync as
|
|
2957
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2610
2958
|
import os5 from "node:os";
|
|
2611
2959
|
import path8 from "node:path";
|
|
2612
2960
|
var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
|
|
@@ -2619,20 +2967,20 @@ function getConfigDir3() {
|
|
|
2619
2967
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
2620
2968
|
return path8.join(getHomeDir2(), ".unbrowse");
|
|
2621
2969
|
}
|
|
2622
|
-
function
|
|
2623
|
-
if (!
|
|
2624
|
-
|
|
2970
|
+
function ensureDir4(dir) {
|
|
2971
|
+
if (!existsSync12(dir))
|
|
2972
|
+
mkdirSync8(dir, { recursive: true });
|
|
2625
2973
|
return dir;
|
|
2626
2974
|
}
|
|
2627
2975
|
function readJsonFile2(file) {
|
|
2628
2976
|
try {
|
|
2629
|
-
return JSON.parse(
|
|
2977
|
+
return JSON.parse(readFileSync8(file, "utf8"));
|
|
2630
2978
|
} catch {
|
|
2631
2979
|
return null;
|
|
2632
2980
|
}
|
|
2633
2981
|
}
|
|
2634
2982
|
function writeJsonFile2(file, value) {
|
|
2635
|
-
|
|
2983
|
+
ensureDir4(path8.dirname(file));
|
|
2636
2984
|
writeFileSync6(file, `${JSON.stringify(value, null, 2)}
|
|
2637
2985
|
`);
|
|
2638
2986
|
}
|
|
@@ -2646,7 +2994,7 @@ function detectRepoRoot2(start2) {
|
|
|
2646
2994
|
let dir = path8.resolve(start2);
|
|
2647
2995
|
const root = path8.parse(dir).root;
|
|
2648
2996
|
while (dir !== root) {
|
|
2649
|
-
if (
|
|
2997
|
+
if (existsSync12(path8.join(dir, ".git")))
|
|
2650
2998
|
return dir;
|
|
2651
2999
|
dir = path8.dirname(dir);
|
|
2652
3000
|
}
|
|
@@ -2679,7 +3027,7 @@ function detectInstallHost2(repoRoot) {
|
|
|
2679
3027
|
function getInstalledVersion(metaUrl) {
|
|
2680
3028
|
const packageRoot = getPackageRoot(metaUrl);
|
|
2681
3029
|
try {
|
|
2682
|
-
const pkg = JSON.parse(
|
|
3030
|
+
const pkg = JSON.parse(readFileSync8(path8.join(packageRoot, "package.json"), "utf8"));
|
|
2683
3031
|
return pkg.version ?? "unknown";
|
|
2684
3032
|
} catch {
|
|
2685
3033
|
return "unknown";
|
|
@@ -2780,6 +3128,7 @@ loadEnv({ quiet: true });
|
|
|
2780
3128
|
loadEnv({ path: ".env.runtime", quiet: true });
|
|
2781
3129
|
var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
|
|
2782
3130
|
var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
|
|
3131
|
+
var walletNudgeShown = false;
|
|
2783
3132
|
function parseArgs(argv) {
|
|
2784
3133
|
const raw = argv.slice(2);
|
|
2785
3134
|
const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : "help";
|
|
@@ -2926,6 +3275,14 @@ function formatSavedDuration(ms) {
|
|
|
2926
3275
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
2927
3276
|
return `${ms}ms`;
|
|
2928
3277
|
}
|
|
3278
|
+
function formatCostUsd(uc) {
|
|
3279
|
+
const usd = uc / 1e6;
|
|
3280
|
+
if (usd >= 1)
|
|
3281
|
+
return `$${usd.toFixed(2)}`;
|
|
3282
|
+
if (usd >= 0.01)
|
|
3283
|
+
return `$${usd.toFixed(3)}`;
|
|
3284
|
+
return `$${usd.toFixed(4)}`;
|
|
3285
|
+
}
|
|
2929
3286
|
function emitImpactSummary(result) {
|
|
2930
3287
|
const impact = result.impact;
|
|
2931
3288
|
if (!impact)
|
|
@@ -2934,14 +3291,17 @@ function emitImpactSummary(result) {
|
|
|
2934
3291
|
const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
|
|
2935
3292
|
const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
|
|
2936
3293
|
const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
|
|
3294
|
+
const costSavedUc = typeof impact.cost_saved_uc === "number" ? impact.cost_saved_uc : 0;
|
|
2937
3295
|
const browserAvoided = impact.browser_avoided === true;
|
|
2938
|
-
if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
|
|
3296
|
+
if (timeSavedMs <= 0 && tokensSaved <= 0 && costSavedUc <= 0 && !browserAvoided)
|
|
2939
3297
|
return;
|
|
2940
3298
|
const parts = [];
|
|
2941
3299
|
if (timeSavedMs > 0)
|
|
2942
3300
|
parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
|
|
2943
3301
|
if (tokensSaved > 0)
|
|
2944
3302
|
parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
|
|
3303
|
+
if (costSavedUc > 0)
|
|
3304
|
+
parts.push(`${formatCostUsd(costSavedUc)} saved`);
|
|
2945
3305
|
if (browserAvoided)
|
|
2946
3306
|
parts.push("browser avoided");
|
|
2947
3307
|
info(parts.join(" \u2022 "));
|
|
@@ -3086,9 +3446,24 @@ async function cmdResolve(flags) {
|
|
|
3086
3446
|
}
|
|
3087
3447
|
result = slimTrace(result);
|
|
3088
3448
|
emitImpactSummary(result);
|
|
3449
|
+
{
|
|
3450
|
+
const entry = impactFromResult("resolve", result, { intent, domain });
|
|
3451
|
+
if (entry)
|
|
3452
|
+
appendImpact(entry);
|
|
3453
|
+
}
|
|
3089
3454
|
emitNextActionSummary(result);
|
|
3090
3455
|
const skill = result.skill;
|
|
3091
3456
|
const trace = result.trace;
|
|
3457
|
+
if (trace?.success && !walletNudgeShown) {
|
|
3458
|
+
try {
|
|
3459
|
+
const { checkWalletConfigured: checkWalletConfigured3 } = await Promise.resolve().then(() => (init_wallet2(), exports_wallet));
|
|
3460
|
+
const wallet = checkWalletConfigured3();
|
|
3461
|
+
if (!wallet.configured) {
|
|
3462
|
+
info("You're indexing routes but have no payout wallet. Run: npx @crossmint/lobster-cli setup");
|
|
3463
|
+
walletNudgeShown = true;
|
|
3464
|
+
}
|
|
3465
|
+
} catch (_e) {}
|
|
3466
|
+
}
|
|
3092
3467
|
if (skill?.skill_id && trace) {
|
|
3093
3468
|
result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
|
|
3094
3469
|
}
|
|
@@ -3246,6 +3621,14 @@ async function cmdExecute(flags) {
|
|
|
3246
3621
|
}
|
|
3247
3622
|
result = slimTrace(result);
|
|
3248
3623
|
emitImpactSummary(result);
|
|
3624
|
+
{
|
|
3625
|
+
const entry = impactFromResult("execute", result, {
|
|
3626
|
+
skill_id: skillId,
|
|
3627
|
+
endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : undefined
|
|
3628
|
+
});
|
|
3629
|
+
if (entry)
|
|
3630
|
+
appendImpact(entry);
|
|
3631
|
+
}
|
|
3249
3632
|
emitNextActionSummary(result);
|
|
3250
3633
|
const pathFlag = flags.path;
|
|
3251
3634
|
const extractFlag = flags.extract;
|
|
@@ -3472,6 +3855,16 @@ async function cmdSetup(flags) {
|
|
|
3472
3855
|
info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
|
|
3473
3856
|
}
|
|
3474
3857
|
}
|
|
3858
|
+
if (report.wallet.configured) {
|
|
3859
|
+
info(`Wallet configured (${report.wallet.provider}): ${report.wallet.wallet_address ?? "linked"}`);
|
|
3860
|
+
} else if (report.wallet.lobster_installed) {
|
|
3861
|
+
info("Wallet not paired \u2014 your indexed routes won't earn payouts.");
|
|
3862
|
+
info("Run: npx @crossmint/lobster-cli setup");
|
|
3863
|
+
} else {
|
|
3864
|
+
info("No wallet configured \u2014 you're indexing routes for free.");
|
|
3865
|
+
info("Set up a wallet so you earn when agents use your routes:");
|
|
3866
|
+
info(" npx @crossmint/lobster-cli setup");
|
|
3867
|
+
}
|
|
3475
3868
|
await recordInstallTelemetryEvent("setup", {
|
|
3476
3869
|
hostType,
|
|
3477
3870
|
status: report.browser_engine.action === "failed" ? "failed" : "installed",
|
|
@@ -3565,7 +3958,8 @@ var CLI_REFERENCE = {
|
|
|
3565
3958
|
{ name: "back", usage: "[--session id]", desc: "Navigate back" },
|
|
3566
3959
|
{ name: "forward", usage: "[--session id]", desc: "Navigate forward" },
|
|
3567
3960
|
{ name: "sync", usage: "[--session id]", desc: "Checkpoint current capture, keep tab open, queue background index + publish, then inspect via skill/publish review" },
|
|
3568
|
-
{ name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" }
|
|
3961
|
+
{ name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, close browse session, then inspect via skill/publish review" },
|
|
3962
|
+
{ name: "stats", usage: "[--json] [--pretty]", desc: "Show lifetime time/tokens/cost saved and marketplace earnings/spending" }
|
|
3569
3963
|
],
|
|
3570
3964
|
globalFlags: [
|
|
3571
3965
|
{ flag: "--pretty", desc: "Indented JSON output" },
|
|
@@ -3606,6 +4000,131 @@ var CLI_REFERENCE = {
|
|
|
3606
4000
|
`unbrowse publish --skill abc --endpoints '[{"endpoint_id":"def","description":"Search court judgments by keywords","action_kind":"search","resource_kind":"judgment"}]'`
|
|
3607
4001
|
]
|
|
3608
4002
|
};
|
|
4003
|
+
function formatTotalDuration(ms) {
|
|
4004
|
+
if (ms >= 3600000)
|
|
4005
|
+
return `${(ms / 3600000).toFixed(1)}h`;
|
|
4006
|
+
if (ms >= 60000)
|
|
4007
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
4008
|
+
if (ms >= 1000)
|
|
4009
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
4010
|
+
return `${ms}ms`;
|
|
4011
|
+
}
|
|
4012
|
+
async function cmdStats(flags) {
|
|
4013
|
+
const pretty = !!flags.pretty;
|
|
4014
|
+
const jsonOnly = !!flags.json;
|
|
4015
|
+
const local = readImpactSummary();
|
|
4016
|
+
const agentId = getAgentId();
|
|
4017
|
+
let profile = null;
|
|
4018
|
+
let earnings = null;
|
|
4019
|
+
let spending = null;
|
|
4020
|
+
const remoteErrors = {};
|
|
4021
|
+
if (agentId) {
|
|
4022
|
+
const results = await Promise.allSettled([
|
|
4023
|
+
getMyProfile(),
|
|
4024
|
+
getCreatorEarnings(agentId),
|
|
4025
|
+
getTransactionHistory(agentId)
|
|
4026
|
+
]);
|
|
4027
|
+
if (results[0].status === "fulfilled")
|
|
4028
|
+
profile = results[0].value;
|
|
4029
|
+
else
|
|
4030
|
+
remoteErrors.profile = results[0].reason?.message ?? String(results[0].reason);
|
|
4031
|
+
if (results[1].status === "fulfilled")
|
|
4032
|
+
earnings = results[1].value;
|
|
4033
|
+
else
|
|
4034
|
+
remoteErrors.earnings = results[1].reason?.message ?? String(results[1].reason);
|
|
4035
|
+
if (results[2].status === "fulfilled")
|
|
4036
|
+
spending = results[2].value;
|
|
4037
|
+
else
|
|
4038
|
+
remoteErrors.spending = results[2].reason?.message ?? String(results[2].reason);
|
|
4039
|
+
} else {
|
|
4040
|
+
remoteErrors.profile = "No agent_id in local config. Run `unbrowse setup` to register.";
|
|
4041
|
+
}
|
|
4042
|
+
const earnedUsd = earnings?.ledger?.total_earned_usd ?? 0;
|
|
4043
|
+
const spentUsd = spending?.ledger?.total_spent_usd ?? 0;
|
|
4044
|
+
const netUsd = earnedUsd - spentUsd;
|
|
4045
|
+
const savedUsd = local.total_cost_saved_uc / 1e6;
|
|
4046
|
+
const payload = {
|
|
4047
|
+
agent_id: agentId,
|
|
4048
|
+
profile,
|
|
4049
|
+
impact: {
|
|
4050
|
+
total_runs: local.total_runs,
|
|
4051
|
+
successful_runs: local.successful_runs,
|
|
4052
|
+
browser_avoided_runs: local.browser_avoided_runs,
|
|
4053
|
+
total_time_saved_ms: local.total_time_saved_ms,
|
|
4054
|
+
total_time_saved_human: formatTotalDuration(local.total_time_saved_ms),
|
|
4055
|
+
total_tokens_saved: local.total_tokens_saved,
|
|
4056
|
+
total_cost_saved_usd: Number(savedUsd.toFixed(6)),
|
|
4057
|
+
avg_time_saved_pct: local.avg_time_saved_pct,
|
|
4058
|
+
avg_tokens_saved_pct: local.avg_tokens_saved_pct,
|
|
4059
|
+
by_source: local.by_source,
|
|
4060
|
+
first_entry_at: local.first_entry_at,
|
|
4061
|
+
last_entry_at: local.last_entry_at,
|
|
4062
|
+
log_path: getImpactLogPath()
|
|
4063
|
+
},
|
|
4064
|
+
earnings: {
|
|
4065
|
+
total_earned_usd: earnedUsd,
|
|
4066
|
+
total_earned_uc: earnings?.ledger?.total_earned_uc ?? 0,
|
|
4067
|
+
transaction_count: earnings?.ledger?.transaction_count ?? 0,
|
|
4068
|
+
last_transaction_at: earnings?.ledger?.last_transaction_at ?? null
|
|
4069
|
+
},
|
|
4070
|
+
spending: {
|
|
4071
|
+
total_spent_usd: spentUsd,
|
|
4072
|
+
total_spent_uc: spending?.ledger?.total_spent_uc ?? 0,
|
|
4073
|
+
transaction_count: spending?.ledger?.transaction_count ?? 0,
|
|
4074
|
+
last_transaction_at: spending?.ledger?.last_transaction_at ?? null
|
|
4075
|
+
},
|
|
4076
|
+
net_usd: netUsd,
|
|
4077
|
+
...Object.keys(remoteErrors).length > 0 ? { remote_errors: remoteErrors } : {}
|
|
4078
|
+
};
|
|
4079
|
+
if (jsonOnly) {
|
|
4080
|
+
output(payload, pretty);
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
const lines = [];
|
|
4084
|
+
lines.push("Unbrowse stats");
|
|
4085
|
+
lines.push(` agent_id: ${agentId ?? "(not registered \u2014 run `unbrowse setup`)"}`);
|
|
4086
|
+
if (profile?.name)
|
|
4087
|
+
lines.push(` name: ${profile.name}`);
|
|
4088
|
+
lines.push("");
|
|
4089
|
+
lines.push("Impact (local, this machine):");
|
|
4090
|
+
if (local.total_runs === 0) {
|
|
4091
|
+
lines.push(" No resolve/execute runs recorded yet.");
|
|
4092
|
+
lines.push(` Log file: ${getImpactLogPath()}`);
|
|
4093
|
+
} else {
|
|
4094
|
+
lines.push(` Runs: ${local.total_runs} (${local.successful_runs} successful, ${local.browser_avoided_runs} browser-avoided)`);
|
|
4095
|
+
if (local.total_time_saved_ms > 0) {
|
|
4096
|
+
lines.push(` Time saved: ${formatTotalDuration(local.total_time_saved_ms)} (avg ${local.avg_time_saved_pct}% faster)`);
|
|
4097
|
+
}
|
|
4098
|
+
if (local.total_tokens_saved > 0) {
|
|
4099
|
+
lines.push(` Tokens saved: ${local.total_tokens_saved.toLocaleString("en-US")} (avg ${local.avg_tokens_saved_pct}% less context)`);
|
|
4100
|
+
}
|
|
4101
|
+
if (savedUsd > 0) {
|
|
4102
|
+
lines.push(` Cost saved: ${formatCostUsd(local.total_cost_saved_uc)}`);
|
|
4103
|
+
}
|
|
4104
|
+
if (Object.keys(local.by_source).length > 0) {
|
|
4105
|
+
const topSources = Object.entries(local.by_source).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
4106
|
+
lines.push(` By source: ${topSources}`);
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
lines.push("");
|
|
4110
|
+
lines.push("Money (backend ledger):");
|
|
4111
|
+
if (!agentId) {
|
|
4112
|
+
lines.push(" (not registered \u2014 earnings/spending unavailable)");
|
|
4113
|
+
} else if (remoteErrors.earnings || remoteErrors.spending) {
|
|
4114
|
+
if (remoteErrors.earnings)
|
|
4115
|
+
lines.push(` earnings: error \u2014 ${remoteErrors.earnings}`);
|
|
4116
|
+
if (remoteErrors.spending)
|
|
4117
|
+
lines.push(` spending: error \u2014 ${remoteErrors.spending}`);
|
|
4118
|
+
} else {
|
|
4119
|
+
lines.push(` Earned: $${earnedUsd.toFixed(4)} (${earnings?.ledger?.transaction_count ?? 0} payouts)`);
|
|
4120
|
+
lines.push(` Spent: $${spentUsd.toFixed(4)} (${spending?.ledger?.transaction_count ?? 0} payments)`);
|
|
4121
|
+
lines.push(` Net: ${netUsd >= 0 ? "+" : ""}$${netUsd.toFixed(4)}`);
|
|
4122
|
+
}
|
|
4123
|
+
lines.push("");
|
|
4124
|
+
info(lines.join(`
|
|
4125
|
+
`));
|
|
4126
|
+
output(payload, pretty);
|
|
4127
|
+
}
|
|
3609
4128
|
function printHelp() {
|
|
3610
4129
|
const r = CLI_REFERENCE;
|
|
3611
4130
|
const lines = ["unbrowse \u2014 shell-safe CLI for the local API", ""];
|
|
@@ -4027,6 +4546,8 @@ async function main() {
|
|
|
4027
4546
|
return cmdUpgrade(flags);
|
|
4028
4547
|
if (command === "connect-chrome")
|
|
4029
4548
|
return cmdConnectChrome();
|
|
4549
|
+
if (command === "stats")
|
|
4550
|
+
return cmdStats(flags);
|
|
4030
4551
|
const KNOWN_COMMANDS = new Set([
|
|
4031
4552
|
"health",
|
|
4032
4553
|
"mcp",
|
|
@@ -4070,7 +4591,8 @@ async function main() {
|
|
|
4070
4591
|
"forward",
|
|
4071
4592
|
"sync",
|
|
4072
4593
|
"close",
|
|
4073
|
-
"connect-chrome"
|
|
4594
|
+
"connect-chrome",
|
|
4595
|
+
"stats"
|
|
4074
4596
|
]);
|
|
4075
4597
|
if (!KNOWN_COMMANDS.has(command)) {
|
|
4076
4598
|
const pack = findSitePack(command);
|
|
@@ -4166,6 +4688,8 @@ async function main() {
|
|
|
4166
4688
|
return cmdClose(flags);
|
|
4167
4689
|
case "connect-chrome":
|
|
4168
4690
|
return cmdConnectChrome();
|
|
4691
|
+
case "stats":
|
|
4692
|
+
return cmdStats(flags);
|
|
4169
4693
|
default:
|
|
4170
4694
|
info(`Unknown command: ${command}`);
|
|
4171
4695
|
printHelp();
|