unbrowse 2.12.1 → 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 -64
- package/bin/unbrowse-wrapper.mjs +14 -85
- package/dist/cli.js +290 -975
- 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 +105 -153
- package/runtime-src/capture/index.ts +5 -7
- package/runtime-src/cli.ts +19 -201
- package/runtime-src/client/index.ts +48 -32
- 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/local-server.ts +6 -35
- package/runtime-src/runtime/setup.ts +7 -11
- package/runtime-src/server.ts +4 -3
- package/runtime-src/version.ts +0 -12
- package/scripts/postinstall.mjs +4 -17
- package/bin/unbrowse-update-hint.mjs +0 -22
- package/dist/mcp.js +0 -20468
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/mcp.ts +0 -1065
- package/runtime-src/runtime/update-hints.ts +0 -351
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;
|
|
@@ -4603,12 +4632,10 @@ async function captureSession(url, authHeaders, cookies, intent, options) {
|
|
|
4603
4632
|
log("capture", `captured ${jsBundleBodies.size} JS bundles for route scanning`);
|
|
4604
4633
|
const responseBodyCount = responseBodies.size;
|
|
4605
4634
|
if (isBlockedAppShell(html) && responseBodyCount < 10 && !hasUsefulCapturedResponses(responseBodies.keys(), url, intent)) {
|
|
4606
|
-
if (options?.forceEphemeral) {
|
|
4607
|
-
|
|
4608
|
-
throw Object.assign(new Error(cloudflareBlocked ? "cloudflare_waf_block" : "blocked_app_shell"), {
|
|
4635
|
+
if (options?.forceEphemeral && html && /Cloudflare|cf\.errors\.css|cf-error-details/i.test(html)) {
|
|
4636
|
+
throw Object.assign(new Error("cloudflare_waf_block"), {
|
|
4609
4637
|
code: "auth_required",
|
|
4610
|
-
login_url:
|
|
4611
|
-
...cloudflareBlocked ? { reason: "cloudflare_waf" } : { reason: "blocked_app_shell" }
|
|
4638
|
+
login_url: url
|
|
4612
4639
|
});
|
|
4613
4640
|
}
|
|
4614
4641
|
retryFreshTab = true;
|
|
@@ -5077,11 +5104,13 @@ var init_capture = __esm(() => {
|
|
|
5077
5104
|
// ../../src/client/index.ts
|
|
5078
5105
|
var exports_client2 = {};
|
|
5079
5106
|
__export(exports_client2, {
|
|
5107
|
+
waitForBackgroundRegistration: () => waitForBackgroundRegistration,
|
|
5080
5108
|
verifyMarketplaceDiscovery: () => verifyMarketplaceDiscovery,
|
|
5081
5109
|
validateManifest: () => validateManifest,
|
|
5082
5110
|
updateEndpointScore: () => updateEndpointScore,
|
|
5083
5111
|
updateEndpointSchema: () => updateEndpointSchema,
|
|
5084
5112
|
syncAgentWallet: () => syncAgentWallet2,
|
|
5113
|
+
startBackgroundRegistration: () => startBackgroundRegistration,
|
|
5085
5114
|
setSkillSplitConfig: () => setSkillSplitConfig,
|
|
5086
5115
|
setSkillPrice: () => setSkillPrice,
|
|
5087
5116
|
searchIntentResolve: () => searchIntentResolve,
|
|
@@ -5217,18 +5246,11 @@ function createInstallTelemetryState2() {
|
|
|
5217
5246
|
}
|
|
5218
5247
|
function getOrCreateInstallTelemetryState2() {
|
|
5219
5248
|
const existing = loadInstallTelemetryState2();
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
saveInstallTelemetryState2(state);
|
|
5226
|
-
return state;
|
|
5227
|
-
}
|
|
5228
|
-
if (!existing?.install_id) {
|
|
5229
|
-
saveInstallTelemetryState2(state);
|
|
5230
|
-
}
|
|
5231
|
-
return state;
|
|
5249
|
+
if (existing?.install_id)
|
|
5250
|
+
return existing;
|
|
5251
|
+
const created = createInstallTelemetryState2();
|
|
5252
|
+
saveInstallTelemetryState2(created);
|
|
5253
|
+
return created;
|
|
5232
5254
|
}
|
|
5233
5255
|
function getInstallId2() {
|
|
5234
5256
|
return getOrCreateInstallTelemetryState2().install_id;
|
|
@@ -5274,7 +5296,6 @@ async function ensureCliInstallTracked2(hostType = detectTelemetryHostType2()) {
|
|
|
5274
5296
|
const createdAt = new Date().toISOString();
|
|
5275
5297
|
const ok = await postTelemetry2("/v1/telemetry/install", {
|
|
5276
5298
|
install_id: state.install_id,
|
|
5277
|
-
landing_token: state.landing_token,
|
|
5278
5299
|
source: "cli-first-seen",
|
|
5279
5300
|
host_type: hostType,
|
|
5280
5301
|
skill: "unbrowse",
|
|
@@ -5294,7 +5315,6 @@ async function recordInstallTelemetryEvent2(source, options) {
|
|
|
5294
5315
|
const createdAt = options?.createdAt ?? new Date().toISOString();
|
|
5295
5316
|
await postTelemetry2("/v1/telemetry/install", {
|
|
5296
5317
|
install_id: getInstallId2(),
|
|
5297
|
-
landing_token: getOrCreateInstallTelemetryState2().landing_token,
|
|
5298
5318
|
source,
|
|
5299
5319
|
host_type: options?.hostType ?? detectTelemetryHostType2(),
|
|
5300
5320
|
skill: options?.skill ?? "unbrowse",
|
|
@@ -5309,7 +5329,6 @@ async function recordFunnelTelemetryEvent2(name, options) {
|
|
|
5309
5329
|
await postTelemetry2("/v1/telemetry/events", {
|
|
5310
5330
|
install_id: getInstallId2(),
|
|
5311
5331
|
session_id: options?.sessionId,
|
|
5312
|
-
landing_token: getOrCreateInstallTelemetryState2().landing_token,
|
|
5313
5332
|
name,
|
|
5314
5333
|
source: options?.source ?? "cli",
|
|
5315
5334
|
host_type: options?.hostType ?? detectTelemetryHostType2(),
|
|
@@ -5530,23 +5549,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
|
|
|
5530
5549
|
rl.close();
|
|
5531
5550
|
}
|
|
5532
5551
|
}
|
|
5533
|
-
async function checkTosStatus2() {
|
|
5552
|
+
async function checkTosStatus2(options) {
|
|
5553
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
5534
5554
|
const config = loadConfig2();
|
|
5535
5555
|
let tosInfo;
|
|
5536
5556
|
try {
|
|
5537
5557
|
tosInfo = await api2("GET", "/v1/tos/current");
|
|
5538
5558
|
} catch {
|
|
5539
|
-
return;
|
|
5559
|
+
return true;
|
|
5540
5560
|
}
|
|
5541
5561
|
if (config?.tos_accepted_version === tosInfo.version) {
|
|
5542
|
-
return;
|
|
5562
|
+
return true;
|
|
5543
5563
|
}
|
|
5544
5564
|
console.log(`
|
|
5545
5565
|
The Unbrowse Terms of Service have been updated.`);
|
|
5546
5566
|
const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
|
|
5547
5567
|
if (!accepted) {
|
|
5548
5568
|
console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
|
|
5549
|
-
|
|
5569
|
+
if (exitOnFailure)
|
|
5570
|
+
process.exit(1);
|
|
5571
|
+
return false;
|
|
5550
5572
|
}
|
|
5551
5573
|
try {
|
|
5552
5574
|
await api2("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
|
|
@@ -5559,16 +5581,20 @@ The Unbrowse Terms of Service have been updated.`);
|
|
|
5559
5581
|
} catch (err) {
|
|
5560
5582
|
console.warn(`Failed to record ToS acceptance: ${err.message}`);
|
|
5561
5583
|
}
|
|
5584
|
+
return true;
|
|
5562
5585
|
}
|
|
5563
5586
|
async function ensureRegistered2(options) {
|
|
5564
5587
|
if (LOCAL_ONLY2)
|
|
5565
5588
|
return;
|
|
5589
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
5566
5590
|
const usableKey = await findUsableApiKey2();
|
|
5567
5591
|
if (usableKey) {
|
|
5568
5592
|
if (usableKey.source === "config") {
|
|
5569
5593
|
console.log("[unbrowse] Restored saved registration.");
|
|
5570
5594
|
}
|
|
5571
|
-
await checkTosStatus2();
|
|
5595
|
+
const accepted2 = await checkTosStatus2({ exitOnFailure });
|
|
5596
|
+
if (!accepted2)
|
|
5597
|
+
return;
|
|
5572
5598
|
try {
|
|
5573
5599
|
const profile = await getMyProfile2();
|
|
5574
5600
|
const wallet = getLocalWalletContext2();
|
|
@@ -5589,7 +5615,9 @@ async function ensureRegistered2(options) {
|
|
|
5589
5615
|
const accepted = await promptTosAcceptance2(tosInfo.summary, tosInfo.url);
|
|
5590
5616
|
if (!accepted) {
|
|
5591
5617
|
console.log("You must accept the Terms of Service to use Unbrowse.");
|
|
5592
|
-
|
|
5618
|
+
if (exitOnFailure)
|
|
5619
|
+
process.exit(1);
|
|
5620
|
+
return;
|
|
5593
5621
|
}
|
|
5594
5622
|
const fallbackName = buildDefaultAgentName2();
|
|
5595
5623
|
const name = options?.promptForEmail ? await promptAgentEmail2(fallbackName) : resolveAgentName2(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
|
|
@@ -5617,8 +5645,36 @@ async function ensureRegistered2(options) {
|
|
|
5617
5645
|
} catch (err) {
|
|
5618
5646
|
console.warn(`Registration failed: ${err.message}`);
|
|
5619
5647
|
console.warn("Set UNBROWSE_API_KEY manually or try again.");
|
|
5620
|
-
|
|
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;
|
|
5621
5673
|
}
|
|
5674
|
+
await Promise.race([
|
|
5675
|
+
backgroundRegistrationPromise,
|
|
5676
|
+
new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
|
5677
|
+
]);
|
|
5622
5678
|
}
|
|
5623
5679
|
function skillCachePath(skillId) {
|
|
5624
5680
|
return join3(getSkillCacheDir(), `${skillId}.json`);
|
|
@@ -6009,7 +6065,7 @@ async function setSkillPrice(skillId, priceUsd) {
|
|
|
6009
6065
|
async function setSkillSplitConfig(skillId, splitConfig) {
|
|
6010
6066
|
return api2("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
|
|
6011
6067
|
}
|
|
6012
|
-
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;
|
|
6013
6069
|
var init_client2 = __esm(() => {
|
|
6014
6070
|
init_cascade();
|
|
6015
6071
|
API_URL2 = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
|
|
@@ -6128,20 +6184,11 @@ function computeCodeHash() {
|
|
|
6128
6184
|
function getGitSha() {
|
|
6129
6185
|
return "unknown";
|
|
6130
6186
|
}
|
|
6131
|
-
|
|
6132
|
-
try {
|
|
6133
|
-
const pkg = JSON.parse(readFileSync3(join4(MODULE_DIR, "..", "package.json"), "utf-8"));
|
|
6134
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
6135
|
-
} catch {
|
|
6136
|
-
return "unknown";
|
|
6137
|
-
}
|
|
6138
|
-
}
|
|
6139
|
-
var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, TRACE_VERSION;
|
|
6187
|
+
var MODULE_DIR, CODE_HASH, GIT_SHA, TRACE_VERSION;
|
|
6140
6188
|
var init_version = __esm(() => {
|
|
6141
6189
|
MODULE_DIR = dirname(fileURLToPath2(import.meta.url));
|
|
6142
6190
|
CODE_HASH = computeCodeHash();
|
|
6143
6191
|
GIT_SHA = getGitSha();
|
|
6144
|
-
PACKAGE_VERSION = getPackageVersion();
|
|
6145
6192
|
TRACE_VERSION = `${CODE_HASH}@${GIT_SHA}`;
|
|
6146
6193
|
});
|
|
6147
6194
|
|
|
@@ -11922,8 +11969,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
|
|
|
11922
11969
|
} catch {}
|
|
11923
11970
|
}
|
|
11924
11971
|
}
|
|
11925
|
-
|
|
11926
|
-
if (!options?.skip_robots_check && !hasAuthContext) {
|
|
11972
|
+
if (!options?.skip_robots_check) {
|
|
11927
11973
|
const allowed = await isAllowedByRobots(url);
|
|
11928
11974
|
if (!allowed) {
|
|
11929
11975
|
const traceId = nanoid5();
|
|
@@ -15315,7 +15361,6 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
|
|
|
15315
15361
|
result: {
|
|
15316
15362
|
message: `Found ${epRanked.length} endpoint(s). Pick one and call POST /v1/skills/${resolvedSkill.skill_id}/execute with params.endpoint_id.`,
|
|
15317
15363
|
skill_id: resolvedSkill.skill_id,
|
|
15318
|
-
suggested_next_operation_id: chunk.available_operation_ids[0],
|
|
15319
15364
|
available_operations: chunk.operations.map((operation) => ({
|
|
15320
15365
|
operation_id: operation.operation_id,
|
|
15321
15366
|
endpoint_id: operation.endpoint_id,
|
|
@@ -16752,7 +16797,7 @@ var init_orchestrator = __esm(async () => {
|
|
|
16752
16797
|
captureDomainLocks = new Map;
|
|
16753
16798
|
skillRouteCache = new Map;
|
|
16754
16799
|
ROUTE_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
|
|
16755
|
-
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");
|
|
16756
16801
|
domainSkillCache = new Map;
|
|
16757
16802
|
DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
16758
16803
|
try {
|
|
@@ -17165,7 +17210,7 @@ async function processIndexJob(job) {
|
|
|
17165
17210
|
}
|
|
17166
17211
|
console.log(`[background-index] completed for ${domain} -> ${published.skill_id}`);
|
|
17167
17212
|
}
|
|
17168
|
-
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;
|
|
17169
17214
|
var init_indexer = __esm(async () => {
|
|
17170
17215
|
init_graph();
|
|
17171
17216
|
init_client2();
|
|
@@ -17173,6 +17218,7 @@ var init_indexer = __esm(async () => {
|
|
|
17173
17218
|
init_domain();
|
|
17174
17219
|
await init_orchestrator();
|
|
17175
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");
|
|
17176
17222
|
SECRET_VALUE_PATTERNS = [
|
|
17177
17223
|
/^eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
|
|
17178
17224
|
/^Bearer\s+\S+/i,
|
|
@@ -17186,7 +17232,6 @@ var init_indexer = __esm(async () => {
|
|
|
17186
17232
|
/^v2\.[A-Za-z0-9_-]{20,}/
|
|
17187
17233
|
];
|
|
17188
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;
|
|
17189
|
-
SKILL_SNAPSHOT_DIR2 = join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
17190
17235
|
indexInFlight = new Map;
|
|
17191
17236
|
});
|
|
17192
17237
|
|
|
@@ -17332,113 +17377,6 @@ var init_session_logs = __esm(() => {
|
|
|
17332
17377
|
init_domain();
|
|
17333
17378
|
});
|
|
17334
17379
|
|
|
17335
|
-
// ../../src/agent-outcome.ts
|
|
17336
|
-
function edgePriority(kind) {
|
|
17337
|
-
switch (kind) {
|
|
17338
|
-
case "parent_child":
|
|
17339
|
-
return 4;
|
|
17340
|
-
case "pagination":
|
|
17341
|
-
return 3;
|
|
17342
|
-
case "dependency":
|
|
17343
|
-
return 2;
|
|
17344
|
-
case "hint":
|
|
17345
|
-
return 1;
|
|
17346
|
-
case "auth":
|
|
17347
|
-
return 0;
|
|
17348
|
-
default:
|
|
17349
|
-
return -1;
|
|
17350
|
-
}
|
|
17351
|
-
}
|
|
17352
|
-
function nextActionWhy(kind, bindingKey, title) {
|
|
17353
|
-
switch (kind) {
|
|
17354
|
-
case "parent_child":
|
|
17355
|
-
return `Likely next detail step after this result. Exposes ${title}.`;
|
|
17356
|
-
case "pagination":
|
|
17357
|
-
return `Likely next page or continuation step. Carries ${bindingKey || "cursor"} forward.`;
|
|
17358
|
-
case "dependency":
|
|
17359
|
-
return `Unlocks the next dependent call using ${bindingKey || "known bindings"}.`;
|
|
17360
|
-
case "auth":
|
|
17361
|
-
return "Useful once authentication is in place.";
|
|
17362
|
-
case "hint":
|
|
17363
|
-
return "Common follow-up action from the current result.";
|
|
17364
|
-
default:
|
|
17365
|
-
return "Likely follow-up action.";
|
|
17366
|
-
}
|
|
17367
|
-
}
|
|
17368
|
-
function operationTitle(operation) {
|
|
17369
|
-
const semantic = [operation.action_kind, operation.resource_kind].filter(Boolean).join(" ").replace(/_/g, " ").trim();
|
|
17370
|
-
return operation.description_out || semantic || operation.endpoint_id;
|
|
17371
|
-
}
|
|
17372
|
-
function buildAgentImpact(timing) {
|
|
17373
|
-
if (!timing?.source)
|
|
17374
|
-
return;
|
|
17375
|
-
return {
|
|
17376
|
-
source: timing.source,
|
|
17377
|
-
cache_hit: timing.cache_hit === true,
|
|
17378
|
-
browser_avoided: !BROWSER_SOURCES.has(timing.source),
|
|
17379
|
-
baseline_total_ms: timing.baseline_total_ms,
|
|
17380
|
-
actual_total_ms: timing.actual_total_ms,
|
|
17381
|
-
time_saved_ms: timing.time_saved_ms,
|
|
17382
|
-
time_saved_pct: timing.time_saved_pct ?? 0,
|
|
17383
|
-
tokens_saved: timing.tokens_saved ?? 0,
|
|
17384
|
-
tokens_saved_pct: timing.tokens_saved_pct ?? 0,
|
|
17385
|
-
baseline_cost_uc: timing.baseline_cost_uc,
|
|
17386
|
-
actual_cost_uc: timing.actual_cost_uc,
|
|
17387
|
-
cost_saved_uc: timing.cost_saved_uc
|
|
17388
|
-
};
|
|
17389
|
-
}
|
|
17390
|
-
function buildNextActions(skill, endpointId, maxActions = 3) {
|
|
17391
|
-
if (!skill?.operation_graph || !endpointId)
|
|
17392
|
-
return [];
|
|
17393
|
-
const graph = skill.operation_graph;
|
|
17394
|
-
const current = graph.operations.find((operation) => operation.endpoint_id === endpointId);
|
|
17395
|
-
if (!current)
|
|
17396
|
-
return [];
|
|
17397
|
-
const byOperationId = new Map(graph.operations.map((operation) => [operation.operation_id, operation]));
|
|
17398
|
-
const scored = new Map;
|
|
17399
|
-
for (const edge of graph.edges) {
|
|
17400
|
-
if (edge.from_operation_id !== current.operation_id)
|
|
17401
|
-
continue;
|
|
17402
|
-
const target = byOperationId.get(edge.to_operation_id);
|
|
17403
|
-
if (!target)
|
|
17404
|
-
continue;
|
|
17405
|
-
const candidate = {
|
|
17406
|
-
operation_id: target.operation_id,
|
|
17407
|
-
endpoint_id: target.endpoint_id,
|
|
17408
|
-
title: operationTitle(target),
|
|
17409
|
-
why: nextActionWhy(edge.kind, edge.binding_key, operationTitle(target)),
|
|
17410
|
-
score: edgePriority(edge.kind) * 10 + Math.round(edge.confidence * 10)
|
|
17411
|
-
};
|
|
17412
|
-
const existing = scored.get(target.operation_id);
|
|
17413
|
-
if (!existing || candidate.score > existing.score) {
|
|
17414
|
-
scored.set(target.operation_id, candidate);
|
|
17415
|
-
}
|
|
17416
|
-
}
|
|
17417
|
-
return [...scored.values()].sort((a, b) => b.score - a.score || a.title.localeCompare(b.title)).slice(0, maxActions).map((candidate) => ({
|
|
17418
|
-
endpoint_id: candidate.endpoint_id,
|
|
17419
|
-
operation_id: candidate.operation_id,
|
|
17420
|
-
title: candidate.title,
|
|
17421
|
-
why: candidate.why,
|
|
17422
|
-
command: `unbrowse execute --skill ${skill.skill_id} --endpoint ${candidate.endpoint_id}`
|
|
17423
|
-
}));
|
|
17424
|
-
}
|
|
17425
|
-
function attachAgentOutcomeHints(payload, opts) {
|
|
17426
|
-
const target = payload;
|
|
17427
|
-
const impact = buildAgentImpact(opts?.timing);
|
|
17428
|
-
if (impact) {
|
|
17429
|
-
target.impact = impact;
|
|
17430
|
-
}
|
|
17431
|
-
const nextActions = buildNextActions(opts?.skill, opts?.endpointId);
|
|
17432
|
-
if (nextActions.length > 0) {
|
|
17433
|
-
target.next_actions = nextActions;
|
|
17434
|
-
}
|
|
17435
|
-
return target;
|
|
17436
|
-
}
|
|
17437
|
-
var BROWSER_SOURCES;
|
|
17438
|
-
var init_agent_outcome = __esm(() => {
|
|
17439
|
-
BROWSER_SOURCES = new Set(["live-capture", "first-pass", "browser-action"]);
|
|
17440
|
-
});
|
|
17441
|
-
|
|
17442
17380
|
// ../../src/api/browse-session.ts
|
|
17443
17381
|
function extractBrowseFailureMessage(value) {
|
|
17444
17382
|
return typeof value === "string" ? value : getKuriErrorMessage(value);
|
|
@@ -17453,6 +17391,8 @@ function isRecoverableBrowseFailure(value) {
|
|
|
17453
17391
|
async function createBrowseSession(sessions, client, injectInterceptor2) {
|
|
17454
17392
|
await client.start().catch(() => {});
|
|
17455
17393
|
const tabId = await client.newTab();
|
|
17394
|
+
if (!tabId)
|
|
17395
|
+
throw new Error("Failed to create browser tab");
|
|
17456
17396
|
await client.harStart(tabId).catch(() => {});
|
|
17457
17397
|
await injectInterceptor2(tabId);
|
|
17458
17398
|
const session = { tabId, url: "about:blank", harActive: true, domain: "" };
|
|
@@ -17475,7 +17415,7 @@ async function adoptExistingBrowseTab(sessions, client, injectInterceptor2, pref
|
|
|
17475
17415
|
const candidate = tabs.find((tab) => {
|
|
17476
17416
|
const domain = extractDomain2(tab.url);
|
|
17477
17417
|
return !!domain && !!normalizedPreferred && domain === normalizedPreferred;
|
|
17478
|
-
}) ?? 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);
|
|
17479
17419
|
if (!candidate?.id)
|
|
17480
17420
|
return null;
|
|
17481
17421
|
await client.harStart(candidate.id).catch(() => {});
|
|
@@ -18032,6 +17972,21 @@ function parseJsonString(value) {
|
|
|
18032
17972
|
return null;
|
|
18033
17973
|
}
|
|
18034
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
|
+
}
|
|
18035
17990
|
async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, options) {
|
|
18036
17991
|
const timeoutMs = options.timeoutMs ?? DEFAULT_SUBMIT_TIMEOUT_MS;
|
|
18037
17992
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -18042,7 +17997,7 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
|
|
|
18042
17997
|
if (waitResult?.status === "found" || waitResult?.status === "ready") {
|
|
18043
17998
|
const url = await client.getCurrentUrl(tabId).catch(() => beforeUrl);
|
|
18044
17999
|
const html = await client.getPageHtml(tabId).catch(() => beforeHtml);
|
|
18045
|
-
return { ok: true, url, html };
|
|
18000
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
|
|
18046
18001
|
}
|
|
18047
18002
|
} catch {}
|
|
18048
18003
|
}
|
|
@@ -18050,20 +18005,20 @@ async function waitForSubmitOutcome(client, tabId, beforeUrl, beforeHtml, option
|
|
|
18050
18005
|
const url = await client.getCurrentUrl(tabId).catch(() => "");
|
|
18051
18006
|
const html = await client.getPageHtml(tabId).catch(() => "");
|
|
18052
18007
|
if (waitFor && isUrlWaitHint(waitFor) && url.includes(waitFor)) {
|
|
18053
|
-
return { ok: true, url, html };
|
|
18008
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
|
|
18054
18009
|
}
|
|
18055
18010
|
if (url && url !== beforeUrl && !url.startsWith("about:blank")) {
|
|
18056
|
-
return { ok: true, url, html };
|
|
18011
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url, html) };
|
|
18057
18012
|
}
|
|
18058
18013
|
if (hasMeaningfulPageChange(beforeHtml, html)) {
|
|
18059
|
-
return { ok: true,
|
|
18014
|
+
return { ok: true, ...await settleSubmitDestination(client, tabId, url || beforeUrl, html) };
|
|
18060
18015
|
}
|
|
18061
18016
|
await sleep(SUBMIT_POLL_INTERVAL_MS);
|
|
18062
18017
|
}
|
|
18063
18018
|
return { ok: false, url: beforeUrl, html: beforeHtml };
|
|
18064
18019
|
}
|
|
18065
18020
|
async function submitBrowseForm(deps, options = {}) {
|
|
18066
|
-
const { client, session,
|
|
18021
|
+
const { client, session, restartCapture, rehydratePlugins } = deps;
|
|
18067
18022
|
const sameOriginFetchFallback = options.sameOriginFetchFallback !== false;
|
|
18068
18023
|
const beforeUrl = await client.getCurrentUrl(session.tabId).catch(() => session.url);
|
|
18069
18024
|
const beforeHtml = await client.getPageHtml(session.tabId).catch(() => "");
|
|
@@ -18089,14 +18044,6 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18089
18044
|
const domOutcome = await waitForSubmitOutcome(client, session.tabId, beforeUrl, beforeHtml, options);
|
|
18090
18045
|
if (domOutcome.ok) {
|
|
18091
18046
|
session.url = domOutcome.url || beforeUrl || session.url;
|
|
18092
|
-
let captureSync2 = null;
|
|
18093
|
-
if (flushCapture) {
|
|
18094
|
-
try {
|
|
18095
|
-
captureSync2 = await flushCapture(session);
|
|
18096
|
-
} catch {
|
|
18097
|
-
captureSync2 = null;
|
|
18098
|
-
}
|
|
18099
|
-
}
|
|
18100
18047
|
await restartCapture(session);
|
|
18101
18048
|
return {
|
|
18102
18049
|
ok: true,
|
|
@@ -18105,8 +18052,7 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18105
18052
|
fallback_used: false,
|
|
18106
18053
|
same_origin_html_rehydrated: false,
|
|
18107
18054
|
wait_for: options.waitFor,
|
|
18108
|
-
submit_meta: submitMeta
|
|
18109
|
-
capture_sync: captureSync2
|
|
18055
|
+
submit_meta: submitMeta
|
|
18110
18056
|
};
|
|
18111
18057
|
}
|
|
18112
18058
|
if (submitError && !isRecoverableBrowseFailure(submitError) && !sameOriginFetchFallback) {
|
|
@@ -18144,14 +18090,6 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18144
18090
|
if (!rehydrate) {
|
|
18145
18091
|
rehydrate = await rehydratePlugins(session.tabId).catch(() => null);
|
|
18146
18092
|
}
|
|
18147
|
-
let captureSync = null;
|
|
18148
|
-
if (flushCapture) {
|
|
18149
|
-
try {
|
|
18150
|
-
captureSync = await flushCapture(session);
|
|
18151
|
-
} catch {
|
|
18152
|
-
captureSync = null;
|
|
18153
|
-
}
|
|
18154
|
-
}
|
|
18155
18093
|
await restartCapture(session);
|
|
18156
18094
|
return {
|
|
18157
18095
|
ok: true,
|
|
@@ -18162,11 +18100,10 @@ async function submitBrowseForm(deps, options = {}) {
|
|
|
18162
18100
|
status: typeof fallbackPayload.status === "number" ? fallbackPayload.status : undefined,
|
|
18163
18101
|
wait_for: options.waitFor,
|
|
18164
18102
|
submit_meta: submitMeta,
|
|
18165
|
-
capture_sync: captureSync,
|
|
18166
18103
|
rehydrate
|
|
18167
18104
|
};
|
|
18168
18105
|
}
|
|
18169
|
-
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;
|
|
18170
18107
|
var init_browse_submit = __esm(() => {
|
|
18171
18108
|
init_browse_session();
|
|
18172
18109
|
});
|
|
@@ -18291,7 +18228,6 @@ import { join as join12 } from "path";
|
|
|
18291
18228
|
function buildAnalyticsSessionPayload(result, opts) {
|
|
18292
18229
|
const source = result.timing?.source ?? result.source;
|
|
18293
18230
|
const apiCalls = result.trace.endpoint_id ? 1 : 0;
|
|
18294
|
-
const browserMode = opts.browser_mode ?? (source === "live-capture" || source === "first-pass" || source === "browser-action" ? "default" : "replaced");
|
|
18295
18231
|
const cachedSkillCalls = opts.cached_skill_calls ?? (apiCalls > 0 && source !== "live-capture" && source !== "first-pass" ? 1 : 0);
|
|
18296
18232
|
const freshIndexCalls = opts.fresh_index_calls ?? (apiCalls > 0 && (source === "live-capture" || source === "first-pass") ? 1 : 0);
|
|
18297
18233
|
return {
|
|
@@ -18303,14 +18239,7 @@ function buildAnalyticsSessionPayload(result, opts) {
|
|
|
18303
18239
|
discovery_queries: opts.discovery_queries,
|
|
18304
18240
|
cached_skill_calls: cachedSkillCalls,
|
|
18305
18241
|
fresh_index_calls: freshIndexCalls,
|
|
18306
|
-
browser_mode:
|
|
18307
|
-
success: result.trace.success ?? true,
|
|
18308
|
-
source,
|
|
18309
|
-
time_saved_ms: result.timing?.time_saved_ms,
|
|
18310
|
-
time_saved_pct: result.timing?.time_saved_pct,
|
|
18311
|
-
tokens_saved: result.trace.tokens_saved ?? result.timing?.tokens_saved,
|
|
18312
|
-
tokens_saved_pct: result.trace.tokens_saved_pct ?? result.timing?.tokens_saved_pct,
|
|
18313
|
-
cost_saved_uc: result.timing?.cost_saved_uc
|
|
18242
|
+
browser_mode: opts.browser_mode ?? "unknown"
|
|
18314
18243
|
};
|
|
18315
18244
|
}
|
|
18316
18245
|
function passiveIndexFromRequests(requests, pageUrl) {
|
|
@@ -18491,7 +18420,11 @@ async function registerRoutes(app) {
|
|
|
18491
18420
|
app.addHook("onRequest", async (req, reply) => {
|
|
18492
18421
|
if (req.url === "/health" || req.url === "/v1/stats")
|
|
18493
18422
|
return;
|
|
18494
|
-
|
|
18423
|
+
let key = getApiKey2();
|
|
18424
|
+
if (!key) {
|
|
18425
|
+
await waitForBackgroundRegistration(15000);
|
|
18426
|
+
key = getApiKey2();
|
|
18427
|
+
}
|
|
18495
18428
|
if (!key) {
|
|
18496
18429
|
return reply.code(401).send({
|
|
18497
18430
|
error: "api_key_required",
|
|
@@ -18507,11 +18440,7 @@ async function registerRoutes(app) {
|
|
|
18507
18440
|
return reply.code(400).send({ error: "intent required" });
|
|
18508
18441
|
try {
|
|
18509
18442
|
const result = await resolveAndExecute(intent, params ?? {}, context, projection, { confirm_unsafe, dry_run, force_capture, client_scope: clientScope });
|
|
18510
|
-
const res =
|
|
18511
|
-
skill: result.skill,
|
|
18512
|
-
endpointId: result.trace.endpoint_id,
|
|
18513
|
-
timing: result.timing
|
|
18514
|
-
});
|
|
18443
|
+
const res = result;
|
|
18515
18444
|
if (result.timing) {
|
|
18516
18445
|
res.timing = result.timing;
|
|
18517
18446
|
}
|
|
@@ -18520,9 +18449,10 @@ async function registerRoutes(app) {
|
|
|
18520
18449
|
res.available_endpoints = innerResult.available_endpoints;
|
|
18521
18450
|
}
|
|
18522
18451
|
await recordAnalyticsSession(buildAnalyticsSessionPayload(result, {
|
|
18452
|
+
browser_mode: "replaced",
|
|
18523
18453
|
discovery_queries: 1
|
|
18524
18454
|
})).catch(() => {});
|
|
18525
|
-
return reply.send(
|
|
18455
|
+
return reply.send(result);
|
|
18526
18456
|
} catch (err) {
|
|
18527
18457
|
return reply.code(500).send({ error: err.message });
|
|
18528
18458
|
}
|
|
@@ -18732,33 +18662,26 @@ async function registerRoutes(app) {
|
|
|
18732
18662
|
recordExecution(freshResult.trace.skill_id, freshResult.trace.endpoint_id, freshResult.trace, skill).catch(() => {});
|
|
18733
18663
|
}
|
|
18734
18664
|
await recordAnalyticsSession(buildAnalyticsSessionPayload(freshResult, {
|
|
18665
|
+
browser_mode: "manual",
|
|
18735
18666
|
discovery_queries: 1
|
|
18736
18667
|
})).catch(() => {});
|
|
18737
|
-
|
|
18668
|
+
return reply.send({
|
|
18738
18669
|
...freshResult,
|
|
18739
18670
|
_recovery: {
|
|
18740
18671
|
reason: "stale_endpoint_404",
|
|
18741
18672
|
original_skill_id: skill_id,
|
|
18742
18673
|
message: "Original endpoint returned 404. Auto-recovered with fresh capture."
|
|
18743
18674
|
}
|
|
18744
|
-
}, {
|
|
18745
|
-
skill: freshResult.skill ?? skill,
|
|
18746
|
-
endpointId: freshResult.trace.endpoint_id,
|
|
18747
|
-
timing: freshResult.timing
|
|
18748
|
-
});
|
|
18749
|
-
return reply.send({
|
|
18750
|
-
...recovered
|
|
18751
18675
|
});
|
|
18752
18676
|
} catch {}
|
|
18753
18677
|
}
|
|
18754
18678
|
await recordAnalyticsSession(buildAnalyticsSessionPayload(execResult, {
|
|
18755
|
-
|
|
18679
|
+
browser_mode: "manual",
|
|
18680
|
+
discovery_queries: 0,
|
|
18681
|
+
cached_skill_calls: execResult.trace.endpoint_id ? 1 : 0,
|
|
18682
|
+
fresh_index_calls: 0
|
|
18756
18683
|
})).catch(() => {});
|
|
18757
|
-
|
|
18758
|
-
skill,
|
|
18759
|
-
endpointId: execResult.trace.endpoint_id
|
|
18760
|
-
});
|
|
18761
|
-
return reply.send(response);
|
|
18684
|
+
return reply.send(execResult);
|
|
18762
18685
|
} catch (err) {
|
|
18763
18686
|
return reply.code(500).send({ error: err.message });
|
|
18764
18687
|
}
|
|
@@ -18860,13 +18783,7 @@ async function registerRoutes(app) {
|
|
|
18860
18783
|
return reply.code(500).send({ error: err.message });
|
|
18861
18784
|
}
|
|
18862
18785
|
});
|
|
18863
|
-
app.get("/health", async (_req, reply) => reply.send({
|
|
18864
|
-
status: "ok",
|
|
18865
|
-
trace_version: TRACE_VERSION,
|
|
18866
|
-
code_hash: CODE_HASH,
|
|
18867
|
-
git_sha: GIT_SHA,
|
|
18868
|
-
package_version: PACKAGE_VERSION
|
|
18869
|
-
}));
|
|
18786
|
+
app.get("/health", async (_req, reply) => reply.send({ status: "ok", trace_version: TRACE_VERSION, code_hash: CODE_HASH, git_sha: GIT_SHA }));
|
|
18870
18787
|
app.get("/v1/sessions/:domain", async (req, reply) => {
|
|
18871
18788
|
const { domain } = req.params;
|
|
18872
18789
|
const query = req.query;
|
|
@@ -18913,71 +18830,6 @@ async function registerRoutes(app) {
|
|
|
18913
18830
|
session.harActive = true;
|
|
18914
18831
|
await injectInterceptor(session.tabId).catch(() => {});
|
|
18915
18832
|
}
|
|
18916
|
-
async function flushBrowseCapture(session, options = {}) {
|
|
18917
|
-
let intercepted = [];
|
|
18918
|
-
try {
|
|
18919
|
-
const raw = await collectInterceptedRequests(session.tabId);
|
|
18920
|
-
intercepted = raw.map((request) => ({
|
|
18921
|
-
url: request.url,
|
|
18922
|
-
method: request.method,
|
|
18923
|
-
request_headers: request.request_headers ?? {},
|
|
18924
|
-
request_body: request.request_body,
|
|
18925
|
-
response_status: request.response_status,
|
|
18926
|
-
response_headers: request.response_headers ?? {},
|
|
18927
|
-
response_body: request.response_body,
|
|
18928
|
-
timestamp: request.timestamp
|
|
18929
|
-
}));
|
|
18930
|
-
} catch {}
|
|
18931
|
-
let harEntries = [];
|
|
18932
|
-
if (session.harActive) {
|
|
18933
|
-
try {
|
|
18934
|
-
const { entries } = await harStop(session.tabId);
|
|
18935
|
-
harEntries = entries;
|
|
18936
|
-
} catch {}
|
|
18937
|
-
}
|
|
18938
|
-
session.harActive = false;
|
|
18939
|
-
const allRequests = mergeBrowseRequests(intercepted, harEntries, session.url);
|
|
18940
|
-
const syncResult = await cacheBrowseRequests({
|
|
18941
|
-
sessionUrl: session.url,
|
|
18942
|
-
sessionDomain: session.domain,
|
|
18943
|
-
requests: allRequests,
|
|
18944
|
-
getPageHtml: () => getPageHtml(session.tabId)
|
|
18945
|
-
});
|
|
18946
|
-
let backgroundPublishQueued = false;
|
|
18947
|
-
if (options.queueBackgroundPublish) {
|
|
18948
|
-
if (allRequests.length > 0) {
|
|
18949
|
-
passiveIndexFromRequests(allRequests, session.url);
|
|
18950
|
-
backgroundPublishQueued = true;
|
|
18951
|
-
} else if (syncResult.skill) {
|
|
18952
|
-
queueBackgroundIndex({
|
|
18953
|
-
skill: { ...syncResult.skill },
|
|
18954
|
-
domain: syncResult.domain,
|
|
18955
|
-
intent: syncResult.skill.intent_signature || `browse ${syncResult.domain}`,
|
|
18956
|
-
contextUrl: session.url,
|
|
18957
|
-
cacheKey: `browse-submit:${syncResult.domain}:${Date.now()}`
|
|
18958
|
-
});
|
|
18959
|
-
backgroundPublishQueued = true;
|
|
18960
|
-
}
|
|
18961
|
-
}
|
|
18962
|
-
return {
|
|
18963
|
-
indexed: syncResult.indexed,
|
|
18964
|
-
mode: syncResult.mode,
|
|
18965
|
-
domain: syncResult.domain,
|
|
18966
|
-
skill_id: syncResult.skill?.skill_id ?? null,
|
|
18967
|
-
endpoint_count: syncResult.skill?.endpoints.length ?? 0,
|
|
18968
|
-
endpoints: (syncResult.skill?.endpoints ?? []).map((endpoint) => ({
|
|
18969
|
-
endpoint_id: endpoint.endpoint_id,
|
|
18970
|
-
method: endpoint.method,
|
|
18971
|
-
url_template: endpoint.url_template,
|
|
18972
|
-
description: endpoint.description,
|
|
18973
|
-
trigger_url: endpoint.trigger_url,
|
|
18974
|
-
action_kind: endpoint.semantic?.action_kind,
|
|
18975
|
-
resource_kind: endpoint.semantic?.resource_kind
|
|
18976
|
-
})),
|
|
18977
|
-
request_count: allRequests.length,
|
|
18978
|
-
background_publish_queued: backgroundPublishQueued
|
|
18979
|
-
};
|
|
18980
|
-
}
|
|
18981
18833
|
app.post("/v1/browse/go", async (req, reply) => {
|
|
18982
18834
|
const { url } = req.body;
|
|
18983
18835
|
if (!url)
|
|
@@ -19034,7 +18886,6 @@ async function registerRoutes(app) {
|
|
|
19034
18886
|
const { session, result, recovered } = await withRecoveredBrowseSession(browseSessions, exports_client, injectInterceptor, async (session2) => submitBrowseForm({
|
|
19035
18887
|
client: exports_client,
|
|
19036
18888
|
session: session2,
|
|
19037
|
-
flushCapture: async (session3) => await flushBrowseCapture(session3, { queueBackgroundPublish: true }),
|
|
19038
18889
|
restartCapture: restartBrowseCapture,
|
|
19039
18890
|
rehydratePlugins: bestEffortRehydratePlugins
|
|
19040
18891
|
}, {
|
|
@@ -19047,10 +18898,8 @@ async function registerRoutes(app) {
|
|
|
19047
18898
|
session.url = result.url || await getCurrentUrl(session.tabId).catch(() => session.url);
|
|
19048
18899
|
session.domain = profileName(session.url);
|
|
19049
18900
|
const statusCode = result.ok ? 200 : result.recoverable ? 502 : 400;
|
|
19050
|
-
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.";
|
|
19051
18901
|
return reply.code(statusCode).send({
|
|
19052
18902
|
...result,
|
|
19053
|
-
next_step: nextStep,
|
|
19054
18903
|
recovered,
|
|
19055
18904
|
tab_id: session.tabId,
|
|
19056
18905
|
url: session.url
|
|
@@ -19160,18 +19009,57 @@ async function registerRoutes(app) {
|
|
|
19160
19009
|
const session = browseSessions.get("default");
|
|
19161
19010
|
if (!session)
|
|
19162
19011
|
return reply.send({ ok: false, error: "no active session" });
|
|
19163
|
-
|
|
19164
|
-
|
|
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(() => {});
|
|
19165
19046
|
return reply.send({
|
|
19166
19047
|
ok: true,
|
|
19167
19048
|
tab_id: session.tabId,
|
|
19168
19049
|
indexed: syncResult.indexed,
|
|
19169
19050
|
mode: syncResult.mode,
|
|
19170
19051
|
domain: syncResult.domain,
|
|
19171
|
-
skill_id: syncResult.skill_id,
|
|
19172
|
-
endpoint_count: syncResult.
|
|
19173
|
-
endpoints: syncResult.endpoints
|
|
19174
|
-
|
|
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
|
+
}))
|
|
19175
19063
|
});
|
|
19176
19064
|
});
|
|
19177
19065
|
app.post("/v1/browse/close", async (_req, reply) => {
|
|
@@ -19181,16 +19069,42 @@ async function registerRoutes(app) {
|
|
|
19181
19069
|
if (session.domain) {
|
|
19182
19070
|
await authProfileSave(session.tabId, session.domain).catch(() => {});
|
|
19183
19071
|
}
|
|
19184
|
-
|
|
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);
|
|
19185
19101
|
await closeTab(session.tabId).catch(() => {});
|
|
19186
19102
|
browseSessions.delete("default");
|
|
19187
19103
|
return reply.send({
|
|
19188
19104
|
ok: true,
|
|
19189
19105
|
indexed: syncResult.indexed,
|
|
19190
19106
|
mode: syncResult.mode,
|
|
19191
|
-
endpoint_count: syncResult.
|
|
19192
|
-
request_count: syncResult.request_count,
|
|
19193
|
-
background_publish_queued: syncResult.background_publish_queued,
|
|
19107
|
+
endpoint_count: syncResult.skill?.endpoints.length ?? 0,
|
|
19194
19108
|
auth_saved: session.domain || null
|
|
19195
19109
|
});
|
|
19196
19110
|
});
|
|
@@ -19217,7 +19131,6 @@ var init_routes = __esm(async () => {
|
|
|
19217
19131
|
init_ratelimit();
|
|
19218
19132
|
init_graph();
|
|
19219
19133
|
init_session_logs();
|
|
19220
|
-
init_agent_outcome();
|
|
19221
19134
|
init_browse_session();
|
|
19222
19135
|
init_browse_submit();
|
|
19223
19136
|
await __promiseAll([
|
|
@@ -19274,7 +19187,7 @@ async function startUnbrowseServer(options = {}) {
|
|
|
19274
19187
|
try {
|
|
19275
19188
|
execSync2("pkill -f chrome-headless-shell", { stdio: "ignore" });
|
|
19276
19189
|
} catch {}
|
|
19277
|
-
|
|
19190
|
+
startBackgroundRegistration();
|
|
19278
19191
|
const app = Fastify({ logger: options.logger ?? true });
|
|
19279
19192
|
await app.register(cors, { origin: true });
|
|
19280
19193
|
await registerRateLimiter(app);
|
|
@@ -19309,7 +19222,6 @@ var init_server = __esm(async () => {
|
|
|
19309
19222
|
|
|
19310
19223
|
// ../../src/cli.ts
|
|
19311
19224
|
import { config as loadEnv } from "dotenv";
|
|
19312
|
-
import { spawn as spawn3 } from "child_process";
|
|
19313
19225
|
|
|
19314
19226
|
// ../../src/client/index.ts
|
|
19315
19227
|
init_cascade();
|
|
@@ -19394,18 +19306,11 @@ function createInstallTelemetryState() {
|
|
|
19394
19306
|
}
|
|
19395
19307
|
function getOrCreateInstallTelemetryState() {
|
|
19396
19308
|
const existing = loadInstallTelemetryState();
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
saveInstallTelemetryState(state);
|
|
19403
|
-
return state;
|
|
19404
|
-
}
|
|
19405
|
-
if (!existing?.install_id) {
|
|
19406
|
-
saveInstallTelemetryState(state);
|
|
19407
|
-
}
|
|
19408
|
-
return state;
|
|
19309
|
+
if (existing?.install_id)
|
|
19310
|
+
return existing;
|
|
19311
|
+
const created = createInstallTelemetryState();
|
|
19312
|
+
saveInstallTelemetryState(created);
|
|
19313
|
+
return created;
|
|
19409
19314
|
}
|
|
19410
19315
|
function getInstallId() {
|
|
19411
19316
|
return getOrCreateInstallTelemetryState().install_id;
|
|
@@ -19451,7 +19356,6 @@ async function ensureCliInstallTracked(hostType = detectTelemetryHostType()) {
|
|
|
19451
19356
|
const createdAt = new Date().toISOString();
|
|
19452
19357
|
const ok = await postTelemetry("/v1/telemetry/install", {
|
|
19453
19358
|
install_id: state.install_id,
|
|
19454
|
-
landing_token: state.landing_token,
|
|
19455
19359
|
source: "cli-first-seen",
|
|
19456
19360
|
host_type: hostType,
|
|
19457
19361
|
skill: "unbrowse",
|
|
@@ -19471,7 +19375,6 @@ async function recordInstallTelemetryEvent(source, options) {
|
|
|
19471
19375
|
const createdAt = options?.createdAt ?? new Date().toISOString();
|
|
19472
19376
|
await postTelemetry("/v1/telemetry/install", {
|
|
19473
19377
|
install_id: getInstallId(),
|
|
19474
|
-
landing_token: getOrCreateInstallTelemetryState().landing_token,
|
|
19475
19378
|
source,
|
|
19476
19379
|
host_type: options?.hostType ?? detectTelemetryHostType(),
|
|
19477
19380
|
skill: options?.skill ?? "unbrowse",
|
|
@@ -19486,7 +19389,6 @@ async function recordFunnelTelemetryEvent(name, options) {
|
|
|
19486
19389
|
await postTelemetry("/v1/telemetry/events", {
|
|
19487
19390
|
install_id: getInstallId(),
|
|
19488
19391
|
session_id: options?.sessionId,
|
|
19489
|
-
landing_token: getOrCreateInstallTelemetryState().landing_token,
|
|
19490
19392
|
name,
|
|
19491
19393
|
source: options?.source ?? "cli",
|
|
19492
19394
|
host_type: options?.hostType ?? detectTelemetryHostType(),
|
|
@@ -19701,23 +19603,26 @@ Email for this agent (leave blank to use a local device id): `, resolve);
|
|
|
19701
19603
|
rl.close();
|
|
19702
19604
|
}
|
|
19703
19605
|
}
|
|
19704
|
-
async function checkTosStatus() {
|
|
19606
|
+
async function checkTosStatus(options) {
|
|
19607
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
19705
19608
|
const config = loadConfig();
|
|
19706
19609
|
let tosInfo;
|
|
19707
19610
|
try {
|
|
19708
19611
|
tosInfo = await api("GET", "/v1/tos/current");
|
|
19709
19612
|
} catch {
|
|
19710
|
-
return;
|
|
19613
|
+
return true;
|
|
19711
19614
|
}
|
|
19712
19615
|
if (config?.tos_accepted_version === tosInfo.version) {
|
|
19713
|
-
return;
|
|
19616
|
+
return true;
|
|
19714
19617
|
}
|
|
19715
19618
|
console.log(`
|
|
19716
19619
|
The Unbrowse Terms of Service have been updated.`);
|
|
19717
19620
|
const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
|
|
19718
19621
|
if (!accepted) {
|
|
19719
19622
|
console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
|
|
19720
|
-
|
|
19623
|
+
if (exitOnFailure)
|
|
19624
|
+
process.exit(1);
|
|
19625
|
+
return false;
|
|
19721
19626
|
}
|
|
19722
19627
|
try {
|
|
19723
19628
|
await api("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
|
|
@@ -19730,16 +19635,20 @@ The Unbrowse Terms of Service have been updated.`);
|
|
|
19730
19635
|
} catch (err) {
|
|
19731
19636
|
console.warn(`Failed to record ToS acceptance: ${err.message}`);
|
|
19732
19637
|
}
|
|
19638
|
+
return true;
|
|
19733
19639
|
}
|
|
19734
19640
|
async function ensureRegistered(options) {
|
|
19735
19641
|
if (LOCAL_ONLY)
|
|
19736
19642
|
return;
|
|
19643
|
+
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
19737
19644
|
const usableKey = await findUsableApiKey();
|
|
19738
19645
|
if (usableKey) {
|
|
19739
19646
|
if (usableKey.source === "config") {
|
|
19740
19647
|
console.log("[unbrowse] Restored saved registration.");
|
|
19741
19648
|
}
|
|
19742
|
-
await checkTosStatus();
|
|
19649
|
+
const accepted2 = await checkTosStatus({ exitOnFailure });
|
|
19650
|
+
if (!accepted2)
|
|
19651
|
+
return;
|
|
19743
19652
|
try {
|
|
19744
19653
|
const profile = await getMyProfile();
|
|
19745
19654
|
const wallet = getLocalWalletContext();
|
|
@@ -19760,7 +19669,9 @@ async function ensureRegistered(options) {
|
|
|
19760
19669
|
const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
|
|
19761
19670
|
if (!accepted) {
|
|
19762
19671
|
console.log("You must accept the Terms of Service to use Unbrowse.");
|
|
19763
|
-
|
|
19672
|
+
if (exitOnFailure)
|
|
19673
|
+
process.exit(1);
|
|
19674
|
+
return;
|
|
19764
19675
|
}
|
|
19765
19676
|
const fallbackName = buildDefaultAgentName();
|
|
19766
19677
|
const name = options?.promptForEmail ? await promptAgentEmail(fallbackName) : resolveAgentName(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
|
|
@@ -19788,7 +19699,8 @@ async function ensureRegistered(options) {
|
|
|
19788
19699
|
} catch (err) {
|
|
19789
19700
|
console.warn(`Registration failed: ${err.message}`);
|
|
19790
19701
|
console.warn("Set UNBROWSE_API_KEY manually or try again.");
|
|
19791
|
-
|
|
19702
|
+
if (exitOnFailure)
|
|
19703
|
+
process.exit(1);
|
|
19792
19704
|
}
|
|
19793
19705
|
}
|
|
19794
19706
|
async function getMyProfile() {
|
|
@@ -20005,22 +19917,14 @@ init_supervisor();
|
|
|
20005
19917
|
import { openSync, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
20006
19918
|
import path6 from "node:path";
|
|
20007
19919
|
import { spawn as spawn2 } from "node:child_process";
|
|
20008
|
-
function
|
|
20009
|
-
return !!runningVersion && runningVersion !== installedVersion;
|
|
20010
|
-
}
|
|
20011
|
-
async function fetchServerHealth(baseUrl, timeoutMs = 2000) {
|
|
19920
|
+
async function isServerHealthy(baseUrl, timeoutMs = 2000) {
|
|
20012
19921
|
try {
|
|
20013
19922
|
const res = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
20014
|
-
|
|
20015
|
-
return null;
|
|
20016
|
-
return await res.json();
|
|
19923
|
+
return res.ok;
|
|
20017
19924
|
} catch {
|
|
20018
|
-
return
|
|
19925
|
+
return false;
|
|
20019
19926
|
}
|
|
20020
19927
|
}
|
|
20021
|
-
async function isServerHealthy(baseUrl, timeoutMs = 2000) {
|
|
20022
|
-
return !!await fetchServerHealth(baseUrl, timeoutMs);
|
|
20023
|
-
}
|
|
20024
19928
|
async function waitForHealthy(baseUrl, timeoutMs) {
|
|
20025
19929
|
const start2 = Date.now();
|
|
20026
19930
|
while (Date.now() - start2 < timeoutMs) {
|
|
@@ -20102,23 +20006,10 @@ function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
|
|
|
20102
20006
|
}
|
|
20103
20007
|
var supervisor = new LocalSupervisor;
|
|
20104
20008
|
async function ensureLocalServer(baseUrl, noAutoStart, metaUrl) {
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20108
|
-
|
|
20109
|
-
if (isServerVersionMismatch(runningVersion, installedVersion)) {
|
|
20110
|
-
const versionInfo = checkServerVersion(baseUrl, metaUrl);
|
|
20111
|
-
if (versionInfo?.needs_restart) {
|
|
20112
|
-
stopServer(baseUrl);
|
|
20113
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
20114
|
-
} else {
|
|
20115
|
-
throw new Error(`Server version mismatch on ${baseUrl}: running ${runningVersion}, installed ${installedVersion}. Run "unbrowse restart" or stop the stale server bound to that port.`);
|
|
20116
|
-
}
|
|
20117
|
-
} else {
|
|
20118
|
-
if (!supervisor.isRunning())
|
|
20119
|
-
await supervisor.start();
|
|
20120
|
-
return;
|
|
20121
|
-
}
|
|
20009
|
+
if (await isServerHealthy(baseUrl)) {
|
|
20010
|
+
if (!supervisor.isRunning())
|
|
20011
|
+
await supervisor.start();
|
|
20012
|
+
return;
|
|
20122
20013
|
}
|
|
20123
20014
|
const pidFile = getServerPidFile(baseUrl);
|
|
20124
20015
|
const existing = readPidState(pidFile);
|
|
@@ -20207,26 +20098,7 @@ async function restartServer(baseUrl, metaUrl) {
|
|
|
20207
20098
|
// ../../src/runtime/paths.ts
|
|
20208
20099
|
import { existsSync as existsSync13, mkdirSync as mkdirSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
20209
20100
|
import path7 from "node:path";
|
|
20210
|
-
import { createRequire as createRequire3 } from "node:module";
|
|
20211
20101
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
20212
|
-
function resolveSiblingEntrypoint2(metaUrl, basename) {
|
|
20213
|
-
const file = fileURLToPath3(metaUrl);
|
|
20214
|
-
return path7.join(path7.dirname(file), `${basename}${path7.extname(file) || ".js"}`);
|
|
20215
|
-
}
|
|
20216
|
-
function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
|
|
20217
|
-
if (path7.extname(entrypoint) !== ".ts")
|
|
20218
|
-
return [entrypoint];
|
|
20219
|
-
if (process.versions.bun)
|
|
20220
|
-
return [entrypoint];
|
|
20221
|
-
try {
|
|
20222
|
-
const req = createRequire3(metaUrl);
|
|
20223
|
-
const tsxPkg = req.resolve("tsx/package.json");
|
|
20224
|
-
const tsxLoader = path7.join(path7.dirname(tsxPkg), "dist", "loader.mjs");
|
|
20225
|
-
if (existsSync13(tsxLoader))
|
|
20226
|
-
return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
|
|
20227
|
-
} catch {}
|
|
20228
|
-
return ["--import", "tsx", entrypoint];
|
|
20229
|
-
}
|
|
20230
20102
|
function isMainModule(metaUrl) {
|
|
20231
20103
|
const entry = process.argv[1];
|
|
20232
20104
|
if (!entry)
|
|
@@ -20248,7 +20120,7 @@ await init_orchestrator();
|
|
|
20248
20120
|
import { join as join13 } from "node:path";
|
|
20249
20121
|
import { homedir as homedir7 } from "node:os";
|
|
20250
20122
|
var UNBROWSE_CONFIG_PATH2 = join13(homedir7(), ".unbrowse", "config.json");
|
|
20251
|
-
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");
|
|
20252
20124
|
var indexInFlight2 = new Map;
|
|
20253
20125
|
async function drainPendingIndexJobs() {
|
|
20254
20126
|
const pending = [...indexInFlight2.values()];
|
|
@@ -20277,209 +20149,9 @@ init_paths();
|
|
|
20277
20149
|
init_client();
|
|
20278
20150
|
init_logger();
|
|
20279
20151
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
20280
|
-
import { existsSync as
|
|
20281
|
-
import os5 from "node:os";
|
|
20282
|
-
import path9 from "node:path";
|
|
20283
|
-
|
|
20284
|
-
// ../../src/runtime/update-hints.ts
|
|
20285
|
-
init_paths();
|
|
20286
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
20152
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "node:fs";
|
|
20287
20153
|
import os4 from "node:os";
|
|
20288
20154
|
import path8 from "node:path";
|
|
20289
|
-
var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
20290
|
-
var CODEX_MARKER = "# Unbrowse update hints — managed by unbrowse setup";
|
|
20291
|
-
function getHomeDir() {
|
|
20292
|
-
return process.env.HOME || os4.homedir();
|
|
20293
|
-
}
|
|
20294
|
-
function getConfigDir3() {
|
|
20295
|
-
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
20296
|
-
return process.env.UNBROWSE_CONFIG_DIR;
|
|
20297
|
-
return path8.join(getHomeDir(), ".unbrowse");
|
|
20298
|
-
}
|
|
20299
|
-
function ensureDir2(dir) {
|
|
20300
|
-
if (!existsSync14(dir))
|
|
20301
|
-
mkdirSync12(dir, { recursive: true });
|
|
20302
|
-
return dir;
|
|
20303
|
-
}
|
|
20304
|
-
function readJsonFile(file) {
|
|
20305
|
-
try {
|
|
20306
|
-
return JSON.parse(readFileSync11(file, "utf8"));
|
|
20307
|
-
} catch {
|
|
20308
|
-
return null;
|
|
20309
|
-
}
|
|
20310
|
-
}
|
|
20311
|
-
function writeJsonFile(file, value) {
|
|
20312
|
-
ensureDir2(path8.dirname(file));
|
|
20313
|
-
writeFileSync11(file, `${JSON.stringify(value, null, 2)}
|
|
20314
|
-
`);
|
|
20315
|
-
}
|
|
20316
|
-
function getInstallSourcePath() {
|
|
20317
|
-
return path8.join(getConfigDir3(), "install-source.json");
|
|
20318
|
-
}
|
|
20319
|
-
function detectRepoRoot(start2) {
|
|
20320
|
-
let dir = path8.resolve(start2);
|
|
20321
|
-
const root = path8.parse(dir).root;
|
|
20322
|
-
while (dir !== root) {
|
|
20323
|
-
if (existsSync14(path8.join(dir, ".git")))
|
|
20324
|
-
return dir;
|
|
20325
|
-
dir = path8.dirname(dir);
|
|
20326
|
-
}
|
|
20327
|
-
return;
|
|
20328
|
-
}
|
|
20329
|
-
function detectInstallMethod(packageRoot) {
|
|
20330
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "repo-clone")
|
|
20331
|
-
return "repo-clone";
|
|
20332
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
|
|
20333
|
-
return "npm-global";
|
|
20334
|
-
if (packageRoot.includes(`${path8.sep}node_modules${path8.sep}`))
|
|
20335
|
-
return "npm-global";
|
|
20336
|
-
return detectRepoRoot(packageRoot) ? "repo-clone" : "unknown";
|
|
20337
|
-
}
|
|
20338
|
-
function detectInstallHost(repoRoot) {
|
|
20339
|
-
const explicit = process.env.UNBROWSE_SETUP_HOST;
|
|
20340
|
-
if (explicit)
|
|
20341
|
-
return explicit;
|
|
20342
|
-
if (!repoRoot)
|
|
20343
|
-
return "unknown";
|
|
20344
|
-
const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir(), ".codex");
|
|
20345
|
-
if (repoRoot === path8.join(codexHome, "skills", "unbrowse"))
|
|
20346
|
-
return "codex";
|
|
20347
|
-
if (repoRoot === path8.join(getHomeDir(), ".claude", "skills", "unbrowse"))
|
|
20348
|
-
return "claude";
|
|
20349
|
-
if (repoRoot === path8.join(getHomeDir(), "unbrowse"))
|
|
20350
|
-
return "off";
|
|
20351
|
-
return "unknown";
|
|
20352
|
-
}
|
|
20353
|
-
function resolveInstallSource(metaUrl) {
|
|
20354
|
-
const packageRoot = getPackageRoot(metaUrl);
|
|
20355
|
-
const envRepoRoot = process.env.UNBROWSE_SETUP_ROOT || undefined;
|
|
20356
|
-
const repoRoot = envRepoRoot || detectRepoRoot(packageRoot);
|
|
20357
|
-
return {
|
|
20358
|
-
method: detectInstallMethod(packageRoot),
|
|
20359
|
-
host: detectInstallHost(repoRoot),
|
|
20360
|
-
package_root: packageRoot,
|
|
20361
|
-
repo_root: repoRoot,
|
|
20362
|
-
recorded_at: new Date().toISOString()
|
|
20363
|
-
};
|
|
20364
|
-
}
|
|
20365
|
-
function saveInstallSource(metaUrl) {
|
|
20366
|
-
const state = resolveInstallSource(metaUrl);
|
|
20367
|
-
writeJsonFile(getInstallSourcePath(), state);
|
|
20368
|
-
return state;
|
|
20369
|
-
}
|
|
20370
|
-
function loadInstallSource(metaUrl) {
|
|
20371
|
-
return readJsonFile(getInstallSourcePath()) ?? resolveInstallSource(metaUrl);
|
|
20372
|
-
}
|
|
20373
|
-
function commandIncludesHook(command, marker) {
|
|
20374
|
-
return typeof command === "string" && command.includes(marker);
|
|
20375
|
-
}
|
|
20376
|
-
function getCodexConfigPath() {
|
|
20377
|
-
const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir(), ".codex");
|
|
20378
|
-
return path8.join(codexHome, "config.toml");
|
|
20379
|
-
}
|
|
20380
|
-
function getClaudeSettingsPath() {
|
|
20381
|
-
return path8.join(getHomeDir(), ".claude", "settings.json");
|
|
20382
|
-
}
|
|
20383
|
-
function getHookScriptPath(metaUrl) {
|
|
20384
|
-
return path8.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
|
|
20385
|
-
}
|
|
20386
|
-
function ensureCodexHooksFeature(content) {
|
|
20387
|
-
if (/\bcodex_hooks\s*=\s*true\b/.test(content))
|
|
20388
|
-
return content;
|
|
20389
|
-
if (/\[features\]/.test(content)) {
|
|
20390
|
-
return content.replace(/\[features\]\r?\n/, (match) => `${match}codex_hooks = true
|
|
20391
|
-
`);
|
|
20392
|
-
}
|
|
20393
|
-
const prefix = content && !content.endsWith(`
|
|
20394
|
-
`) ? `
|
|
20395
|
-
` : "";
|
|
20396
|
-
return `${content}${prefix}[features]
|
|
20397
|
-
codex_hooks = true
|
|
20398
|
-
`;
|
|
20399
|
-
}
|
|
20400
|
-
function writeCodexHook(metaUrl) {
|
|
20401
|
-
const configPath = getCodexConfigPath();
|
|
20402
|
-
if (!existsSync14(path8.dirname(configPath))) {
|
|
20403
|
-
return { host: "codex", action: "not-detected", config_file: configPath };
|
|
20404
|
-
}
|
|
20405
|
-
try {
|
|
20406
|
-
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
20407
|
-
const fileExistsBefore = existsSync14(configPath);
|
|
20408
|
-
let content = fileExistsBefore ? readFileSync11(configPath, "utf8") : "";
|
|
20409
|
-
const previous = content;
|
|
20410
|
-
content = ensureCodexHooksFeature(content);
|
|
20411
|
-
if (!content.includes("unbrowse-update-hint.mjs")) {
|
|
20412
|
-
const command = `node "${hookScript}"`;
|
|
20413
|
-
const prefix = content && !content.endsWith(`
|
|
20414
|
-
`) ? `
|
|
20415
|
-
` : "";
|
|
20416
|
-
content += `${prefix}${CODEX_MARKER}
|
|
20417
|
-
[[hooks]]
|
|
20418
|
-
event = "SessionStart"
|
|
20419
|
-
command = ${JSON.stringify(command)}
|
|
20420
|
-
`;
|
|
20421
|
-
}
|
|
20422
|
-
if (content !== previous) {
|
|
20423
|
-
writeFileSync11(configPath, content, "utf8");
|
|
20424
|
-
return {
|
|
20425
|
-
host: "codex",
|
|
20426
|
-
action: fileExistsBefore ? "updated" : "installed",
|
|
20427
|
-
config_file: configPath
|
|
20428
|
-
};
|
|
20429
|
-
}
|
|
20430
|
-
return { host: "codex", action: "already-installed", config_file: configPath };
|
|
20431
|
-
} catch (error) {
|
|
20432
|
-
return {
|
|
20433
|
-
host: "codex",
|
|
20434
|
-
action: "failed",
|
|
20435
|
-
config_file: configPath,
|
|
20436
|
-
message: error instanceof Error ? error.message : String(error)
|
|
20437
|
-
};
|
|
20438
|
-
}
|
|
20439
|
-
}
|
|
20440
|
-
function writeClaudeHook(metaUrl) {
|
|
20441
|
-
const settingsPath = getClaudeSettingsPath();
|
|
20442
|
-
if (!existsSync14(path8.dirname(settingsPath))) {
|
|
20443
|
-
return { host: "claude", action: "not-detected", config_file: settingsPath };
|
|
20444
|
-
}
|
|
20445
|
-
try {
|
|
20446
|
-
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
20447
|
-
const command = `node "${hookScript}"`;
|
|
20448
|
-
const fileExistsBefore = existsSync14(settingsPath);
|
|
20449
|
-
const settings = readJsonFile(settingsPath) ?? {};
|
|
20450
|
-
settings.hooks ??= {};
|
|
20451
|
-
settings.hooks.SessionStart ??= [];
|
|
20452
|
-
const existing = settings.hooks.SessionStart.some((entry) => Array.isArray(entry.hooks) && entry.hooks.some((hook) => commandIncludesHook(hook.command, "unbrowse-update-hint.mjs")));
|
|
20453
|
-
if (!existing) {
|
|
20454
|
-
settings.hooks.SessionStart.push({
|
|
20455
|
-
hooks: [{ type: "command", command }]
|
|
20456
|
-
});
|
|
20457
|
-
writeJsonFile(settingsPath, settings);
|
|
20458
|
-
return {
|
|
20459
|
-
host: "claude",
|
|
20460
|
-
action: fileExistsBefore ? "updated" : "installed",
|
|
20461
|
-
config_file: settingsPath
|
|
20462
|
-
};
|
|
20463
|
-
}
|
|
20464
|
-
return { host: "claude", action: "already-installed", config_file: settingsPath };
|
|
20465
|
-
} catch (error) {
|
|
20466
|
-
return {
|
|
20467
|
-
host: "claude",
|
|
20468
|
-
action: "failed",
|
|
20469
|
-
config_file: settingsPath,
|
|
20470
|
-
message: error instanceof Error ? error.message : String(error)
|
|
20471
|
-
};
|
|
20472
|
-
}
|
|
20473
|
-
}
|
|
20474
|
-
function configureUpdateHintHooks(metaUrl, install) {
|
|
20475
|
-
if (process.env.UNBROWSE_DISABLE_UPDATE_HINTS === "1")
|
|
20476
|
-
return [];
|
|
20477
|
-
const source = install ?? loadInstallSource(metaUrl);
|
|
20478
|
-
const configuredHosts = source.host === "codex" || source.host === "claude" ? [source.host] : ["codex", "claude"];
|
|
20479
|
-
return configuredHosts.map((host) => host === "codex" ? writeCodexHook(metaUrl) : writeClaudeHook(metaUrl));
|
|
20480
|
-
}
|
|
20481
|
-
|
|
20482
|
-
// ../../src/runtime/setup.ts
|
|
20483
20155
|
function hasBinary(name) {
|
|
20484
20156
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
20485
20157
|
try {
|
|
@@ -20499,18 +20171,18 @@ function detectPackageManagers() {
|
|
|
20499
20171
|
}
|
|
20500
20172
|
function resolveConfigHome() {
|
|
20501
20173
|
if (process.platform === "win32") {
|
|
20502
|
-
return process.env.APPDATA ||
|
|
20174
|
+
return process.env.APPDATA || path8.join(os4.homedir(), "AppData", "Roaming");
|
|
20503
20175
|
}
|
|
20504
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
20176
|
+
return process.env.XDG_CONFIG_HOME || path8.join(os4.homedir(), ".config");
|
|
20505
20177
|
}
|
|
20506
20178
|
function getOpenCodeGlobalCommandsDir() {
|
|
20507
|
-
return
|
|
20179
|
+
return path8.join(resolveConfigHome(), "opencode", "commands");
|
|
20508
20180
|
}
|
|
20509
20181
|
function getOpenCodeProjectCommandsDir(cwd) {
|
|
20510
|
-
return
|
|
20182
|
+
return path8.join(cwd, ".opencode", "commands");
|
|
20511
20183
|
}
|
|
20512
20184
|
function detectOpenCode(cwd) {
|
|
20513
|
-
return hasBinary("opencode") ||
|
|
20185
|
+
return hasBinary("opencode") || existsSync14(path8.join(resolveConfigHome(), "opencode")) || existsSync14(path8.join(cwd, ".opencode"));
|
|
20514
20186
|
}
|
|
20515
20187
|
function renderOpenCodeCommand() {
|
|
20516
20188
|
return `---
|
|
@@ -20538,13 +20210,13 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
20538
20210
|
if (scope === "auto" && !detected) {
|
|
20539
20211
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
20540
20212
|
}
|
|
20541
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" :
|
|
20213
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path8.join(cwd, ".opencode")) ? "project" : "global";
|
|
20542
20214
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
20543
|
-
const commandFile =
|
|
20215
|
+
const commandFile = path8.join(ensureDir(commandsDir), "unbrowse.md");
|
|
20544
20216
|
const content = renderOpenCodeCommand();
|
|
20545
|
-
const action2 =
|
|
20546
|
-
|
|
20547
|
-
|
|
20217
|
+
const action2 = existsSync14(commandFile) ? "updated" : "installed";
|
|
20218
|
+
mkdirSync12(path8.dirname(commandFile), { recursive: true });
|
|
20219
|
+
writeFileSync11(commandFile, content);
|
|
20548
20220
|
return {
|
|
20549
20221
|
detected: detected || scope !== "auto",
|
|
20550
20222
|
action: action2,
|
|
@@ -20554,10 +20226,10 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
20554
20226
|
}
|
|
20555
20227
|
async function ensureBrowserEngineInstalled() {
|
|
20556
20228
|
const binary = findKuriBinary();
|
|
20557
|
-
if (
|
|
20229
|
+
if (existsSync14(binary)) {
|
|
20558
20230
|
return { installed: true, action: "already-installed" };
|
|
20559
20231
|
}
|
|
20560
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) =>
|
|
20232
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path8.join(candidate, "build.zig")));
|
|
20561
20233
|
if (!sourceDir) {
|
|
20562
20234
|
return {
|
|
20563
20235
|
installed: false,
|
|
@@ -20579,7 +20251,7 @@ async function ensureBrowserEngineInstalled() {
|
|
|
20579
20251
|
timeout: 300000
|
|
20580
20252
|
});
|
|
20581
20253
|
const builtBinary = findKuriBinary();
|
|
20582
|
-
if (
|
|
20254
|
+
if (existsSync14(builtBinary)) {
|
|
20583
20255
|
return {
|
|
20584
20256
|
installed: true,
|
|
20585
20257
|
action: "installed",
|
|
@@ -20598,15 +20270,14 @@ async function ensureBrowserEngineInstalled() {
|
|
|
20598
20270
|
}
|
|
20599
20271
|
async function runSetup(options) {
|
|
20600
20272
|
const cwd = options?.cwd || process.cwd();
|
|
20601
|
-
const installSource = saveInstallSource(import.meta.url);
|
|
20602
20273
|
const hostEnv = detectHostEnvironment();
|
|
20603
20274
|
log("setup", `detected host environment: ${hostEnv}`);
|
|
20604
20275
|
const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
|
|
20605
20276
|
const walletCheck = checkWalletConfigured();
|
|
20606
20277
|
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
20607
|
-
const lobsterInstalled = hasBinary("lobstercash") ||
|
|
20278
|
+
const lobsterInstalled = hasBinary("lobstercash") || existsSync14(path8.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
20608
20279
|
if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
|
|
20609
|
-
console.log("[unbrowse]
|
|
20280
|
+
console.log("[unbrowse] lobster.cash skill detected but wallet not configured — running wallet setup...");
|
|
20610
20281
|
try {
|
|
20611
20282
|
execFileSync3("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
20612
20283
|
stdio: "inherit",
|
|
@@ -20617,202 +20288,30 @@ async function runSetup(options) {
|
|
|
20617
20288
|
console.log(`[unbrowse] wallet configured (${recheck.provider})`);
|
|
20618
20289
|
}
|
|
20619
20290
|
} catch {
|
|
20620
|
-
console.warn("[unbrowse]
|
|
20291
|
+
console.warn("[unbrowse] lobster.cash wallet setup failed or was skipped — continuing without wallet");
|
|
20621
20292
|
}
|
|
20622
20293
|
}
|
|
20623
20294
|
const finalWalletCheck = checkWalletConfigured();
|
|
20624
20295
|
const wallet = {
|
|
20625
20296
|
...finalWalletCheck,
|
|
20626
20297
|
lobster_installed: lobsterInstalled,
|
|
20627
|
-
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 ? "
|
|
20628
|
-
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"
|
|
20629
20300
|
};
|
|
20630
20301
|
return {
|
|
20631
20302
|
os: {
|
|
20632
20303
|
platform: process.platform,
|
|
20633
|
-
release:
|
|
20304
|
+
release: os4.release(),
|
|
20634
20305
|
arch: process.arch
|
|
20635
20306
|
},
|
|
20636
20307
|
host_environment: hostEnv,
|
|
20637
20308
|
package_managers: detectPackageManagers(),
|
|
20638
20309
|
browser_engine: browser,
|
|
20639
20310
|
opencode: writeOpenCodeCommand(options?.opencode ?? "auto", cwd),
|
|
20640
|
-
update_hints: configureUpdateHintHooks(import.meta.url, installSource),
|
|
20641
20311
|
wallet
|
|
20642
20312
|
};
|
|
20643
20313
|
}
|
|
20644
20314
|
|
|
20645
|
-
// ../../src/runtime/update-hints.ts
|
|
20646
|
-
init_paths();
|
|
20647
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync13 } from "node:fs";
|
|
20648
|
-
import os6 from "node:os";
|
|
20649
|
-
import path10 from "node:path";
|
|
20650
|
-
var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
|
|
20651
|
-
var DEFAULT_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
|
|
20652
|
-
function getHomeDir2() {
|
|
20653
|
-
return process.env.HOME || os6.homedir();
|
|
20654
|
-
}
|
|
20655
|
-
function getConfigDir4() {
|
|
20656
|
-
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
20657
|
-
return process.env.UNBROWSE_CONFIG_DIR;
|
|
20658
|
-
return path10.join(getHomeDir2(), ".unbrowse");
|
|
20659
|
-
}
|
|
20660
|
-
function ensureDir3(dir) {
|
|
20661
|
-
if (!existsSync16(dir))
|
|
20662
|
-
mkdirSync14(dir, { recursive: true });
|
|
20663
|
-
return dir;
|
|
20664
|
-
}
|
|
20665
|
-
function readJsonFile2(file) {
|
|
20666
|
-
try {
|
|
20667
|
-
return JSON.parse(readFileSync12(file, "utf8"));
|
|
20668
|
-
} catch {
|
|
20669
|
-
return null;
|
|
20670
|
-
}
|
|
20671
|
-
}
|
|
20672
|
-
function writeJsonFile2(file, value) {
|
|
20673
|
-
ensureDir3(path10.dirname(file));
|
|
20674
|
-
writeFileSync13(file, `${JSON.stringify(value, null, 2)}
|
|
20675
|
-
`);
|
|
20676
|
-
}
|
|
20677
|
-
function getInstallSourcePath2() {
|
|
20678
|
-
return path10.join(getConfigDir4(), "install-source.json");
|
|
20679
|
-
}
|
|
20680
|
-
function getUpdateCheckStatePath() {
|
|
20681
|
-
return path10.join(getConfigDir4(), "update-check.json");
|
|
20682
|
-
}
|
|
20683
|
-
function detectRepoRoot2(start2) {
|
|
20684
|
-
let dir = path10.resolve(start2);
|
|
20685
|
-
const root = path10.parse(dir).root;
|
|
20686
|
-
while (dir !== root) {
|
|
20687
|
-
if (existsSync16(path10.join(dir, ".git")))
|
|
20688
|
-
return dir;
|
|
20689
|
-
dir = path10.dirname(dir);
|
|
20690
|
-
}
|
|
20691
|
-
return;
|
|
20692
|
-
}
|
|
20693
|
-
function detectInstallMethod2(packageRoot) {
|
|
20694
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "repo-clone")
|
|
20695
|
-
return "repo-clone";
|
|
20696
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
|
|
20697
|
-
return "npm-global";
|
|
20698
|
-
if (packageRoot.includes(`${path10.sep}node_modules${path10.sep}`))
|
|
20699
|
-
return "npm-global";
|
|
20700
|
-
return detectRepoRoot2(packageRoot) ? "repo-clone" : "unknown";
|
|
20701
|
-
}
|
|
20702
|
-
function detectInstallHost2(repoRoot) {
|
|
20703
|
-
const explicit = process.env.UNBROWSE_SETUP_HOST;
|
|
20704
|
-
if (explicit)
|
|
20705
|
-
return explicit;
|
|
20706
|
-
if (!repoRoot)
|
|
20707
|
-
return "unknown";
|
|
20708
|
-
const codexHome = process.env.CODEX_HOME || path10.join(getHomeDir2(), ".codex");
|
|
20709
|
-
if (repoRoot === path10.join(codexHome, "skills", "unbrowse"))
|
|
20710
|
-
return "codex";
|
|
20711
|
-
if (repoRoot === path10.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
|
|
20712
|
-
return "claude";
|
|
20713
|
-
if (repoRoot === path10.join(getHomeDir2(), "unbrowse"))
|
|
20714
|
-
return "off";
|
|
20715
|
-
return "unknown";
|
|
20716
|
-
}
|
|
20717
|
-
function getInstalledVersion(metaUrl) {
|
|
20718
|
-
const packageRoot = getPackageRoot(metaUrl);
|
|
20719
|
-
try {
|
|
20720
|
-
const pkg = JSON.parse(readFileSync12(path10.join(packageRoot, "package.json"), "utf8"));
|
|
20721
|
-
return pkg.version ?? "unknown";
|
|
20722
|
-
} catch {
|
|
20723
|
-
return "unknown";
|
|
20724
|
-
}
|
|
20725
|
-
}
|
|
20726
|
-
function resolveInstallSource2(metaUrl) {
|
|
20727
|
-
const packageRoot = getPackageRoot(metaUrl);
|
|
20728
|
-
const envRepoRoot = process.env.UNBROWSE_SETUP_ROOT || undefined;
|
|
20729
|
-
const repoRoot = envRepoRoot || detectRepoRoot2(packageRoot);
|
|
20730
|
-
return {
|
|
20731
|
-
method: detectInstallMethod2(packageRoot),
|
|
20732
|
-
host: detectInstallHost2(repoRoot),
|
|
20733
|
-
package_root: packageRoot,
|
|
20734
|
-
repo_root: repoRoot,
|
|
20735
|
-
recorded_at: new Date().toISOString()
|
|
20736
|
-
};
|
|
20737
|
-
}
|
|
20738
|
-
function loadInstallSource2(metaUrl) {
|
|
20739
|
-
return readJsonFile2(getInstallSourcePath2()) ?? resolveInstallSource2(metaUrl);
|
|
20740
|
-
}
|
|
20741
|
-
function compareSemver(a, b) {
|
|
20742
|
-
const parse = (value) => value.split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
20743
|
-
const left = parse(a);
|
|
20744
|
-
const right = parse(b);
|
|
20745
|
-
const max = Math.max(left.length, right.length);
|
|
20746
|
-
for (let i = 0;i < max; i++) {
|
|
20747
|
-
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
20748
|
-
if (diff !== 0)
|
|
20749
|
-
return diff;
|
|
20750
|
-
}
|
|
20751
|
-
return 0;
|
|
20752
|
-
}
|
|
20753
|
-
async function fetchLatestVersion() {
|
|
20754
|
-
try {
|
|
20755
|
-
const res = await fetch("https://registry.npmjs.org/unbrowse/latest", {
|
|
20756
|
-
signal: AbortSignal.timeout(8000),
|
|
20757
|
-
headers: { Accept: "application/json" }
|
|
20758
|
-
});
|
|
20759
|
-
if (!res.ok)
|
|
20760
|
-
return null;
|
|
20761
|
-
const body = await res.json();
|
|
20762
|
-
return typeof body.version === "string" && body.version.trim() ? body.version.trim() : null;
|
|
20763
|
-
} catch {
|
|
20764
|
-
return null;
|
|
20765
|
-
}
|
|
20766
|
-
}
|
|
20767
|
-
function getUpdateIntervalMs() {
|
|
20768
|
-
const value = Number.parseInt(process.env.UNBROWSE_UPDATE_CHECK_INTERVAL_MS ?? "", 10);
|
|
20769
|
-
return Number.isFinite(value) && value > 0 ? value : DEFAULT_INTERVAL_MS2;
|
|
20770
|
-
}
|
|
20771
|
-
function buildUpgradeCommand(install) {
|
|
20772
|
-
if (install.method === "repo-clone" && install.repo_root) {
|
|
20773
|
-
const host = install.host === "unknown" || install.host === "auto" ? "off" : install.host;
|
|
20774
|
-
return `cd ${install.repo_root} && git pull --ff-only && ./setup --host ${host}`;
|
|
20775
|
-
}
|
|
20776
|
-
return `curl -fsSL ${INSTALL_SCRIPT_URL} | bash`;
|
|
20777
|
-
}
|
|
20778
|
-
async function checkForUpdates(metaUrl, options) {
|
|
20779
|
-
const installed = getInstalledVersion(metaUrl);
|
|
20780
|
-
const install = loadInstallSource2(metaUrl);
|
|
20781
|
-
const statePath = getUpdateCheckStatePath();
|
|
20782
|
-
const state = readJsonFile2(statePath) ?? {};
|
|
20783
|
-
const checkedAt = new Date().toISOString();
|
|
20784
|
-
const intervalMs = getUpdateIntervalMs();
|
|
20785
|
-
const lastChecked = state.latest_checked_at ? Date.parse(state.latest_checked_at) : Number.NaN;
|
|
20786
|
-
const useCache = !options?.force && !!state.latest_version && Number.isFinite(lastChecked) && Date.now() - lastChecked < intervalMs;
|
|
20787
|
-
const latest = useCache ? state.latest_version ?? null : await fetchLatestVersion();
|
|
20788
|
-
if (!useCache) {
|
|
20789
|
-
writeJsonFile2(statePath, {
|
|
20790
|
-
...state,
|
|
20791
|
-
checked_at: checkedAt,
|
|
20792
|
-
latest_version: latest ?? undefined,
|
|
20793
|
-
latest_checked_at: checkedAt
|
|
20794
|
-
});
|
|
20795
|
-
}
|
|
20796
|
-
return {
|
|
20797
|
-
installed,
|
|
20798
|
-
latest,
|
|
20799
|
-
has_update: !!latest && compareSemver(latest, installed) > 0,
|
|
20800
|
-
install,
|
|
20801
|
-
command: buildUpgradeCommand(install),
|
|
20802
|
-
checked_at: checkedAt,
|
|
20803
|
-
cached: useCache
|
|
20804
|
-
};
|
|
20805
|
-
}
|
|
20806
|
-
function recordUpdateHint(latestVersion) {
|
|
20807
|
-
const statePath = getUpdateCheckStatePath();
|
|
20808
|
-
const state = readJsonFile2(statePath) ?? {};
|
|
20809
|
-
writeJsonFile2(statePath, {
|
|
20810
|
-
...state,
|
|
20811
|
-
notified_version: latestVersion,
|
|
20812
|
-
notified_at: new Date().toISOString()
|
|
20813
|
-
});
|
|
20814
|
-
}
|
|
20815
|
-
|
|
20816
20315
|
// ../../src/cli.ts
|
|
20817
20316
|
loadEnv({ quiet: true });
|
|
20818
20317
|
loadEnv({ path: ".env.runtime", quiet: true });
|
|
@@ -20841,8 +20340,8 @@ function parseArgs(argv) {
|
|
|
20841
20340
|
}
|
|
20842
20341
|
return { command, args: positional, flags };
|
|
20843
20342
|
}
|
|
20844
|
-
async function api3(method,
|
|
20845
|
-
const res = await fetch(`${BASE_URL}${
|
|
20343
|
+
async function api3(method, path9, body) {
|
|
20344
|
+
const res = await fetch(`${BASE_URL}${path9}`, {
|
|
20846
20345
|
method,
|
|
20847
20346
|
headers: {
|
|
20848
20347
|
...body ? { "Content-Type": "application/json" } : {},
|
|
@@ -20925,73 +20424,15 @@ function slimTrace(obj) {
|
|
|
20925
20424
|
out.result = obj.result;
|
|
20926
20425
|
if (obj.available_endpoints)
|
|
20927
20426
|
out.available_endpoints = obj.available_endpoints;
|
|
20928
|
-
if (obj.impact)
|
|
20929
|
-
out.impact = obj.impact;
|
|
20930
|
-
if (obj.next_actions)
|
|
20931
|
-
out.next_actions = obj.next_actions;
|
|
20932
|
-
if (obj.next_step)
|
|
20933
|
-
out.next_step = obj.next_step;
|
|
20934
20427
|
if (obj.source)
|
|
20935
20428
|
out.source = obj.source;
|
|
20936
20429
|
if (obj.skill)
|
|
20937
20430
|
out.skill = obj.skill;
|
|
20938
20431
|
return out;
|
|
20939
20432
|
}
|
|
20940
|
-
function formatSavedDuration(ms) {
|
|
20941
|
-
if (ms >= 60000)
|
|
20942
|
-
return `${(ms / 60000).toFixed(1)}m`;
|
|
20943
|
-
if (ms >= 1e4)
|
|
20944
|
-
return `${Math.round(ms / 1000)}s`;
|
|
20945
|
-
if (ms >= 1000)
|
|
20946
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
20947
|
-
return `${ms}ms`;
|
|
20948
|
-
}
|
|
20949
|
-
function emitImpactSummary(result) {
|
|
20950
|
-
const impact = result.impact;
|
|
20951
|
-
if (!impact)
|
|
20952
|
-
return;
|
|
20953
|
-
const timeSavedMs = typeof impact.time_saved_ms === "number" ? impact.time_saved_ms : 0;
|
|
20954
|
-
const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
|
|
20955
|
-
const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
|
|
20956
|
-
const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
|
|
20957
|
-
const browserAvoided = impact.browser_avoided === true;
|
|
20958
|
-
if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
|
|
20959
|
-
return;
|
|
20960
|
-
const parts = [];
|
|
20961
|
-
if (timeSavedMs > 0)
|
|
20962
|
-
parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
|
|
20963
|
-
if (tokensSaved > 0)
|
|
20964
|
-
parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
|
|
20965
|
-
if (browserAvoided)
|
|
20966
|
-
parts.push("browser avoided");
|
|
20967
|
-
info(parts.join(" \u2022 "));
|
|
20968
|
-
}
|
|
20969
|
-
function emitNextActionSummary(result) {
|
|
20970
|
-
const nextActions = Array.isArray(result.next_actions) ? result.next_actions : [];
|
|
20971
|
-
if (nextActions.length === 0)
|
|
20972
|
-
return;
|
|
20973
|
-
info("Likely next actions:");
|
|
20974
|
-
for (const action2 of nextActions.slice(0, 3)) {
|
|
20975
|
-
const command = typeof action2.command === "string" ? action2.command : "";
|
|
20976
|
-
const title = typeof action2.title === "string" ? action2.title : action2.endpoint_id ?? "next step";
|
|
20977
|
-
const why = typeof action2.why === "string" ? action2.why : "";
|
|
20978
|
-
info(` ${command || title}${why ? ` # ${why}` : ""}`);
|
|
20979
|
-
}
|
|
20980
|
-
}
|
|
20981
20433
|
async function cmdHealth(flags) {
|
|
20982
20434
|
output(await api3("GET", "/health"), !!flags.pretty);
|
|
20983
20435
|
}
|
|
20984
|
-
function telemetryDomainFromInput(domain, url) {
|
|
20985
|
-
if (domain?.trim())
|
|
20986
|
-
return domain.trim().replace(/^www\./, "");
|
|
20987
|
-
if (!url?.trim())
|
|
20988
|
-
return null;
|
|
20989
|
-
try {
|
|
20990
|
-
return new URL(url).hostname.replace(/^www\./, "");
|
|
20991
|
-
} catch {
|
|
20992
|
-
return null;
|
|
20993
|
-
}
|
|
20994
|
-
}
|
|
20995
20436
|
async function cmdResolve(flags) {
|
|
20996
20437
|
const intent = flags.intent;
|
|
20997
20438
|
if (!intent)
|
|
@@ -21008,9 +20449,6 @@ async function cmdResolve(flags) {
|
|
|
21008
20449
|
hostType,
|
|
21009
20450
|
properties: {
|
|
21010
20451
|
command: "resolve",
|
|
21011
|
-
intent,
|
|
21012
|
-
domain: telemetryDomainFromInput(flags.domain, flags.url),
|
|
21013
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
21014
20452
|
has_url: typeof flags.url === "string",
|
|
21015
20453
|
has_domain: typeof flags.domain === "string",
|
|
21016
20454
|
auto_execute: !!flags.execute
|
|
@@ -21137,9 +20575,6 @@ async function cmdResolve(flags) {
|
|
|
21137
20575
|
hostType,
|
|
21138
20576
|
properties: {
|
|
21139
20577
|
command: "resolve",
|
|
21140
|
-
intent,
|
|
21141
|
-
domain: telemetryDomainFromInput(domain, url),
|
|
21142
|
-
url: url ?? null,
|
|
21143
20578
|
source: result.source,
|
|
21144
20579
|
auto_execute: autoExecute,
|
|
21145
20580
|
explicit_endpoint: explicitEndpointId ?? null
|
|
@@ -21147,8 +20582,6 @@ async function cmdResolve(flags) {
|
|
|
21147
20582
|
});
|
|
21148
20583
|
}
|
|
21149
20584
|
result = slimTrace(result);
|
|
21150
|
-
emitImpactSummary(result);
|
|
21151
|
-
emitNextActionSummary(result);
|
|
21152
20585
|
const skill = result.skill;
|
|
21153
20586
|
const trace = result.trace;
|
|
21154
20587
|
if (skill?.skill_id && trace) {
|
|
@@ -21162,9 +20595,6 @@ async function cmdResolve(flags) {
|
|
|
21162
20595
|
hostType,
|
|
21163
20596
|
properties: {
|
|
21164
20597
|
command: "resolve",
|
|
21165
|
-
intent,
|
|
21166
|
-
domain: telemetryDomainFromInput(flags.domain, flags.url),
|
|
21167
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
21168
20598
|
failure_stage: "resolve",
|
|
21169
20599
|
failure_reason: message
|
|
21170
20600
|
}
|
|
@@ -21172,8 +20602,8 @@ async function cmdResolve(flags) {
|
|
|
21172
20602
|
throw error;
|
|
21173
20603
|
}
|
|
21174
20604
|
}
|
|
21175
|
-
function drillPath(data,
|
|
21176
|
-
const segments =
|
|
20605
|
+
function drillPath(data, path9) {
|
|
20606
|
+
const segments = path9.split(/\./).flatMap((s) => {
|
|
21177
20607
|
const m = s.match(/^(.+)\[\]$/);
|
|
21178
20608
|
return m ? [m[1], "[]"] : [s];
|
|
21179
20609
|
});
|
|
@@ -21200,9 +20630,9 @@ function drillPath(data, path11) {
|
|
|
21200
20630
|
}
|
|
21201
20631
|
return values;
|
|
21202
20632
|
}
|
|
21203
|
-
function resolveDotPath(obj,
|
|
20633
|
+
function resolveDotPath(obj, path9) {
|
|
21204
20634
|
let cur = obj;
|
|
21205
|
-
for (const key of
|
|
20635
|
+
for (const key of path9.split(".")) {
|
|
21206
20636
|
if (cur == null || typeof cur !== "object")
|
|
21207
20637
|
return;
|
|
21208
20638
|
cur = cur[key];
|
|
@@ -21219,8 +20649,8 @@ function applyExtract(items, extractSpec) {
|
|
|
21219
20649
|
return items.map((item) => {
|
|
21220
20650
|
const row = {};
|
|
21221
20651
|
let hasValue = false;
|
|
21222
|
-
for (const { alias, path:
|
|
21223
|
-
const val = resolveDotPath(item,
|
|
20652
|
+
for (const { alias, path: path9 } of fields) {
|
|
20653
|
+
const val = resolveDotPath(item, path9);
|
|
21224
20654
|
row[alias] = val ?? null;
|
|
21225
20655
|
if (val != null)
|
|
21226
20656
|
hasValue = true;
|
|
@@ -21263,9 +20693,6 @@ async function cmdExecute(flags) {
|
|
|
21263
20693
|
hostType,
|
|
21264
20694
|
properties: {
|
|
21265
20695
|
command: "execute",
|
|
21266
|
-
intent: typeof flags.intent === "string" ? flags.intent : null,
|
|
21267
|
-
domain: telemetryDomainFromInput(undefined, flags.url),
|
|
21268
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
21269
20696
|
skill_id: skillId,
|
|
21270
20697
|
endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
|
|
21271
20698
|
}
|
|
@@ -21296,17 +20723,12 @@ async function cmdExecute(flags) {
|
|
|
21296
20723
|
hostType,
|
|
21297
20724
|
properties: {
|
|
21298
20725
|
command: "execute",
|
|
21299
|
-
intent: typeof flags.intent === "string" ? flags.intent : null,
|
|
21300
|
-
domain: telemetryDomainFromInput(undefined, flags.url),
|
|
21301
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
21302
20726
|
skill_id: skillId,
|
|
21303
20727
|
endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
|
|
21304
20728
|
}
|
|
21305
20729
|
});
|
|
21306
20730
|
}
|
|
21307
20731
|
result = slimTrace(result);
|
|
21308
|
-
emitImpactSummary(result);
|
|
21309
|
-
emitNextActionSummary(result);
|
|
21310
20732
|
const pathFlag = flags.path;
|
|
21311
20733
|
const extractFlag = flags.extract;
|
|
21312
20734
|
const limitFlag = flags.limit ? Number(flags.limit) : undefined;
|
|
@@ -21314,12 +20736,7 @@ async function cmdExecute(flags) {
|
|
|
21314
20736
|
const rawFlag = !!flags.raw;
|
|
21315
20737
|
if (schemaFlag && !rawFlag) {
|
|
21316
20738
|
const data = result.result;
|
|
21317
|
-
output({
|
|
21318
|
-
trace: result.trace,
|
|
21319
|
-
schema: schemaOf(data),
|
|
21320
|
-
...result.impact ? { impact: result.impact } : {},
|
|
21321
|
-
...result.next_actions ? { next_actions: result.next_actions } : {}
|
|
21322
|
-
}, !!flags.pretty);
|
|
20739
|
+
output({ trace: result.trace, schema: schemaOf(data) }, !!flags.pretty);
|
|
21323
20740
|
return;
|
|
21324
20741
|
}
|
|
21325
20742
|
if (!rawFlag && (pathFlag || extractFlag || limitFlag)) {
|
|
@@ -21328,13 +20745,7 @@ async function cmdExecute(flags) {
|
|
|
21328
20745
|
const extracted = extractFlag ? applyExtract(items, extractFlag) : items;
|
|
21329
20746
|
const limited = limitFlag ? extracted.slice(0, limitFlag) : extracted;
|
|
21330
20747
|
const trace = result.trace;
|
|
21331
|
-
const out = {
|
|
21332
|
-
trace: result.trace,
|
|
21333
|
-
data: limited,
|
|
21334
|
-
count: limited.length,
|
|
21335
|
-
...result.impact ? { impact: result.impact } : {},
|
|
21336
|
-
...result.next_actions ? { next_actions: result.next_actions } : {}
|
|
21337
|
-
};
|
|
20748
|
+
const out = { trace: result.trace, data: limited, count: limited.length };
|
|
21338
20749
|
if (trace?.skill_id && trace?.endpoint_id && limited.length > 0) {
|
|
21339
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"}]'`;
|
|
21340
20751
|
}
|
|
@@ -21347,8 +20758,6 @@ async function cmdExecute(flags) {
|
|
|
21347
20758
|
const schema = schemaOf(result.result);
|
|
21348
20759
|
output({
|
|
21349
20760
|
trace: result.trace,
|
|
21350
|
-
...result.impact ? { impact: result.impact } : {},
|
|
21351
|
-
...result.next_actions ? { next_actions: result.next_actions } : {},
|
|
21352
20761
|
extraction_hints: {
|
|
21353
20762
|
message: "Response is large. Use --path/--extract/--limit to filter, or --schema to see structure, or --raw for full response.",
|
|
21354
20763
|
schema_tree: schema,
|
|
@@ -21366,9 +20775,6 @@ async function cmdExecute(flags) {
|
|
|
21366
20775
|
hostType,
|
|
21367
20776
|
properties: {
|
|
21368
20777
|
command: "execute",
|
|
21369
|
-
intent: typeof flags.intent === "string" ? flags.intent : null,
|
|
21370
|
-
domain: telemetryDomainFromInput(undefined, flags.url),
|
|
21371
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
21372
20778
|
skill_id: skillId,
|
|
21373
20779
|
failure_stage: "execute",
|
|
21374
20780
|
failure_reason: message
|
|
@@ -21440,57 +20846,11 @@ async function cmdSearch(flags) {
|
|
|
21440
20846
|
if (!intent)
|
|
21441
20847
|
die("--intent is required");
|
|
21442
20848
|
const domain = flags.domain;
|
|
21443
|
-
const
|
|
20849
|
+
const path9 = domain ? "/v1/search/domain" : "/v1/search";
|
|
21444
20850
|
const body = { intent, k: Number(flags.k) || 5 };
|
|
21445
20851
|
if (domain)
|
|
21446
20852
|
body.domain = domain;
|
|
21447
|
-
|
|
21448
|
-
await ensureCliInstallTracked(hostType);
|
|
21449
|
-
await recordFunnelTelemetryEvent("cli_invoked", {
|
|
21450
|
-
source: "cli",
|
|
21451
|
-
hostType,
|
|
21452
|
-
properties: { command: "search" }
|
|
21453
|
-
});
|
|
21454
|
-
await recordFunnelTelemetryEvent("search_started", {
|
|
21455
|
-
source: "cli",
|
|
21456
|
-
hostType,
|
|
21457
|
-
properties: {
|
|
21458
|
-
command: "search",
|
|
21459
|
-
intent,
|
|
21460
|
-
domain: domain ?? null,
|
|
21461
|
-
k: body.k
|
|
21462
|
-
}
|
|
21463
|
-
});
|
|
21464
|
-
try {
|
|
21465
|
-
const result = await api3("POST", path11, body);
|
|
21466
|
-
const results = Array.isArray(result.results) ? result.results : [];
|
|
21467
|
-
await recordFunnelTelemetryEvent("search_completed", {
|
|
21468
|
-
source: "cli",
|
|
21469
|
-
hostType,
|
|
21470
|
-
properties: {
|
|
21471
|
-
command: "search",
|
|
21472
|
-
intent,
|
|
21473
|
-
domain: domain ?? null,
|
|
21474
|
-
k: body.k,
|
|
21475
|
-
result_count: results.length
|
|
21476
|
-
}
|
|
21477
|
-
});
|
|
21478
|
-
output(result, !!flags.pretty);
|
|
21479
|
-
} catch (error) {
|
|
21480
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
21481
|
-
await recordFunnelTelemetryEvent("search_failed", {
|
|
21482
|
-
source: "cli",
|
|
21483
|
-
hostType,
|
|
21484
|
-
properties: {
|
|
21485
|
-
command: "search",
|
|
21486
|
-
intent,
|
|
21487
|
-
domain: domain ?? null,
|
|
21488
|
-
failure_stage: "search",
|
|
21489
|
-
failure_reason: message
|
|
21490
|
-
}
|
|
21491
|
-
});
|
|
21492
|
-
throw error;
|
|
21493
|
-
}
|
|
20853
|
+
output(await api3("POST", path9, body), !!flags.pretty);
|
|
21494
20854
|
}
|
|
21495
20855
|
async function cmdSessions(flags) {
|
|
21496
20856
|
const domain = flags.domain;
|
|
@@ -21521,11 +20881,6 @@ async function cmdSetup(flags) {
|
|
|
21521
20881
|
if (report.opencode.action === "installed" || report.opencode.action === "updated") {
|
|
21522
20882
|
info(`Open Code command installed at ${report.opencode.command_file}`);
|
|
21523
20883
|
}
|
|
21524
|
-
for (const hook of report.update_hints) {
|
|
21525
|
-
if (hook.action === "installed" || hook.action === "updated") {
|
|
21526
|
-
info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
|
|
21527
|
-
}
|
|
21528
|
-
}
|
|
21529
20884
|
await recordInstallTelemetryEvent("setup", {
|
|
21530
20885
|
hostType,
|
|
21531
20886
|
status: report.browser_engine.action === "failed" ? "failed" : "installed",
|
|
@@ -21586,9 +20941,7 @@ async function cmdSetup(flags) {
|
|
|
21586
20941
|
var CLI_REFERENCE = {
|
|
21587
20942
|
commands: [
|
|
21588
20943
|
{ name: "health", usage: "", desc: "Server health check" },
|
|
21589
|
-
{ name: "mcp", usage: "[--no-auto-start]", desc: "Run the stdio MCP server" },
|
|
21590
20944
|
{ name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
|
|
21591
|
-
{ name: "upgrade", usage: "", desc: "Check latest release and print the right upgrade command" },
|
|
21592
20945
|
{ name: "resolve", usage: '--intent "..." --url "..." [opts]', desc: "Resolve intent \u2192 search/capture/execute" },
|
|
21593
20946
|
{ name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
|
|
21594
20947
|
{ name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
|
|
@@ -21600,7 +20953,7 @@ var CLI_REFERENCE = {
|
|
|
21600
20953
|
{ name: "search", usage: '--intent "..." [--domain "..."]', desc: "Search marketplace" },
|
|
21601
20954
|
{ name: "sessions", usage: '--domain "..." [--limit N]', desc: "Debug session logs" },
|
|
21602
20955
|
{ name: "go", usage: "<url>", desc: "Open a live Kuri browser tab for capture-first workflows" },
|
|
21603
|
-
{ 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" },
|
|
21604
20957
|
{ name: "snap", usage: "[--filter interactive]", desc: "A11y snapshot with @eN refs" },
|
|
21605
20958
|
{ name: "click", usage: "<ref>", desc: "Click element by ref (e.g. e5)" },
|
|
21606
20959
|
{ name: "fill", usage: "<ref> <value>", desc: "Fill input by ref" },
|
|
@@ -21637,7 +20990,6 @@ var CLI_REFERENCE = {
|
|
|
21637
20990
|
],
|
|
21638
20991
|
examples: [
|
|
21639
20992
|
"unbrowse setup",
|
|
21640
|
-
"unbrowse mcp",
|
|
21641
20993
|
'unbrowse resolve --intent "top stories" --url "https://news.ycombinator.com" --execute',
|
|
21642
20994
|
'unbrowse resolve --intent "get timeline" --url "https://x.com"',
|
|
21643
20995
|
'unbrowse go "https://www.mandai.com/en/ticketing/admission-and-rides/parks-selection.html"',
|
|
@@ -21676,7 +21028,7 @@ function printHelp() {
|
|
|
21676
21028
|
for (const e of r.examples) {
|
|
21677
21029
|
lines.push(` ${e}`);
|
|
21678
21030
|
}
|
|
21679
|
-
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");
|
|
21680
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.");
|
|
21681
21033
|
lines.push("");
|
|
21682
21034
|
process.stderr.write(lines.join(`
|
|
@@ -21705,55 +21057,23 @@ function cmdStop(flags) {
|
|
|
21705
21057
|
info("No server running.");
|
|
21706
21058
|
}
|
|
21707
21059
|
async function cmdUpgrade(flags) {
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
info("Checking for updates...");
|
|
21060
|
+
info("Checking for updates...");
|
|
21061
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
21711
21062
|
try {
|
|
21712
|
-
const result =
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
21063
|
+
const result = execSync3("npm view unbrowse version", { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
21064
|
+
const versionInfo = checkServerVersion(BASE_URL, import.meta.url);
|
|
21065
|
+
const installed = versionInfo?.installed ?? "unknown";
|
|
21066
|
+
if (result === installed) {
|
|
21067
|
+
info(`Already at latest version: ${installed}`);
|
|
21716
21068
|
return;
|
|
21717
21069
|
}
|
|
21718
|
-
|
|
21719
|
-
|
|
21720
|
-
|
|
21721
|
-
return;
|
|
21722
|
-
}
|
|
21723
|
-
info(`Update available: ${result.installed} -> ${result.latest}`);
|
|
21724
|
-
info(`Run: ${result.command}`);
|
|
21725
|
-
if (!hintOnly) {
|
|
21726
|
-
info("Tip: `unbrowse setup` now installs session-start update hints for Codex and Claude when those hosts are present.");
|
|
21727
|
-
}
|
|
21728
|
-
recordUpdateHint(result.latest);
|
|
21070
|
+
info(`Update available: ${installed} -> ${result}`);
|
|
21071
|
+
info("Run: npm install -g unbrowse@latest");
|
|
21072
|
+
info("Then: unbrowse restart");
|
|
21729
21073
|
} catch (err) {
|
|
21730
|
-
|
|
21731
|
-
info(`Could not check for updates: ${err.message}`);
|
|
21074
|
+
info(`Could not check for updates: ${err.message}`);
|
|
21732
21075
|
}
|
|
21733
21076
|
}
|
|
21734
|
-
async function cmdMcp(flags) {
|
|
21735
|
-
const entrypoint = resolveSiblingEntrypoint2(import.meta.url, "mcp");
|
|
21736
|
-
const child = spawn3(process.execPath, [...runtimeArgsForEntrypoint2(import.meta.url, entrypoint), ...flags["no-auto-start"] ? ["--no-auto-start"] : []], {
|
|
21737
|
-
cwd: process.cwd(),
|
|
21738
|
-
stdio: "inherit",
|
|
21739
|
-
env: {
|
|
21740
|
-
...process.env,
|
|
21741
|
-
MCP_SERVER_MODE: "1"
|
|
21742
|
-
}
|
|
21743
|
-
});
|
|
21744
|
-
const code = await new Promise((resolve, reject) => {
|
|
21745
|
-
child.once("error", reject);
|
|
21746
|
-
child.once("exit", (exitCode, signal) => {
|
|
21747
|
-
if (signal) {
|
|
21748
|
-
process.kill(process.pid, signal);
|
|
21749
|
-
return;
|
|
21750
|
-
}
|
|
21751
|
-
resolve(exitCode ?? 1);
|
|
21752
|
-
});
|
|
21753
|
-
});
|
|
21754
|
-
if (code !== 0)
|
|
21755
|
-
process.exit(code);
|
|
21756
|
-
}
|
|
21757
21077
|
async function cmdSiteHelp(pack, flags) {
|
|
21758
21078
|
if (flags.deps) {
|
|
21759
21079
|
const graph = buildDepsGraph(pack);
|
|
@@ -22025,8 +21345,6 @@ async function main() {
|
|
|
22025
21345
|
await cmdSetup(flags);
|
|
22026
21346
|
return;
|
|
22027
21347
|
}
|
|
22028
|
-
if (command === "mcp")
|
|
22029
|
-
return cmdMcp(flags);
|
|
22030
21348
|
if (command === "status")
|
|
22031
21349
|
return cmdStatus(flags);
|
|
22032
21350
|
if (command === "stop") {
|
|
@@ -22041,7 +21359,6 @@ async function main() {
|
|
|
22041
21359
|
return cmdConnectChrome();
|
|
22042
21360
|
const KNOWN_COMMANDS = new Set([
|
|
22043
21361
|
"health",
|
|
22044
|
-
"mcp",
|
|
22045
21362
|
"setup",
|
|
22046
21363
|
"resolve",
|
|
22047
21364
|
"execute",
|
|
@@ -22102,8 +21419,6 @@ async function main() {
|
|
|
22102
21419
|
switch (command) {
|
|
22103
21420
|
case "health":
|
|
22104
21421
|
return cmdHealth(flags);
|
|
22105
|
-
case "mcp":
|
|
22106
|
-
return cmdMcp(flags);
|
|
22107
21422
|
case "setup":
|
|
22108
21423
|
return cmdSetup(flags);
|
|
22109
21424
|
case "resolve":
|