unbrowse 3.0.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +274 -62
- package/dist/mcp.js +4 -4
- package/package.json +1 -1
- package/runtime-src/build-info.generated.ts +4 -4
- package/runtime-src/cli.ts +26 -0
- package/runtime-src/client/index.ts +25 -1
- package/runtime-src/execution/index.ts +33 -21
- package/runtime-src/kuri/client.ts +5 -1
- package/runtime-src/orchestrator/index.ts +7 -3
- package/runtime-src/payments/lobster-pay.ts +182 -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", BUILD_GIT_SHA = "c9f9ce0e5cb1", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAiLCJnaXRfc2hhIjoiYzlmOWNlMGU1Y2IxIiwiY29kZV9oYXNoIjoiMTQ4OGZjMWQ5MmI3IiwidHJhY2VfdmVyc2lvbiI6IjE0ODhmYzFkOTJiN0BjOWY5Y2UwZTVjYjEiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTA1VDA2OjUwOjA4LjQzNVoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "_2pdxyoIEYkKUHO0nXaXTubP5SrZCa-8CqStcPBsXxw", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
|
|
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 existsSync5, mkdirSync as mkdirSync2, 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 (existsSync5(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 (existsSync5(tsxLoader))
|
|
322
437
|
return ["--import", pathToFileURL(tsxLoader).href, entrypoint];
|
|
323
438
|
} catch {}
|
|
324
439
|
return ["--import", "tsx", entrypoint];
|
|
@@ -327,7 +442,7 @@ function getUnbrowseHome() {
|
|
|
327
442
|
return path.join(os.homedir(), ".unbrowse");
|
|
328
443
|
}
|
|
329
444
|
function ensureDir(dir) {
|
|
330
|
-
if (!
|
|
445
|
+
if (!existsSync5(dir))
|
|
331
446
|
mkdirSync2(dir, { recursive: true });
|
|
332
447
|
return dir;
|
|
333
448
|
}
|
|
@@ -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 existsSync7 } 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) => existsSync7(candidate)) ?? candidates[0] ?? kuriBinaryName();
|
|
548
665
|
}
|
|
549
666
|
var KURI_DEFAULT_PORT = 7700, defaultBrokerState, brokerClients;
|
|
550
667
|
var init_client2 = __esm(() => {
|
|
@@ -584,11 +701,11 @@ var init_transform = __esm(() => {
|
|
|
584
701
|
});
|
|
585
702
|
|
|
586
703
|
// ../../src/debug-trace.ts
|
|
587
|
-
import { join as
|
|
704
|
+
import { join as join5 } from "node:path";
|
|
588
705
|
import { nanoid as nanoid3 } from "nanoid";
|
|
589
706
|
var TRACE_DIR;
|
|
590
707
|
var init_debug_trace = __esm(() => {
|
|
591
|
-
TRACE_DIR = process.env.TRACES_DIR ??
|
|
708
|
+
TRACE_DIR = process.env.TRACES_DIR ?? join5(process.cwd(), "traces");
|
|
592
709
|
});
|
|
593
710
|
|
|
594
711
|
// ../../src/publish/sanitize.ts
|
|
@@ -678,8 +795,8 @@ var init_bundle_scanner = __esm(() => {
|
|
|
678
795
|
});
|
|
679
796
|
|
|
680
797
|
// ../../src/vault/index.ts
|
|
681
|
-
import { join as
|
|
682
|
-
import { homedir as
|
|
798
|
+
import { join as join6 } from "path";
|
|
799
|
+
import { homedir as homedir4 } from "os";
|
|
683
800
|
function normalizeKeytarModule(mod) {
|
|
684
801
|
let candidate = mod;
|
|
685
802
|
for (let depth = 0;depth < 3; depth++) {
|
|
@@ -701,9 +818,9 @@ var init_vault = __esm(async () => {
|
|
|
701
818
|
try {
|
|
702
819
|
keytar = normalizeKeytarModule(await import("keytar"));
|
|
703
820
|
} catch {}
|
|
704
|
-
VAULT_DIR =
|
|
705
|
-
VAULT_FILE =
|
|
706
|
-
KEY_FILE =
|
|
821
|
+
VAULT_DIR = join6(homedir4(), ".unbrowse", "vault");
|
|
822
|
+
VAULT_FILE = join6(VAULT_DIR, "credentials.enc");
|
|
823
|
+
KEY_FILE = join6(VAULT_DIR, ".key");
|
|
707
824
|
vaultLock = Promise.resolve();
|
|
708
825
|
});
|
|
709
826
|
|
|
@@ -854,7 +971,7 @@ var init_schema_review = __esm(() => {
|
|
|
854
971
|
});
|
|
855
972
|
|
|
856
973
|
// ../../src/indexer/index.ts
|
|
857
|
-
import { join as
|
|
974
|
+
import { join as join7 } from "node:path";
|
|
858
975
|
var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
|
|
859
976
|
var init_indexer = __esm(async () => {
|
|
860
977
|
init_graph();
|
|
@@ -869,7 +986,7 @@ var init_indexer = __esm(async () => {
|
|
|
869
986
|
init_graph();
|
|
870
987
|
init_schema_review();
|
|
871
988
|
await init_orchestrator();
|
|
872
|
-
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
989
|
+
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join7(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
873
990
|
indexInFlight = new Map;
|
|
874
991
|
pendingIndexJobs = new Map;
|
|
875
992
|
});
|
|
@@ -1046,8 +1163,8 @@ var init_routing_telemetry = __esm(() => {
|
|
|
1046
1163
|
});
|
|
1047
1164
|
// ../../src/orchestrator/index.ts
|
|
1048
1165
|
import { nanoid as nanoid9 } from "nanoid";
|
|
1049
|
-
import { existsSync as
|
|
1050
|
-
import { dirname as dirname2, join as
|
|
1166
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync3, readFileSync as readFileSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
1167
|
+
import { dirname as dirname2, join as join8 } from "node:path";
|
|
1051
1168
|
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
1169
|
var init_orchestrator = __esm(async () => {
|
|
1053
1170
|
init_client();
|
|
@@ -1077,12 +1194,12 @@ var init_orchestrator = __esm(async () => {
|
|
|
1077
1194
|
captureInFlight = new Map;
|
|
1078
1195
|
captureDomainLocks = new Map;
|
|
1079
1196
|
skillRouteCache = new Map;
|
|
1080
|
-
ROUTE_CACHE_FILE =
|
|
1081
|
-
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
1197
|
+
ROUTE_CACHE_FILE = join8(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
|
|
1198
|
+
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1082
1199
|
domainSkillCache = new Map;
|
|
1083
|
-
DOMAIN_CACHE_FILE =
|
|
1200
|
+
DOMAIN_CACHE_FILE = join8(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
1084
1201
|
try {
|
|
1085
|
-
if (
|
|
1202
|
+
if (existsSync8(DOMAIN_CACHE_FILE)) {
|
|
1086
1203
|
const data = JSON.parse(readFileSync5(DOMAIN_CACHE_FILE, "utf-8"));
|
|
1087
1204
|
for (const [k, v] of Object.entries(data)) {
|
|
1088
1205
|
const entry = v;
|
|
@@ -1099,7 +1216,7 @@ var init_orchestrator = __esm(async () => {
|
|
|
1099
1216
|
_routeCacheDirty = false;
|
|
1100
1217
|
try {
|
|
1101
1218
|
const dir = dirname2(ROUTE_CACHE_FILE);
|
|
1102
|
-
if (!
|
|
1219
|
+
if (!existsSync8(dir))
|
|
1103
1220
|
mkdirSync4(dir, { recursive: true });
|
|
1104
1221
|
const entries = Object.fromEntries(skillRouteCache);
|
|
1105
1222
|
writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
|
|
@@ -1107,7 +1224,7 @@ var init_orchestrator = __esm(async () => {
|
|
|
1107
1224
|
}, 5000);
|
|
1108
1225
|
routeCacheFlushTimer.unref?.();
|
|
1109
1226
|
try {
|
|
1110
|
-
if (
|
|
1227
|
+
if (existsSync8(ROUTE_CACHE_FILE)) {
|
|
1111
1228
|
const data = JSON.parse(readFileSync5(ROUTE_CACHE_FILE, "utf-8"));
|
|
1112
1229
|
for (const [k, v] of Object.entries(data)) {
|
|
1113
1230
|
const entry = v;
|
|
@@ -1198,6 +1315,60 @@ var init_orchestrator = __esm(async () => {
|
|
|
1198
1315
|
]);
|
|
1199
1316
|
});
|
|
1200
1317
|
|
|
1318
|
+
// ../../src/payments/wallet.ts
|
|
1319
|
+
var exports_wallet = {};
|
|
1320
|
+
__export(exports_wallet, {
|
|
1321
|
+
getWalletContext: () => getWalletContext2,
|
|
1322
|
+
checkWalletConfigured: () => checkWalletConfigured2
|
|
1323
|
+
});
|
|
1324
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
|
|
1325
|
+
import { homedir as homedir5 } from "node:os";
|
|
1326
|
+
import { join as join10 } from "node:path";
|
|
1327
|
+
function asNonEmptyString2(value) {
|
|
1328
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1329
|
+
}
|
|
1330
|
+
function getLobsterWalletFromLocalConfig2() {
|
|
1331
|
+
const agentsPath = join10(process.env.HOME || homedir5(), ".lobster", "agents.json");
|
|
1332
|
+
if (!existsSync12(agentsPath))
|
|
1333
|
+
return;
|
|
1334
|
+
try {
|
|
1335
|
+
const raw = JSON.parse(readFileSync8(agentsPath, "utf8"));
|
|
1336
|
+
const activeAgentId = asNonEmptyString2(raw.activeAgentId);
|
|
1337
|
+
const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString2(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
|
|
1338
|
+
return asNonEmptyString2(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString2(activeAgent?.walletAddress) ?? asNonEmptyString2(activeAgent?.wallet_address);
|
|
1339
|
+
} catch {
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
function getWalletContext2() {
|
|
1344
|
+
const lobsterWallet = asNonEmptyString2(process.env.LOBSTER_WALLET_ADDRESS);
|
|
1345
|
+
if (lobsterWallet) {
|
|
1346
|
+
return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
|
|
1347
|
+
}
|
|
1348
|
+
const genericWallet = asNonEmptyString2(process.env.AGENT_WALLET_ADDRESS);
|
|
1349
|
+
if (genericWallet) {
|
|
1350
|
+
return {
|
|
1351
|
+
wallet_address: genericWallet,
|
|
1352
|
+
wallet_provider: asNonEmptyString2(process.env.AGENT_WALLET_PROVIDER)
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
const localLobsterWallet = getLobsterWalletFromLocalConfig2();
|
|
1356
|
+
if (localLobsterWallet) {
|
|
1357
|
+
return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
|
|
1358
|
+
}
|
|
1359
|
+
return {};
|
|
1360
|
+
}
|
|
1361
|
+
function checkWalletConfigured2() {
|
|
1362
|
+
const wallet = getWalletContext2();
|
|
1363
|
+
if (!wallet.wallet_address)
|
|
1364
|
+
return { configured: false };
|
|
1365
|
+
return {
|
|
1366
|
+
configured: true,
|
|
1367
|
+
provider: wallet.wallet_provider ?? "unknown"
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
var init_wallet2 = () => {};
|
|
1371
|
+
|
|
1201
1372
|
// ../../src/cli.ts
|
|
1202
1373
|
import { config as loadEnv } from "dotenv";
|
|
1203
1374
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -1207,9 +1378,9 @@ init_version();
|
|
|
1207
1378
|
init_cascade();
|
|
1208
1379
|
init_wallet();
|
|
1209
1380
|
init_telemetry_attribution();
|
|
1210
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as
|
|
1211
|
-
import { join as
|
|
1212
|
-
import { homedir as
|
|
1381
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
|
|
1382
|
+
import { join as join4 } from "path";
|
|
1383
|
+
import { homedir as homedir3, hostname } from "os";
|
|
1213
1384
|
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
1214
1385
|
import { createInterface } from "readline";
|
|
1215
1386
|
var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
@@ -1244,13 +1415,13 @@ function decodeBase64Json(value) {
|
|
|
1244
1415
|
function getConfigDir() {
|
|
1245
1416
|
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
1246
1417
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
1247
|
-
return PROFILE_NAME ?
|
|
1418
|
+
return PROFILE_NAME ? join4(homedir3(), ".unbrowse", "profiles", PROFILE_NAME) : join4(homedir3(), ".unbrowse");
|
|
1248
1419
|
}
|
|
1249
1420
|
function getConfigPath() {
|
|
1250
|
-
return
|
|
1421
|
+
return join4(getConfigDir(), "config.json");
|
|
1251
1422
|
}
|
|
1252
1423
|
function getInstallTelemetryPath() {
|
|
1253
|
-
return
|
|
1424
|
+
return join4(getConfigDir(), "install-state.json");
|
|
1254
1425
|
}
|
|
1255
1426
|
function getLandingToken() {
|
|
1256
1427
|
const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
|
|
@@ -1265,7 +1436,7 @@ function getActiveProfile() {
|
|
|
1265
1436
|
function loadConfig() {
|
|
1266
1437
|
try {
|
|
1267
1438
|
const configPath = getConfigPath();
|
|
1268
|
-
if (
|
|
1439
|
+
if (existsSync4(configPath)) {
|
|
1269
1440
|
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1270
1441
|
}
|
|
1271
1442
|
} catch {}
|
|
@@ -1274,14 +1445,14 @@ function loadConfig() {
|
|
|
1274
1445
|
function saveConfig(config) {
|
|
1275
1446
|
const configDir = getConfigDir();
|
|
1276
1447
|
const configPath = getConfigPath();
|
|
1277
|
-
if (!
|
|
1448
|
+
if (!existsSync4(configDir))
|
|
1278
1449
|
mkdirSync(configDir, { recursive: true });
|
|
1279
1450
|
writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
1280
1451
|
}
|
|
1281
1452
|
function loadInstallTelemetryState() {
|
|
1282
1453
|
try {
|
|
1283
1454
|
const statePath = getInstallTelemetryPath();
|
|
1284
|
-
if (
|
|
1455
|
+
if (existsSync4(statePath)) {
|
|
1285
1456
|
return JSON.parse(readFileSync3(statePath, "utf-8"));
|
|
1286
1457
|
}
|
|
1287
1458
|
} catch {}
|
|
@@ -1290,7 +1461,7 @@ function loadInstallTelemetryState() {
|
|
|
1290
1461
|
function saveInstallTelemetryState(state) {
|
|
1291
1462
|
const configDir = getConfigDir();
|
|
1292
1463
|
const statePath = getInstallTelemetryPath();
|
|
1293
|
-
if (!
|
|
1464
|
+
if (!existsSync4(configDir))
|
|
1294
1465
|
mkdirSync(configDir, { recursive: true });
|
|
1295
1466
|
writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1296
1467
|
}
|
|
@@ -1547,6 +1718,26 @@ async function apiRequest(method, path, body, opts) {
|
|
|
1547
1718
|
const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
|
|
1548
1719
|
const legacyPaymentTerms = res.headers.get("X-Payment-Required");
|
|
1549
1720
|
const terms = paymentRequired ? decodeBase64Json(paymentRequired) : legacyPaymentTerms ? JSON.parse(legacyPaymentTerms) : data.terms;
|
|
1721
|
+
try {
|
|
1722
|
+
const { isLobsterAvailable: isLobsterAvailable2, payAndRetry: payAndRetry2 } = await Promise.resolve().then(() => (init_lobster_pay(), exports_lobster_pay));
|
|
1723
|
+
if (isLobsterAvailable2()) {
|
|
1724
|
+
const fullUrl = `${API_URL}${path}`;
|
|
1725
|
+
const paidResult = await payAndRetry2(fullUrl, {
|
|
1726
|
+
body,
|
|
1727
|
+
headers: {
|
|
1728
|
+
"Content-Type": "application/json",
|
|
1729
|
+
"Accept-Encoding": "gzip, deflate",
|
|
1730
|
+
...releaseAttestationHeaders,
|
|
1731
|
+
...key ? { Authorization: `Bearer ${key}` } : {}
|
|
1732
|
+
}
|
|
1733
|
+
});
|
|
1734
|
+
if (paidResult) {
|
|
1735
|
+
return { data: paidResult.data, headers: new Headers };
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
} catch (payErr) {
|
|
1739
|
+
console.warn(`[x402] lobster pay-and-retry failed: ${payErr.message}`);
|
|
1740
|
+
}
|
|
1550
1741
|
const err = new Error(`Payment required: ${data.error ?? "This skill requires payment"}`);
|
|
1551
1742
|
err.x402 = true;
|
|
1552
1743
|
err.terms = terms;
|
|
@@ -2150,7 +2341,7 @@ async function restartServer(baseUrl, metaUrl) {
|
|
|
2150
2341
|
}
|
|
2151
2342
|
|
|
2152
2343
|
// ../../src/runtime/paths.ts
|
|
2153
|
-
import { existsSync as
|
|
2344
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
2154
2345
|
import path3 from "node:path";
|
|
2155
2346
|
import { createRequire as createRequire3 } from "node:module";
|
|
2156
2347
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
@@ -2170,7 +2361,7 @@ function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
|
|
|
2170
2361
|
const req = createRequire3(metaUrl);
|
|
2171
2362
|
const tsxPkg = req.resolve("tsx/package.json");
|
|
2172
2363
|
const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
|
|
2173
|
-
if (
|
|
2364
|
+
if (existsSync6(tsxLoader))
|
|
2174
2365
|
return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
|
|
2175
2366
|
} catch {}
|
|
2176
2367
|
return ["--import", "tsx", entrypoint];
|
|
@@ -2200,8 +2391,8 @@ init_publish();
|
|
|
2200
2391
|
init_settings();
|
|
2201
2392
|
init_graph();
|
|
2202
2393
|
init_schema_review();
|
|
2203
|
-
import { join as
|
|
2204
|
-
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
2394
|
+
import { join as join9 } from "node:path";
|
|
2395
|
+
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
2205
2396
|
var indexInFlight2 = new Map;
|
|
2206
2397
|
var pendingIndexJobs2 = new Map;
|
|
2207
2398
|
async function drainPendingIndexJobs() {
|
|
@@ -2238,14 +2429,14 @@ init_paths();
|
|
|
2238
2429
|
init_client2();
|
|
2239
2430
|
init_logger();
|
|
2240
2431
|
init_wallet();
|
|
2241
|
-
import { execFileSync as
|
|
2242
|
-
import { existsSync as
|
|
2432
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2433
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2243
2434
|
import os4 from "node:os";
|
|
2244
2435
|
import path7 from "node:path";
|
|
2245
2436
|
|
|
2246
2437
|
// ../../src/runtime/update-hints.ts
|
|
2247
2438
|
init_paths();
|
|
2248
|
-
import { existsSync as
|
|
2439
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2249
2440
|
import os3 from "node:os";
|
|
2250
2441
|
import path6 from "node:path";
|
|
2251
2442
|
var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -2259,7 +2450,7 @@ function getConfigDir2() {
|
|
|
2259
2450
|
return path6.join(getHomeDir(), ".unbrowse");
|
|
2260
2451
|
}
|
|
2261
2452
|
function ensureDir2(dir) {
|
|
2262
|
-
if (!
|
|
2453
|
+
if (!existsSync9(dir))
|
|
2263
2454
|
mkdirSync5(dir, { recursive: true });
|
|
2264
2455
|
return dir;
|
|
2265
2456
|
}
|
|
@@ -2282,7 +2473,7 @@ function detectRepoRoot(start2) {
|
|
|
2282
2473
|
let dir = path6.resolve(start2);
|
|
2283
2474
|
const root = path6.parse(dir).root;
|
|
2284
2475
|
while (dir !== root) {
|
|
2285
|
-
if (
|
|
2476
|
+
if (existsSync9(path6.join(dir, ".git")))
|
|
2286
2477
|
return dir;
|
|
2287
2478
|
dir = path6.dirname(dir);
|
|
2288
2479
|
}
|
|
@@ -2361,12 +2552,12 @@ codex_hooks = true
|
|
|
2361
2552
|
}
|
|
2362
2553
|
function writeCodexHook(metaUrl) {
|
|
2363
2554
|
const configPath = getCodexConfigPath();
|
|
2364
|
-
if (!
|
|
2555
|
+
if (!existsSync9(path6.dirname(configPath))) {
|
|
2365
2556
|
return { host: "codex", action: "not-detected", config_file: configPath };
|
|
2366
2557
|
}
|
|
2367
2558
|
try {
|
|
2368
2559
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
2369
|
-
const fileExistsBefore =
|
|
2560
|
+
const fileExistsBefore = existsSync9(configPath);
|
|
2370
2561
|
let content = fileExistsBefore ? readFileSync6(configPath, "utf8") : "";
|
|
2371
2562
|
const previous = content;
|
|
2372
2563
|
content = ensureCodexHooksFeature(content);
|
|
@@ -2401,13 +2592,13 @@ command = ${JSON.stringify(command)}
|
|
|
2401
2592
|
}
|
|
2402
2593
|
function writeClaudeHook(metaUrl) {
|
|
2403
2594
|
const settingsPath = getClaudeSettingsPath();
|
|
2404
|
-
if (!
|
|
2595
|
+
if (!existsSync9(path6.dirname(settingsPath))) {
|
|
2405
2596
|
return { host: "claude", action: "not-detected", config_file: settingsPath };
|
|
2406
2597
|
}
|
|
2407
2598
|
try {
|
|
2408
2599
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
2409
2600
|
const command = `node "${hookScript}"`;
|
|
2410
|
-
const fileExistsBefore =
|
|
2601
|
+
const fileExistsBefore = existsSync9(settingsPath);
|
|
2411
2602
|
const settings = readJsonFile(settingsPath) ?? {};
|
|
2412
2603
|
settings.hooks ??= {};
|
|
2413
2604
|
settings.hooks.SessionStart ??= [];
|
|
@@ -2445,7 +2636,7 @@ function configureUpdateHintHooks(metaUrl, install) {
|
|
|
2445
2636
|
function hasBinary(name) {
|
|
2446
2637
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
2447
2638
|
try {
|
|
2448
|
-
|
|
2639
|
+
execFileSync3(checker, [name], { stdio: "ignore" });
|
|
2449
2640
|
return true;
|
|
2450
2641
|
} catch {
|
|
2451
2642
|
return false;
|
|
@@ -2472,7 +2663,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
|
|
|
2472
2663
|
return path7.join(cwd, ".opencode", "commands");
|
|
2473
2664
|
}
|
|
2474
2665
|
function detectOpenCode(cwd) {
|
|
2475
|
-
return hasBinary("opencode") ||
|
|
2666
|
+
return hasBinary("opencode") || existsSync10(path7.join(resolveConfigHome(), "opencode")) || existsSync10(path7.join(cwd, ".opencode"));
|
|
2476
2667
|
}
|
|
2477
2668
|
function renderOpenCodeCommand() {
|
|
2478
2669
|
return `---
|
|
@@ -2500,11 +2691,11 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
2500
2691
|
if (scope === "auto" && !detected) {
|
|
2501
2692
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
2502
2693
|
}
|
|
2503
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" :
|
|
2694
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync10(path7.join(cwd, ".opencode")) ? "project" : "global";
|
|
2504
2695
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
2505
2696
|
const commandFile = path7.join(ensureDir(commandsDir), "unbrowse.md");
|
|
2506
2697
|
const content = renderOpenCodeCommand();
|
|
2507
|
-
const action2 =
|
|
2698
|
+
const action2 = existsSync10(commandFile) ? "updated" : "installed";
|
|
2508
2699
|
mkdirSync6(path7.dirname(commandFile), { recursive: true });
|
|
2509
2700
|
writeFileSync5(commandFile, content);
|
|
2510
2701
|
return {
|
|
@@ -2516,10 +2707,10 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
2516
2707
|
}
|
|
2517
2708
|
async function ensureBrowserEngineInstalled() {
|
|
2518
2709
|
const binary = findKuriBinary();
|
|
2519
|
-
if (
|
|
2710
|
+
if (existsSync10(binary)) {
|
|
2520
2711
|
return { installed: true, action: "already-installed" };
|
|
2521
2712
|
}
|
|
2522
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) =>
|
|
2713
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync10(path7.join(candidate, "build.zig")));
|
|
2523
2714
|
if (!sourceDir) {
|
|
2524
2715
|
return {
|
|
2525
2716
|
installed: false,
|
|
@@ -2535,13 +2726,13 @@ async function ensureBrowserEngineInstalled() {
|
|
|
2535
2726
|
};
|
|
2536
2727
|
}
|
|
2537
2728
|
try {
|
|
2538
|
-
|
|
2729
|
+
execFileSync3("zig", ["build", "-Doptimize=ReleaseFast"], {
|
|
2539
2730
|
cwd: sourceDir,
|
|
2540
2731
|
stdio: "inherit",
|
|
2541
2732
|
timeout: 300000
|
|
2542
2733
|
});
|
|
2543
2734
|
const builtBinary = findKuriBinary();
|
|
2544
|
-
if (
|
|
2735
|
+
if (existsSync10(builtBinary)) {
|
|
2545
2736
|
return {
|
|
2546
2737
|
installed: true,
|
|
2547
2738
|
action: "installed",
|
|
@@ -2566,11 +2757,11 @@ async function runSetup(options) {
|
|
|
2566
2757
|
const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
|
|
2567
2758
|
const walletCheck = checkWalletConfigured();
|
|
2568
2759
|
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
2569
|
-
const lobsterInstalled = hasBinary("lobstercash") ||
|
|
2760
|
+
const lobsterInstalled = hasBinary("lobstercash") || existsSync10(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
2570
2761
|
if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
|
|
2571
2762
|
console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
|
|
2572
2763
|
try {
|
|
2573
|
-
|
|
2764
|
+
execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
2574
2765
|
stdio: "inherit",
|
|
2575
2766
|
timeout: 60000
|
|
2576
2767
|
});
|
|
@@ -2606,7 +2797,7 @@ async function runSetup(options) {
|
|
|
2606
2797
|
|
|
2607
2798
|
// ../../src/runtime/update-hints.ts
|
|
2608
2799
|
init_paths();
|
|
2609
|
-
import { existsSync as
|
|
2800
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2610
2801
|
import os5 from "node:os";
|
|
2611
2802
|
import path8 from "node:path";
|
|
2612
2803
|
var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
|
|
@@ -2620,7 +2811,7 @@ function getConfigDir3() {
|
|
|
2620
2811
|
return path8.join(getHomeDir2(), ".unbrowse");
|
|
2621
2812
|
}
|
|
2622
2813
|
function ensureDir3(dir) {
|
|
2623
|
-
if (!
|
|
2814
|
+
if (!existsSync11(dir))
|
|
2624
2815
|
mkdirSync7(dir, { recursive: true });
|
|
2625
2816
|
return dir;
|
|
2626
2817
|
}
|
|
@@ -2646,7 +2837,7 @@ function detectRepoRoot2(start2) {
|
|
|
2646
2837
|
let dir = path8.resolve(start2);
|
|
2647
2838
|
const root = path8.parse(dir).root;
|
|
2648
2839
|
while (dir !== root) {
|
|
2649
|
-
if (
|
|
2840
|
+
if (existsSync11(path8.join(dir, ".git")))
|
|
2650
2841
|
return dir;
|
|
2651
2842
|
dir = path8.dirname(dir);
|
|
2652
2843
|
}
|
|
@@ -2780,6 +2971,7 @@ loadEnv({ quiet: true });
|
|
|
2780
2971
|
loadEnv({ path: ".env.runtime", quiet: true });
|
|
2781
2972
|
var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
|
|
2782
2973
|
var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
|
|
2974
|
+
var walletNudgeShown = false;
|
|
2783
2975
|
function parseArgs(argv) {
|
|
2784
2976
|
const raw = argv.slice(2);
|
|
2785
2977
|
const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : "help";
|
|
@@ -3089,6 +3281,16 @@ async function cmdResolve(flags) {
|
|
|
3089
3281
|
emitNextActionSummary(result);
|
|
3090
3282
|
const skill = result.skill;
|
|
3091
3283
|
const trace = result.trace;
|
|
3284
|
+
if (trace?.success && !walletNudgeShown) {
|
|
3285
|
+
try {
|
|
3286
|
+
const { checkWalletConfigured: checkWalletConfigured3 } = await Promise.resolve().then(() => (init_wallet2(), exports_wallet));
|
|
3287
|
+
const wallet = checkWalletConfigured3();
|
|
3288
|
+
if (!wallet.configured) {
|
|
3289
|
+
info("You're indexing routes but have no payout wallet. Run: npx @crossmint/lobster-cli setup");
|
|
3290
|
+
walletNudgeShown = true;
|
|
3291
|
+
}
|
|
3292
|
+
} catch (_e) {}
|
|
3293
|
+
}
|
|
3092
3294
|
if (skill?.skill_id && trace) {
|
|
3093
3295
|
result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
|
|
3094
3296
|
}
|
|
@@ -3472,6 +3674,16 @@ async function cmdSetup(flags) {
|
|
|
3472
3674
|
info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
|
|
3473
3675
|
}
|
|
3474
3676
|
}
|
|
3677
|
+
if (report.wallet.configured) {
|
|
3678
|
+
info(`Wallet configured (${report.wallet.provider}): ${report.wallet.wallet_address ?? "linked"}`);
|
|
3679
|
+
} else if (report.wallet.lobster_installed) {
|
|
3680
|
+
info("Wallet not paired \u2014 your indexed routes won't earn payouts.");
|
|
3681
|
+
info("Run: npx @crossmint/lobster-cli setup");
|
|
3682
|
+
} else {
|
|
3683
|
+
info("No wallet configured \u2014 you're indexing routes for free.");
|
|
3684
|
+
info("Set up a wallet so you earn when agents use your routes:");
|
|
3685
|
+
info(" npx @crossmint/lobster-cli setup");
|
|
3686
|
+
}
|
|
3475
3687
|
await recordInstallTelemetryEvent("setup", {
|
|
3476
3688
|
hostType,
|
|
3477
3689
|
status: report.browser_engine.action === "failed" ? "failed" : "installed",
|
package/dist/mcp.js
CHANGED
|
@@ -108,11 +108,11 @@ import { dirname, join, parse } from "path";
|
|
|
108
108
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
109
109
|
|
|
110
110
|
// ../../src/build-info.generated.ts
|
|
111
|
-
var BUILD_RELEASE_VERSION = "3.0
|
|
112
|
-
var BUILD_GIT_SHA = "
|
|
111
|
+
var BUILD_RELEASE_VERSION = "3.1.0";
|
|
112
|
+
var BUILD_GIT_SHA = "c9f9ce0e5cb1";
|
|
113
113
|
var BUILD_CODE_HASH = "1488fc1d92b7";
|
|
114
|
-
var BUILD_RELEASE_MANIFEST_BASE64 = "
|
|
115
|
-
var BUILD_RELEASE_MANIFEST_SIGNATURE = "
|
|
114
|
+
var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAiLCJnaXRfc2hhIjoiYzlmOWNlMGU1Y2IxIiwiY29kZV9oYXNoIjoiMTQ4OGZjMWQ5MmI3IiwidHJhY2VfdmVyc2lvbiI6IjE0ODhmYzFkOTJiN0BjOWY5Y2UwZTVjYjEiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTA1VDA2OjUwOjA4LjQzNVoifQ";
|
|
115
|
+
var BUILD_RELEASE_MANIFEST_SIGNATURE = "_2pdxyoIEYkKUHO0nXaXTubP5SrZCa-8CqStcPBsXxw";
|
|
116
116
|
var BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
|
|
117
117
|
|
|
118
118
|
// ../../src/version.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export const BUILD_RELEASE_VERSION = "3.0
|
|
2
|
-
export const BUILD_GIT_SHA = "
|
|
1
|
+
export const BUILD_RELEASE_VERSION = "3.1.0";
|
|
2
|
+
export const BUILD_GIT_SHA = "c9f9ce0e5cb1";
|
|
3
3
|
export const BUILD_CODE_HASH = "1488fc1d92b7";
|
|
4
|
-
export const BUILD_RELEASE_MANIFEST_BASE64 = "
|
|
5
|
-
export const BUILD_RELEASE_MANIFEST_SIGNATURE = "
|
|
4
|
+
export const BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAiLCJnaXRfc2hhIjoiYzlmOWNlMGU1Y2IxIiwiY29kZV9oYXNoIjoiMTQ4OGZjMWQ5MmI3IiwidHJhY2VfdmVyc2lvbiI6IjE0ODhmYzFkOTJiN0BjOWY5Y2UwZTVjYjEiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTA1VDA2OjUwOjA4LjQzNVoifQ";
|
|
5
|
+
export const BUILD_RELEASE_MANIFEST_SIGNATURE = "_2pdxyoIEYkKUHO0nXaXTubP5SrZCa-8CqStcPBsXxw";
|
|
6
6
|
export const BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
|
package/runtime-src/cli.ts
CHANGED
|
@@ -30,6 +30,7 @@ loadEnv({ path: ".env.runtime", quiet: true });
|
|
|
30
30
|
|
|
31
31
|
const BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
|
|
32
32
|
const CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
|
|
33
|
+
let walletNudgeShown = false;
|
|
33
34
|
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
35
36
|
// Arg parser
|
|
@@ -374,6 +375,19 @@ async function cmdResolve(flags: Record<string, string | boolean>): Promise<void
|
|
|
374
375
|
|
|
375
376
|
const skill = result.skill as Record<string, unknown> | undefined;
|
|
376
377
|
const trace = result.trace as Record<string, unknown> | undefined;
|
|
378
|
+
|
|
379
|
+
// Nudge wallet setup after successful resolve that indexed routes
|
|
380
|
+
if (trace?.success && !walletNudgeShown) {
|
|
381
|
+
try {
|
|
382
|
+
const { checkWalletConfigured } = await import("./payments/wallet.js");
|
|
383
|
+
const wallet = checkWalletConfigured();
|
|
384
|
+
if (!wallet.configured) {
|
|
385
|
+
info("You're indexing routes but have no payout wallet. Run: npx @crossmint/lobster-cli setup");
|
|
386
|
+
walletNudgeShown = true;
|
|
387
|
+
}
|
|
388
|
+
} catch (_e) { /* non-fatal */ }
|
|
389
|
+
}
|
|
390
|
+
|
|
377
391
|
if (skill?.skill_id && trace) {
|
|
378
392
|
(result as Record<string, unknown>)._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
|
|
379
393
|
}
|
|
@@ -812,6 +826,18 @@ async function cmdSetup(flags: Record<string, string | boolean>): Promise<void>
|
|
|
812
826
|
}
|
|
813
827
|
}
|
|
814
828
|
|
|
829
|
+
// Wallet status — tell the user if they're missing payout config
|
|
830
|
+
if (report.wallet.configured) {
|
|
831
|
+
info(`Wallet configured (${report.wallet.provider}): ${(report.wallet as Record<string, unknown>).wallet_address ?? "linked"}`);
|
|
832
|
+
} else if ((report.wallet as Record<string, unknown>).lobster_installed) {
|
|
833
|
+
info("Wallet not paired — your indexed routes won't earn payouts.");
|
|
834
|
+
info("Run: npx @crossmint/lobster-cli setup");
|
|
835
|
+
} else {
|
|
836
|
+
info("No wallet configured — you're indexing routes for free.");
|
|
837
|
+
info("Set up a wallet so you earn when agents use your routes:");
|
|
838
|
+
info(" npx @crossmint/lobster-cli setup");
|
|
839
|
+
}
|
|
840
|
+
|
|
815
841
|
await recordInstallTelemetryEvent("setup", {
|
|
816
842
|
hostType,
|
|
817
843
|
status: report.browser_engine.action === "failed" ? "failed" : "installed",
|
|
@@ -497,7 +497,8 @@ async function apiRequest<T = unknown>(
|
|
|
497
497
|
throw new Error("ToS update required. Restart unbrowse to accept new terms.");
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
|
|
500
|
+
|
|
501
|
+
// Handle x402 payment required — attempt lobster pay-and-retry before surfacing
|
|
501
502
|
if (res.status === 402) {
|
|
502
503
|
const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
|
|
503
504
|
const legacyPaymentTerms = res.headers.get("X-Payment-Required");
|
|
@@ -506,6 +507,29 @@ async function apiRequest<T = unknown>(
|
|
|
506
507
|
: legacyPaymentTerms
|
|
507
508
|
? JSON.parse(legacyPaymentTerms)
|
|
508
509
|
: (data as Record<string, unknown>).terms;
|
|
510
|
+
|
|
511
|
+
// Try lobster.cash automatic payment before throwing
|
|
512
|
+
try {
|
|
513
|
+
const { isLobsterAvailable, payAndRetry } = await import("../payments/lobster-pay.js");
|
|
514
|
+
if (isLobsterAvailable()) {
|
|
515
|
+
const fullUrl = `${API_URL}${path}`;
|
|
516
|
+
const paidResult = await payAndRetry<T>(fullUrl, {
|
|
517
|
+
body,
|
|
518
|
+
headers: {
|
|
519
|
+
"Content-Type": "application/json",
|
|
520
|
+
"Accept-Encoding": "gzip, deflate",
|
|
521
|
+
...releaseAttestationHeaders,
|
|
522
|
+
...(key ? { Authorization: `Bearer ${key}` } : {}),
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
if (paidResult) {
|
|
526
|
+
return { data: paidResult.data, headers: new Headers() };
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} catch (payErr) {
|
|
530
|
+
console.warn(`[x402] lobster pay-and-retry failed: ${(payErr as Error).message}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
509
533
|
const err = new Error(`Payment required: ${(data as Record<string, unknown>).error ?? "This skill requires payment"}`);
|
|
510
534
|
(err as Error & { x402: boolean; terms: unknown; status: number }).x402 = true;
|
|
511
535
|
(err as Error & { terms: unknown }).terms = terms;
|
|
@@ -1882,28 +1882,40 @@ export async function executeEndpoint(
|
|
|
1882
1882
|
wallet_configured: !!wallet.wallet_address,
|
|
1883
1883
|
});
|
|
1884
1884
|
if (gate.status === "payment_required" || gate.status === "wallet_not_configured" || gate.status === "insufficient_balance") {
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
trace
|
|
1897
|
-
|
|
1885
|
+
// If lobster wallet is available, let execution proceed —
|
|
1886
|
+
// the client-level apiRequest will handle 402 pay-and-retry automatically.
|
|
1887
|
+
let lobsterAvailable = false;
|
|
1888
|
+
try {
|
|
1889
|
+
const { isLobsterAvailable } = await import("../payments/lobster-pay.js");
|
|
1890
|
+
lobsterAvailable = isLobsterAvailable();
|
|
1891
|
+
} catch {}
|
|
1892
|
+
|
|
1893
|
+
if (lobsterAvailable && gate.status === "payment_required") {
|
|
1894
|
+
console.log(`[payment] ${skill.skill_id}: lobster available — proceeding with auto-pay`);
|
|
1895
|
+
} else {
|
|
1896
|
+
const trace: ExecutionTrace = stampTrace({
|
|
1897
|
+
trace_id: nanoid(),
|
|
1898
|
+
skill_id: skill.skill_id,
|
|
1899
|
+
endpoint_id: endpoint.endpoint_id,
|
|
1900
|
+
started_at: new Date().toISOString(),
|
|
1901
|
+
completed_at: new Date().toISOString(),
|
|
1902
|
+
success: false,
|
|
1903
|
+
status_code: 402,
|
|
1898
1904
|
error: "payment_required",
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1905
|
+
});
|
|
1906
|
+
return {
|
|
1907
|
+
trace,
|
|
1908
|
+
result: {
|
|
1909
|
+
error: "payment_required",
|
|
1910
|
+
price_usd: gate.requirement?.amount,
|
|
1911
|
+
payment_status: gate.status,
|
|
1912
|
+
message: gate.message,
|
|
1913
|
+
wallet_provider: wallet.wallet_provider ?? "lobster.cash",
|
|
1914
|
+
wallet_address: wallet.wallet_address,
|
|
1915
|
+
indexing_fallback_available: true,
|
|
1916
|
+
},
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1907
1919
|
}
|
|
1908
1920
|
}
|
|
1909
1921
|
|
|
@@ -216,7 +216,10 @@ function falseyEnv(value: string | undefined): boolean {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
export function resolveKuriLaunchConfig(env: NodeJS.ProcessEnv = process.env): KuriLaunchConfig {
|
|
219
|
-
const
|
|
219
|
+
const explicitHeadless = env.KURI_HEADLESS ?? env.HEADLESS;
|
|
220
|
+
const headless = explicitHeadless !== undefined
|
|
221
|
+
? envFlag(explicitHeadless)
|
|
222
|
+
: (process.platform === "linux" && !env.DISPLAY); // auto-headless when no display on Linux
|
|
220
223
|
const cleanRoom = envFlag(env.UNBROWSE_LOCAL_ONLY) || envFlag(env.KURI_CLEAN_ROOM);
|
|
221
224
|
const browserCookieOptOut = falseyEnv(env.UNBROWSE_IMPORT_BROWSER_COOKIES);
|
|
222
225
|
const explicitAttach = envFlag(env.KURI_ATTACH_EXISTING_CHROME ?? env.UNBROWSE_ATTACH_EXISTING_CHROME);
|
|
@@ -240,6 +243,7 @@ function currentBundledKuriTarget(): string | null {
|
|
|
240
243
|
if (process.platform === "darwin" && process.arch === "x64") return "darwin-x64";
|
|
241
244
|
if (process.platform === "linux" && process.arch === "arm64") return "linux-arm64";
|
|
242
245
|
if (process.platform === "linux" && process.arch === "x64") return "linux-x64";
|
|
246
|
+
if (process.platform === "win32" && process.arch === "x64") return "win-x64";
|
|
243
247
|
return null;
|
|
244
248
|
}
|
|
245
249
|
|
|
@@ -3015,7 +3015,11 @@ export async function resolveAndExecute(
|
|
|
3015
3015
|
console.log(`[prefetch] error: ${(prefetchErr as Error).message}`);
|
|
3016
3016
|
}
|
|
3017
3017
|
// --- Payment gate: only for marketplace-sourced paid skills ---
|
|
3018
|
-
|
|
3018
|
+
const dynamicPrice = source === "marketplace"
|
|
3019
|
+
? (skill.base_price_usd ?? await (await import("../payments/index.js")).fetchDynamicPrice(skill.skill_id))
|
|
3020
|
+
: null;
|
|
3021
|
+
const effectivePrice = typeof dynamicPrice === "string" ? parseFloat(dynamicPrice) : (dynamicPrice ?? 0);
|
|
3022
|
+
if (source === "marketplace" && effectivePrice > 0) {
|
|
3019
3023
|
try {
|
|
3020
3024
|
const walletCheck = checkWalletConfigured();
|
|
3021
3025
|
const wallet = getLocalWalletContext();
|
|
@@ -3023,7 +3027,7 @@ export async function resolveAndExecute(
|
|
|
3023
3027
|
skill.skill_id,
|
|
3024
3028
|
candidate.endpoint.endpoint_id,
|
|
3025
3029
|
{
|
|
3026
|
-
price_usd: String(
|
|
3030
|
+
price_usd: String(effectivePrice),
|
|
3027
3031
|
wallet_configured: walletCheck.configured,
|
|
3028
3032
|
},
|
|
3029
3033
|
);
|
|
@@ -3038,7 +3042,7 @@ export async function resolveAndExecute(
|
|
|
3038
3042
|
return {
|
|
3039
3043
|
result: {
|
|
3040
3044
|
error: "payment_required",
|
|
3041
|
-
price_usd:
|
|
3045
|
+
price_usd: effectivePrice,
|
|
3042
3046
|
payment_status: paymentResult.status,
|
|
3043
3047
|
message: paymentResult.message,
|
|
3044
3048
|
next_step: paymentResult.next_step,
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lobster.cash x402 payment bridge.
|
|
3
|
+
*
|
|
4
|
+
* Handles the pay-and-retry cycle:
|
|
5
|
+
* 1. Receives x402 payment terms from a 402 response
|
|
6
|
+
* 2. Calls `lobstercash x402 fetch <url>` to pay + retry
|
|
7
|
+
* 3. Returns the paid response body
|
|
8
|
+
*
|
|
9
|
+
* Delegation boundary:
|
|
10
|
+
* - Unbrowse owns: detecting 402, passing the URL, using the result
|
|
11
|
+
* - Lobster owns: wallet signing, transaction broadcast, proof construction
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
|
|
19
|
+
const LOBSTER_PAY_TIMEOUT_MS = 30_000;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the lobster CLI command. Prefers the direct binary if
|
|
23
|
+
* installed globally, otherwise falls back to npx.
|
|
24
|
+
*/
|
|
25
|
+
function getLobsterCommand(): { cmd: string; prefix: string[] } | null {
|
|
26
|
+
// 1. Direct binary in PATH
|
|
27
|
+
try {
|
|
28
|
+
execFileSync("lobstercash", ["--version"], { stdio: "ignore", timeout: 3_000 });
|
|
29
|
+
return { cmd: "lobstercash", prefix: [] };
|
|
30
|
+
} catch (_e) { /* not in PATH */ }
|
|
31
|
+
|
|
32
|
+
// 2. Check npm global bin (often not in agent PATH)
|
|
33
|
+
try {
|
|
34
|
+
const npmPrefix = execFileSync("npm", ["config", "get", "prefix"], { encoding: "utf8", timeout: 5_000 }).trim();
|
|
35
|
+
const lobsterPath = join(npmPrefix, "bin", "lobstercash");
|
|
36
|
+
if (existsSync(lobsterPath)) {
|
|
37
|
+
execFileSync(lobsterPath, ["--version"], { stdio: "ignore", timeout: 3_000 });
|
|
38
|
+
return { cmd: lobsterPath, prefix: [] };
|
|
39
|
+
}
|
|
40
|
+
} catch (_e) { /* npm not available */ }
|
|
41
|
+
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let cachedCommand: { cmd: string; prefix: string[] } | null | undefined = undefined;
|
|
46
|
+
function lobsterCmd(): { cmd: string; prefix: string[] } | null {
|
|
47
|
+
if (cachedCommand === undefined) cachedCommand = getLobsterCommand();
|
|
48
|
+
return cachedCommand;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LobsterPayResult {
|
|
52
|
+
success: boolean;
|
|
53
|
+
body: string;
|
|
54
|
+
statusCode?: number;
|
|
55
|
+
error?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if the lobster CLI is available and wallet is configured.
|
|
60
|
+
*/
|
|
61
|
+
export function isLobsterAvailable(): boolean {
|
|
62
|
+
const agentsPath = join(process.env.HOME || homedir(), ".lobster", "agents.json");
|
|
63
|
+
return existsSync(agentsPath);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Pay for an x402-gated URL via lobster.cash and return the response.
|
|
68
|
+
*
|
|
69
|
+
* Shells out to `lobstercash x402 fetch <url>` which handles:
|
|
70
|
+
* - Reading 402 + payment terms
|
|
71
|
+
* - Signing + broadcasting the USDC transfer
|
|
72
|
+
* - Retrying with PAYMENT-SIGNATURE header
|
|
73
|
+
* - Returning the paid response
|
|
74
|
+
*/
|
|
75
|
+
export function lobsterX402Fetch(
|
|
76
|
+
url: string,
|
|
77
|
+
options?: {
|
|
78
|
+
jsonBody?: string;
|
|
79
|
+
headers?: Record<string, string>;
|
|
80
|
+
timeoutMs?: number;
|
|
81
|
+
},
|
|
82
|
+
): Promise<LobsterPayResult> {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
const resolved = lobsterCmd();
|
|
85
|
+
if (!resolved) {
|
|
86
|
+
resolve({ success: false, body: "", error: "lobstercash CLI not in PATH" });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const { cmd, prefix } = resolved;
|
|
90
|
+
const args = [...prefix, "x402", "fetch", url, "--debug"];
|
|
91
|
+
|
|
92
|
+
if (options?.jsonBody) {
|
|
93
|
+
args.push("--json", options.jsonBody);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (options?.headers) {
|
|
97
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
98
|
+
args.push("--header", `${key}:${value}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const timeout = options?.timeoutMs ?? LOBSTER_PAY_TIMEOUT_MS;
|
|
103
|
+
args.push("--timeout", String(timeout));
|
|
104
|
+
|
|
105
|
+
execFile(cmd, args, { timeout: timeout + 5_000, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
106
|
+
if (err) {
|
|
107
|
+
const msg = stderr?.trim() || err.message;
|
|
108
|
+
console.warn(`[lobster-pay] x402 fetch failed: ${msg}`);
|
|
109
|
+
resolve({ success: false, body: "", error: msg });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (stderr) {
|
|
114
|
+
for (const line of stderr.split("\n").filter(Boolean)) {
|
|
115
|
+
console.log(`[lobster-pay] ${line}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse status from stdout header line: "Status: 200"
|
|
120
|
+
const statusMatch = stdout.match(/^Status:\s*(\d+)/m);
|
|
121
|
+
const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : undefined;
|
|
122
|
+
|
|
123
|
+
if (statusCode && statusCode >= 400) {
|
|
124
|
+
resolve({ success: false, body: stdout, statusCode, error: `HTTP ${statusCode}` });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
resolve({ success: true, body: stdout, statusCode });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Pay for an x402-gated API request and return the parsed JSON response.
|
|
135
|
+
*
|
|
136
|
+
* This is the high-level entry point for the pay-and-retry loop.
|
|
137
|
+
* Returns null if payment fails or lobster is unavailable.
|
|
138
|
+
*/
|
|
139
|
+
export async function payAndRetry<T = unknown>(
|
|
140
|
+
fullUrl: string,
|
|
141
|
+
options?: {
|
|
142
|
+
body?: unknown;
|
|
143
|
+
headers?: Record<string, string>;
|
|
144
|
+
},
|
|
145
|
+
): Promise<{ data: T; paid: true } | null> {
|
|
146
|
+
if (!isLobsterAvailable()) {
|
|
147
|
+
console.log("[lobster-pay] lobster.cash not configured — skipping payment");
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log(`[lobster-pay] attempting x402 payment for ${fullUrl}`);
|
|
152
|
+
|
|
153
|
+
const result = await lobsterX402Fetch(fullUrl, {
|
|
154
|
+
jsonBody: options?.body ? JSON.stringify(options.body) : undefined,
|
|
155
|
+
headers: options?.headers,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!result.success) {
|
|
159
|
+
console.warn(`[lobster-pay] payment failed: ${result.error}`);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
// lobstercash x402 fetch outputs header lines before the JSON body:
|
|
164
|
+
// x402 FETCH <url>
|
|
165
|
+
// Status: 200
|
|
166
|
+
// Content-Type: application/json
|
|
167
|
+
// <blank line>
|
|
168
|
+
// {"actual":"json",...}
|
|
169
|
+
// Extract the JSON portion by finding the first '{' or '['
|
|
170
|
+
const raw = result.body;
|
|
171
|
+
const jsonStart = Math.min(
|
|
172
|
+
...[raw.indexOf("{"), raw.indexOf("[")].filter((i) => i >= 0),
|
|
173
|
+
);
|
|
174
|
+
const jsonStr = jsonStart >= 0 ? raw.slice(jsonStart) : raw;
|
|
175
|
+
const data = JSON.parse(jsonStr) as T;
|
|
176
|
+
console.log("[lobster-pay] payment successful — got paid response");
|
|
177
|
+
return { data, paid: true };
|
|
178
|
+
} catch (_e) {
|
|
179
|
+
console.warn("[lobster-pay] paid response was not valid JSON");
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repo_url": "https://github.com/justrach/kuri.git",
|
|
3
3
|
"branch": "adding-extensions",
|
|
4
|
-
"source_sha": "
|
|
5
|
-
"built_at": "2026-04-
|
|
4
|
+
"source_sha": "08eecbe3740f046a46f656eed7ebfc66c1bad9bb",
|
|
5
|
+
"built_at": "2026-04-05T06:43:57.212Z",
|
|
6
6
|
"binaries": {
|
|
7
7
|
"darwin-arm64": {
|
|
8
8
|
"zig_target": "aarch64-macos",
|
|
9
|
-
"sha256": "
|
|
9
|
+
"sha256": "1553633e722d18059dedffa8a52d55ed6c052e4961fd2753ee0b62be60b241bf"
|
|
10
10
|
},
|
|
11
11
|
"darwin-x64": {
|
|
12
12
|
"zig_target": "x86_64-macos",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "b5eb07e631c6ddad64019c8d0c86c32cb76a74ff0791ac5611a3aa3550767ec8"
|
|
14
14
|
},
|
|
15
15
|
"linux-arm64": {
|
|
16
16
|
"zig_target": "aarch64-linux",
|
|
17
|
-
"sha256": "
|
|
17
|
+
"sha256": "ea88a26f7b335d5842b0c1d83bfa4066bed0a119284560f6bd3833f1d240cce2"
|
|
18
18
|
},
|
|
19
19
|
"linux-x64": {
|
|
20
20
|
"zig_target": "x86_64-linux",
|
|
21
|
-
"sha256": "
|
|
21
|
+
"sha256": "175a7c59e458e952a26974f0fb5c2ce374e56f2c4c352903b481b5aa5a16978f"
|
|
22
|
+
},
|
|
23
|
+
"win-x64": {
|
|
24
|
+
"zig_target": "x86_64-windows",
|
|
25
|
+
"sha256": "176291ad9827a183ba7322ddb56cc1fa5edc7c214a264ecdf8a1d5d18366d686"
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
}
|
|
Binary file
|