unbrowse 2.12.0 → 2.12.2
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/README.md +5 -56
- package/bin/unbrowse-wrapper.mjs +0 -0
- package/dist/cli.js +232 -471
- package/package.json +1 -1
- package/runtime-src/analytics-session.ts +5 -27
- package/runtime-src/api/browse-session.ts +3 -1
- package/runtime-src/api/browse-submit.ts +27 -34
- package/runtime-src/api/routes.ts +103 -145
- package/runtime-src/cli.ts +8 -175
- package/runtime-src/client/index.ts +44 -15
- package/runtime-src/execution/index.ts +1 -6
- package/runtime-src/indexer/index.ts +2 -4
- package/runtime-src/kuri/client.ts +35 -2
- package/runtime-src/orchestrator/index.ts +2 -2
- package/runtime-src/reverse-engineer/index.ts +0 -8
- package/runtime-src/runtime/setup.ts +7 -7
- package/runtime-src/server.ts +4 -3
- package/dist/mcp.js +0 -20392
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/mcp.ts +0 -1065
package/dist/cli.js
CHANGED
|
@@ -680,6 +680,31 @@ async function waitForTabRegistration(tabId, timeoutMs = 2000) {
|
|
|
680
680
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
681
681
|
}
|
|
682
682
|
}
|
|
683
|
+
async function createTabViaChromeCdp(url = "about:blank") {
|
|
684
|
+
if (!kuriCdpPort)
|
|
685
|
+
return "";
|
|
686
|
+
try {
|
|
687
|
+
const res = await fetch(`http://127.0.0.1:${kuriCdpPort}/json/new?${url}`, {
|
|
688
|
+
method: "PUT",
|
|
689
|
+
signal: AbortSignal.timeout(5000)
|
|
690
|
+
});
|
|
691
|
+
const target = await res.json();
|
|
692
|
+
return target?.id ?? target?.targetId ?? "";
|
|
693
|
+
} catch (err) {
|
|
694
|
+
log("kuri", `Chrome tab creation failed: ${err instanceof Error ? err.message : err}`);
|
|
695
|
+
return "";
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async function findReusableIdleTab() {
|
|
699
|
+
await ensureTabsDiscovered();
|
|
700
|
+
try {
|
|
701
|
+
const tabs = await kuriGet("/tabs");
|
|
702
|
+
const candidate = tabs.find((tab) => /^(about:blank|chrome:\/\/newtab\/?)$/i.test(tab?.url ?? ""));
|
|
703
|
+
return candidate?.id ?? "";
|
|
704
|
+
} catch {
|
|
705
|
+
return "";
|
|
706
|
+
}
|
|
707
|
+
}
|
|
683
708
|
async function navigate(tabId, url) {
|
|
684
709
|
await kuriGet("/navigate", { tab_id: tabId, url });
|
|
685
710
|
}
|
|
@@ -869,8 +894,17 @@ async function newTab(url) {
|
|
|
869
894
|
const params = {};
|
|
870
895
|
if (url)
|
|
871
896
|
params.url = url;
|
|
872
|
-
|
|
873
|
-
|
|
897
|
+
let tabId = "";
|
|
898
|
+
try {
|
|
899
|
+
const result = await kuriGet("/tab/new", params);
|
|
900
|
+
tabId = result?.tab_id ?? result?.id ?? result?.targetId ?? "";
|
|
901
|
+
} catch {
|
|
902
|
+
tabId = "";
|
|
903
|
+
}
|
|
904
|
+
if (!tabId)
|
|
905
|
+
tabId = await createTabViaChromeCdp(url ?? "about:blank");
|
|
906
|
+
if (!tabId)
|
|
907
|
+
tabId = await findReusableIdleTab();
|
|
874
908
|
if (tabId) {
|
|
875
909
|
await waitForTabRegistration(tabId).catch(() => {});
|
|
876
910
|
}
|
|
@@ -3625,12 +3659,7 @@ function templatizePathSegments(templateUrl, originalUrl, context) {
|
|
|
3625
3659
|
try {
|
|
3626
3660
|
const contextSegments = new URL(context.pageUrl).pathname.split("/");
|
|
3627
3661
|
const contextSeg = contextSegments[i];
|
|
3628
|
-
|
|
3629
|
-
const prevContextSeg = contextSegments[i - 1] ?? "";
|
|
3630
|
-
const nextSeg = tSegments[i + 1] ?? "";
|
|
3631
|
-
const nextContextSeg = contextSegments[i + 1] ?? "";
|
|
3632
|
-
const hasStructuralNeighborMatch = !!prevSeg && !!prevContextSeg && prevSeg === prevContextSeg || !!nextSeg && !!nextContextSeg && nextSeg === nextContextSeg;
|
|
3633
|
-
if (contextSeg && contextSeg !== tSeg && hasStructuralNeighborMatch && !contextSeg.includes(".") && contextSeg.length >= 2 && contextSeg.length <= 40 && !/^(api|v\d+|www|en|es|fr|de|latest|search|i)$/i.test(contextSeg)) {
|
|
3662
|
+
if (contextSeg && contextSeg !== tSeg && !contextSeg.includes(".") && contextSeg.length >= 2 && contextSeg.length <= 40 && !/^(api|v\d+|www|en|es|fr|de|latest|search|i)$/i.test(contextSeg)) {
|
|
3634
3663
|
const paramName = inferParamName(tSegments, i, "slug", usedNames);
|
|
3635
3664
|
tSegments[i] = `{${paramName}}`;
|
|
3636
3665
|
pathParams[paramName] = contextSeg;
|
|
@@ -5075,11 +5104,13 @@ var init_capture = __esm(() => {
|
|
|
5075
5104
|
// ../../src/client/index.ts
|
|
5076
5105
|
var exports_client2 = {};
|
|
5077
5106
|
__export(exports_client2, {
|
|
5107
|
+
waitForBackgroundRegistration: () => waitForBackgroundRegistration,
|
|
5078
5108
|
verifyMarketplaceDiscovery: () => verifyMarketplaceDiscovery,
|
|
5079
5109
|
validateManifest: () => validateManifest,
|
|
5080
5110
|
updateEndpointScore: () => updateEndpointScore,
|
|
5081
5111
|
updateEndpointSchema: () => updateEndpointSchema,
|
|
5082
5112
|
syncAgentWallet: () => syncAgentWallet2,
|
|
5113
|
+
startBackgroundRegistration: () => startBackgroundRegistration,
|
|
5083
5114
|
setSkillSplitConfig: () => setSkillSplitConfig,
|
|
5084
5115
|
setSkillPrice: () => setSkillPrice,
|
|
5085
5116
|
searchIntentResolve: () => searchIntentResolve,
|
|
@@ -5518,23 +5549,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
|
|
|
5518
5549
|
rl.close();
|
|
5519
5550
|
}
|
|
5520
5551
|
}
|
|
5521
|
-
async function checkTosStatus2() {
|
|
5552
|
+
async function checkTosStatus2(options) {
|
|
5553
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
5522
5554
|
const config = loadConfig2();
|
|
5523
5555
|
let tosInfo;
|
|
5524
5556
|
try {
|
|
5525
5557
|
tosInfo = await api2("GET", "/v1/tos/current");
|
|
5526
5558
|
} catch {
|
|
5527
|
-
return;
|
|
5559
|
+
return true;
|
|
5528
5560
|
}
|
|
5529
5561
|
if (config?.tos_accepted_version === tosInfo.version) {
|
|
5530
|
-
return;
|
|
5562
|
+
return true;
|
|
5531
5563
|
}
|
|
5532
5564
|
console.log(`
|
|
5533
5565
|
The Unbrowse Terms of Service have been updated.`);
|
|
5534
5566
|
const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
|
|
5535
5567
|
if (!accepted) {
|
|
5536
5568
|
console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
|
|
5537
|
-
|
|
5569
|
+
if (exitOnFailure)
|
|
5570
|
+
process.exit(1);
|
|
5571
|
+
return false;
|
|
5538
5572
|
}
|
|
5539
5573
|
try {
|
|
5540
5574
|
await api2("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
|
|
@@ -5547,16 +5581,20 @@ The Unbrowse Terms of Service have been updated.`);
|
|
|
5547
5581
|
} catch (err) {
|
|
5548
5582
|
console.warn(`Failed to record ToS acceptance: ${err.message}`);
|
|
5549
5583
|
}
|
|
5584
|
+
return true;
|
|
5550
5585
|
}
|
|
5551
5586
|
async function ensureRegistered2(options) {
|
|
5552
5587
|
if (LOCAL_ONLY2)
|
|
5553
5588
|
return;
|
|
5589
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
5554
5590
|
const usableKey = await findUsableApiKey2();
|
|
5555
5591
|
if (usableKey) {
|
|
5556
5592
|
if (usableKey.source === "config") {
|
|
5557
5593
|
console.log("[unbrowse] Restored saved registration.");
|
|
5558
5594
|
}
|
|
5559
|
-
await checkTosStatus2();
|
|
5595
|
+
const accepted2 = await checkTosStatus2({ exitOnFailure });
|
|
5596
|
+
if (!accepted2)
|
|
5597
|
+
return;
|
|
5560
5598
|
try {
|
|
5561
5599
|
const profile = await getMyProfile2();
|
|
5562
5600
|
const wallet = getLocalWalletContext2();
|
|
@@ -5577,7 +5615,9 @@ async function ensureRegistered2(options) {
|
|
|
5577
5615
|
const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
|
|
5578
5616
|
if (!accepted) {
|
|
5579
5617
|
console.log("You must accept the Terms of Service to use Unbrowse.");
|
|
5580
|
-
|
|
5618
|
+
if (exitOnFailure)
|
|
5619
|
+
process.exit(1);
|
|
5620
|
+
return;
|
|
5581
5621
|
}
|
|
5582
5622
|
const fallbackName = buildDefaultAgentName2();
|
|
5583
5623
|
const name = options?.promptForEmail ? await promptAgentEmail2(fallbackName) : resolveAgentName2(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
|
|
@@ -5605,8 +5645,36 @@ async function ensureRegistered2(options) {
|
|
|
5605
5645
|
} catch (err) {
|
|
5606
5646
|
console.warn(`Registration failed: ${err.message}`);
|
|
5607
5647
|
console.warn("Set UNBROWSE_API_KEY manually or try again.");
|
|
5608
|
-
|
|
5648
|
+
if (exitOnFailure)
|
|
5649
|
+
process.exit(1);
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
function startBackgroundRegistration(options) {
|
|
5653
|
+
if (LOCAL_ONLY2)
|
|
5654
|
+
return Promise.resolve();
|
|
5655
|
+
if (backgroundRegistrationPromise)
|
|
5656
|
+
return backgroundRegistrationPromise;
|
|
5657
|
+
backgroundRegistrationPromise = ensureRegistered2({
|
|
5658
|
+
promptForEmail: options?.promptForEmail,
|
|
5659
|
+
exitOnFailure: false
|
|
5660
|
+
}).catch((err) => {
|
|
5661
|
+
console.warn(`[unbrowse] Background registration failed: ${err.message}`);
|
|
5662
|
+
}).finally(() => {
|
|
5663
|
+
backgroundRegistrationPromise = null;
|
|
5664
|
+
});
|
|
5665
|
+
return backgroundRegistrationPromise;
|
|
5666
|
+
}
|
|
5667
|
+
async function waitForBackgroundRegistration(timeoutMs = 0) {
|
|
5668
|
+
if (!backgroundRegistrationPromise)
|
|
5669
|
+
return;
|
|
5670
|
+
if (timeoutMs <= 0) {
|
|
5671
|
+
await backgroundRegistrationPromise;
|
|
5672
|
+
return;
|
|
5609
5673
|
}
|
|
5674
|
+
await Promise.race([
|
|
5675
|
+
backgroundRegistrationPromise,
|
|
5676
|
+
new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
|
5677
|
+
]);
|
|
5610
5678
|
}
|
|
5611
5679
|
function skillCachePath(skillId) {
|
|
5612
5680
|
return join3(getSkillCacheDir(), `${skillId}.json`);
|
|
@@ -5997,7 +6065,7 @@ async function setSkillPrice(skillId, priceUsd) {
|
|
|
5997
6065
|
async function setSkillSplitConfig(skillId, splitConfig) {
|
|
5998
6066
|
return api2("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
|
|
5999
6067
|
}
|
|
6000
|
-
var API_URL2, PROFILE_NAME2, recentLocalSkills2, LOCAL_ONLY2, EMAIL_RE2, API_TIMEOUT_MS2, PUBLISH_TIMEOUT_MS2;
|
|
6068
|
+
var API_URL2, PROFILE_NAME2, recentLocalSkills2, LOCAL_ONLY2, EMAIL_RE2, API_TIMEOUT_MS2, PUBLISH_TIMEOUT_MS2, backgroundRegistrationPromise = null;
|
|
6001
6069
|
var init_client2 = __esm(() => {
|
|
6002
6070
|
init_cascade();
|
|
6003
6071
|
API_URL2 = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
|
|
@@ -11901,8 +11969,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
|
|
|
11901
11969
|
} catch {}
|
|
11902
11970
|
}
|
|
11903
11971
|
}
|
|
11904
|
-
|
|
11905
|
-
if (!options?.skip_robots_check && !hasAuthContext) {
|
|
11972
|
+
if (!options?.skip_robots_check) {
|
|
11906
11973
|
const allowed = await isAllowedByRobots(url);
|
|
11907
11974
|
if (!allowed) {
|
|
11908
11975
|
const traceId = nanoid5();
|
|
@@ -15294,7 +15361,6 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
|
|
|
15294
15361
|
result: {
|
|
15295
15362
|
message: `Found ${epRanked.length} endpoint(s). Pick one and call POST /v1/skills/${resolvedSkill.skill_id}/execute with params.endpoint_id.`,
|
|
15296
15363
|
skill_id: resolvedSkill.skill_id,
|
|
15297
|
-
suggested_next_operation_id: chunk.available_operation_ids[0],
|
|
15298
15364
|
available_operations: chunk.operations.map((operation) => ({
|
|
15299
15365
|
operation_id: operation.operation_id,
|
|
15300
15366
|
endpoint_id: operation.endpoint_id,
|
|
@@ -16731,7 +16797,7 @@ var init_orchestrator = __esm(async () => {
|
|
|
16731
16797
|
captureDomainLocks = new Map;
|
|
16732
16798
|
skillRouteCache = new Map;
|
|
16733
16799
|
ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
|
|
16734
|
-
SKILL_SNAPSHOT_DIR = join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
16800
|
+
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
16735
16801
|
domainSkillCache = new Map;
|
|
16736
16802
|
DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
16737
16803
|
try {
|
|
@@ -17144,7 +17210,7 @@ async function processIndexJob(job) {
|
|
|
17144
17210
|
}
|
|
17145
17211
|
console.log(`[background-index] completed for ${domain} -> ${published.skill_id}`);
|
|
17146
17212
|
}
|
|
17147
|
-
var UNBROWSE_CONFIG_PATH, SECRET_VALUE_PATTERNS, SECRET_KEY_PATTERNS,
|
|
17213
|
+
var UNBROWSE_CONFIG_PATH, SKILL_SNAPSHOT_DIR2, SECRET_VALUE_PATTERNS, SECRET_KEY_PATTERNS, indexInFlight;
|
|
17148
17214
|
var init_indexer = __esm(async () => {
|
|
17149
17215
|
init_graph();
|
|
17150
17216
|
init_client2();
|
|
@@ -17152,6 +17218,7 @@ var init_indexer = __esm(async () => {
|
|
|
17152
17218
|
init_domain();
|
|
17153
17219
|
await init_orchestrator();
|
|
17154
17220
|
UNBROWSE_CONFIG_PATH = join10(homedir6(), ".unbrowse", "config.json");
|
|
17221
|
+
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
17155
17222
|
SECRET_VALUE_PATTERNS = [
|
|
17156
17223
|
/^eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
|
|
17157
17224
|
/^Bearer\s+\S+/i,
|
|
@@ -17165,7 +17232,6 @@ var init_indexer = __esm(async () => {
|
|
|
17165
17232
|
/^v2\.[A-Za-z0-9_-]{20,}/
|
|
17166
17233
|
];
|
|
17167
17234
|
SECRET_KEY_PATTERNS = /^(api[_-]?key|access[_-]?token|auth[_-]?token|secret[_-]?key|private[_-]?key|password|passwd|session[_-]?id|session[_-]?token|csrf[_-]?token|client[_-]?secret|bearer|refresh[_-]?token|id[_-]?token|jwt|nonce|otp|pin|ssn|credit[_-]?card)$/i;
|
|
17168
|
-
SKILL_SNAPSHOT_DIR2 = join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
17169
17235
|
indexInFlight = new Map;
|
|
17170
17236
|
});
|
|
17171
17237
|
|
|
@@ -17311,113 +17377,6 @@ var init_session_logs = __esm(() => {
|
|
|
17311
17377
|
init_domain();
|
|
17312
17378
|
});
|
|
17313
17379
|
|
|
17314
|
-
// ../../src/agent-outcome.ts
|
|
17315
|
-
function edgePriority(kind) {
|
|
17316
|
-
switch (kind) {
|
|
17317
|
-
case "parent_child":
|
|
17318
|
-
return 4;
|
|
17319
|
-
case "pagination":
|
|
17320
|
-
return 3;
|
|
17321
|
-
case "dependency":
|
|
17322
|
-
return 2;
|
|
17323
|
-
case "hint":
|
|
17324
|
-
return 1;
|
|
17325
|
-
case "auth":
|
|
17326
|
-
return 0;
|
|
17327
|
-
default:
|
|
17328
|
-
return -1;
|
|
17329
|
-
}
|
|
17330
|
-
}
|
|
17331
|
-
function nextActionWhy(kind, bindingKey, title) {
|
|
17332
|
-
switch (kind) {
|
|
17333
|
-
case "parent_child":
|
|
17334
|
-
return `Likely next detail step after this result. Exposes ${title}.`;
|
|
17335
|
-
case "pagination":
|
|
17336
|
-
return `Likely next page or continuation step. Carries ${bindingKey || "cursor"} forward.`;
|
|
17337
|
-
case "dependency":
|
|
17338
|
-
return `Unlocks the next dependent call using ${bindingKey || "known bindings"}.`;
|
|
17339
|
-
case "auth":
|
|
17340
|
-
return "Useful once authentication is in place.";
|
|
17341
|
-
case "hint":
|
|
17342
|
-
return "Common follow-up action from the current result.";
|
|
17343
|
-
default:
|
|
17344
|
-
return "Likely follow-up action.";
|
|
17345
|
-
}
|
|
17346
|
-
}
|
|
17347
|
-
function operationTitle(operation) {
|
|
17348
|
-
const semantic = [operation.action_kind, operation.resource_kind].filter(Boolean).join(" ").replace(/_/g, " ").trim();
|
|
17349
|
-
return operation.description_out || semantic || operation.endpoint_id;
|
|
17350
|
-
}
|
|
17351
|
-
function buildAgentImpact(timing) {
|
|
17352
|
-
if (!timing?.source)
|
|
17353
|
-
return;
|
|
17354
|
-
return {
|
|
17355
|
-
source: timing.source,
|
|
17356
|
-
cache_hit: timing.cache_hit === true,
|
|
17357
|
-
browser_avoided: !BROWSER_SOURCES.has(timing.source),
|
|
17358
|
-
baseline_total_ms: timing.baseline_total_ms,
|
|
17359
|
-
actual_total_ms: timing.actual_total_ms,
|
|
17360
|
-
time_saved_ms: timing.time_saved_ms,
|
|
17361
|
-
time_saved_pct: timing.time_saved_pct ?? 0,
|
|
17362
|
-
tokens_saved: timing.tokens_saved ?? 0,
|
|
17363
|
-
tokens_saved_pct: timing.tokens_saved_pct ?? 0,
|
|
17364
|
-
baseline_cost_uc: timing.baseline_cost_uc,
|
|
17365
|
-
actual_cost_uc: timing.actual_cost_uc,
|
|
17366
|
-
cost_saved_uc: timing.cost_saved_uc
|
|
17367
|
-
};
|
|
17368
|
-
}
|
|
17369
|
-
function buildNextActions(skill, endpointId, maxActions = 3) {
|
|
17370
|
-
if (!skill?.operation_graph || !endpointId)
|
|
17371
|
-
return [];
|
|
17372
|
-
const graph = skill.operation_graph;
|
|
17373
|
-
const current = graph.operations.find((operation) => operation.endpoint_id === endpointId);
|
|
17374
|
-
if (!current)
|
|
17375
|
-
return [];
|
|
17376
|
-
const byOperationId = new Map(graph.operations.map((operation) => [operation.operation_id, operation]));
|
|
17377
|
-
const scored = new Map;
|
|
17378
|
-
for (const edge of graph.edges) {
|
|
17379
|
-
if (edge.from_operation_id !== current.operation_id)
|
|
17380
|
-
continue;
|
|
17381
|
-
const target = byOperationId.get(edge.to_operation_id);
|
|
17382
|
-
if (!target)
|
|
17383
|
-
continue;
|
|
17384
|
-
const candidate = {
|
|
17385
|
-
operation_id: target.operation_id,
|
|
17386
|
-
endpoint_id: target.endpoint_id,
|
|
17387
|
-
title: operationTitle(target),
|
|
17388
|
-
why: nextActionWhy(edge.kind, edge.binding_key, operationTitle(target)),
|
|
17389
|
-
score: edgePriority(edge.kind) * 10 + Math.round(edge.confidence * 10)
|
|
17390
|
-
};
|
|
17391
|
-
const existing = scored.get(target.operation_id);
|
|
17392
|
-
if (!existing || candidate.score > existing.score) {
|
|
17393
|
-
scored.set(target.operation_id, candidate);
|
|
17394
|
-
}
|
|
17395
|
-
}
|
|
17396
|
-
return [...scored.values()].sort((a, b) => b.score - a.score || a.title.localeCompare(b.title)).slice(0, maxActions).map((candidate) => ({
|
|
17397
|
-
endpoint_id: candidate.endpoint_id,
|
|
17398
|
-
operation_id: candidate.operation_id,
|
|
17399
|
-
title: candidate.title,
|
|
17400
|
-
why: candidate.why,
|
|
17401
|
-
command: `unbrowse execute --skill ${skill.skill_id} --endpoint ${candidate.endpoint_id}`
|
|
17402
|
-
}));
|
|
17403
|
-
}
|
|
17404
|
-
function attachAgentOutcomeHints(payload, opts) {
|
|
17405
|
-
const target = payload;
|
|
17406
|
-
const impact = buildAgentImpact(opts?.timing);
|
|
17407
|
-
if (impact) {
|
|
17408
|
-
target.impact = impact;
|
|
17409
|
-
}
|
|
17410
|
-
const nextActions = buildNextActions(opts?.skill, opts?.endpointId);
|
|
17411
|
-
if (nextActions.length > 0) {
|
|
17412
|
-
target.next_actions = nextActions;
|
|
17413
|
-
}
|
|
17414
|
-
return target;
|
|
17415
|
-
}
|
|
17416
|
-
var BROWSER_SOURCES;
|
|
17417
|
-
var init_agent_outcome = __esm(() => {
|
|
17418
|
-
BROWSER_SOURCES = new Set(["live-capture", "first-pass", "browser-action"]);
|
|
17419
|
-
});
|
|
17420
|
-
|
|
17421
17380
|
// ../../src/api/browse-session.ts
|
|
17422
17381
|
function extractBrowseFailureMessage(value) {
|
|
17423
17382
|
return typeof value === "string" ? value : getKuriErrorMessage(value);
|
|
@@ -17432,6 +17391,8 @@ function isRecoverableBrowseFailure(value) {
|
|
|
17432
17391
|
async function createBrowseSession(sessions, client, injectInterceptor2) {
|
|
17433
17392
|
await client.start().catch(() => {});
|
|
17434
17393
|
const tabId = await client.newTab();
|
|
17394
|
+
if (!tabId)
|
|
17395
|
+
throw new Error("Failed to create browser tab");
|
|
17435
17396
|
await client.harStart(tabId).catch(() => {});
|
|
17436
17397
|
await injectInterceptor2(tabId);
|
|
17437
17398
|
const session = { tabId, url: "about:blank", harActive: true, domain: "" };
|
|
@@ -17454,7 +17415,7 @@ async function adoptExistingBrowseTab(sessions, client, injectInterceptor2, pref
|
|
|
17454
17415
|
const candidate = tabs.find((tab) => {
|
|
17455
17416
|
const domain = extractDomain2(tab.url);
|
|
17456
17417
|
return !!domain && !!normalizedPreferred && domain === normalizedPreferred;
|
|
17457
|
-
}) ?? tabs.find((tab) => /^https?:\/\//.test(tab.url ?? ""));
|
|
17418
|
+
}) ?? tabs.find((tab) => /^(about:blank|chrome:\/\/newtab\/?)$/i.test(tab.url ?? "")) ?? (!normalizedPreferred ? tabs.find((tab) => /^https?:\/\//.test(tab.url ?? "")) : undefined);
|
|
17458
17419
|
if (!candidate?.id)
|
|
17459
17420
|
return null;
|
|
17460
17421
|
await client.harStart(candidate.id).catch(() => {});
|
|
@@ -18011,6 +17972,21 @@ function parseJsonString(value) {
|
|
|
18011
17972
|
return null;
|
|
18012
17973
|
}
|
|
18013
17974
|
}
|
|
17975
|
+
async function settleSubmitDestination(client, tabId, url, html) {
|
|
17976
|
+
let settledUrl = url;
|
|
17977
|
+
let settledHtml = html;
|
|
17978
|
+
const deadline = Date.now() + SUBMIT_SETTLE_WINDOW_MS;
|
|
17979
|
+
while (Date.now() < deadline) {
|
|
17980
|
+
await sleep(Math.min(SUBMIT_POLL_INTERVAL_MS, Math.max(50, deadline - Date.now())));
|
|
17981
|
+
const nextUrl = await client.getCurrentUrl(tabId).catch(() => "");
|
|
17982
|
+
const nextHtml = await client.getPageHtml(tabId).catch(() => "");
|
|
17983
|
+
if (nextUrl && !nextUrl.startsWith("about:blank"))
|
|
17984
|
+
settledUrl = nextUrl;
|
|
17985
|
+
if (nextHtml)
|
|
17986
|
+
settledHtml = nextHtml;
|
|
17987
|
+
}
|
|
17988
|
+
return { url: settledUrl, html: settledHtml };
|
|
17989
|
+
}
|
|
18014
17990
|
async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, options) {
|
|
18015
17991
|
const timeoutMs = options.timeoutMs ?? DEFAULT_SUBMIT_TIMEOUT_MS;
|
|
18016
17992
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -18021,7 +17997,7 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
|
|
|
18021
17997
|
if (waitResult?.status === "found" || waitResult?.status === "ready") {
|
|
18022
17998
|
const url = await client.getCurrentUrl(tabId).catch(() => beforeUrl);
|
|
18023
17999
|
const html = await client.getPageHtml(tabId).catch(() => beforeHtml);
|
|
18024
|
-
return { ok: true, url, html };
|
|
18000
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
|
|
18025
18001
|
}
|
|
18026
18002
|
} catch {}
|
|
18027
18003
|
}
|
|
@@ -18029,20 +18005,20 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
|
|
|
18029
18005
|
const url = await client.getCurrentUrl(tabId).catch(() => "");
|
|
18030
18006
|
const html = await client.getPageHtml(tabId).catch(() => "");
|
|
18031
18007
|
if (waitFor && isUrlWaitHint(waitFor) && url.includes(waitFor)) {
|
|
18032
|
-
return { ok: true, url, html };
|
|
18008
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
|
|
18033
18009
|
}
|
|
18034
18010
|
if (url && url !== beforeUrl && !url.startsWith("about:blank")) {
|
|
18035
|
-
return { ok: true, url, html };
|
|
18011
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
|
|
18036
18012
|
}
|
|
18037
18013
|
if (hasMeaningfulPageChange(beforeHtml, html)) {
|
|
18038
|
-
return { ok: true,
|
|
18014
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url || beforeUrl, html) };
|
|
18039
18015
|
}
|
|
18040
18016
|
await sleep(SUBMIT_POLL_INTERVAL_MS);
|
|
18041
18017
|
}
|
|
18042
18018
|
return { ok: false, url: beforeUrl, html: beforeHtml };
|
|
18043
18019
|
}
|
|
18044
18020
|
async function submitBrowseForm(deps, options = {}) {
|
|
18045
|
-
const { client, session,
|
|
18021
|
+
const { client, session, restartCapture, rehydratePlugins } = deps;
|
|
18046
18022
|
const sameOriginFetchFallback = options.sameOriginFetchFallback !== false;
|
|
18047
18023
|
const beforeUrl = await client.getCurrentUrl(session.tabId).catch(() => session.url);
|
|
18048
18024
|
const beforeHtml = await client.getPageHtml(session.tabId).catch(() => "");
|
|
@@ -18068,14 +18044,6 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18068
18044
|
const domOutcome = await waitForSubmitOutcome(client, session.tabId, beforeUrl, beforeHtml, options);
|
|
18069
18045
|
if (domOutcome.ok) {
|
|
18070
18046
|
session.url = domOutcome.url || beforeUrl || session.url;
|
|
18071
|
-
let captureSync2 = null;
|
|
18072
|
-
if (flushCapture) {
|
|
18073
|
-
try {
|
|
18074
|
-
captureSync2 = await flushCapture(session);
|
|
18075
|
-
} catch {
|
|
18076
|
-
captureSync2 = null;
|
|
18077
|
-
}
|
|
18078
|
-
}
|
|
18079
18047
|
await restartCapture(session);
|
|
18080
18048
|
return {
|
|
18081
18049
|
ok: true,
|
|
@@ -18084,8 +18052,7 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18084
18052
|
fallback_used: false,
|
|
18085
18053
|
same_origin_html_rehydrated: false,
|
|
18086
18054
|
wait_for: options.waitFor,
|
|
18087
|
-
submit_meta: submitMeta
|
|
18088
|
-
capture_sync: captureSync2
|
|
18055
|
+
submit_meta: submitMeta
|
|
18089
18056
|
};
|
|
18090
18057
|
}
|
|
18091
18058
|
if (submitError && !isRecoverableBrowseFailure(submitError) && !sameOriginFetchFallback) {
|
|
@@ -18123,14 +18090,6 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18123
18090
|
if (!rehydrate) {
|
|
18124
18091
|
rehydrate = await rehydratePlugins(session.tabId).catch(() => null);
|
|
18125
18092
|
}
|
|
18126
|
-
let captureSync = null;
|
|
18127
|
-
if (flushCapture) {
|
|
18128
|
-
try {
|
|
18129
|
-
captureSync = await flushCapture(session);
|
|
18130
|
-
} catch {
|
|
18131
|
-
captureSync = null;
|
|
18132
|
-
}
|
|
18133
|
-
}
|
|
18134
18093
|
await restartCapture(session);
|
|
18135
18094
|
return {
|
|
18136
18095
|
ok: true,
|
|
@@ -18141,11 +18100,10 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18141
18100
|
status: typeof fallbackPayload.status === "number" ? fallbackPayload.status : undefined,
|
|
18142
18101
|
wait_for: options.waitFor,
|
|
18143
18102
|
submit_meta: submitMeta,
|
|
18144
|
-
capture_sync: captureSync,
|
|
18145
18103
|
rehydrate
|
|
18146
18104
|
};
|
|
18147
18105
|
}
|
|
18148
|
-
var DEFAULT_SUBMIT_TIMEOUT_MS = 8000, SUBMIT_POLL_INTERVAL_MS = 250;
|
|
18106
|
+
var DEFAULT_SUBMIT_TIMEOUT_MS = 8000, SUBMIT_POLL_INTERVAL_MS = 250, SUBMIT_SETTLE_WINDOW_MS = 1000;
|
|
18149
18107
|
var init_browse_submit = __esm(() => {
|
|
18150
18108
|
init_browse_session();
|
|
18151
18109
|
});
|
|
@@ -18270,7 +18228,6 @@ import { join as join12 } from "path";
|
|
|
18270
18228
|
function buildAnalyticsSessionPayload(result, opts) {
|
|
18271
18229
|
const source = result.timing?.source ?? result.source;
|
|
18272
18230
|
const apiCalls = result.trace.endpoint_id ? 1 : 0;
|
|
18273
|
-
const browserMode = opts.browser_mode ?? (source === "live-capture" || source === "first-pass" || source === "browser-action" ? "default" : "replaced");
|
|
18274
18231
|
const cachedSkillCalls = opts.cached_skill_calls ?? (apiCalls > 0 && source !== "live-capture" && source !== "first-pass" ? 1 : 0);
|
|
18275
18232
|
const freshIndexCalls = opts.fresh_index_calls ?? (apiCalls > 0 && (source === "live-capture" || source === "first-pass") ? 1 : 0);
|
|
18276
18233
|
return {
|
|
@@ -18282,14 +18239,7 @@ function buildAnalyticsSessionPayload(result, opts) {
|
|
|
18282
18239
|
discovery_queries: opts.discovery_queries,
|
|
18283
18240
|
cached_skill_calls: cachedSkillCalls,
|
|
18284
18241
|
fresh_index_calls: freshIndexCalls,
|
|
18285
|
-
browser_mode:
|
|
18286
|
-
success: result.trace.success ?? true,
|
|
18287
|
-
source,
|
|
18288
|
-
time_saved_ms: result.timing?.time_saved_ms,
|
|
18289
|
-
time_saved_pct: result.timing?.time_saved_pct,
|
|
18290
|
-
tokens_saved: result.trace.tokens_saved ?? result.timing?.tokens_saved,
|
|
18291
|
-
tokens_saved_pct: result.trace.tokens_saved_pct ?? result.timing?.tokens_saved_pct,
|
|
18292
|
-
cost_saved_uc: result.timing?.cost_saved_uc
|
|
18242
|
+
browser_mode: opts.browser_mode ?? "unknown"
|
|
18293
18243
|
};
|
|
18294
18244
|
}
|
|
18295
18245
|
function passiveIndexFromRequests(requests, pageUrl) {
|
|
@@ -18470,7 +18420,11 @@ async function registerRoutes(app) {
|
|
|
18470
18420
|
app.addHook("onRequest", async (req, reply) => {
|
|
18471
18421
|
if (req.url === "/health" || req.url === "/v1/stats")
|
|
18472
18422
|
return;
|
|
18473
|
-
|
|
18423
|
+
let key = getApiKey2();
|
|
18424
|
+
if (!key) {
|
|
18425
|
+
await waitForBackgroundRegistration(15000);
|
|
18426
|
+
key = getApiKey2();
|
|
18427
|
+
}
|
|
18474
18428
|
if (!key) {
|
|
18475
18429
|
return reply.code(401).send({
|
|
18476
18430
|
error: "api_key_required",
|
|
@@ -18486,11 +18440,7 @@ async function registerRoutes(app) {
|
|
|
18486
18440
|
return reply.code(400).send({ error: "intent required" });
|
|
18487
18441
|
try {
|
|
18488
18442
|
const result = await resolveAndExecute(intent, params ?? {}, context, projection, { confirm_unsafe, dry_run, force_capture, client_scope: clientScope });
|
|
18489
|
-
const res =
|
|
18490
|
-
skill: result.skill,
|
|
18491
|
-
endpointId: result.trace.endpoint_id,
|
|
18492
|
-
timing: result.timing
|
|
18493
|
-
});
|
|
18443
|
+
const res = result;
|
|
18494
18444
|
if (result.timing) {
|
|
18495
18445
|
res.timing = result.timing;
|
|
18496
18446
|
}
|
|
@@ -18499,9 +18449,10 @@ async function registerRoutes(app) {
|
|
|
18499
18449
|
res.available_endpoints = innerResult.available_endpoints;
|
|
18500
18450
|
}
|
|
18501
18451
|
await recordAnalyticsSession(buildAnalyticsSessionPayload(result, {
|
|
18452
|
+
browser_mode: "replaced",
|
|
18502
18453
|
discovery_queries: 1
|
|
18503
18454
|
})).catch(() => {});
|
|
18504
|
-
return reply.send(
|
|
18455
|
+
return reply.send(result);
|
|
18505
18456
|
} catch (err) {
|
|
18506
18457
|
return reply.code(500).send({ error: err.message });
|
|
18507
18458
|
}
|
|
@@ -18711,33 +18662,26 @@ async function registerRoutes(app) {
|
|
|
18711
18662
|
recordExecution(freshResult.trace.skill_id, freshResult.trace.endpoint_id, freshResult.trace, skill).catch(() => {});
|
|
18712
18663
|
}
|
|
18713
18664
|
await recordAnalyticsSession(buildAnalyticsSessionPayload(freshResult, {
|
|
18665
|
+
browser_mode: "manual",
|
|
18714
18666
|
discovery_queries: 1
|
|
18715
18667
|
})).catch(() => {});
|
|
18716
|
-
|
|
18668
|
+
return reply.send({
|
|
18717
18669
|
...freshResult,
|
|
18718
18670
|
_recovery: {
|
|
18719
18671
|
reason: "stale_endpoint_404",
|
|
18720
18672
|
original_skill_id: skill_id,
|
|
18721
18673
|
message: "Original endpoint returned 404. Auto-recovered with fresh capture."
|
|
18722
18674
|
}
|
|
18723
|
-
}, {
|
|
18724
|
-
skill: freshResult.skill ?? skill,
|
|
18725
|
-
endpointId: freshResult.trace.endpoint_id,
|
|
18726
|
-
timing: freshResult.timing
|
|
18727
|
-
});
|
|
18728
|
-
return reply.send({
|
|
18729
|
-
...recovered
|
|
18730
18675
|
});
|
|
18731
18676
|
} catch {}
|
|
18732
18677
|
}
|
|
18733
18678
|
await recordAnalyticsSession(buildAnalyticsSessionPayload(execResult, {
|
|
18734
|
-
|
|
18679
|
+
browser_mode: "manual",
|
|
18680
|
+
discovery_queries: 0,
|
|
18681
|
+
cached_skill_calls: execResult.trace.endpoint_id ? 1 : 0,
|
|
18682
|
+
fresh_index_calls: 0
|
|
18735
18683
|
})).catch(() => {});
|
|
18736
|
-
|
|
18737
|
-
skill,
|
|
18738
|
-
endpointId: execResult.trace.endpoint_id
|
|
18739
|
-
});
|
|
18740
|
-
return reply.send(response);
|
|
18684
|
+
return reply.send(execResult);
|
|
18741
18685
|
} catch (err) {
|
|
18742
18686
|
return reply.code(500).send({ error: err.message });
|
|
18743
18687
|
}
|
|
@@ -18886,71 +18830,6 @@ async function registerRoutes(app) {
|
|
|
18886
18830
|
session.harActive = true;
|
|
18887
18831
|
await injectInterceptor(session.tabId).catch(() => {});
|
|
18888
18832
|
}
|
|
18889
|
-
async function flushBrowseCapture(session, options = {}) {
|
|
18890
|
-
let intercepted = [];
|
|
18891
|
-
try {
|
|
18892
|
-
const raw = await collectInterceptedRequests(session.tabId);
|
|
18893
|
-
intercepted = raw.map((request) => ({
|
|
18894
|
-
url: request.url,
|
|
18895
|
-
method: request.method,
|
|
18896
|
-
request_headers: request.request_headers ?? {},
|
|
18897
|
-
request_body: request.request_body,
|
|
18898
|
-
response_status: request.response_status,
|
|
18899
|
-
response_headers: request.response_headers ?? {},
|
|
18900
|
-
response_body: request.response_body,
|
|
18901
|
-
timestamp: request.timestamp
|
|
18902
|
-
}));
|
|
18903
|
-
} catch {}
|
|
18904
|
-
let harEntries = [];
|
|
18905
|
-
if (session.harActive) {
|
|
18906
|
-
try {
|
|
18907
|
-
const { entries } = await harStop(session.tabId);
|
|
18908
|
-
harEntries = entries;
|
|
18909
|
-
} catch {}
|
|
18910
|
-
}
|
|
18911
|
-
session.harActive = false;
|
|
18912
|
-
const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
|
|
18913
|
-
const syncResult = await cacheBrowseRequests({
|
|
18914
|
-
sessionUrl: session.url,
|
|
18915
|
-
sessionDomain: session.domain,
|
|
18916
|
-
requests: allRequests,
|
|
18917
|
-
getPageHtml: () => getPageHtml(session.tabId)
|
|
18918
|
-
});
|
|
18919
|
-
let backgroundPublishQueued = false;
|
|
18920
|
-
if (options.queueBackgroundPublish) {
|
|
18921
|
-
if (allRequests.length > 0) {
|
|
18922
|
-
passiveIndexFromRequests(allRequests, session.url);
|
|
18923
|
-
backgroundPublishQueued = true;
|
|
18924
|
-
} else if (syncResult.skill) {
|
|
18925
|
-
queueBackgroundIndex({
|
|
18926
|
-
skill: { ...syncResult.skill },
|
|
18927
|
-
domain: syncResult.domain,
|
|
18928
|
-
intent: syncResult.skill.intent_signature || `browse ${syncResult.domain}`,
|
|
18929
|
-
contextUrl: session.url,
|
|
18930
|
-
cacheKey: `browse-submit:${syncResult.domain}:${Date.now()}`
|
|
18931
|
-
});
|
|
18932
|
-
backgroundPublishQueued = true;
|
|
18933
|
-
}
|
|
18934
|
-
}
|
|
18935
|
-
return {
|
|
18936
|
-
indexed: syncResult.indexed,
|
|
18937
|
-
mode: syncResult.mode,
|
|
18938
|
-
domain: syncResult.domain,
|
|
18939
|
-
skill_id: syncResult.skill?.skill_id ?? null,
|
|
18940
|
-
endpoint_count: syncResult.skill?.endpoints.length ?? 0,
|
|
18941
|
-
endpoints: (syncResult.skill?.endpoints ?? []).map((endpoint) => ({
|
|
18942
|
-
endpoint_id: endpoint.endpoint_id,
|
|
18943
|
-
method: endpoint.method,
|
|
18944
|
-
url_template: endpoint.url_template,
|
|
18945
|
-
description: endpoint.description,
|
|
18946
|
-
trigger_url: endpoint.trigger_url,
|
|
18947
|
-
action_kind: endpoint.semantic?.action_kind,
|
|
18948
|
-
resource_kind: endpoint.semantic?.resource_kind
|
|
18949
|
-
})),
|
|
18950
|
-
request_count: allRequests.length,
|
|
18951
|
-
background_publish_queued: backgroundPublishQueued
|
|
18952
|
-
};
|
|
18953
|
-
}
|
|
18954
18833
|
app.post("/v1/browse/go", async (req, reply) => {
|
|
18955
18834
|
const { url } = req.body;
|
|
18956
18835
|
if (!url)
|
|
@@ -19007,7 +18886,6 @@ async function registerRoutes(app) {
|
|
|
19007
18886
|
const { session, result, recovered } = await withRecoveredBrowseSession(browseSessions, exports_client, injectInterceptor, async (session2) => submitBrowseForm({
|
|
19008
18887
|
client: exports_client,
|
|
19009
18888
|
session: session2,
|
|
19010
|
-
flushCapture: async (session3) => await flushBrowseCapture(session3, { queueBackgroundPublish: true }),
|
|
19011
18889
|
restartCapture: restartBrowseCapture,
|
|
19012
18890
|
rehydratePlugins: bestEffortRehydratePlugins
|
|
19013
18891
|
}, {
|
|
@@ -19020,10 +18898,8 @@ async function registerRoutes(app) {
|
|
|
19020
18898
|
session.url = result.url || await getCurrentUrl(session.tabId).catch(() => session.url);
|
|
19021
18899
|
session.domain = profileName(session.url);
|
|
19022
18900
|
const statusCode = result.ok ? 200 : result.recoverable ? 502 : 400;
|
|
19023
|
-
const nextStep = result.ok ? result.capture_sync?.background_publish_queued ? "Background publish queued for this step. Continue the flow, then run `unbrowse close` when you're done to save auth and finalize any remaining capture." : "If more UI steps remain, continue the flow. Run `unbrowse close` when you're done to save auth and finalize capture." : "Inspect the page state with `unbrowse snap --filter interactive`, then retry submit with selectors or a wait hint if needed.";
|
|
19024
18901
|
return reply.code(statusCode).send({
|
|
19025
18902
|
...result,
|
|
19026
|
-
next_step: nextStep,
|
|
19027
18903
|
recovered,
|
|
19028
18904
|
tab_id: session.tabId,
|
|
19029
18905
|
url: session.url
|
|
@@ -19133,18 +19009,57 @@ async function registerRoutes(app) {
|
|
|
19133
19009
|
const session = browseSessions.get("default");
|
|
19134
19010
|
if (!session)
|
|
19135
19011
|
return reply.send({ ok: false, error: "no active session" });
|
|
19136
|
-
|
|
19137
|
-
|
|
19012
|
+
let intercepted = [];
|
|
19013
|
+
try {
|
|
19014
|
+
const raw = await collectInterceptedRequests(session.tabId);
|
|
19015
|
+
intercepted = raw.map((request) => ({
|
|
19016
|
+
url: request.url,
|
|
19017
|
+
method: request.method,
|
|
19018
|
+
request_headers: request.request_headers ?? {},
|
|
19019
|
+
request_body: request.request_body,
|
|
19020
|
+
response_status: request.response_status,
|
|
19021
|
+
response_headers: request.response_headers ?? {},
|
|
19022
|
+
response_body: request.response_body,
|
|
19023
|
+
timestamp: request.timestamp
|
|
19024
|
+
}));
|
|
19025
|
+
} catch {}
|
|
19026
|
+
let harEntries = [];
|
|
19027
|
+
if (session.harActive) {
|
|
19028
|
+
try {
|
|
19029
|
+
const { entries } = await harStop(session.tabId);
|
|
19030
|
+
harEntries = entries;
|
|
19031
|
+
} catch {}
|
|
19032
|
+
}
|
|
19033
|
+
session.harActive = false;
|
|
19034
|
+
const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
|
|
19035
|
+
const syncResult = await cacheBrowseRequests({
|
|
19036
|
+
sessionUrl: session.url,
|
|
19037
|
+
sessionDomain: session.domain,
|
|
19038
|
+
requests: allRequests,
|
|
19039
|
+
getPageHtml: () => getPageHtml(session.tabId)
|
|
19040
|
+
});
|
|
19041
|
+
await networkEnable(session.tabId).catch(() => {});
|
|
19042
|
+
await harStart(session.tabId).catch(() => {});
|
|
19043
|
+
await scriptInject(session.tabId, INTERCEPTOR_SCRIPT).catch(() => {});
|
|
19044
|
+
session.harActive = true;
|
|
19045
|
+
await injectInterceptor(session.tabId).catch(() => {});
|
|
19138
19046
|
return reply.send({
|
|
19139
19047
|
ok: true,
|
|
19140
19048
|
tab_id: session.tabId,
|
|
19141
19049
|
indexed: syncResult.indexed,
|
|
19142
19050
|
mode: syncResult.mode,
|
|
19143
19051
|
domain: syncResult.domain,
|
|
19144
|
-
skill_id: syncResult.skill_id,
|
|
19145
|
-
endpoint_count: syncResult.
|
|
19146
|
-
endpoints: syncResult.endpoints
|
|
19147
|
-
|
|
19052
|
+
skill_id: syncResult.skill?.skill_id ?? null,
|
|
19053
|
+
endpoint_count: syncResult.skill?.endpoints.length ?? 0,
|
|
19054
|
+
endpoints: (syncResult.skill?.endpoints ?? []).map((endpoint) => ({
|
|
19055
|
+
endpoint_id: endpoint.endpoint_id,
|
|
19056
|
+
method: endpoint.method,
|
|
19057
|
+
url_template: endpoint.url_template,
|
|
19058
|
+
description: endpoint.description,
|
|
19059
|
+
trigger_url: endpoint.trigger_url,
|
|
19060
|
+
action_kind: endpoint.semantic?.action_kind,
|
|
19061
|
+
resource_kind: endpoint.semantic?.resource_kind
|
|
19062
|
+
}))
|
|
19148
19063
|
});
|
|
19149
19064
|
});
|
|
19150
19065
|
app.post("/v1/browse/close", async (_req, reply) => {
|
|
@@ -19154,16 +19069,42 @@ async function registerRoutes(app) {
|
|
|
19154
19069
|
if (session.domain) {
|
|
19155
19070
|
await authProfileSave(session.tabId, session.domain).catch(() => {});
|
|
19156
19071
|
}
|
|
19157
|
-
|
|
19072
|
+
let intercepted = [];
|
|
19073
|
+
try {
|
|
19074
|
+
const raw = await collectInterceptedRequests(session.tabId);
|
|
19075
|
+
intercepted = raw.map((r) => ({
|
|
19076
|
+
url: r.url,
|
|
19077
|
+
method: r.method,
|
|
19078
|
+
request_headers: r.request_headers ?? {},
|
|
19079
|
+
request_body: r.request_body,
|
|
19080
|
+
response_status: r.response_status,
|
|
19081
|
+
response_headers: r.response_headers ?? {},
|
|
19082
|
+
response_body: r.response_body,
|
|
19083
|
+
timestamp: r.timestamp
|
|
19084
|
+
}));
|
|
19085
|
+
} catch {}
|
|
19086
|
+
let harEntries = [];
|
|
19087
|
+
if (session.harActive) {
|
|
19088
|
+
try {
|
|
19089
|
+
const { entries } = await harStop(session.tabId);
|
|
19090
|
+
harEntries = entries;
|
|
19091
|
+
} catch {}
|
|
19092
|
+
}
|
|
19093
|
+
const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
|
|
19094
|
+
const syncResult = await cacheBrowseRequests({
|
|
19095
|
+
sessionUrl: session.url,
|
|
19096
|
+
sessionDomain: session.domain,
|
|
19097
|
+
requests: allRequests,
|
|
19098
|
+
getPageHtml: () => getPageHtml(session.tabId)
|
|
19099
|
+
});
|
|
19100
|
+
passiveIndexFromRequests(allRequests, session.url);
|
|
19158
19101
|
await closeTab(session.tabId).catch(() => {});
|
|
19159
19102
|
browseSessions.delete("default");
|
|
19160
19103
|
return reply.send({
|
|
19161
19104
|
ok: true,
|
|
19162
19105
|
indexed: syncResult.indexed,
|
|
19163
19106
|
mode: syncResult.mode,
|
|
19164
|
-
endpoint_count: syncResult.
|
|
19165
|
-
request_count: syncResult.request_count,
|
|
19166
|
-
background_publish_queued: syncResult.background_publish_queued,
|
|
19107
|
+
endpoint_count: syncResult.skill?.endpoints.length ?? 0,
|
|
19167
19108
|
auth_saved: session.domain || null
|
|
19168
19109
|
});
|
|
19169
19110
|
});
|
|
@@ -19190,7 +19131,6 @@ var init_routes = __esm(async () => {
|
|
|
19190
19131
|
init_ratelimit();
|
|
19191
19132
|
init_graph();
|
|
19192
19133
|
init_session_logs();
|
|
19193
|
-
init_agent_outcome();
|
|
19194
19134
|
init_browse_session();
|
|
19195
19135
|
init_browse_submit();
|
|
19196
19136
|
await __promiseAll([
|
|
@@ -19247,7 +19187,7 @@ async function startUnbrowseServer(options = {}) {
|
|
|
19247
19187
|
try {
|
|
19248
19188
|
execSync2("pkill -f chrome-headless-shell", { stdio: "ignore" });
|
|
19249
19189
|
} catch {}
|
|
19250
|
-
|
|
19190
|
+
startBackgroundRegistration();
|
|
19251
19191
|
const app = Fastify({ logger: options.logger ?? true });
|
|
19252
19192
|
await app.register(cors, { origin: true });
|
|
19253
19193
|
await registerRateLimiter(app);
|
|
@@ -19282,7 +19222,6 @@ var init_server = __esm(async () => {
|
|
|
19282
19222
|
|
|
19283
19223
|
// ../../src/cli.ts
|
|
19284
19224
|
import { config as loadEnv } from "dotenv";
|
|
19285
|
-
import { spawn as spawn3 } from "child_process";
|
|
19286
19225
|
|
|
19287
19226
|
// ../../src/client/index.ts
|
|
19288
19227
|
init_cascade();
|
|
@@ -19664,23 +19603,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
|
|
|
19664
19603
|
rl.close();
|
|
19665
19604
|
}
|
|
19666
19605
|
}
|
|
19667
|
-
async function checkTosStatus() {
|
|
19606
|
+
async function checkTosStatus(options) {
|
|
19607
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
19668
19608
|
const config = loadConfig();
|
|
19669
19609
|
let tosInfo;
|
|
19670
19610
|
try {
|
|
19671
19611
|
tosInfo = await api("GET", "/v1/tos/current");
|
|
19672
19612
|
} catch {
|
|
19673
|
-
return;
|
|
19613
|
+
return true;
|
|
19674
19614
|
}
|
|
19675
19615
|
if (config?.tos_accepted_version === tosInfo.version) {
|
|
19676
|
-
return;
|
|
19616
|
+
return true;
|
|
19677
19617
|
}
|
|
19678
19618
|
console.log(`
|
|
19679
19619
|
The Unbrowse Terms of Service have been updated.`);
|
|
19680
19620
|
const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
|
|
19681
19621
|
if (!accepted) {
|
|
19682
19622
|
console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
|
|
19683
|
-
|
|
19623
|
+
if (exitOnFailure)
|
|
19624
|
+
process.exit(1);
|
|
19625
|
+
return false;
|
|
19684
19626
|
}
|
|
19685
19627
|
try {
|
|
19686
19628
|
await api("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
|
|
@@ -19693,16 +19635,20 @@ The Unbrowse Terms of Service have been updated.`);
|
|
|
19693
19635
|
} catch (err) {
|
|
19694
19636
|
console.warn(`Failed to record ToS acceptance: ${err.message}`);
|
|
19695
19637
|
}
|
|
19638
|
+
return true;
|
|
19696
19639
|
}
|
|
19697
19640
|
async function ensureRegistered(options) {
|
|
19698
19641
|
if (LOCAL_ONLY)
|
|
19699
19642
|
return;
|
|
19643
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
19700
19644
|
const usableKey = await findUsableApiKey();
|
|
19701
19645
|
if (usableKey) {
|
|
19702
19646
|
if (usableKey.source === "config") {
|
|
19703
19647
|
console.log("[unbrowse] Restored saved registration.");
|
|
19704
19648
|
}
|
|
19705
|
-
await checkTosStatus();
|
|
19649
|
+
const accepted2 = await checkTosStatus({ exitOnFailure });
|
|
19650
|
+
if (!accepted2)
|
|
19651
|
+
return;
|
|
19706
19652
|
try {
|
|
19707
19653
|
const profile = await getMyProfile();
|
|
19708
19654
|
const wallet = getLocalWalletContext();
|
|
@@ -19723,7 +19669,9 @@ async function ensureRegistered(options) {
|
|
|
19723
19669
|
const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
|
|
19724
19670
|
if (!accepted) {
|
|
19725
19671
|
console.log("You must accept the Terms of Service to use Unbrowse.");
|
|
19726
|
-
|
|
19672
|
+
if (exitOnFailure)
|
|
19673
|
+
process.exit(1);
|
|
19674
|
+
return;
|
|
19727
19675
|
}
|
|
19728
19676
|
const fallbackName = buildDefaultAgentName();
|
|
19729
19677
|
const name = options?.promptForEmail ? await promptAgentEmail(fallbackName) : resolveAgentName(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
|
|
@@ -19751,7 +19699,8 @@ async function ensureRegistered(options) {
|
|
|
19751
19699
|
} catch (err) {
|
|
19752
19700
|
console.warn(`Registration failed: ${err.message}`);
|
|
19753
19701
|
console.warn("Set UNBROWSE_API_KEY manually or try again.");
|
|
19754
|
-
|
|
19702
|
+
if (exitOnFailure)
|
|
19703
|
+
process.exit(1);
|
|
19755
19704
|
}
|
|
19756
19705
|
}
|
|
19757
19706
|
async function getMyProfile() {
|
|
@@ -20149,26 +20098,7 @@ async function restartServer(baseUrl, metaUrl) {
|
|
|
20149
20098
|
// ../../src/runtime/paths.ts
|
|
20150
20099
|
import { existsSync as existsSync13, mkdirSync as mkdirSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
20151
20100
|
import path7 from "node:path";
|
|
20152
|
-
import { createRequire as createRequire3 } from "node:module";
|
|
20153
20101
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
20154
|
-
function resolveSiblingEntrypoint2(metaUrl, basename) {
|
|
20155
|
-
const file = fileURLToPath3(metaUrl);
|
|
20156
|
-
return path7.join(path7.dirname(file), `${basename}${path7.extname(file) || ".js"}`);
|
|
20157
|
-
}
|
|
20158
|
-
function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
|
|
20159
|
-
if (path7.extname(entrypoint) !== ".ts")
|
|
20160
|
-
return [entrypoint];
|
|
20161
|
-
if (process.versions.bun)
|
|
20162
|
-
return [entrypoint];
|
|
20163
|
-
try {
|
|
20164
|
-
const req = createRequire3(metaUrl);
|
|
20165
|
-
const tsxPkg = req.resolve("tsx/package.json");
|
|
20166
|
-
const tsxLoader = path7.join(path7.dirname(tsxPkg), "dist", "loader.mjs");
|
|
20167
|
-
if (existsSync13(tsxLoader))
|
|
20168
|
-
return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
|
|
20169
|
-
} catch {}
|
|
20170
|
-
return ["--import", "tsx", entrypoint];
|
|
20171
|
-
}
|
|
20172
20102
|
function isMainModule(metaUrl) {
|
|
20173
20103
|
const entry = process.argv[1];
|
|
20174
20104
|
if (!entry)
|
|
@@ -20190,7 +20120,7 @@ await init_orchestrator();
|
|
|
20190
20120
|
import { join as join13 } from "node:path";
|
|
20191
20121
|
import { homedir as homedir7 } from "node:os";
|
|
20192
20122
|
var UNBROWSE_CONFIG_PATH2 = join13(homedir7(), ".unbrowse", "config.json");
|
|
20193
|
-
var SKILL_SNAPSHOT_DIR3 = join13(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
20123
|
+
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join13(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
20194
20124
|
var indexInFlight2 = new Map;
|
|
20195
20125
|
async function drainPendingIndexJobs() {
|
|
20196
20126
|
const pending = [...indexInFlight2.values()];
|
|
@@ -20347,7 +20277,7 @@ async function runSetup(options) {
|
|
|
20347
20277
|
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
20348
20278
|
const lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
20349
20279
|
if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
|
|
20350
|
-
console.log("[unbrowse]
|
|
20280
|
+
console.log("[unbrowse] lobster.cash skill detected but wallet not configured — running wallet setup...");
|
|
20351
20281
|
try {
|
|
20352
20282
|
execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
20353
20283
|
stdio: "inherit",
|
|
@@ -20358,15 +20288,15 @@ async function runSetup(options) {
|
|
|
20358
20288
|
console.log(`[unbrowse] wallet configured (${recheck.provider})`);
|
|
20359
20289
|
}
|
|
20360
20290
|
} catch {
|
|
20361
|
-
console.warn("[unbrowse]
|
|
20291
|
+
console.warn("[unbrowse] lobster.cash wallet setup failed or was skipped — continuing without wallet");
|
|
20362
20292
|
}
|
|
20363
20293
|
}
|
|
20364
20294
|
const finalWalletCheck = checkWalletConfigured();
|
|
20365
20295
|
const wallet = {
|
|
20366
20296
|
...finalWalletCheck,
|
|
20367
20297
|
lobster_installed: lobsterInstalled,
|
|
20368
|
-
message: finalWalletCheck.configured ? `Wallet configured (${finalWalletCheck.provider}). This address is the contributor truth: it is synced onto your agent profile, used for contributor payouts when your routes earn, and used for paid-route spending.` : lobsterInstalled ? "
|
|
20369
|
-
install_hint: finalWalletCheck.configured ? undefined : lobsterInstalled ? "
|
|
20298
|
+
message: finalWalletCheck.configured ? `Wallet configured (${finalWalletCheck.provider}). This address is the contributor truth: it is synced onto your agent profile, used for contributor payouts when your routes earn, and used for paid-route spending.` : lobsterInstalled ? "lobster.cash installed but wallet not paired. Pair it now so this wallet address becomes your contributor payout target and your paid-route spending wallet. Run: lobstercash setup" : "No wallet configured. Install/pair a wallet so your contributor payouts have a destination address and premium-route spending can clear automatically. Without it you stay in free indexing mode only.",
|
|
20299
|
+
install_hint: finalWalletCheck.configured ? undefined : lobsterInstalled ? "lobstercash setup" : "npx skills add https://github.com/Crossmint/lobstercash-cli-skills --global --yes"
|
|
20370
20300
|
};
|
|
20371
20301
|
return {
|
|
20372
20302
|
os: {
|
|
@@ -20494,73 +20424,15 @@ function slimTrace(obj) {
|
|
|
20494
20424
|
out.result = obj.result;
|
|
20495
20425
|
if (obj.available_endpoints)
|
|
20496
20426
|
out.available_endpoints = obj.available_endpoints;
|
|
20497
|
-
if (obj.impact)
|
|
20498
|
-
out.impact = obj.impact;
|
|
20499
|
-
if (obj.next_actions)
|
|
20500
|
-
out.next_actions = obj.next_actions;
|
|
20501
|
-
if (obj.next_step)
|
|
20502
|
-
out.next_step = obj.next_step;
|
|
20503
20427
|
if (obj.source)
|
|
20504
20428
|
out.source = obj.source;
|
|
20505
20429
|
if (obj.skill)
|
|
20506
20430
|
out.skill = obj.skill;
|
|
20507
20431
|
return out;
|
|
20508
20432
|
}
|
|
20509
|
-
function formatSavedDuration(ms) {
|
|
20510
|
-
if (ms >= 60000)
|
|
20511
|
-
return `${(ms / 60000).toFixed(1)}m`;
|
|
20512
|
-
if (ms >= 1e4)
|
|
20513
|
-
return `${Math.round(ms / 1000)}s`;
|
|
20514
|
-
if (ms >= 1000)
|
|
20515
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
20516
|
-
return `${ms}ms`;
|
|
20517
|
-
}
|
|
20518
|
-
function emitImpactSummary(result) {
|
|
20519
|
-
const impact = result.impact;
|
|
20520
|
-
if (!impact)
|
|
20521
|
-
return;
|
|
20522
|
-
const timeSavedMs = typeof impact.time_saved_ms === "number" ? impact.time_saved_ms : 0;
|
|
20523
|
-
const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
|
|
20524
|
-
const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
|
|
20525
|
-
const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
|
|
20526
|
-
const browserAvoided = impact.browser_avoided === true;
|
|
20527
|
-
if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
|
|
20528
|
-
return;
|
|
20529
|
-
const parts = [];
|
|
20530
|
-
if (timeSavedMs > 0)
|
|
20531
|
-
parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
|
|
20532
|
-
if (tokensSaved > 0)
|
|
20533
|
-
parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
|
|
20534
|
-
if (browserAvoided)
|
|
20535
|
-
parts.push("browser avoided");
|
|
20536
|
-
info(parts.join(" \u2022 "));
|
|
20537
|
-
}
|
|
20538
|
-
function emitNextActionSummary(result) {
|
|
20539
|
-
const nextActions = Array.isArray(result.next_actions) ? result.next_actions : [];
|
|
20540
|
-
if (nextActions.length === 0)
|
|
20541
|
-
return;
|
|
20542
|
-
info("Likely next actions:");
|
|
20543
|
-
for (const action2 of nextActions.slice(0, 3)) {
|
|
20544
|
-
const command = typeof action2.command === "string" ? action2.command : "";
|
|
20545
|
-
const title = typeof action2.title === "string" ? action2.title : action2.endpoint_id ?? "next step";
|
|
20546
|
-
const why = typeof action2.why === "string" ? action2.why : "";
|
|
20547
|
-
info(` ${command || title}${why ? ` # ${why}` : ""}`);
|
|
20548
|
-
}
|
|
20549
|
-
}
|
|
20550
20433
|
async function cmdHealth(flags) {
|
|
20551
20434
|
output(await api3("GET", "/health"), !!flags.pretty);
|
|
20552
20435
|
}
|
|
20553
|
-
function telemetryDomainFromInput(domain, url) {
|
|
20554
|
-
if (domain?.trim())
|
|
20555
|
-
return domain.trim().replace(/^www\./, "");
|
|
20556
|
-
if (!url?.trim())
|
|
20557
|
-
return null;
|
|
20558
|
-
try {
|
|
20559
|
-
return new URL(url).hostname.replace(/^www\./, "");
|
|
20560
|
-
} catch {
|
|
20561
|
-
return null;
|
|
20562
|
-
}
|
|
20563
|
-
}
|
|
20564
20436
|
async function cmdResolve(flags) {
|
|
20565
20437
|
const intent = flags.intent;
|
|
20566
20438
|
if (!intent)
|
|
@@ -20577,9 +20449,6 @@ async function cmdResolve(flags) {
|
|
|
20577
20449
|
hostType,
|
|
20578
20450
|
properties: {
|
|
20579
20451
|
command: "resolve",
|
|
20580
|
-
intent,
|
|
20581
|
-
domain: telemetryDomainFromInput(flags.domain, flags.url),
|
|
20582
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
20583
20452
|
has_url: typeof flags.url === "string",
|
|
20584
20453
|
has_domain: typeof flags.domain === "string",
|
|
20585
20454
|
auto_execute: !!flags.execute
|
|
@@ -20706,9 +20575,6 @@ async function cmdResolve(flags) {
|
|
|
20706
20575
|
hostType,
|
|
20707
20576
|
properties: {
|
|
20708
20577
|
command: "resolve",
|
|
20709
|
-
intent,
|
|
20710
|
-
domain: telemetryDomainFromInput(domain, url),
|
|
20711
|
-
url: url ?? null,
|
|
20712
20578
|
source: result.source,
|
|
20713
20579
|
auto_execute: autoExecute,
|
|
20714
20580
|
explicit_endpoint: explicitEndpointId ?? null
|
|
@@ -20716,8 +20582,6 @@ async function cmdResolve(flags) {
|
|
|
20716
20582
|
});
|
|
20717
20583
|
}
|
|
20718
20584
|
result = slimTrace(result);
|
|
20719
|
-
emitImpactSummary(result);
|
|
20720
|
-
emitNextActionSummary(result);
|
|
20721
20585
|
const skill = result.skill;
|
|
20722
20586
|
const trace = result.trace;
|
|
20723
20587
|
if (skill?.skill_id && trace) {
|
|
@@ -20731,9 +20595,6 @@ async function cmdResolve(flags) {
|
|
|
20731
20595
|
hostType,
|
|
20732
20596
|
properties: {
|
|
20733
20597
|
command: "resolve",
|
|
20734
|
-
intent,
|
|
20735
|
-
domain: telemetryDomainFromInput(flags.domain, flags.url),
|
|
20736
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
20737
20598
|
failure_stage: "resolve",
|
|
20738
20599
|
failure_reason: message
|
|
20739
20600
|
}
|
|
@@ -20832,9 +20693,6 @@ async function cmdExecute(flags) {
|
|
|
20832
20693
|
hostType,
|
|
20833
20694
|
properties: {
|
|
20834
20695
|
command: "execute",
|
|
20835
|
-
intent: typeof flags.intent === "string" ? flags.intent : null,
|
|
20836
|
-
domain: telemetryDomainFromInput(undefined, flags.url),
|
|
20837
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
20838
20696
|
skill_id: skillId,
|
|
20839
20697
|
endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
|
|
20840
20698
|
}
|
|
@@ -20865,17 +20723,12 @@ async function cmdExecute(flags) {
|
|
|
20865
20723
|
hostType,
|
|
20866
20724
|
properties: {
|
|
20867
20725
|
command: "execute",
|
|
20868
|
-
intent: typeof flags.intent === "string" ? flags.intent : null,
|
|
20869
|
-
domain: telemetryDomainFromInput(undefined, flags.url),
|
|
20870
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
20871
20726
|
skill_id: skillId,
|
|
20872
20727
|
endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
|
|
20873
20728
|
}
|
|
20874
20729
|
});
|
|
20875
20730
|
}
|
|
20876
20731
|
result = slimTrace(result);
|
|
20877
|
-
emitImpactSummary(result);
|
|
20878
|
-
emitNextActionSummary(result);
|
|
20879
20732
|
const pathFlag = flags.path;
|
|
20880
20733
|
const extractFlag = flags.extract;
|
|
20881
20734
|
const limitFlag = flags.limit ? Number(flags.limit) : undefined;
|
|
@@ -20883,12 +20736,7 @@ async function cmdExecute(flags) {
|
|
|
20883
20736
|
const rawFlag = !!flags.raw;
|
|
20884
20737
|
if (schemaFlag && !rawFlag) {
|
|
20885
20738
|
const data = result.result;
|
|
20886
|
-
output({
|
|
20887
|
-
trace: result.trace,
|
|
20888
|
-
schema: schemaOf(data),
|
|
20889
|
-
...result.impact ? { impact: result.impact } : {},
|
|
20890
|
-
...result.next_actions ? { next_actions: result.next_actions } : {}
|
|
20891
|
-
}, !!flags.pretty);
|
|
20739
|
+
output({ trace: result.trace, schema: schemaOf(data) }, !!flags.pretty);
|
|
20892
20740
|
return;
|
|
20893
20741
|
}
|
|
20894
20742
|
if (!rawFlag && (pathFlag || extractFlag || limitFlag)) {
|
|
@@ -20897,13 +20745,7 @@ async function cmdExecute(flags) {
|
|
|
20897
20745
|
const extracted = extractFlag ? applyExtract(items, extractFlag) : items;
|
|
20898
20746
|
const limited = limitFlag ? extracted.slice(0, limitFlag) : extracted;
|
|
20899
20747
|
const trace = result.trace;
|
|
20900
|
-
const out = {
|
|
20901
|
-
trace: result.trace,
|
|
20902
|
-
data: limited,
|
|
20903
|
-
count: limited.length,
|
|
20904
|
-
...result.impact ? { impact: result.impact } : {},
|
|
20905
|
-
...result.next_actions ? { next_actions: result.next_actions } : {}
|
|
20906
|
-
};
|
|
20748
|
+
const out = { trace: result.trace, data: limited, count: limited.length };
|
|
20907
20749
|
if (trace?.skill_id && trace?.endpoint_id && limited.length > 0) {
|
|
20908
20750
|
out._review_hint = `After presenting results, improve this endpoint's description: unbrowse review --skill ${trace.skill_id} --endpoints '[{"endpoint_id":"${trace.endpoint_id}","description":"DESCRIBE WHAT THIS RETURNS","action_kind":"ACTION","resource_kind":"RESOURCE"}]'`;
|
|
20909
20751
|
}
|
|
@@ -20916,8 +20758,6 @@ async function cmdExecute(flags) {
|
|
|
20916
20758
|
const schema = schemaOf(result.result);
|
|
20917
20759
|
output({
|
|
20918
20760
|
trace: result.trace,
|
|
20919
|
-
...result.impact ? { impact: result.impact } : {},
|
|
20920
|
-
...result.next_actions ? { next_actions: result.next_actions } : {},
|
|
20921
20761
|
extraction_hints: {
|
|
20922
20762
|
message: "Response is large. Use --path/--extract/--limit to filter, or --schema to see structure, or --raw for full response.",
|
|
20923
20763
|
schema_tree: schema,
|
|
@@ -20935,9 +20775,6 @@ async function cmdExecute(flags) {
|
|
|
20935
20775
|
hostType,
|
|
20936
20776
|
properties: {
|
|
20937
20777
|
command: "execute",
|
|
20938
|
-
intent: typeof flags.intent === "string" ? flags.intent : null,
|
|
20939
|
-
domain: telemetryDomainFromInput(undefined, flags.url),
|
|
20940
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
20941
20778
|
skill_id: skillId,
|
|
20942
20779
|
failure_stage: "execute",
|
|
20943
20780
|
failure_reason: message
|
|
@@ -21013,53 +20850,7 @@ async function cmdSearch(flags) {
|
|
|
21013
20850
|
const body = { intent, k: Number(flags.k) || 5 };
|
|
21014
20851
|
if (domain)
|
|
21015
20852
|
body.domain = domain;
|
|
21016
|
-
|
|
21017
|
-
await ensureCliInstallTracked(hostType);
|
|
21018
|
-
await recordFunnelTelemetryEvent("cli_invoked", {
|
|
21019
|
-
source: "cli",
|
|
21020
|
-
hostType,
|
|
21021
|
-
properties: { command: "search" }
|
|
21022
|
-
});
|
|
21023
|
-
await recordFunnelTelemetryEvent("search_started", {
|
|
21024
|
-
source: "cli",
|
|
21025
|
-
hostType,
|
|
21026
|
-
properties: {
|
|
21027
|
-
command: "search",
|
|
21028
|
-
intent,
|
|
21029
|
-
domain: domain ?? null,
|
|
21030
|
-
k: body.k
|
|
21031
|
-
}
|
|
21032
|
-
});
|
|
21033
|
-
try {
|
|
21034
|
-
const result = await api3("POST", path9, body);
|
|
21035
|
-
const results = Array.isArray(result.results) ? result.results : [];
|
|
21036
|
-
await recordFunnelTelemetryEvent("search_completed", {
|
|
21037
|
-
source: "cli",
|
|
21038
|
-
hostType,
|
|
21039
|
-
properties: {
|
|
21040
|
-
command: "search",
|
|
21041
|
-
intent,
|
|
21042
|
-
domain: domain ?? null,
|
|
21043
|
-
k: body.k,
|
|
21044
|
-
result_count: results.length
|
|
21045
|
-
}
|
|
21046
|
-
});
|
|
21047
|
-
output(result, !!flags.pretty);
|
|
21048
|
-
} catch (error) {
|
|
21049
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
21050
|
-
await recordFunnelTelemetryEvent("search_failed", {
|
|
21051
|
-
source: "cli",
|
|
21052
|
-
hostType,
|
|
21053
|
-
properties: {
|
|
21054
|
-
command: "search",
|
|
21055
|
-
intent,
|
|
21056
|
-
domain: domain ?? null,
|
|
21057
|
-
failure_stage: "search",
|
|
21058
|
-
failure_reason: message
|
|
21059
|
-
}
|
|
21060
|
-
});
|
|
21061
|
-
throw error;
|
|
21062
|
-
}
|
|
20853
|
+
output(await api3("POST", path9, body), !!flags.pretty);
|
|
21063
20854
|
}
|
|
21064
20855
|
async function cmdSessions(flags) {
|
|
21065
20856
|
const domain = flags.domain;
|
|
@@ -21150,7 +20941,6 @@ async function cmdSetup(flags) {
|
|
|
21150
20941
|
var CLI_REFERENCE = {
|
|
21151
20942
|
commands: [
|
|
21152
20943
|
{ name: "health", usage: "", desc: "Server health check" },
|
|
21153
|
-
{ name: "mcp", usage: "[--no-auto-start]", desc: "Run the stdio MCP server" },
|
|
21154
20944
|
{ name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
|
|
21155
20945
|
{ name: "resolve", usage: '--intent "..." --url "..." [opts]', desc: "Resolve intent \u2192 search/capture/execute" },
|
|
21156
20946
|
{ name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
|
|
@@ -21163,7 +20953,7 @@ var CLI_REFERENCE = {
|
|
|
21163
20953
|
{ name: "search", usage: '--intent "..." [--domain "..."]', desc: "Search marketplace" },
|
|
21164
20954
|
{ name: "sessions", usage: '--domain "..." [--limit N]', desc: "Debug session logs" },
|
|
21165
20955
|
{ name: "go", usage: "<url>", desc: "Open a live Kuri browser tab for capture-first workflows" },
|
|
21166
|
-
{ name: "submit", usage: "[--form-selector sel] [--submit-selector sel] [--wait-for hint]", desc: "Submit current form
|
|
20956
|
+
{ name: "submit", usage: "[--form-selector sel] [--submit-selector sel] [--wait-for hint]", desc: "Submit current form with DOM-first + same-origin rehydrate fallback for JS-heavy flows" },
|
|
21167
20957
|
{ name: "snap", usage: "[--filter interactive]", desc: "A11y snapshot with @eN refs" },
|
|
21168
20958
|
{ name: "click", usage: "<ref>", desc: "Click element by ref (e.g. e5)" },
|
|
21169
20959
|
{ name: "fill", usage: "<ref> <value>", desc: "Fill input by ref" },
|
|
@@ -21200,7 +20990,6 @@ var CLI_REFERENCE = {
|
|
|
21200
20990
|
],
|
|
21201
20991
|
examples: [
|
|
21202
20992
|
"unbrowse setup",
|
|
21203
|
-
"unbrowse mcp",
|
|
21204
20993
|
'unbrowse resolve --intent "top stories" --url "https://news.ycombinator.com" --execute',
|
|
21205
20994
|
'unbrowse resolve --intent "get timeline" --url "https://x.com"',
|
|
21206
20995
|
'unbrowse go "https://www.mandai.com/en/ticketing/admission-and-rides/parks-selection.html"',
|
|
@@ -21239,7 +21028,7 @@ function printHelp() {
|
|
|
21239
21028
|
for (const e of r.examples) {
|
|
21240
21029
|
lines.push(` ${e}`);
|
|
21241
21030
|
}
|
|
21242
|
-
lines.push("", "Browser workflow:", " 1. go -> open the live tab you want to work in", " 2. snap -> inspect refs and confirm the page state", " 3. click/fill/eval -> set real page state", " 4. submit -> prefer DOM submit; auto-
|
|
21031
|
+
lines.push("", "Browser workflow:", " 1. go -> open the live tab you want to work in", " 2. snap -> inspect refs and confirm the page state", " 3. click/fill/eval -> set real page state", " 4. submit -> prefer DOM submit; auto-falls back to same-origin rehydrate", " 5. sync -> flush captured routes after a successful step", " 6. close -> finish capture + indexing");
|
|
21243
21032
|
lines.push("", "JS-heavy forms:", " Prefer real calendar/time clicks before submit.", " If the UI is flaky, inspect hidden inputs/cookies with eval, then submit the real form.");
|
|
21244
21033
|
lines.push("");
|
|
21245
21034
|
process.stderr.write(lines.join(`
|
|
@@ -21285,29 +21074,6 @@ async function cmdUpgrade(flags) {
|
|
|
21285
21074
|
info(`Could not check for updates: ${err.message}`);
|
|
21286
21075
|
}
|
|
21287
21076
|
}
|
|
21288
|
-
async function cmdMcp(flags) {
|
|
21289
|
-
const entrypoint = resolveSiblingEntrypoint2(import.meta.url, "mcp");
|
|
21290
|
-
const child = spawn3(process.execPath, [...runtimeArgsForEntrypoint2(import.meta.url, entrypoint), ...flags["no-auto-start"] ? ["--no-auto-start"] : []], {
|
|
21291
|
-
cwd: process.cwd(),
|
|
21292
|
-
stdio: "inherit",
|
|
21293
|
-
env: {
|
|
21294
|
-
...process.env,
|
|
21295
|
-
MCP_SERVER_MODE: "1"
|
|
21296
|
-
}
|
|
21297
|
-
});
|
|
21298
|
-
const code = await new Promise((resolve, reject) => {
|
|
21299
|
-
child.once("error", reject);
|
|
21300
|
-
child.once("exit", (exitCode, signal) => {
|
|
21301
|
-
if (signal) {
|
|
21302
|
-
process.kill(process.pid, signal);
|
|
21303
|
-
return;
|
|
21304
|
-
}
|
|
21305
|
-
resolve(exitCode ?? 1);
|
|
21306
|
-
});
|
|
21307
|
-
});
|
|
21308
|
-
if (code !== 0)
|
|
21309
|
-
process.exit(code);
|
|
21310
|
-
}
|
|
21311
21077
|
async function cmdSiteHelp(pack, flags) {
|
|
21312
21078
|
if (flags.deps) {
|
|
21313
21079
|
const graph = buildDepsGraph(pack);
|
|
@@ -21579,8 +21345,6 @@ async function main() {
|
|
|
21579
21345
|
await cmdSetup(flags);
|
|
21580
21346
|
return;
|
|
21581
21347
|
}
|
|
21582
|
-
if (command === "mcp")
|
|
21583
|
-
return cmdMcp(flags);
|
|
21584
21348
|
if (command === "status")
|
|
21585
21349
|
return cmdStatus(flags);
|
|
21586
21350
|
if (command === "stop") {
|
|
@@ -21595,7 +21359,6 @@ async function main() {
|
|
|
21595
21359
|
return cmdConnectChrome();
|
|
21596
21360
|
const KNOWN_COMMANDS = new Set([
|
|
21597
21361
|
"health",
|
|
21598
|
-
"mcp",
|
|
21599
21362
|
"setup",
|
|
21600
21363
|
"resolve",
|
|
21601
21364
|
"execute",
|
|
@@ -21656,8 +21419,6 @@ async function main() {
|
|
|
21656
21419
|
switch (command) {
|
|
21657
21420
|
case "health":
|
|
21658
21421
|
return cmdHealth(flags);
|
|
21659
|
-
case "mcp":
|
|
21660
|
-
return cmdMcp(flags);
|
|
21661
21422
|
case "setup":
|
|
21662
21423
|
return cmdSetup(flags);
|
|
21663
21424
|
case "resolve":
|