unbrowse 2.12.4 → 2.12.7
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 +10 -127
- package/dist/cli.js +493 -3442
- package/package.json +6 -15
- package/runtime-src/api/routes.ts +29 -1338
- package/runtime-src/auth/index.ts +33 -296
- package/runtime-src/capture/index.ts +123 -466
- package/runtime-src/cli.ts +374 -1288
- package/runtime-src/client/index.ts +30 -696
- package/runtime-src/execution/index.ts +143 -694
- package/runtime-src/graph/index.ts +10 -128
- package/runtime-src/intent-match.ts +27 -27
- package/runtime-src/kuri/client.ts +242 -1210
- package/runtime-src/orchestrator/index.ts +499 -2753
- package/runtime-src/reverse-engineer/index.ts +22 -239
- package/runtime-src/runtime/local-server.ts +22 -238
- package/runtime-src/runtime/paths.ts +5 -9
- package/runtime-src/runtime/setup.ts +1 -56
- package/runtime-src/server.ts +11 -11
- package/runtime-src/transform/schema-hints.ts +358 -0
- package/runtime-src/types/skill.ts +2 -488
- package/runtime-src/verification/index.ts +14 -35
- package/runtime-src/version.ts +8 -68
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/SKILL.md +0 -754
- package/bin/unbrowse-update-hint.mjs +0 -22
- package/bin/unbrowse-wrapper.mjs +0 -107
- package/bin/unbrowse.js +0 -37
- package/dist/mcp.js +0 -1796
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/analytics-session.ts +0 -55
- package/runtime-src/api/browse-index.ts +0 -254
- package/runtime-src/api/browse-session.ts +0 -648
- package/runtime-src/api/browse-submit-prereqs.ts +0 -48
- package/runtime-src/api/browse-submit.ts +0 -1184
- package/runtime-src/auth/runtime.ts +0 -116
- package/runtime-src/browser/index.ts +0 -643
- package/runtime-src/browser/types.ts +0 -41
- package/runtime-src/build-info.generated.ts +0 -4
- package/runtime-src/capture/prefetch.ts +0 -122
- package/runtime-src/capture/rsc.ts +0 -45
- package/runtime-src/cli/shortcuts.ts +0 -273
- package/runtime-src/client/graph-client.ts +0 -99
- package/runtime-src/execution/robots.ts +0 -167
- package/runtime-src/execution/search-forms.ts +0 -188
- package/runtime-src/graph/planner.ts +0 -411
- package/runtime-src/graph/session.ts +0 -294
- package/runtime-src/graph/trace-store.ts +0 -136
- package/runtime-src/indexer/index.ts +0 -441
- package/runtime-src/mcp.ts +0 -1522
- package/runtime-src/orchestrator/browser-agent.ts +0 -374
- package/runtime-src/orchestrator/dag-advisor.ts +0 -59
- package/runtime-src/orchestrator/dag-feedback.ts +0 -256
- package/runtime-src/orchestrator/first-pass-action.ts +0 -403
- package/runtime-src/orchestrator/passive-publish.ts +0 -182
- package/runtime-src/orchestrator/timing-economics.ts +0 -80
- package/runtime-src/payments/cascade.ts +0 -137
- package/runtime-src/payments/index.ts +0 -268
- package/runtime-src/payments/wallet.ts +0 -98
- package/runtime-src/publish/sanitize.ts +0 -197
- package/runtime-src/publish-admission.ts +0 -279
- package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
- package/runtime-src/router.ts +0 -17
- package/runtime-src/routing-telemetry.ts +0 -395
- package/runtime-src/runtime/browser-access.ts +0 -11
- package/runtime-src/runtime/browser-auth.ts +0 -12
- package/runtime-src/runtime/browser-host.ts +0 -48
- package/runtime-src/runtime/lifecycle.ts +0 -17
- package/runtime-src/runtime/supervisor.ts +0 -69
- package/runtime-src/runtime/update-hints.ts +0 -351
- package/runtime-src/settings.ts +0 -221
- package/runtime-src/single-binary.ts +0 -141
- package/runtime-src/site-policy.ts +0 -54
- package/runtime-src/stale-cleanup-runner.ts +0 -144
- package/runtime-src/stale-cleanup.ts +0 -133
- package/runtime-src/telemetry-attribution.ts +0 -120
- package/runtime-src/telemetry.ts +0 -253
- package/runtime-src/verification/auth-gate.ts +0 -8
- package/runtime-src/verification/candidates.ts +0 -27
- package/runtime-src/verification/matrix.ts +0 -30
- package/runtime-src/workflow/artifact.ts +0 -161
- package/runtime-src/workflow/compile.ts +0 -808
- package/runtime-src/workflow/publish.ts +0 -205
- package/runtime-src/workflow/runtime.ts +0 -213
- package/scripts/postinstall.mjs +0 -105
- package/scripts/release-assets.mjs +0 -24
- package/scripts/verify-release-assets.mjs +0 -39
- package/vendor/kuri/manifest.json +0 -24
package/dist/cli.js
CHANGED
|
@@ -1,1230 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
var __create = Object.create;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __defProp = Object.defineProperty;
|
|
7
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
10
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
11
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
12
|
-
for (let key of __getOwnPropNames(mod))
|
|
13
|
-
if (!__hasOwnProp.call(to, key))
|
|
14
|
-
__defProp(to, key, {
|
|
15
|
-
get: () => mod[key],
|
|
16
|
-
enumerable: true
|
|
17
|
-
});
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
21
|
-
var __promiseAll = (args) => Promise.all(args);
|
|
22
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
23
|
-
|
|
24
|
-
// ../../src/build-info.generated.ts
|
|
25
|
-
var BUILD_GIT_SHA = "4967715e153e", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMi4xMS4wIiwiZ2l0X3NoYSI6IjQ5Njc3MTVlMTUzZSIsImNvZGVfaGFzaCI6IjE0ODhmYzFkOTJiNyIsInRyYWNlX3ZlcnNpb24iOiIxNDg4ZmMxZDkyYjdANDk2NzcxNWUxNTNlIiwiaXNzdWVkX2F0IjoiMjAyNi0wNC0wM1QyMTo0ODoyNS4yNzhaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "";
|
|
26
|
-
|
|
27
|
-
// ../../src/version.ts
|
|
28
|
-
import { createHash } from "crypto";
|
|
29
|
-
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
30
|
-
import { dirname, join } from "path";
|
|
31
|
-
import { fileURLToPath } from "url";
|
|
32
|
-
function collectTsFiles(dir) {
|
|
33
|
-
const results = [];
|
|
34
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
35
|
-
const full = join(dir, entry.name);
|
|
36
|
-
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
37
|
-
results.push(...collectTsFiles(full));
|
|
38
|
-
} else if (entry.name.endsWith(".ts")) {
|
|
39
|
-
results.push(full);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return results;
|
|
43
|
-
}
|
|
44
|
-
function hashFiles(srcDir, files) {
|
|
45
|
-
const hash = createHash("sha256");
|
|
46
|
-
for (const file of files) {
|
|
47
|
-
hash.update(file.slice(srcDir.length));
|
|
48
|
-
hash.update(readFileSync(file, "utf-8"));
|
|
49
|
-
}
|
|
50
|
-
return hash.digest("hex").slice(0, 12);
|
|
51
|
-
}
|
|
52
|
-
function resolveCodeHashSourceDir(moduleDir) {
|
|
53
|
-
const candidates = [
|
|
54
|
-
moduleDir,
|
|
55
|
-
join(moduleDir, "runtime-src"),
|
|
56
|
-
join(moduleDir, "..", "runtime-src"),
|
|
57
|
-
join(moduleDir, "src"),
|
|
58
|
-
join(moduleDir, "..", "src")
|
|
59
|
-
];
|
|
60
|
-
for (const candidate of candidates) {
|
|
61
|
-
try {
|
|
62
|
-
if (!existsSync(candidate))
|
|
63
|
-
continue;
|
|
64
|
-
const files = collectTsFiles(candidate);
|
|
65
|
-
if (files.length > 0)
|
|
66
|
-
return candidate;
|
|
67
|
-
} catch {}
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
function computeCodeHashForDir(srcDir) {
|
|
72
|
-
const files = collectTsFiles(srcDir).sort();
|
|
73
|
-
if (files.length === 0)
|
|
74
|
-
throw new Error(`No TypeScript sources found in ${srcDir}`);
|
|
75
|
-
return hashFiles(srcDir, files);
|
|
76
|
-
}
|
|
77
|
-
function computeCodeHash() {
|
|
78
|
-
try {
|
|
79
|
-
const srcDir = resolveCodeHashSourceDir(MODULE_DIR);
|
|
80
|
-
if (srcDir)
|
|
81
|
-
return computeCodeHashForDir(srcDir);
|
|
82
|
-
} catch {}
|
|
83
|
-
const pkgVersion = getPackageVersion();
|
|
84
|
-
if (pkgVersion !== "unknown") {
|
|
85
|
-
return createHash("sha256").update(`package:${pkgVersion}`).digest("hex").slice(0, 12);
|
|
86
|
-
}
|
|
87
|
-
return "compiled";
|
|
88
|
-
}
|
|
89
|
-
function getGitSha() {
|
|
90
|
-
return BUILD_GIT_SHA?.trim() || "unknown";
|
|
91
|
-
}
|
|
92
|
-
function getPackageVersion() {
|
|
93
|
-
try {
|
|
94
|
-
const pkg = JSON.parse(readFileSync(join(MODULE_DIR, "..", "package.json"), "utf-8"));
|
|
95
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
96
|
-
} catch {
|
|
97
|
-
return "unknown";
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
var MODULE_DIR, CODE_HASH, GIT_SHA, PACKAGE_VERSION, TRACE_VERSION, RELEASE_MANIFEST_BASE64, RELEASE_MANIFEST_SIGNATURE;
|
|
101
|
-
var init_version = __esm(() => {
|
|
102
|
-
MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
103
|
-
CODE_HASH = BUILD_CODE_HASH?.trim() || computeCodeHash();
|
|
104
|
-
GIT_SHA = getGitSha();
|
|
105
|
-
PACKAGE_VERSION = getPackageVersion();
|
|
106
|
-
TRACE_VERSION = `${CODE_HASH}@${GIT_SHA}`;
|
|
107
|
-
RELEASE_MANIFEST_BASE64 = BUILD_RELEASE_MANIFEST_BASE64?.trim() || "";
|
|
108
|
-
RELEASE_MANIFEST_SIGNATURE = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// ../../src/payments/cascade.ts
|
|
112
|
-
import bs58 from "bs58";
|
|
113
|
-
var init_cascade = () => {};
|
|
114
|
-
|
|
115
|
-
// ../../src/payments/wallet.ts
|
|
116
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
117
|
-
import { homedir } from "node:os";
|
|
118
|
-
import { join as join2 } from "node:path";
|
|
119
|
-
function asNonEmptyString(value) {
|
|
120
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
121
|
-
}
|
|
122
|
-
function getLobsterWalletFromLocalConfig() {
|
|
123
|
-
const agentsPath = join2(process.env.HOME || homedir(), ".lobster", "agents.json");
|
|
124
|
-
if (!existsSync2(agentsPath))
|
|
125
|
-
return;
|
|
126
|
-
try {
|
|
127
|
-
const raw = JSON.parse(readFileSync2(agentsPath, "utf8"));
|
|
128
|
-
const activeAgentId = asNonEmptyString(raw.activeAgentId);
|
|
129
|
-
const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
|
|
130
|
-
return asNonEmptyString(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString(activeAgent?.walletAddress) ?? asNonEmptyString(activeAgent?.wallet_address);
|
|
131
|
-
} catch {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
function getWalletContext() {
|
|
136
|
-
const lobsterWallet = asNonEmptyString(process.env.LOBSTER_WALLET_ADDRESS);
|
|
137
|
-
if (lobsterWallet) {
|
|
138
|
-
return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
|
|
139
|
-
}
|
|
140
|
-
const genericWallet = asNonEmptyString(process.env.AGENT_WALLET_ADDRESS);
|
|
141
|
-
if (genericWallet) {
|
|
142
|
-
return {
|
|
143
|
-
wallet_address: genericWallet,
|
|
144
|
-
wallet_provider: asNonEmptyString(process.env.AGENT_WALLET_PROVIDER)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
const localLobsterWallet = getLobsterWalletFromLocalConfig();
|
|
148
|
-
if (localLobsterWallet) {
|
|
149
|
-
return { wallet_address: localLobsterWallet, wallet_provider: "lobster.cash" };
|
|
150
|
-
}
|
|
151
|
-
return {};
|
|
152
|
-
}
|
|
153
|
-
function checkWalletConfigured() {
|
|
154
|
-
const wallet = getWalletContext();
|
|
155
|
-
if (!wallet.wallet_address)
|
|
156
|
-
return { configured: false };
|
|
157
|
-
return {
|
|
158
|
-
configured: true,
|
|
159
|
-
provider: wallet.wallet_provider ?? "unknown"
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
var init_wallet = () => {};
|
|
163
|
-
// ../../src/runtime/browser-host.ts
|
|
164
|
-
function detectHostEnvironment() {
|
|
165
|
-
if (process.env.OPENCLAW_RUNTIME)
|
|
166
|
-
return "openclaw";
|
|
167
|
-
if (process.env.OPENAI_TOOL_RUNTIME)
|
|
168
|
-
return "openai";
|
|
169
|
-
if (process.env.MCP_SERVER_MODE)
|
|
170
|
-
return "mcp";
|
|
171
|
-
if (process.env.UNBROWSE_NATIVE)
|
|
172
|
-
return "native";
|
|
173
|
-
return "unknown";
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ../../src/telemetry-attribution.ts
|
|
177
|
-
function sanitizeAttributionValue(value) {
|
|
178
|
-
if (typeof value !== "string")
|
|
179
|
-
return;
|
|
180
|
-
const trimmed = value.trim();
|
|
181
|
-
if (!trimmed)
|
|
182
|
-
return;
|
|
183
|
-
return trimmed.slice(0, MAX_ATTRIBUTION_VALUE_LENGTH);
|
|
184
|
-
}
|
|
185
|
-
function hasTelemetryAttribution(value) {
|
|
186
|
-
if (!value)
|
|
187
|
-
return false;
|
|
188
|
-
return TELEMETRY_ATTRIBUTION_KEYS.some((key) => Boolean(sanitizeAttributionValue(value[key])));
|
|
189
|
-
}
|
|
190
|
-
function sanitizeTelemetryAttribution(raw) {
|
|
191
|
-
if (!raw)
|
|
192
|
-
return;
|
|
193
|
-
const cleaned = {};
|
|
194
|
-
for (const key of TELEMETRY_ATTRIBUTION_KEYS) {
|
|
195
|
-
const value = sanitizeAttributionValue(raw[key]);
|
|
196
|
-
if (value)
|
|
197
|
-
cleaned[key] = value;
|
|
198
|
-
}
|
|
199
|
-
return hasTelemetryAttribution(cleaned) ? cleaned : undefined;
|
|
200
|
-
}
|
|
201
|
-
function mergeTelemetryAttribution(base, incoming) {
|
|
202
|
-
const merged = sanitizeTelemetryAttribution({
|
|
203
|
-
...base ?? {},
|
|
204
|
-
...incoming ?? {}
|
|
205
|
-
});
|
|
206
|
-
return merged;
|
|
207
|
-
}
|
|
208
|
-
function mergeTelemetryProperties(properties, attribution) {
|
|
209
|
-
const cleanedAttribution = sanitizeTelemetryAttribution(attribution);
|
|
210
|
-
if (!cleanedAttribution)
|
|
211
|
-
return properties;
|
|
212
|
-
return {
|
|
213
|
-
...cleanedAttribution,
|
|
214
|
-
...properties ?? {}
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function decodeTelemetryAttribution(value) {
|
|
218
|
-
if (!value)
|
|
219
|
-
return;
|
|
220
|
-
try {
|
|
221
|
-
const decoded = Buffer.from(value, "base64").toString("utf8");
|
|
222
|
-
return sanitizeTelemetryAttribution(JSON.parse(decoded));
|
|
223
|
-
} catch {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
var TELEMETRY_ATTRIBUTION_KEYS, MAX_ATTRIBUTION_VALUE_LENGTH = 160;
|
|
228
|
-
var init_telemetry_attribution = __esm(() => {
|
|
229
|
-
TELEMETRY_ATTRIBUTION_KEYS = [
|
|
230
|
-
"utm_source",
|
|
231
|
-
"utm_medium",
|
|
232
|
-
"utm_campaign",
|
|
233
|
-
"utm_content",
|
|
234
|
-
"utm_term",
|
|
235
|
-
"utm_id",
|
|
236
|
-
"gclid",
|
|
237
|
-
"wbraid",
|
|
238
|
-
"gbraid",
|
|
239
|
-
"fbclid",
|
|
240
|
-
"twclid",
|
|
241
|
-
"ttclid",
|
|
242
|
-
"msclkid",
|
|
243
|
-
"li_fat_id",
|
|
244
|
-
"referrer_host",
|
|
245
|
-
"channel",
|
|
246
|
-
"campaign_id",
|
|
247
|
-
"campaign_name",
|
|
248
|
-
"content_id",
|
|
249
|
-
"content_type",
|
|
250
|
-
"creative_id",
|
|
251
|
-
"ad_id",
|
|
252
|
-
"adset_id",
|
|
253
|
-
"inferred_icp",
|
|
254
|
-
"variant_id",
|
|
255
|
-
"experiment_id",
|
|
256
|
-
"icp"
|
|
257
|
-
];
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// ../../src/runtime/paths.ts
|
|
261
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, realpathSync } from "node:fs";
|
|
262
|
-
import os from "node:os";
|
|
263
|
-
import path from "node:path";
|
|
264
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
265
|
-
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
266
|
-
function getModuleDir(metaUrl) {
|
|
267
|
-
return path.dirname(fileURLToPath2(metaUrl));
|
|
268
|
-
}
|
|
269
|
-
function getPackageRoot(metaUrl) {
|
|
270
|
-
if (process.env.UNBROWSE_PACKAGE_ROOT)
|
|
271
|
-
return process.env.UNBROWSE_PACKAGE_ROOT;
|
|
272
|
-
let dir = getModuleDir(metaUrl);
|
|
273
|
-
const root = path.parse(dir).root;
|
|
274
|
-
while (dir !== root) {
|
|
275
|
-
if (existsSync4(path.join(dir, "package.json")))
|
|
276
|
-
return dir;
|
|
277
|
-
dir = path.dirname(dir);
|
|
278
|
-
}
|
|
279
|
-
return getModuleDir(metaUrl);
|
|
280
|
-
}
|
|
281
|
-
function resolveSiblingEntrypoint(metaUrl, basename) {
|
|
282
|
-
const file = fileURLToPath2(metaUrl);
|
|
283
|
-
return path.join(path.dirname(file), `${basename}${path.extname(file) || ".js"}`);
|
|
284
|
-
}
|
|
285
|
-
function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
|
|
286
|
-
if (path.extname(entrypoint) !== ".ts")
|
|
287
|
-
return [entrypoint];
|
|
288
|
-
if (process.versions.bun)
|
|
289
|
-
return [entrypoint];
|
|
290
|
-
try {
|
|
291
|
-
const req = createRequire2(metaUrl);
|
|
292
|
-
const tsxPkg = req.resolve("tsx/package.json");
|
|
293
|
-
const tsxLoader = path.join(path.dirname(tsxPkg), "dist", "loader.mjs");
|
|
294
|
-
if (existsSync4(tsxLoader))
|
|
295
|
-
return ["--import", pathToFileURL(tsxLoader).href, entrypoint];
|
|
296
|
-
} catch {}
|
|
297
|
-
return ["--import", "tsx", entrypoint];
|
|
298
|
-
}
|
|
299
|
-
function getUnbrowseHome() {
|
|
300
|
-
return path.join(os.homedir(), ".unbrowse");
|
|
301
|
-
}
|
|
302
|
-
function ensureDir(dir) {
|
|
303
|
-
if (!existsSync4(dir))
|
|
304
|
-
mkdirSync2(dir, { recursive: true });
|
|
305
|
-
return dir;
|
|
306
|
-
}
|
|
307
|
-
function getLogsDir() {
|
|
308
|
-
return ensureDir(path.join(getUnbrowseHome(), "logs"));
|
|
309
|
-
}
|
|
310
|
-
function getRunDir() {
|
|
311
|
-
return ensureDir(process.env.UNBROWSE_RUN_DIR || path.join(getUnbrowseHome(), "run"));
|
|
312
|
-
}
|
|
313
|
-
function sanitizeSegment(value) {
|
|
314
|
-
return value.replace(/[^a-zA-Z0-9.-]+/g, "_");
|
|
315
|
-
}
|
|
316
|
-
function getServerPidFile(baseUrl) {
|
|
317
|
-
const url = new URL(baseUrl);
|
|
318
|
-
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
319
|
-
const host = sanitizeSegment(url.hostname || "127.0.0.1");
|
|
320
|
-
return path.join(getRunDir(), `server-${host}-${port}.json`);
|
|
321
|
-
}
|
|
322
|
-
function getServerAutostartLogFile() {
|
|
323
|
-
return path.join(getLogsDir(), "server-autostart.log");
|
|
324
|
-
}
|
|
325
|
-
var init_paths = () => {};
|
|
326
|
-
|
|
327
|
-
// ../../src/runtime/supervisor.ts
|
|
328
|
-
class LocalSupervisor {
|
|
329
|
-
running = false;
|
|
330
|
-
startTime = 0;
|
|
331
|
-
async start() {
|
|
332
|
-
this.running = true;
|
|
333
|
-
this.startTime = Date.now();
|
|
334
|
-
}
|
|
335
|
-
async stop() {
|
|
336
|
-
this.running = false;
|
|
337
|
-
}
|
|
338
|
-
isRunning() {
|
|
339
|
-
return this.running;
|
|
340
|
-
}
|
|
341
|
-
async healthCheck() {
|
|
342
|
-
return {
|
|
343
|
-
healthy: this.running,
|
|
344
|
-
uptime_ms: this.running ? Date.now() - this.startTime : 0
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
var init_supervisor = () => {};
|
|
349
|
-
// ../../src/graph/index.ts
|
|
350
|
-
var PAGINATION_KEYS;
|
|
351
|
-
var init_graph = __esm(() => {
|
|
352
|
-
PAGINATION_KEYS = new Set(["cursor", "page", "offset", "page_token", "next_cursor", "after", "before", "start", "next_page", "continuation_token", "skip", "from", "scroll_id"]);
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// ../../src/client/index.ts
|
|
356
|
-
function sanitizeProfileName2(value) {
|
|
357
|
-
return value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
358
|
-
}
|
|
359
|
-
var API_URL2, PROFILE_NAME2, recentLocalSkills2, LOCAL_ONLY2, API_TIMEOUT_MS2, PUBLISH_TIMEOUT_MS2;
|
|
360
|
-
var init_client = __esm(() => {
|
|
361
|
-
init_version();
|
|
362
|
-
init_cascade();
|
|
363
|
-
init_wallet();
|
|
364
|
-
init_telemetry_attribution();
|
|
365
|
-
API_URL2 = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
|
|
366
|
-
PROFILE_NAME2 = sanitizeProfileName2(process.env.UNBROWSE_PROFILE ?? "");
|
|
367
|
-
recentLocalSkills2 = new Map;
|
|
368
|
-
LOCAL_ONLY2 = process.env.UNBROWSE_LOCAL_ONLY === "1";
|
|
369
|
-
API_TIMEOUT_MS2 = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
|
|
370
|
-
PUBLISH_TIMEOUT_MS2 = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// ../../src/marketplace/index.ts
|
|
374
|
-
import { nanoid } from "nanoid";
|
|
375
|
-
var init_marketplace = __esm(() => {
|
|
376
|
-
init_client();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// ../../src/domain.ts
|
|
380
|
-
var CC_TLDS;
|
|
381
|
-
var init_domain = __esm(() => {
|
|
382
|
-
CC_TLDS = new Set([
|
|
383
|
-
"co.uk",
|
|
384
|
-
"co.nz",
|
|
385
|
-
"co.jp",
|
|
386
|
-
"co.kr",
|
|
387
|
-
"co.in",
|
|
388
|
-
"com.br",
|
|
389
|
-
"com.au",
|
|
390
|
-
"com.cn",
|
|
391
|
-
"com.mx",
|
|
392
|
-
"com.ar",
|
|
393
|
-
"com.tw",
|
|
394
|
-
"org.uk",
|
|
395
|
-
"gov.uk",
|
|
396
|
-
"ac.uk",
|
|
397
|
-
"net.au",
|
|
398
|
-
"org.au"
|
|
399
|
-
]);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// ../../src/publish-admission.ts
|
|
403
|
-
function readPositiveIntEnv(name, fallback) {
|
|
404
|
-
const parsed = Number.parseInt(process.env[name] ?? "", 10);
|
|
405
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
406
|
-
}
|
|
407
|
-
function readProbabilityEnv(name, fallback) {
|
|
408
|
-
const parsed = Number.parseFloat(process.env[name] ?? "");
|
|
409
|
-
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? parsed : fallback;
|
|
410
|
-
}
|
|
411
|
-
var DEFAULT_PUBLISH_ENDPOINT_LIMIT, MIN_PUBLISH_RELIABILITY;
|
|
412
|
-
var init_publish_admission = __esm(() => {
|
|
413
|
-
init_domain();
|
|
414
|
-
DEFAULT_PUBLISH_ENDPOINT_LIMIT = readPositiveIntEnv("UNBROWSE_PUBLISH_ENDPOINT_LIMIT", 12);
|
|
415
|
-
MIN_PUBLISH_RELIABILITY = readProbabilityEnv("UNBROWSE_PUBLISH_MIN_RELIABILITY", 0.2);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// ../../src/logger.ts
|
|
419
|
-
import fs from "node:fs";
|
|
420
|
-
import path4 from "node:path";
|
|
421
|
-
import os2 from "node:os";
|
|
422
|
-
function getLogFile() {
|
|
423
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
424
|
-
return path4.join(LOG_DIR, `unbrowse-${date}.log`);
|
|
425
|
-
}
|
|
426
|
-
function ensureLogDir() {
|
|
427
|
-
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
428
|
-
}
|
|
429
|
-
function log(module, message) {
|
|
430
|
-
const ts = new Date().toTimeString().slice(0, 8);
|
|
431
|
-
const line = `[${ts}] [${module}] ${message}`;
|
|
432
|
-
console.log(line);
|
|
433
|
-
try {
|
|
434
|
-
ensureLogDir();
|
|
435
|
-
fs.appendFileSync(getLogFile(), line + `
|
|
436
|
-
`);
|
|
437
|
-
} catch {}
|
|
438
|
-
}
|
|
439
|
-
var LOG_DIR;
|
|
440
|
-
var init_logger = __esm(() => {
|
|
441
|
-
LOG_DIR = path4.join(os2.homedir(), ".unbrowse", "logs");
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
// ../../src/kuri/client.ts
|
|
445
|
-
import { execFileSync, spawn as spawn2 } from "node:child_process";
|
|
446
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
447
|
-
import path5 from "node:path";
|
|
448
|
-
function createBrokerState(port = KURI_DEFAULT_PORT) {
|
|
449
|
-
return {
|
|
450
|
-
process: null,
|
|
451
|
-
port,
|
|
452
|
-
cdpPort: null,
|
|
453
|
-
managedChrome: false,
|
|
454
|
-
ready: false,
|
|
455
|
-
startPromise: null,
|
|
456
|
-
requestedPort: port
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
function kuriBinaryName() {
|
|
460
|
-
return process.platform === "win32" ? "kuri.exe" : "kuri";
|
|
461
|
-
}
|
|
462
|
-
function currentBundledKuriTarget() {
|
|
463
|
-
if (process.platform === "darwin" && process.arch === "arm64")
|
|
464
|
-
return "darwin-arm64";
|
|
465
|
-
if (process.platform === "darwin" && process.arch === "x64")
|
|
466
|
-
return "darwin-x64";
|
|
467
|
-
if (process.platform === "linux" && process.arch === "arm64")
|
|
468
|
-
return "linux-arm64";
|
|
469
|
-
if (process.platform === "linux" && process.arch === "x64")
|
|
470
|
-
return "linux-x64";
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
function resolveBinaryOnPath(name) {
|
|
474
|
-
const checker = process.platform === "win32" ? "where" : "which";
|
|
475
|
-
try {
|
|
476
|
-
const output = execFileSync(checker, [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
477
|
-
const match = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
478
|
-
return match || null;
|
|
479
|
-
} catch {
|
|
480
|
-
return null;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
function addCandidate(candidates, candidate) {
|
|
484
|
-
if (!candidate)
|
|
485
|
-
return;
|
|
486
|
-
if (!candidates.includes(candidate))
|
|
487
|
-
candidates.push(candidate);
|
|
488
|
-
}
|
|
489
|
-
function getKuriSourceCandidates() {
|
|
490
|
-
const packageRoot = getPackageRoot(import.meta.url);
|
|
491
|
-
const candidates = [];
|
|
492
|
-
addCandidate(candidates, path5.join(packageRoot, "vendor", "kuri-src"));
|
|
493
|
-
addCandidate(candidates, path5.join(packageRoot, "submodules", "kuri"));
|
|
494
|
-
if (process.env.KURI_PATH)
|
|
495
|
-
addCandidate(candidates, process.env.KURI_PATH);
|
|
496
|
-
if (process.env.HOME)
|
|
497
|
-
addCandidate(candidates, path5.join(process.env.HOME, "kuri"));
|
|
498
|
-
return candidates;
|
|
499
|
-
}
|
|
500
|
-
function getKuriBinaryCandidates() {
|
|
501
|
-
const packageRoot = getPackageRoot(import.meta.url);
|
|
502
|
-
const binaryName = kuriBinaryName();
|
|
503
|
-
const target = currentBundledKuriTarget();
|
|
504
|
-
const candidates = [];
|
|
505
|
-
if (target)
|
|
506
|
-
addCandidate(candidates, path5.join(packageRoot, "vendor", "kuri", target, binaryName));
|
|
507
|
-
if (target)
|
|
508
|
-
addCandidate(candidates, path5.join(packageRoot, "packages", "skill", "vendor", "kuri", target, binaryName));
|
|
509
|
-
for (const sourceDir of getKuriSourceCandidates()) {
|
|
510
|
-
addCandidate(candidates, path5.join(sourceDir, "zig-out", "bin", binaryName));
|
|
511
|
-
}
|
|
512
|
-
addCandidate(candidates, resolveBinaryOnPath("kuri"));
|
|
513
|
-
return candidates;
|
|
514
|
-
}
|
|
515
|
-
function findKuriBinary() {
|
|
516
|
-
if (process.env.KURI_BIN)
|
|
517
|
-
return process.env.KURI_BIN;
|
|
518
|
-
const candidates = getKuriBinaryCandidates();
|
|
519
|
-
return candidates.find((candidate) => existsSync6(candidate)) ?? candidates[0] ?? kuriBinaryName();
|
|
520
|
-
}
|
|
521
|
-
var KURI_DEFAULT_PORT = 7700, defaultBrokerState, brokerClients;
|
|
522
|
-
var init_client2 = __esm(() => {
|
|
523
|
-
init_logger();
|
|
524
|
-
init_paths();
|
|
525
|
-
defaultBrokerState = createBrokerState();
|
|
526
|
-
brokerClients = new Map;
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
// ../../src/telemetry.ts
|
|
530
|
-
var errorAccumulator, autoFiledKeys;
|
|
531
|
-
var init_telemetry = __esm(() => {
|
|
532
|
-
init_version();
|
|
533
|
-
errorAccumulator = new Map;
|
|
534
|
-
autoFiledKeys = new Set;
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
// ../../src/runtime/browser-access.ts
|
|
538
|
-
var init_browser_access = () => {};
|
|
539
|
-
|
|
540
|
-
// ../../src/capture/index.ts
|
|
541
|
-
import { nanoid as nanoid2 } from "nanoid";
|
|
542
|
-
var activeTabRegistry, interceptorInjectedTabs;
|
|
543
|
-
var init_capture = __esm(() => {
|
|
544
|
-
init_client2();
|
|
545
|
-
init_domain();
|
|
546
|
-
init_logger();
|
|
547
|
-
init_browser_access();
|
|
548
|
-
activeTabRegistry = new Set;
|
|
549
|
-
interceptorInjectedTabs = new Set;
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
// ../../src/transform/index.ts
|
|
553
|
-
var EPHEMERAL_KEYS;
|
|
554
|
-
var init_transform = __esm(() => {
|
|
555
|
-
EPHEMERAL_KEYS = new Set(["trackingId", "$type", "recipeType"]);
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
// ../../src/debug-trace.ts
|
|
559
|
-
import { join as join4 } from "node:path";
|
|
560
|
-
import { nanoid as nanoid3 } from "nanoid";
|
|
561
|
-
var TRACE_DIR;
|
|
562
|
-
var init_debug_trace = __esm(() => {
|
|
563
|
-
TRACE_DIR = process.env.TRACES_DIR ?? join4(process.cwd(), "traces");
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
// ../../src/publish/sanitize.ts
|
|
567
|
-
var init_sanitize = () => {};
|
|
568
|
-
|
|
569
|
-
// ../../src/reverse-engineer/description-prompt.ts
|
|
570
|
-
var init_description_prompt = __esm(() => {
|
|
571
|
-
init_transform();
|
|
572
|
-
init_sanitize();
|
|
573
|
-
});
|
|
574
|
-
// ../../src/reverse-engineer/index.ts
|
|
575
|
-
import { nanoid as nanoid4 } from "nanoid";
|
|
576
|
-
var ALLOWED_METHODS, STRIP_HEADERS, REPLAY_HEADER_EXACT, SAFE_HEADERS, AD_SCHEMA_KEYS;
|
|
577
|
-
var init_reverse_engineer = __esm(() => {
|
|
578
|
-
init_transform();
|
|
579
|
-
init_domain();
|
|
580
|
-
init_graph();
|
|
581
|
-
init_debug_trace();
|
|
582
|
-
init_description_prompt();
|
|
583
|
-
ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
584
|
-
STRIP_HEADERS = new Set([
|
|
585
|
-
"cookie",
|
|
586
|
-
"authorization",
|
|
587
|
-
"x-csrf-token",
|
|
588
|
-
"x-api-key",
|
|
589
|
-
"api-key",
|
|
590
|
-
"x-auth-token",
|
|
591
|
-
"x-app-key",
|
|
592
|
-
"x-app-secret",
|
|
593
|
-
"content-length",
|
|
594
|
-
"host",
|
|
595
|
-
"x-goog-api-key",
|
|
596
|
-
"x-server-token",
|
|
597
|
-
"x-goog-encode-response-if-executable",
|
|
598
|
-
"x-clientdetails",
|
|
599
|
-
"x-javascript-user-agent"
|
|
600
|
-
]);
|
|
601
|
-
REPLAY_HEADER_EXACT = new Set([
|
|
602
|
-
"accept",
|
|
603
|
-
"csrf-token",
|
|
604
|
-
"origin",
|
|
605
|
-
"x-requested-with",
|
|
606
|
-
"x-restli-protocol-version"
|
|
607
|
-
]);
|
|
608
|
-
SAFE_HEADERS = new Set([
|
|
609
|
-
"accept",
|
|
610
|
-
"accept-encoding",
|
|
611
|
-
"accept-language",
|
|
612
|
-
"cache-control",
|
|
613
|
-
"content-type",
|
|
614
|
-
"origin",
|
|
615
|
-
"referer",
|
|
616
|
-
"user-agent",
|
|
617
|
-
"pragma",
|
|
618
|
-
"if-none-match",
|
|
619
|
-
"if-modified-since",
|
|
620
|
-
"range",
|
|
621
|
-
"dnt",
|
|
622
|
-
"connection",
|
|
623
|
-
"sec-ch-ua",
|
|
624
|
-
"sec-ch-ua-mobile",
|
|
625
|
-
"sec-ch-ua-platform",
|
|
626
|
-
"sec-fetch-dest",
|
|
627
|
-
"sec-fetch-mode",
|
|
628
|
-
"sec-fetch-site",
|
|
629
|
-
"x-requested-with"
|
|
630
|
-
]);
|
|
631
|
-
AD_SCHEMA_KEYS = new Set([
|
|
632
|
-
"campaignid",
|
|
633
|
-
"creativeid",
|
|
634
|
-
"creativetype",
|
|
635
|
-
"creativecontent",
|
|
636
|
-
"orderid",
|
|
637
|
-
"impressionurl",
|
|
638
|
-
"clickurl",
|
|
639
|
-
"customerid",
|
|
640
|
-
"adunitid",
|
|
641
|
-
"adslot",
|
|
642
|
-
"adsize",
|
|
643
|
-
"lineitemid"
|
|
644
|
-
]);
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
// ../../src/reverse-engineer/bundle-scanner.ts
|
|
648
|
-
var init_bundle_scanner = __esm(() => {
|
|
649
|
-
init_logger();
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
// ../../src/vault/index.ts
|
|
653
|
-
import { join as join5 } from "path";
|
|
654
|
-
import { homedir as homedir3 } from "os";
|
|
655
|
-
function normalizeKeytarModule(mod) {
|
|
656
|
-
let candidate = mod;
|
|
657
|
-
for (let depth = 0;depth < 3; depth++) {
|
|
658
|
-
if (!candidate || typeof candidate !== "object" || !("default" in candidate))
|
|
659
|
-
break;
|
|
660
|
-
candidate = candidate.default;
|
|
661
|
-
}
|
|
662
|
-
if (!candidate)
|
|
663
|
-
return null;
|
|
664
|
-
if (typeof candidate.setPassword === "function" && typeof candidate.getPassword === "function" && typeof candidate.deletePassword === "function") {
|
|
665
|
-
return candidate;
|
|
666
|
-
}
|
|
667
|
-
return null;
|
|
668
|
-
}
|
|
669
|
-
var KEYTAR_UNAVAILABLE, keytar = null, VAULT_DIR, VAULT_FILE, KEY_FILE, vaultLock;
|
|
670
|
-
var init_vault = __esm(async () => {
|
|
671
|
-
init_logger();
|
|
672
|
-
KEYTAR_UNAVAILABLE = Symbol("KEYTAR_UNAVAILABLE");
|
|
673
|
-
try {
|
|
674
|
-
keytar = normalizeKeytarModule(await import("keytar"));
|
|
675
|
-
} catch {}
|
|
676
|
-
VAULT_DIR = join5(homedir3(), ".unbrowse", "vault");
|
|
677
|
-
VAULT_FILE = join5(VAULT_DIR, "credentials.enc");
|
|
678
|
-
KEY_FILE = join5(VAULT_DIR, ".key");
|
|
679
|
-
vaultLock = Promise.resolve();
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// ../../src/auth/index.ts
|
|
683
|
-
var init_auth = __esm(async () => {
|
|
684
|
-
init_client2();
|
|
685
|
-
init_domain();
|
|
686
|
-
init_logger();
|
|
687
|
-
init_supervisor();
|
|
688
|
-
await init_vault();
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
// ../../src/auth/runtime.ts
|
|
692
|
-
class LocalAuthRuntime {
|
|
693
|
-
sessions = new Map;
|
|
694
|
-
async resolveAuth(dep) {
|
|
695
|
-
if (dep.strategy === "none")
|
|
696
|
-
return { authenticated: true };
|
|
697
|
-
const session = this.sessions.get(dep.domain);
|
|
698
|
-
if (session && session.expires > Date.now()) {
|
|
699
|
-
return { authenticated: true, session_token: session.token };
|
|
700
|
-
}
|
|
701
|
-
if (dep.strategy === "refresh_session" && session) {
|
|
702
|
-
const refreshed = await this.refreshSession(dep.domain);
|
|
703
|
-
if (refreshed) {
|
|
704
|
-
const updated = this.sessions.get(dep.domain);
|
|
705
|
-
return { authenticated: true, session_token: updated?.token };
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return { authenticated: false };
|
|
709
|
-
}
|
|
710
|
-
async isSessionValid(domain) {
|
|
711
|
-
const session = this.sessions.get(domain);
|
|
712
|
-
return !!session && session.expires > Date.now();
|
|
713
|
-
}
|
|
714
|
-
async refreshSession(domain) {
|
|
715
|
-
const session = this.sessions.get(domain);
|
|
716
|
-
if (session) {
|
|
717
|
-
session.expires = Date.now() + 3600000;
|
|
718
|
-
return true;
|
|
719
|
-
}
|
|
720
|
-
return false;
|
|
721
|
-
}
|
|
722
|
-
async loginIfNeeded(domain, _loginUrl) {
|
|
723
|
-
const refreshed = await this.refreshSession(domain);
|
|
724
|
-
if (refreshed)
|
|
725
|
-
return true;
|
|
726
|
-
return false;
|
|
727
|
-
}
|
|
728
|
-
setSession(domain, token, ttlMs = 3600000) {
|
|
729
|
-
this.sessions.set(domain, { token, expires: Date.now() + ttlMs });
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
var authRuntime;
|
|
733
|
-
var init_runtime = __esm(() => {
|
|
734
|
-
authRuntime = new LocalAuthRuntime;
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
// ../../src/transform/drift.ts
|
|
738
|
-
var init_drift = __esm(() => {
|
|
739
|
-
init_transform();
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
// ../../src/execution/retry.ts
|
|
743
|
-
var RETRYABLE_STATUSES;
|
|
744
|
-
var init_retry = __esm(() => {
|
|
745
|
-
RETRYABLE_STATUSES = new Set([500, 502, 503, 504, 429]);
|
|
746
|
-
});
|
|
747
|
-
// ../../src/extraction/index.ts
|
|
748
|
-
import * as cheerio from "cheerio";
|
|
749
|
-
var STRIP_TAGS, CHROME_TAGS;
|
|
750
|
-
var init_extraction = __esm(() => {
|
|
751
|
-
STRIP_TAGS = new Set(["script", "style", "noscript", "svg", "iframe"]);
|
|
752
|
-
CHROME_TAGS = new Set(["nav", "footer", "header"]);
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
// ../../src/graph/agent-augment.ts
|
|
756
|
-
var DEFAULT_MODEL, ENABLED, AUGMENT_TIMEOUT_MS, MAX_AUGMENT_ENDPOINTS, MAX_AUGMENT_PAYLOAD_CHARS, GENERIC_SEMANTIC_TYPES;
|
|
757
|
-
var init_agent_augment = __esm(() => {
|
|
758
|
-
init_graph();
|
|
759
|
-
DEFAULT_MODEL = process.env.UNBROWSE_AGENT_SEMANTIC_MODEL ?? process.env.UNBROWSE_AGENT_JUDGE_MODEL ?? "gpt-4.1-mini";
|
|
760
|
-
ENABLED = process.env.UNBROWSE_AGENT_SEMANTIC_AUGMENT !== "0";
|
|
761
|
-
AUGMENT_TIMEOUT_MS = Number(process.env.UNBROWSE_AGENT_SEMANTIC_TIMEOUT_MS ?? 8000);
|
|
762
|
-
MAX_AUGMENT_ENDPOINTS = Math.max(1, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_ENDPOINTS ?? 6));
|
|
763
|
-
MAX_AUGMENT_PAYLOAD_CHARS = Math.max(4000, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_PAYLOAD_CHARS ?? 24000));
|
|
764
|
-
GENERIC_SEMANTIC_TYPES = new Set(["identifier", "input", "resource", "entity", "item"]);
|
|
765
|
-
});
|
|
766
|
-
|
|
767
|
-
// ../../src/execution/search-forms.ts
|
|
768
|
-
var SEARCH_FIELD_NAMES, LOGIN_FIELD_NAMES, SUPPORTED_INPUT_TYPES;
|
|
769
|
-
var init_search_forms = __esm(() => {
|
|
770
|
-
SEARCH_FIELD_NAMES = new Set([
|
|
771
|
-
"q",
|
|
772
|
-
"query",
|
|
773
|
-
"search",
|
|
774
|
-
"keyword",
|
|
775
|
-
"keywords",
|
|
776
|
-
"term",
|
|
777
|
-
"terms",
|
|
778
|
-
"find",
|
|
779
|
-
"lookup",
|
|
780
|
-
"filter",
|
|
781
|
-
"s",
|
|
782
|
-
"text",
|
|
783
|
-
"input"
|
|
784
|
-
]);
|
|
785
|
-
LOGIN_FIELD_NAMES = new Set([
|
|
786
|
-
"password",
|
|
787
|
-
"passwd",
|
|
788
|
-
"pass",
|
|
789
|
-
"pwd",
|
|
790
|
-
"confirm_password",
|
|
791
|
-
"username",
|
|
792
|
-
"email",
|
|
793
|
-
"login",
|
|
794
|
-
"user"
|
|
795
|
-
]);
|
|
796
|
-
SUPPORTED_INPUT_TYPES = new Set([
|
|
797
|
-
"text",
|
|
798
|
-
"search",
|
|
799
|
-
"hidden",
|
|
800
|
-
"date",
|
|
801
|
-
"number",
|
|
802
|
-
"tel",
|
|
803
|
-
"email"
|
|
804
|
-
]);
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
// ../../src/workflow/artifact.ts
|
|
808
|
-
var init_artifact = __esm(() => {
|
|
809
|
-
init_logger();
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
// ../../src/workflow/publish.ts
|
|
813
|
-
var init_publish = __esm(() => {
|
|
814
|
-
init_logger();
|
|
815
|
-
init_sanitize();
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
// ../../src/settings.ts
|
|
819
|
-
var init_settings = __esm(() => {
|
|
820
|
-
init_domain();
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
// ../../src/indexer/index.ts
|
|
824
|
-
import { join as join6 } from "node:path";
|
|
825
|
-
var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
|
|
826
|
-
var init_indexer = __esm(async () => {
|
|
827
|
-
init_graph();
|
|
828
|
-
init_client();
|
|
829
|
-
init_marketplace();
|
|
830
|
-
init_publish_admission();
|
|
831
|
-
init_domain();
|
|
832
|
-
init_sanitize();
|
|
833
|
-
init_artifact();
|
|
834
|
-
init_publish();
|
|
835
|
-
init_settings();
|
|
836
|
-
await init_orchestrator();
|
|
837
|
-
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join6(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
838
|
-
indexInFlight = new Map;
|
|
839
|
-
pendingIndexJobs = new Map;
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
// ../../src/payments/index.ts
|
|
843
|
-
var PRICING_API_URL;
|
|
844
|
-
var init_payments = __esm(() => {
|
|
845
|
-
PRICING_API_URL = process.env.UNBROWSE_BACKEND_URL ?? "https://beta-api.unbrowse.ai";
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
// ../../src/execution/robots.ts
|
|
849
|
-
var TTL_MS, cache;
|
|
850
|
-
var init_robots = __esm(() => {
|
|
851
|
-
TTL_MS = 24 * 60 * 60 * 1000;
|
|
852
|
-
cache = new Map;
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
// ../../src/site-policy.ts
|
|
856
|
-
var MUTATION_METHODS;
|
|
857
|
-
var init_site_policy = __esm(() => {
|
|
858
|
-
init_domain();
|
|
859
|
-
MUTATION_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
// ../../src/workflow/compile.ts
|
|
863
|
-
import { nanoid as nanoid5 } from "nanoid";
|
|
864
|
-
import { load as load2 } from "cheerio";
|
|
865
|
-
var init_compile = () => {};
|
|
866
|
-
// ../../src/execution/index.ts
|
|
867
|
-
import { nanoid as nanoid6 } from "nanoid";
|
|
868
|
-
var VALID_VERIFICATION_STATUSES, STOPWORDS;
|
|
869
|
-
var init_execution = __esm(async () => {
|
|
870
|
-
init_capture();
|
|
871
|
-
init_capture();
|
|
872
|
-
init_reverse_engineer();
|
|
873
|
-
init_bundle_scanner();
|
|
874
|
-
init_marketplace();
|
|
875
|
-
init_runtime();
|
|
876
|
-
init_transform();
|
|
877
|
-
init_drift();
|
|
878
|
-
init_client();
|
|
879
|
-
init_client();
|
|
880
|
-
init_retry();
|
|
881
|
-
init_domain();
|
|
882
|
-
init_extraction();
|
|
883
|
-
init_graph();
|
|
884
|
-
init_agent_augment();
|
|
885
|
-
init_logger();
|
|
886
|
-
init_version();
|
|
887
|
-
init_search_forms();
|
|
888
|
-
init_payments();
|
|
889
|
-
init_robots();
|
|
890
|
-
init_site_policy();
|
|
891
|
-
init_artifact();
|
|
892
|
-
init_compile();
|
|
893
|
-
init_publish();
|
|
894
|
-
await __promiseAll([
|
|
895
|
-
init_vault(),
|
|
896
|
-
init_auth(),
|
|
897
|
-
init_indexer(),
|
|
898
|
-
init_orchestrator()
|
|
899
|
-
]);
|
|
900
|
-
VALID_VERIFICATION_STATUSES = new Set(["verified", "unverified", "failed", "pending"]);
|
|
901
|
-
STOPWORDS = new Set([
|
|
902
|
-
"the",
|
|
903
|
-
"a",
|
|
904
|
-
"an",
|
|
905
|
-
"and",
|
|
906
|
-
"or",
|
|
907
|
-
"of",
|
|
908
|
-
"to",
|
|
909
|
-
"in",
|
|
910
|
-
"for",
|
|
911
|
-
"on",
|
|
912
|
-
"with",
|
|
913
|
-
"from",
|
|
914
|
-
"get",
|
|
915
|
-
"all",
|
|
916
|
-
"this",
|
|
917
|
-
"that",
|
|
918
|
-
"is",
|
|
919
|
-
"are",
|
|
920
|
-
"was",
|
|
921
|
-
"be",
|
|
922
|
-
"it",
|
|
923
|
-
"at",
|
|
924
|
-
"by",
|
|
925
|
-
"not",
|
|
926
|
-
"com",
|
|
927
|
-
"www",
|
|
928
|
-
"https",
|
|
929
|
-
"http",
|
|
930
|
-
"html",
|
|
931
|
-
"htm"
|
|
932
|
-
]);
|
|
933
|
-
});
|
|
934
|
-
|
|
935
|
-
// ../../src/graph/planner.ts
|
|
936
|
-
var MUTABLE_METHODS, sessionNegatives, sessionTraces;
|
|
937
|
-
var init_planner = __esm(() => {
|
|
938
|
-
init_graph();
|
|
939
|
-
init_runtime();
|
|
940
|
-
MUTABLE_METHODS = new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
941
|
-
sessionNegatives = new Map;
|
|
942
|
-
sessionTraces = new Map;
|
|
943
|
-
});
|
|
944
|
-
|
|
945
|
-
// ../../src/client/graph-client.ts
|
|
946
|
-
var API_URL3, GRAPH_TIMEOUT_MS;
|
|
947
|
-
var init_graph_client = __esm(() => {
|
|
948
|
-
init_client();
|
|
949
|
-
API_URL3 = process.env.UNBROWSE_BACKEND_URL ?? "https://beta-api.unbrowse.ai";
|
|
950
|
-
GRAPH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_GRAPH_TIMEOUT_MS ?? "4000", 10);
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
// ../../src/orchestrator/dag-advisor.ts
|
|
954
|
-
var init_dag_advisor = __esm(() => {
|
|
955
|
-
init_planner();
|
|
956
|
-
init_graph_client();
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
// ../../src/orchestrator/dag-feedback.ts
|
|
960
|
-
import { nanoid as nanoid7 } from "nanoid";
|
|
961
|
-
var SESSION_ID, lastWriteAt, pendingTimers;
|
|
962
|
-
var init_dag_feedback = __esm(() => {
|
|
963
|
-
init_client();
|
|
964
|
-
init_graph_client();
|
|
965
|
-
init_graph();
|
|
966
|
-
SESSION_ID = nanoid7();
|
|
967
|
-
lastWriteAt = new Map;
|
|
968
|
-
pendingTimers = new Map;
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
// ../../src/graph/trace-store.ts
|
|
972
|
-
var init_trace_store = () => {};
|
|
973
|
-
|
|
974
|
-
// ../../src/orchestrator/passive-publish.ts
|
|
975
|
-
var passivePublishInFlight;
|
|
976
|
-
var init_passive_publish = __esm(() => {
|
|
977
|
-
init_client();
|
|
978
|
-
init_publish_admission();
|
|
979
|
-
init_marketplace();
|
|
980
|
-
init_artifact();
|
|
981
|
-
init_publish();
|
|
982
|
-
passivePublishInFlight = new Map;
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
// ../../src/capture/prefetch.ts
|
|
986
|
-
var init_prefetch = __esm(async () => {
|
|
987
|
-
init_graph();
|
|
988
|
-
await init_execution();
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
// ../../src/orchestrator/first-pass-action.ts
|
|
992
|
-
var init_first_pass_action = __esm(() => {
|
|
993
|
-
init_client2();
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
// ../../src/orchestrator/timing-economics.ts
|
|
997
|
-
var TOKEN_COST_PER_MILLION_USD = 3, TOKEN_COST_UC, SAVINGS_SOURCES;
|
|
998
|
-
var init_timing_economics = __esm(() => {
|
|
999
|
-
TOKEN_COST_UC = Math.round(TOKEN_COST_PER_MILLION_USD);
|
|
1000
|
-
SAVINGS_SOURCES = new Set([
|
|
1001
|
-
"marketplace",
|
|
1002
|
-
"route-cache",
|
|
1003
|
-
"first-pass",
|
|
1004
|
-
"direct-fetch",
|
|
1005
|
-
"browser-action"
|
|
1006
|
-
]);
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
// ../../src/routing-telemetry.ts
|
|
1010
|
-
import { nanoid as nanoid8 } from "nanoid";
|
|
1011
|
-
var init_routing_telemetry = __esm(() => {
|
|
1012
|
-
init_telemetry();
|
|
1013
|
-
});
|
|
1014
|
-
// ../../src/orchestrator/index.ts
|
|
1015
|
-
import { nanoid as nanoid9 } from "nanoid";
|
|
1016
|
-
import { existsSync as existsSync7, writeFileSync as writeFileSync3, readFileSync as readFileSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
1017
|
-
import { dirname as dirname2, join as join7 } from "node:path";
|
|
1018
|
-
var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
|
|
1019
|
-
var init_orchestrator = __esm(async () => {
|
|
1020
|
-
init_client();
|
|
1021
|
-
init_client2();
|
|
1022
|
-
init_telemetry();
|
|
1023
|
-
init_marketplace();
|
|
1024
|
-
init_graph();
|
|
1025
|
-
init_dag_advisor();
|
|
1026
|
-
init_domain();
|
|
1027
|
-
init_debug_trace();
|
|
1028
|
-
init_dag_feedback();
|
|
1029
|
-
init_search_forms();
|
|
1030
|
-
init_trace_store();
|
|
1031
|
-
init_passive_publish();
|
|
1032
|
-
init_first_pass_action();
|
|
1033
|
-
init_timing_economics();
|
|
1034
|
-
init_payments();
|
|
1035
|
-
init_wallet();
|
|
1036
|
-
init_version();
|
|
1037
|
-
init_runtime();
|
|
1038
|
-
init_routing_telemetry();
|
|
1039
|
-
await __promiseAll([
|
|
1040
|
-
init_execution(),
|
|
1041
|
-
init_prefetch()
|
|
1042
|
-
]);
|
|
1043
|
-
LIVE_CAPTURE_TIMEOUT_MS = Number(process.env.UNBROWSE_LIVE_CAPTURE_TIMEOUT_MS ?? "120000");
|
|
1044
|
-
capturedDomainCache = new Map;
|
|
1045
|
-
captureInFlight = new Map;
|
|
1046
|
-
captureDomainLocks = new Map;
|
|
1047
|
-
skillRouteCache = new Map;
|
|
1048
|
-
ROUTE_CACHE_FILE = join7(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
|
|
1049
|
-
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join7(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1050
|
-
domainSkillCache = new Map;
|
|
1051
|
-
DOMAIN_CACHE_FILE = join7(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
1052
|
-
try {
|
|
1053
|
-
if (existsSync7(DOMAIN_CACHE_FILE)) {
|
|
1054
|
-
const data = JSON.parse(readFileSync5(DOMAIN_CACHE_FILE, "utf-8"));
|
|
1055
|
-
for (const [k, v] of Object.entries(data)) {
|
|
1056
|
-
const entry = v;
|
|
1057
|
-
if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
|
|
1058
|
-
domainSkillCache.set(k, entry);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
console.error(`[domain-cache] loaded ${domainSkillCache.size} entries from disk`);
|
|
1062
|
-
}
|
|
1063
|
-
} catch {}
|
|
1064
|
-
routeCacheFlushTimer = setInterval(() => {
|
|
1065
|
-
if (!_routeCacheDirty)
|
|
1066
|
-
return;
|
|
1067
|
-
_routeCacheDirty = false;
|
|
1068
|
-
try {
|
|
1069
|
-
const dir = dirname2(ROUTE_CACHE_FILE);
|
|
1070
|
-
if (!existsSync7(dir))
|
|
1071
|
-
mkdirSync4(dir, { recursive: true });
|
|
1072
|
-
const entries = Object.fromEntries(skillRouteCache);
|
|
1073
|
-
writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
|
|
1074
|
-
} catch {}
|
|
1075
|
-
}, 5000);
|
|
1076
|
-
routeCacheFlushTimer.unref?.();
|
|
1077
|
-
try {
|
|
1078
|
-
if (existsSync7(ROUTE_CACHE_FILE)) {
|
|
1079
|
-
const data = JSON.parse(readFileSync5(ROUTE_CACHE_FILE, "utf-8"));
|
|
1080
|
-
for (const [k, v] of Object.entries(data)) {
|
|
1081
|
-
const entry = v;
|
|
1082
|
-
if (Date.now() - entry.ts < 24 * 60 * 60000) {
|
|
1083
|
-
skillRouteCache.set(k, entry);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
console.error(`[route-cache] loaded ${skillRouteCache.size} entries from disk`);
|
|
1087
|
-
}
|
|
1088
|
-
} catch {}
|
|
1089
|
-
routeResultCache = new Map;
|
|
1090
|
-
ROUTE_CACHE_TTL = 24 * 60 * 60000;
|
|
1091
|
-
MARKETPLACE_HYDRATE_LIMIT = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_HYDRATE_LIMIT ?? 4));
|
|
1092
|
-
MARKETPLACE_GET_SKILL_TIMEOUT_MS = Math.max(250, Number(process.env.UNBROWSE_MARKETPLACE_GET_SKILL_TIMEOUT_MS ?? 2500));
|
|
1093
|
-
MARKETPLACE_DOMAIN_SEARCH_K = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_DOMAIN_SEARCH_K ?? 5));
|
|
1094
|
-
MARKETPLACE_GLOBAL_SEARCH_K = Math.max(1, Number(process.env.UNBROWSE_MARKETPLACE_GLOBAL_SEARCH_K ?? 10));
|
|
1095
|
-
SEARCH_INTENT_STOPWORDS = new Set([
|
|
1096
|
-
"a",
|
|
1097
|
-
"an",
|
|
1098
|
-
"and",
|
|
1099
|
-
"are",
|
|
1100
|
-
"at",
|
|
1101
|
-
"be",
|
|
1102
|
-
"boss",
|
|
1103
|
-
"but",
|
|
1104
|
-
"by",
|
|
1105
|
-
"do",
|
|
1106
|
-
"doing",
|
|
1107
|
-
"fact",
|
|
1108
|
-
"for",
|
|
1109
|
-
"from",
|
|
1110
|
-
"get",
|
|
1111
|
-
"going",
|
|
1112
|
-
"had",
|
|
1113
|
-
"has",
|
|
1114
|
-
"have",
|
|
1115
|
-
"i",
|
|
1116
|
-
"if",
|
|
1117
|
-
"im",
|
|
1118
|
-
"in",
|
|
1119
|
-
"into",
|
|
1120
|
-
"is",
|
|
1121
|
-
"it",
|
|
1122
|
-
"its",
|
|
1123
|
-
"just",
|
|
1124
|
-
"let",
|
|
1125
|
-
"like",
|
|
1126
|
-
"me",
|
|
1127
|
-
"my",
|
|
1128
|
-
"now",
|
|
1129
|
-
"of",
|
|
1130
|
-
"on",
|
|
1131
|
-
"or",
|
|
1132
|
-
"our",
|
|
1133
|
-
"s",
|
|
1134
|
-
"says",
|
|
1135
|
-
"search",
|
|
1136
|
-
"should",
|
|
1137
|
-
"show",
|
|
1138
|
-
"so",
|
|
1139
|
-
"take",
|
|
1140
|
-
"taking",
|
|
1141
|
-
"tell",
|
|
1142
|
-
"that",
|
|
1143
|
-
"the",
|
|
1144
|
-
"their",
|
|
1145
|
-
"them",
|
|
1146
|
-
"there",
|
|
1147
|
-
"these",
|
|
1148
|
-
"they",
|
|
1149
|
-
"this",
|
|
1150
|
-
"thoroughly",
|
|
1151
|
-
"to",
|
|
1152
|
-
"up",
|
|
1153
|
-
"us",
|
|
1154
|
-
"was",
|
|
1155
|
-
"we",
|
|
1156
|
-
"were",
|
|
1157
|
-
"what",
|
|
1158
|
-
"where",
|
|
1159
|
-
"which",
|
|
1160
|
-
"who",
|
|
1161
|
-
"why",
|
|
1162
|
-
"with",
|
|
1163
|
-
"would",
|
|
1164
|
-
"you",
|
|
1165
|
-
"your"
|
|
1166
|
-
]);
|
|
1167
|
-
});
|
|
1168
3
|
|
|
1169
4
|
// ../../src/cli.ts
|
|
1170
5
|
import { config as loadEnv } from "dotenv";
|
|
1171
|
-
import { spawn as spawn3 } from "child_process";
|
|
1172
6
|
|
|
1173
7
|
// ../../src/client/index.ts
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2 } from "fs";
|
|
1179
|
-
import { join as join3 } from "path";
|
|
1180
|
-
import { homedir as homedir2, hostname } from "os";
|
|
1181
|
-
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { homedir, hostname } from "os";
|
|
11
|
+
import { randomBytes } from "crypto";
|
|
1182
12
|
import { createInterface } from "readline";
|
|
1183
13
|
var API_URL = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbrowse.ai";
|
|
1184
14
|
var PROFILE_NAME = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
|
|
1185
15
|
var recentLocalSkills = new Map;
|
|
1186
16
|
var LOCAL_ONLY = process.env.UNBROWSE_LOCAL_ONLY === "1";
|
|
1187
|
-
function decodeBase64Json(value) {
|
|
1188
|
-
try {
|
|
1189
|
-
if (typeof globalThis !== "undefined" && typeof globalThis.atob === "function") {
|
|
1190
|
-
const binary = globalThis.atob(value);
|
|
1191
|
-
const bytes = new Uint8Array(binary.length);
|
|
1192
|
-
for (let i = 0;i < binary.length; i++) {
|
|
1193
|
-
bytes[i] = binary.charCodeAt(i);
|
|
1194
|
-
}
|
|
1195
|
-
return JSON.parse(new TextDecoder("utf-8").decode(bytes));
|
|
1196
|
-
}
|
|
1197
|
-
return JSON.parse(Buffer.from(value, "base64").toString("utf8"));
|
|
1198
|
-
} catch {
|
|
1199
|
-
return;
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
17
|
function getConfigDir() {
|
|
1203
18
|
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
1204
19
|
return process.env.UNBROWSE_CONFIG_DIR;
|
|
1205
|
-
return PROFILE_NAME ?
|
|
20
|
+
return PROFILE_NAME ? join(homedir(), ".unbrowse", "profiles", PROFILE_NAME) : join(homedir(), ".unbrowse");
|
|
1206
21
|
}
|
|
1207
22
|
function getConfigPath() {
|
|
1208
|
-
return
|
|
1209
|
-
}
|
|
1210
|
-
function getInstallTelemetryPath() {
|
|
1211
|
-
return join3(getConfigDir(), "install-state.json");
|
|
1212
|
-
}
|
|
1213
|
-
function getLandingToken() {
|
|
1214
|
-
const token = process.env.UNBROWSE_LANDING_TOKEN?.trim();
|
|
1215
|
-
return token ? token : undefined;
|
|
23
|
+
return join(getConfigDir(), "config.json");
|
|
1216
24
|
}
|
|
1217
25
|
function sanitizeProfileName(value) {
|
|
1218
26
|
return value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1219
27
|
}
|
|
1220
|
-
function getActiveProfile() {
|
|
1221
|
-
return PROFILE_NAME || "default";
|
|
1222
|
-
}
|
|
1223
28
|
function loadConfig() {
|
|
1224
29
|
try {
|
|
1225
30
|
const configPath = getConfigPath();
|
|
1226
|
-
if (
|
|
1227
|
-
return JSON.parse(
|
|
31
|
+
if (existsSync(configPath)) {
|
|
32
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1228
33
|
}
|
|
1229
34
|
} catch {}
|
|
1230
35
|
return null;
|
|
@@ -1232,151 +37,10 @@ function loadConfig() {
|
|
|
1232
37
|
function saveConfig(config) {
|
|
1233
38
|
const configDir = getConfigDir();
|
|
1234
39
|
const configPath = getConfigPath();
|
|
1235
|
-
if (!
|
|
40
|
+
if (!existsSync(configDir))
|
|
1236
41
|
mkdirSync(configDir, { recursive: true });
|
|
1237
42
|
writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
1238
43
|
}
|
|
1239
|
-
function loadInstallTelemetryState() {
|
|
1240
|
-
try {
|
|
1241
|
-
const statePath = getInstallTelemetryPath();
|
|
1242
|
-
if (existsSync3(statePath)) {
|
|
1243
|
-
return JSON.parse(readFileSync3(statePath, "utf-8"));
|
|
1244
|
-
}
|
|
1245
|
-
} catch {}
|
|
1246
|
-
return null;
|
|
1247
|
-
}
|
|
1248
|
-
function saveInstallTelemetryState(state) {
|
|
1249
|
-
const configDir = getConfigDir();
|
|
1250
|
-
const statePath = getInstallTelemetryPath();
|
|
1251
|
-
if (!existsSync3(configDir))
|
|
1252
|
-
mkdirSync(configDir, { recursive: true });
|
|
1253
|
-
writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1254
|
-
}
|
|
1255
|
-
function createInstallTelemetryState() {
|
|
1256
|
-
return {
|
|
1257
|
-
install_id: `install_${randomBytes(8).toString("hex")}`,
|
|
1258
|
-
first_seen_at: new Date().toISOString()
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
function readInstallAttributionFromEnv() {
|
|
1262
|
-
return decodeTelemetryAttribution(process.env.UNBROWSE_ATTRIBUTION_B64);
|
|
1263
|
-
}
|
|
1264
|
-
function getOrCreateInstallTelemetryState() {
|
|
1265
|
-
const existing = loadInstallTelemetryState();
|
|
1266
|
-
const incomingAttribution = readInstallAttributionFromEnv();
|
|
1267
|
-
if (existing?.install_id) {
|
|
1268
|
-
const mergedAttribution = mergeTelemetryAttribution(existing.attribution, incomingAttribution);
|
|
1269
|
-
if (JSON.stringify(mergedAttribution ?? null) !== JSON.stringify(existing.attribution ?? null)) {
|
|
1270
|
-
const nextState2 = {
|
|
1271
|
-
...existing,
|
|
1272
|
-
attribution: mergedAttribution
|
|
1273
|
-
};
|
|
1274
|
-
saveInstallTelemetryState(nextState2);
|
|
1275
|
-
return nextState2;
|
|
1276
|
-
}
|
|
1277
|
-
return existing;
|
|
1278
|
-
}
|
|
1279
|
-
const created = createInstallTelemetryState();
|
|
1280
|
-
const nextState = {
|
|
1281
|
-
...created,
|
|
1282
|
-
attribution: incomingAttribution
|
|
1283
|
-
};
|
|
1284
|
-
saveInstallTelemetryState(nextState);
|
|
1285
|
-
return nextState;
|
|
1286
|
-
}
|
|
1287
|
-
function getInstallId() {
|
|
1288
|
-
return getOrCreateInstallTelemetryState().install_id;
|
|
1289
|
-
}
|
|
1290
|
-
function getTelemetryAttribution() {
|
|
1291
|
-
return getOrCreateInstallTelemetryState().attribution;
|
|
1292
|
-
}
|
|
1293
|
-
function detectTelemetryHostType() {
|
|
1294
|
-
switch (detectHostEnvironment()) {
|
|
1295
|
-
case "openai":
|
|
1296
|
-
return "codex";
|
|
1297
|
-
case "openclaw":
|
|
1298
|
-
return "openclaw";
|
|
1299
|
-
case "mcp":
|
|
1300
|
-
return "mcp";
|
|
1301
|
-
case "native":
|
|
1302
|
-
return "native";
|
|
1303
|
-
case "unknown":
|
|
1304
|
-
default:
|
|
1305
|
-
return "cli";
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
async function postTelemetry(path, body) {
|
|
1309
|
-
if (LOCAL_ONLY)
|
|
1310
|
-
return false;
|
|
1311
|
-
try {
|
|
1312
|
-
const key = getApiKey();
|
|
1313
|
-
const res = await fetch(`${API_URL}${path}`, {
|
|
1314
|
-
method: "POST",
|
|
1315
|
-
headers: {
|
|
1316
|
-
"Content-Type": "application/json",
|
|
1317
|
-
"Accept-Encoding": "gzip, deflate",
|
|
1318
|
-
...key ? { Authorization: `Bearer ${key}` } : {}
|
|
1319
|
-
},
|
|
1320
|
-
body: JSON.stringify(body)
|
|
1321
|
-
});
|
|
1322
|
-
return res.ok;
|
|
1323
|
-
} catch {
|
|
1324
|
-
return false;
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
async function ensureCliInstallTracked(hostType = detectTelemetryHostType()) {
|
|
1328
|
-
const state = getOrCreateInstallTelemetryState();
|
|
1329
|
-
if (state.cli_first_seen_reported_at)
|
|
1330
|
-
return;
|
|
1331
|
-
const createdAt = new Date().toISOString();
|
|
1332
|
-
const landingToken = getLandingToken();
|
|
1333
|
-
const ok = await postTelemetry("/v1/telemetry/install", {
|
|
1334
|
-
install_id: state.install_id,
|
|
1335
|
-
landing_token: landingToken,
|
|
1336
|
-
source: "cli-first-seen",
|
|
1337
|
-
host_type: hostType,
|
|
1338
|
-
skill: "unbrowse",
|
|
1339
|
-
status: "installed",
|
|
1340
|
-
created_at: createdAt,
|
|
1341
|
-
properties: mergeTelemetryProperties({
|
|
1342
|
-
profile: getActiveProfile(),
|
|
1343
|
-
first_seen_at: state.first_seen_at
|
|
1344
|
-
}, state.attribution)
|
|
1345
|
-
});
|
|
1346
|
-
if (!ok)
|
|
1347
|
-
return;
|
|
1348
|
-
state.cli_first_seen_reported_at = createdAt;
|
|
1349
|
-
saveInstallTelemetryState(state);
|
|
1350
|
-
}
|
|
1351
|
-
async function recordInstallTelemetryEvent(source, options) {
|
|
1352
|
-
const createdAt = options?.createdAt ?? new Date().toISOString();
|
|
1353
|
-
const landingToken = getLandingToken();
|
|
1354
|
-
await postTelemetry("/v1/telemetry/install", {
|
|
1355
|
-
install_id: getInstallId(),
|
|
1356
|
-
landing_token: landingToken,
|
|
1357
|
-
source,
|
|
1358
|
-
host_type: options?.hostType ?? detectTelemetryHostType(),
|
|
1359
|
-
skill: options?.skill ?? "unbrowse",
|
|
1360
|
-
skill_version: options?.skillVersion,
|
|
1361
|
-
status: options?.status ?? "installed",
|
|
1362
|
-
created_at: createdAt,
|
|
1363
|
-
properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
async function recordFunnelTelemetryEvent(name, options) {
|
|
1367
|
-
const createdAt = options?.createdAt ?? new Date().toISOString();
|
|
1368
|
-
const landingToken = getLandingToken();
|
|
1369
|
-
await postTelemetry("/v1/telemetry/events", {
|
|
1370
|
-
install_id: getInstallId(),
|
|
1371
|
-
session_id: options?.sessionId,
|
|
1372
|
-
landing_token: landingToken,
|
|
1373
|
-
name,
|
|
1374
|
-
source: options?.source ?? "cli",
|
|
1375
|
-
host_type: options?.hostType ?? detectTelemetryHostType(),
|
|
1376
|
-
created_at: createdAt,
|
|
1377
|
-
properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
44
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/i;
|
|
1381
45
|
function normalizeAgentEmail(value) {
|
|
1382
46
|
return value.trim().toLowerCase();
|
|
@@ -1391,9 +55,6 @@ function resolveAgentName(preferredEmail, fallbackName) {
|
|
|
1391
55
|
const normalized = normalizeAgentEmail(preferredEmail ?? "");
|
|
1392
56
|
return isValidAgentEmail(normalized) ? normalized : fallbackName;
|
|
1393
57
|
}
|
|
1394
|
-
function getLocalWalletContext() {
|
|
1395
|
-
return getWalletContext();
|
|
1396
|
-
}
|
|
1397
58
|
function getApiKey() {
|
|
1398
59
|
if (LOCAL_ONLY)
|
|
1399
60
|
return "local-only";
|
|
@@ -1407,7 +68,6 @@ function getApiKey() {
|
|
|
1407
68
|
return "";
|
|
1408
69
|
}
|
|
1409
70
|
var API_TIMEOUT_MS = parseInt(process.env.UNBROWSE_API_TIMEOUT ?? "8000", 10);
|
|
1410
|
-
var PUBLISH_TIMEOUT_MS = parseInt(process.env.UNBROWSE_PUBLISH_TIMEOUT ?? "30000", 10);
|
|
1411
71
|
async function validateApiKey(key) {
|
|
1412
72
|
const controller = new AbortController;
|
|
1413
73
|
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
@@ -1465,10 +125,10 @@ async function findUsableApiKey() {
|
|
|
1465
125
|
}
|
|
1466
126
|
return null;
|
|
1467
127
|
}
|
|
1468
|
-
async function
|
|
128
|
+
async function api(method, path, body, opts) {
|
|
1469
129
|
const key = opts?.noAuth ? "" : getApiKey();
|
|
1470
130
|
const controller = new AbortController;
|
|
1471
|
-
const timer = setTimeout(() => controller.abort(),
|
|
131
|
+
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
1472
132
|
let res;
|
|
1473
133
|
try {
|
|
1474
134
|
res = await fetch(`${API_URL}${path}`, {
|
|
@@ -1476,11 +136,6 @@ async function apiRequest(method, path, body, opts) {
|
|
|
1476
136
|
headers: {
|
|
1477
137
|
"Content-Type": "application/json",
|
|
1478
138
|
"Accept-Encoding": "gzip, deflate",
|
|
1479
|
-
"X-Unbrowse-Trace-Version": TRACE_VERSION,
|
|
1480
|
-
"X-Unbrowse-Code-Hash": CODE_HASH,
|
|
1481
|
-
"X-Unbrowse-Git-Sha": GIT_SHA,
|
|
1482
|
-
...RELEASE_MANIFEST_BASE64 ? { "X-Unbrowse-Release-Manifest": RELEASE_MANIFEST_BASE64 } : {},
|
|
1483
|
-
...RELEASE_MANIFEST_SIGNATURE ? { "X-Unbrowse-Release-Signature": RELEASE_MANIFEST_SIGNATURE } : {},
|
|
1484
139
|
...key ? { Authorization: `Bearer ${key}` } : {}
|
|
1485
140
|
},
|
|
1486
141
|
body: body ? JSON.stringify(body) : undefined,
|
|
@@ -1501,25 +156,11 @@ async function apiRequest(method, path, body, opts) {
|
|
|
1501
156
|
console.warn("[unbrowse] Please restart the unbrowse service to accept the new terms.");
|
|
1502
157
|
throw new Error("ToS update required. Restart unbrowse to accept new terms.");
|
|
1503
158
|
}
|
|
1504
|
-
if (res.status === 402) {
|
|
1505
|
-
const paymentRequired = res.headers.get("PAYMENT-REQUIRED");
|
|
1506
|
-
const legacyPaymentTerms = res.headers.get("X-Payment-Required");
|
|
1507
|
-
const terms = paymentRequired ? decodeBase64Json(paymentRequired) : legacyPaymentTerms ? JSON.parse(legacyPaymentTerms) : data.terms;
|
|
1508
|
-
const err = new Error(`Payment required: ${data.error ?? "This skill requires payment"}`);
|
|
1509
|
-
err.x402 = true;
|
|
1510
|
-
err.terms = terms;
|
|
1511
|
-
err.status = 402;
|
|
1512
|
-
throw err;
|
|
1513
|
-
}
|
|
1514
159
|
if (!res.ok) {
|
|
1515
160
|
const errData = data;
|
|
1516
161
|
const msg = errData.details?.length ? `${errData.error}: ${errData.details.join("; ")}` : errData.error ?? `API HTTP ${res.status}`;
|
|
1517
162
|
throw new Error(msg);
|
|
1518
163
|
}
|
|
1519
|
-
return { data, headers: res.headers };
|
|
1520
|
-
}
|
|
1521
|
-
async function api(method, path, body, opts) {
|
|
1522
|
-
const { data } = await apiRequest(method, path, body, opts);
|
|
1523
164
|
return data;
|
|
1524
165
|
}
|
|
1525
166
|
async function promptTosAcceptance(summary, tosUrl) {
|
|
@@ -1578,26 +219,23 @@ Email for this agent (leave blank to use a local device id): `, resolve);
|
|
|
1578
219
|
rl.close();
|
|
1579
220
|
}
|
|
1580
221
|
}
|
|
1581
|
-
async function checkTosStatus(
|
|
1582
|
-
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
222
|
+
async function checkTosStatus() {
|
|
1583
223
|
const config = loadConfig();
|
|
1584
224
|
let tosInfo;
|
|
1585
225
|
try {
|
|
1586
226
|
tosInfo = await api("GET", "/v1/tos/current");
|
|
1587
227
|
} catch {
|
|
1588
|
-
return
|
|
228
|
+
return;
|
|
1589
229
|
}
|
|
1590
230
|
if (config?.tos_accepted_version === tosInfo.version) {
|
|
1591
|
-
return
|
|
231
|
+
return;
|
|
1592
232
|
}
|
|
1593
233
|
console.log(`
|
|
1594
234
|
The Unbrowse Terms of Service have been updated.`);
|
|
1595
235
|
const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
|
|
1596
236
|
if (!accepted) {
|
|
1597
237
|
console.log("You must accept the updated Terms of Service to continue using Unbrowse.");
|
|
1598
|
-
|
|
1599
|
-
process.exit(1);
|
|
1600
|
-
return false;
|
|
238
|
+
process.exit(1);
|
|
1601
239
|
}
|
|
1602
240
|
try {
|
|
1603
241
|
await api("POST", "/v1/agents/accept-tos", { tos_version: tosInfo.version });
|
|
@@ -1610,27 +248,16 @@ The Unbrowse Terms of Service have been updated.`);
|
|
|
1610
248
|
} catch (err) {
|
|
1611
249
|
console.warn(`Failed to record ToS acceptance: ${err.message}`);
|
|
1612
250
|
}
|
|
1613
|
-
return true;
|
|
1614
251
|
}
|
|
1615
252
|
async function ensureRegistered(options) {
|
|
1616
253
|
if (LOCAL_ONLY)
|
|
1617
254
|
return;
|
|
1618
|
-
const exitOnFailure = options?.exitOnFailure ?? true;
|
|
1619
255
|
const usableKey = await findUsableApiKey();
|
|
1620
256
|
if (usableKey) {
|
|
1621
257
|
if (usableKey.source === "config") {
|
|
1622
258
|
console.log("[unbrowse] Restored saved registration.");
|
|
1623
259
|
}
|
|
1624
|
-
|
|
1625
|
-
if (!accepted2)
|
|
1626
|
-
return;
|
|
1627
|
-
try {
|
|
1628
|
-
const profile = await getMyProfile();
|
|
1629
|
-
const wallet = getLocalWalletContext();
|
|
1630
|
-
if (wallet.wallet_address && profile.wallet_address !== wallet.wallet_address) {
|
|
1631
|
-
await syncAgentWallet(wallet);
|
|
1632
|
-
}
|
|
1633
|
-
} catch {}
|
|
260
|
+
await checkTosStatus();
|
|
1634
261
|
return;
|
|
1635
262
|
}
|
|
1636
263
|
let tosInfo;
|
|
@@ -1644,16 +271,13 @@ async function ensureRegistered(options) {
|
|
|
1644
271
|
const accepted = await promptTosAcceptance(tosInfo.summary, tosInfo.url);
|
|
1645
272
|
if (!accepted) {
|
|
1646
273
|
console.log("You must accept the Terms of Service to use Unbrowse.");
|
|
1647
|
-
|
|
1648
|
-
process.exit(1);
|
|
1649
|
-
return;
|
|
274
|
+
process.exit(1);
|
|
1650
275
|
}
|
|
1651
276
|
const fallbackName = buildDefaultAgentName();
|
|
1652
277
|
const name = options?.promptForEmail ? await promptAgentEmail(fallbackName) : resolveAgentName(process.env.UNBROWSE_AGENT_EMAIL, fallbackName);
|
|
1653
278
|
console.log(`Registering as "${name}"...`);
|
|
1654
279
|
try {
|
|
1655
|
-
const
|
|
1656
|
-
const { agent_id, api_key } = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet });
|
|
280
|
+
const { agent_id, api_key } = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version });
|
|
1657
281
|
process.env.UNBROWSE_API_KEY = api_key;
|
|
1658
282
|
saveConfig({
|
|
1659
283
|
api_key,
|
|
@@ -1661,254 +285,91 @@ async function ensureRegistered(options) {
|
|
|
1661
285
|
agent_name: name,
|
|
1662
286
|
registered_at: new Date().toISOString(),
|
|
1663
287
|
tos_accepted_version: tosInfo.version,
|
|
1664
|
-
tos_accepted_at: new Date().toISOString()
|
|
1665
|
-
...wallet
|
|
1666
|
-
});
|
|
1667
|
-
await recordFunnelTelemetryEvent("registration_succeeded", {
|
|
1668
|
-
source: "cli",
|
|
1669
|
-
properties: {
|
|
1670
|
-
prompt_for_email: options?.promptForEmail === true
|
|
1671
|
-
}
|
|
288
|
+
tos_accepted_at: new Date().toISOString()
|
|
1672
289
|
});
|
|
1673
290
|
console.log(`Registered as ${name}. API key saved to ~/.unbrowse/config.json`);
|
|
1674
291
|
} catch (err) {
|
|
1675
292
|
console.warn(`Registration failed: ${err.message}`);
|
|
1676
293
|
console.warn("Set UNBROWSE_API_KEY manually or try again.");
|
|
1677
|
-
|
|
1678
|
-
process.exit(1);
|
|
294
|
+
process.exit(1);
|
|
1679
295
|
}
|
|
1680
296
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
297
|
+
|
|
298
|
+
// ../../src/runtime/local-server.ts
|
|
299
|
+
import { openSync, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
300
|
+
import path2 from "node:path";
|
|
301
|
+
import { spawn } from "node:child_process";
|
|
302
|
+
|
|
303
|
+
// ../../src/runtime/paths.ts
|
|
304
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, realpathSync } from "node:fs";
|
|
305
|
+
import os from "node:os";
|
|
306
|
+
import path from "node:path";
|
|
307
|
+
import { createRequire } from "node:module";
|
|
308
|
+
import { fileURLToPath } from "node:url";
|
|
309
|
+
function getModuleDir(metaUrl) {
|
|
310
|
+
return path.dirname(fileURLToPath(metaUrl));
|
|
311
|
+
}
|
|
312
|
+
function getPackageRoot(metaUrl) {
|
|
313
|
+
if (process.env.UNBROWSE_PACKAGE_ROOT)
|
|
314
|
+
return process.env.UNBROWSE_PACKAGE_ROOT;
|
|
315
|
+
const dir = getModuleDir(metaUrl);
|
|
316
|
+
const base = path.basename(dir);
|
|
317
|
+
return base === "src" || base === "dist" ? path.dirname(dir) : dir;
|
|
1683
318
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
await api("POST", "/v1/agents/wallet", wallet);
|
|
1688
|
-
const config = loadConfig();
|
|
1689
|
-
if (!config)
|
|
1690
|
-
return;
|
|
1691
|
-
saveConfig({ ...config, ...wallet });
|
|
319
|
+
function resolveSiblingEntrypoint(metaUrl, basename) {
|
|
320
|
+
const file = fileURLToPath(metaUrl);
|
|
321
|
+
return path.join(path.dirname(file), `${basename}${path.extname(file) || ".js"}`);
|
|
1692
322
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
requires: [],
|
|
1707
|
-
enables: ["feed", "notifications", "messages"],
|
|
1708
|
-
parallel_with: [],
|
|
1709
|
-
parallel_safe: false,
|
|
1710
|
-
needs_auth: false,
|
|
1711
|
-
description: "Authenticate with LinkedIn"
|
|
1712
|
-
},
|
|
1713
|
-
{
|
|
1714
|
-
match: ["feed"],
|
|
1715
|
-
intent: "get feed posts",
|
|
1716
|
-
url: "https://www.linkedin.com/feed/",
|
|
1717
|
-
requires: ["login"],
|
|
1718
|
-
enables: [],
|
|
1719
|
-
parallel_with: ["notifications", "messages"],
|
|
1720
|
-
parallel_safe: true,
|
|
1721
|
-
needs_auth: true,
|
|
1722
|
-
description: "Fetch your LinkedIn feed"
|
|
1723
|
-
},
|
|
1724
|
-
{
|
|
1725
|
-
match: ["notifications", "notifs"],
|
|
1726
|
-
intent: "get notifications",
|
|
1727
|
-
url: "https://www.linkedin.com/notifications/",
|
|
1728
|
-
requires: ["login"],
|
|
1729
|
-
enables: [],
|
|
1730
|
-
parallel_with: ["feed", "messages"],
|
|
1731
|
-
parallel_safe: true,
|
|
1732
|
-
needs_auth: true,
|
|
1733
|
-
description: "Fetch your notifications"
|
|
1734
|
-
},
|
|
1735
|
-
{
|
|
1736
|
-
match: ["messages", "msgs"],
|
|
1737
|
-
intent: "get messages",
|
|
1738
|
-
url: "https://www.linkedin.com/messaging/",
|
|
1739
|
-
requires: ["login"],
|
|
1740
|
-
enables: [],
|
|
1741
|
-
parallel_with: ["feed", "notifications"],
|
|
1742
|
-
parallel_safe: true,
|
|
1743
|
-
needs_auth: true,
|
|
1744
|
-
description: "Fetch your messages"
|
|
1745
|
-
}
|
|
1746
|
-
]
|
|
1747
|
-
};
|
|
1748
|
-
var github = {
|
|
1749
|
-
site: "github",
|
|
1750
|
-
aliases: ["github", "gh"],
|
|
1751
|
-
domain: "github.com",
|
|
1752
|
-
login_url: "https://github.com/login",
|
|
1753
|
-
description: "GitHub — code hosting",
|
|
1754
|
-
tasks: [
|
|
1755
|
-
{
|
|
1756
|
-
match: ["login"],
|
|
1757
|
-
intent: "login",
|
|
1758
|
-
url: "https://github.com/login",
|
|
1759
|
-
requires: [],
|
|
1760
|
-
enables: ["trending", "notifications", "repos"],
|
|
1761
|
-
parallel_with: [],
|
|
1762
|
-
parallel_safe: false,
|
|
1763
|
-
needs_auth: false,
|
|
1764
|
-
description: "Authenticate with GitHub"
|
|
1765
|
-
},
|
|
1766
|
-
{
|
|
1767
|
-
match: ["trending"],
|
|
1768
|
-
intent: "get trending repositories",
|
|
1769
|
-
url: "https://github.com/trending",
|
|
1770
|
-
requires: [],
|
|
1771
|
-
enables: [],
|
|
1772
|
-
parallel_with: ["notifications", "repos"],
|
|
1773
|
-
parallel_safe: true,
|
|
1774
|
-
needs_auth: false,
|
|
1775
|
-
description: "Fetch trending repositories (no auth needed)"
|
|
1776
|
-
},
|
|
1777
|
-
{
|
|
1778
|
-
match: ["notifications", "notifs"],
|
|
1779
|
-
intent: "get notifications",
|
|
1780
|
-
url: "https://github.com/notifications",
|
|
1781
|
-
requires: ["login"],
|
|
1782
|
-
enables: [],
|
|
1783
|
-
parallel_with: ["trending", "repos"],
|
|
1784
|
-
parallel_safe: true,
|
|
1785
|
-
needs_auth: true,
|
|
1786
|
-
description: "Fetch your notifications"
|
|
1787
|
-
},
|
|
1788
|
-
{
|
|
1789
|
-
match: ["repos"],
|
|
1790
|
-
intent: "get repositories",
|
|
1791
|
-
url: "https://github.com",
|
|
1792
|
-
requires: ["login"],
|
|
1793
|
-
enables: [],
|
|
1794
|
-
parallel_with: ["trending", "notifications"],
|
|
1795
|
-
parallel_safe: true,
|
|
1796
|
-
needs_auth: true,
|
|
1797
|
-
description: "Fetch your repositories"
|
|
1798
|
-
}
|
|
1799
|
-
]
|
|
1800
|
-
};
|
|
1801
|
-
var SITE_PACKS = [linkedin, github];
|
|
1802
|
-
function findSitePack(name) {
|
|
1803
|
-
const lower = name.toLowerCase();
|
|
1804
|
-
return SITE_PACKS.find((p) => p.aliases.includes(lower));
|
|
323
|
+
function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
|
|
324
|
+
if (path.extname(entrypoint) !== ".ts")
|
|
325
|
+
return [entrypoint];
|
|
326
|
+
if (process.versions.bun)
|
|
327
|
+
return [entrypoint];
|
|
328
|
+
try {
|
|
329
|
+
const req = createRequire(metaUrl);
|
|
330
|
+
const tsxPkg = req.resolve("tsx/package.json");
|
|
331
|
+
const tsxLoader = path.join(path.dirname(tsxPkg), "dist", "loader.mjs");
|
|
332
|
+
if (existsSync2(tsxLoader))
|
|
333
|
+
return ["--import", tsxLoader, entrypoint];
|
|
334
|
+
} catch {}
|
|
335
|
+
return ["--import", "tsx", entrypoint];
|
|
1805
336
|
}
|
|
1806
|
-
function
|
|
1807
|
-
|
|
1808
|
-
return pack.tasks.find((t) => t.match.includes(lower));
|
|
337
|
+
function getUnbrowseHome() {
|
|
338
|
+
return path.join(os.homedir(), ".unbrowse");
|
|
1809
339
|
}
|
|
1810
|
-
function
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
requires: task.requires,
|
|
1815
|
-
enables: task.enables,
|
|
1816
|
-
parallel_with: task.parallel_with
|
|
1817
|
-
};
|
|
1818
|
-
}
|
|
1819
|
-
return graph;
|
|
340
|
+
function ensureDir(dir) {
|
|
341
|
+
if (!existsSync2(dir))
|
|
342
|
+
mkdirSync2(dir, { recursive: true });
|
|
343
|
+
return dir;
|
|
1820
344
|
}
|
|
1821
|
-
function
|
|
1822
|
-
|
|
1823
|
-
const resolved = new Set;
|
|
1824
|
-
const remaining = new Set(taskNames.map((n) => n.toLowerCase()));
|
|
1825
|
-
const allPrereqs = new Set;
|
|
1826
|
-
for (const name of remaining) {
|
|
1827
|
-
const task = findTask(pack, name);
|
|
1828
|
-
if (task) {
|
|
1829
|
-
for (const req of task.requires) {
|
|
1830
|
-
if (!remaining.has(req))
|
|
1831
|
-
allPrereqs.add(req);
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
if (allPrereqs.size > 0) {
|
|
1836
|
-
waves.push({
|
|
1837
|
-
wave: 1,
|
|
1838
|
-
commands: [...allPrereqs].map((t) => `unbrowse ${pack.site} ${t}`),
|
|
1839
|
-
reason: "prerequisite"
|
|
1840
|
-
});
|
|
1841
|
-
for (const p of allPrereqs)
|
|
1842
|
-
resolved.add(p);
|
|
1843
|
-
}
|
|
1844
|
-
let waveNum = waves.length + 1;
|
|
1845
|
-
while (remaining.size > 0) {
|
|
1846
|
-
const ready = [];
|
|
1847
|
-
for (const name of remaining) {
|
|
1848
|
-
const task = findTask(pack, name);
|
|
1849
|
-
if (!task) {
|
|
1850
|
-
remaining.delete(name);
|
|
1851
|
-
continue;
|
|
1852
|
-
}
|
|
1853
|
-
const depsOk = task.requires.every((r) => resolved.has(r));
|
|
1854
|
-
if (depsOk)
|
|
1855
|
-
ready.push(name);
|
|
1856
|
-
}
|
|
1857
|
-
if (ready.length === 0) {
|
|
1858
|
-
waves.push({
|
|
1859
|
-
wave: waveNum,
|
|
1860
|
-
commands: [...remaining].map((t) => `unbrowse ${pack.site} ${t}`),
|
|
1861
|
-
reason: "unresolvable dependencies"
|
|
1862
|
-
});
|
|
1863
|
-
break;
|
|
1864
|
-
}
|
|
1865
|
-
waves.push({
|
|
1866
|
-
wave: waveNum,
|
|
1867
|
-
commands: ready.map((t) => `unbrowse ${pack.site} ${t}`),
|
|
1868
|
-
reason: ready.length > 1 ? "independent after prerequisites" : "sequential"
|
|
1869
|
-
});
|
|
1870
|
-
for (const r of ready) {
|
|
1871
|
-
resolved.add(r);
|
|
1872
|
-
remaining.delete(r);
|
|
1873
|
-
}
|
|
1874
|
-
waveNum++;
|
|
1875
|
-
}
|
|
1876
|
-
return waves;
|
|
345
|
+
function getLogsDir() {
|
|
346
|
+
return ensureDir(path.join(getUnbrowseHome(), "logs"));
|
|
1877
347
|
}
|
|
1878
|
-
function
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
return
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
348
|
+
function getRunDir() {
|
|
349
|
+
return ensureDir(process.env.UNBROWSE_RUN_DIR || path.join(getUnbrowseHome(), "run"));
|
|
350
|
+
}
|
|
351
|
+
function sanitizeSegment(value) {
|
|
352
|
+
return value.replace(/[^a-zA-Z0-9.-]+/g, "_");
|
|
353
|
+
}
|
|
354
|
+
function getServerPidFile(baseUrl) {
|
|
355
|
+
const url = new URL(baseUrl);
|
|
356
|
+
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
357
|
+
const host = sanitizeSegment(url.hostname || "127.0.0.1");
|
|
358
|
+
return path.join(getRunDir(), `server-${host}-${port}.json`);
|
|
359
|
+
}
|
|
360
|
+
function getServerAutostartLogFile() {
|
|
361
|
+
return path.join(getLogsDir(), "server-autostart.log");
|
|
1887
362
|
}
|
|
1888
363
|
|
|
1889
364
|
// ../../src/runtime/local-server.ts
|
|
1890
|
-
|
|
1891
|
-
init_supervisor();
|
|
1892
|
-
init_version();
|
|
1893
|
-
import { openSync, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1894
|
-
import path2 from "node:path";
|
|
1895
|
-
import { spawn } from "node:child_process";
|
|
1896
|
-
function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
|
|
1897
|
-
return !!runningVersion && runningVersion !== installedVersion || !!runningCodeHash && !!installedCodeHash && runningCodeHash !== installedCodeHash;
|
|
1898
|
-
}
|
|
1899
|
-
async function fetchServerHealth(baseUrl, timeoutMs = 2000) {
|
|
365
|
+
async function isServerHealthy(baseUrl, timeoutMs = 2000) {
|
|
1900
366
|
try {
|
|
1901
367
|
const res = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
1902
|
-
|
|
1903
|
-
return null;
|
|
1904
|
-
return await res.json();
|
|
368
|
+
return res.ok;
|
|
1905
369
|
} catch {
|
|
1906
|
-
return
|
|
370
|
+
return false;
|
|
1907
371
|
}
|
|
1908
372
|
}
|
|
1909
|
-
async function isServerHealthy(baseUrl, timeoutMs = 2000) {
|
|
1910
|
-
return !!await fetchServerHealth(baseUrl, timeoutMs);
|
|
1911
|
-
}
|
|
1912
373
|
async function waitForHealthy(baseUrl, timeoutMs) {
|
|
1913
374
|
const start = Date.now();
|
|
1914
375
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -1928,7 +389,7 @@ function isPidAlive(pid) {
|
|
|
1928
389
|
}
|
|
1929
390
|
function readPidState(pidFile) {
|
|
1930
391
|
try {
|
|
1931
|
-
return JSON.parse(
|
|
392
|
+
return JSON.parse(readFileSync2(pidFile, "utf-8"));
|
|
1932
393
|
} catch {
|
|
1933
394
|
return null;
|
|
1934
395
|
}
|
|
@@ -1944,199 +405,58 @@ function deriveListenEnv(baseUrl) {
|
|
|
1944
405
|
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
1945
406
|
return { HOST: host, PORT: port, UNBROWSE_URL: baseUrl };
|
|
1946
407
|
}
|
|
1947
|
-
function getVersion(metaUrl) {
|
|
1948
|
-
try {
|
|
1949
|
-
const root = getPackageRoot(metaUrl);
|
|
1950
|
-
const pkg = JSON.parse(readFileSync4(path2.join(root, "package.json"), "utf-8"));
|
|
1951
|
-
return pkg.version ?? "unknown";
|
|
1952
|
-
} catch {
|
|
1953
|
-
return "unknown";
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
function isCompiledBinary() {
|
|
1957
|
-
return !!(process.versions.bun && !process.argv[1]?.match(/\.(ts|js|mjs)$/));
|
|
1958
|
-
}
|
|
1959
|
-
function getServerSpawnSpec(metaUrl, entrypoint = resolveSiblingEntrypoint(metaUrl, "index")) {
|
|
1960
|
-
if (isCompiledBinary()) {
|
|
1961
|
-
return {
|
|
1962
|
-
command: process.execPath,
|
|
1963
|
-
args: ["serve"],
|
|
1964
|
-
cwd: process.cwd(),
|
|
1965
|
-
recordedEntrypoint: `${process.execPath} serve`
|
|
1966
|
-
};
|
|
1967
|
-
}
|
|
1968
|
-
return {
|
|
1969
|
-
command: process.execPath,
|
|
1970
|
-
args: runtimeArgsForEntrypoint(metaUrl, entrypoint),
|
|
1971
|
-
cwd: getPackageRoot(metaUrl),
|
|
1972
|
-
recordedEntrypoint: entrypoint
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
var MAX_RESTART_ATTEMPTS = 3;
|
|
1976
|
-
var RESTART_BACKOFF_MS = 2000;
|
|
1977
|
-
function spawnServer(baseUrl, metaUrl, pidFile, restartCount = 0) {
|
|
1978
|
-
const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
|
|
1979
|
-
const spawnSpec = getServerSpawnSpec(metaUrl, entrypoint);
|
|
1980
|
-
const logFile = getServerAutostartLogFile();
|
|
1981
|
-
ensureDir(path2.dirname(logFile));
|
|
1982
|
-
const logFd = openSync(logFile, "a");
|
|
1983
|
-
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
1984
|
-
cwd: spawnSpec.cwd,
|
|
1985
|
-
detached: true,
|
|
1986
|
-
stdio: ["ignore", logFd, logFd],
|
|
1987
|
-
env: {
|
|
1988
|
-
...process.env,
|
|
1989
|
-
...deriveListenEnv(baseUrl),
|
|
1990
|
-
UNBROWSE_NON_INTERACTIVE: process.env.UNBROWSE_NON_INTERACTIVE || "1",
|
|
1991
|
-
UNBROWSE_TOS_ACCEPTED: process.env.UNBROWSE_TOS_ACCEPTED || "1",
|
|
1992
|
-
UNBROWSE_PID_FILE: pidFile
|
|
1993
|
-
}
|
|
1994
|
-
});
|
|
1995
|
-
child.unref();
|
|
1996
|
-
const state = {
|
|
1997
|
-
pid: child.pid,
|
|
1998
|
-
base_url: baseUrl,
|
|
1999
|
-
started_at: new Date().toISOString(),
|
|
2000
|
-
entrypoint: spawnSpec.recordedEntrypoint,
|
|
2001
|
-
version: getVersion(metaUrl),
|
|
2002
|
-
code_hash: CODE_HASH,
|
|
2003
|
-
restart_count: restartCount
|
|
2004
|
-
};
|
|
2005
|
-
writeFileSync2(pidFile, JSON.stringify(state, null, 2));
|
|
2006
|
-
return state;
|
|
2007
|
-
}
|
|
2008
|
-
var supervisor = new LocalSupervisor;
|
|
2009
408
|
async function ensureLocalServer(baseUrl, noAutoStart, metaUrl) {
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
if (initialHealth) {
|
|
2013
|
-
const runningVersion = initialHealth.package_version;
|
|
2014
|
-
const runningCodeHash = initialHealth.code_hash;
|
|
2015
|
-
if (isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, CODE_HASH)) {
|
|
2016
|
-
const versionInfo = checkServerVersion(baseUrl, metaUrl, {
|
|
2017
|
-
runningVersion,
|
|
2018
|
-
runningCodeHash
|
|
2019
|
-
});
|
|
2020
|
-
if (versionInfo?.needs_restart) {
|
|
2021
|
-
stopServer(baseUrl);
|
|
2022
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2023
|
-
} else {
|
|
2024
|
-
throw new Error(`Server runtime mismatch on ${baseUrl}: running ${runningVersion ?? "unknown"} (${runningCodeHash ?? "unknown"}), installed ${installedVersion} (${CODE_HASH}). Run "unbrowse restart" or stop the stale server bound to that port.`);
|
|
2025
|
-
}
|
|
2026
|
-
} else {
|
|
2027
|
-
if (!supervisor.isRunning())
|
|
2028
|
-
await supervisor.start();
|
|
2029
|
-
return;
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
409
|
+
if (await isServerHealthy(baseUrl))
|
|
410
|
+
return;
|
|
2032
411
|
const pidFile = getServerPidFile(baseUrl);
|
|
2033
412
|
const existing = readPidState(pidFile);
|
|
2034
413
|
if (existing?.pid && isPidAlive(existing.pid)) {
|
|
2035
|
-
if (await waitForHealthy(baseUrl, 15000))
|
|
2036
|
-
if (!supervisor.isRunning())
|
|
2037
|
-
await supervisor.start();
|
|
414
|
+
if (await waitForHealthy(baseUrl, 15000))
|
|
2038
415
|
return;
|
|
2039
|
-
}
|
|
2040
|
-
try {
|
|
2041
|
-
process.kill(existing.pid, "SIGTERM");
|
|
2042
|
-
} catch {}
|
|
2043
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
2044
|
-
clearStalePidFile(pidFile);
|
|
2045
|
-
if (supervisor.isRunning())
|
|
2046
|
-
await supervisor.stop();
|
|
2047
416
|
} else if (existing) {
|
|
2048
417
|
clearStalePidFile(pidFile);
|
|
2049
418
|
}
|
|
2050
419
|
if (noAutoStart) {
|
|
2051
420
|
throw new Error("Server not running and auto-start disabled (--no-auto-start).");
|
|
2052
421
|
}
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
if (await waitForHealthy(baseUrl, 30000)) {
|
|
2056
|
-
await supervisor.start();
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
const state = readPidState(pidFile);
|
|
2060
|
-
if (state?.pid) {
|
|
2061
|
-
try {
|
|
2062
|
-
process.kill(state.pid, "SIGTERM");
|
|
2063
|
-
} catch {}
|
|
2064
|
-
}
|
|
2065
|
-
clearStalePidFile(pidFile);
|
|
2066
|
-
if (attempt < MAX_RESTART_ATTEMPTS) {
|
|
2067
|
-
const backoff = RESTART_BACKOFF_MS * (attempt + 1);
|
|
2068
|
-
await new Promise((r) => setTimeout(r, backoff));
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
422
|
+
const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
|
|
423
|
+
const packageRoot = getPackageRoot(metaUrl);
|
|
2071
424
|
const logFile = getServerAutostartLogFile();
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
if (
|
|
2094
|
-
return
|
|
2095
|
-
|
|
2096
|
-
process.kill(state.pid, "SIGTERM");
|
|
2097
|
-
clearStalePidFile(pidFile);
|
|
2098
|
-
if (supervisor.isRunning())
|
|
2099
|
-
supervisor.stop();
|
|
2100
|
-
return true;
|
|
2101
|
-
} catch {
|
|
2102
|
-
clearStalePidFile(pidFile);
|
|
2103
|
-
return false;
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
async function restartServer(baseUrl, metaUrl) {
|
|
2107
|
-
stopServer(baseUrl);
|
|
2108
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
2109
|
-
await ensureLocalServer(baseUrl, false, metaUrl);
|
|
425
|
+
ensureDir(path2.dirname(logFile));
|
|
426
|
+
const logFd = openSync(logFile, "a");
|
|
427
|
+
const child = spawn(process.execPath, runtimeArgsForEntrypoint(metaUrl, entrypoint), {
|
|
428
|
+
cwd: packageRoot,
|
|
429
|
+
detached: true,
|
|
430
|
+
stdio: ["ignore", logFd, logFd],
|
|
431
|
+
env: {
|
|
432
|
+
...process.env,
|
|
433
|
+
...deriveListenEnv(baseUrl),
|
|
434
|
+
UNBROWSE_NON_INTERACTIVE: process.env.UNBROWSE_NON_INTERACTIVE || "1",
|
|
435
|
+
UNBROWSE_TOS_ACCEPTED: process.env.UNBROWSE_TOS_ACCEPTED || "1",
|
|
436
|
+
UNBROWSE_PID_FILE: pidFile
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
child.unref();
|
|
440
|
+
writeFileSync2(pidFile, JSON.stringify({
|
|
441
|
+
pid: child.pid,
|
|
442
|
+
base_url: baseUrl,
|
|
443
|
+
started_at: new Date().toISOString(),
|
|
444
|
+
entrypoint
|
|
445
|
+
}, null, 2));
|
|
446
|
+
if (await waitForHealthy(baseUrl, 30000))
|
|
447
|
+
return;
|
|
448
|
+
throw new Error(`Server failed to start. Check ${logFile}`);
|
|
2110
449
|
}
|
|
2111
450
|
|
|
2112
451
|
// ../../src/runtime/paths.ts
|
|
2113
|
-
import { existsSync as
|
|
452
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
2114
453
|
import path3 from "node:path";
|
|
2115
|
-
import {
|
|
2116
|
-
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
2117
|
-
function resolveSiblingEntrypoint2(metaUrl, basename) {
|
|
2118
|
-
const file = fileURLToPath3(metaUrl);
|
|
2119
|
-
return path3.join(path3.dirname(file), `${basename}${path3.extname(file) || ".js"}`);
|
|
2120
|
-
}
|
|
2121
|
-
function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
|
|
2122
|
-
if (path3.extname(entrypoint) !== ".ts")
|
|
2123
|
-
return [entrypoint];
|
|
2124
|
-
if (process.versions.bun)
|
|
2125
|
-
return [entrypoint];
|
|
2126
|
-
try {
|
|
2127
|
-
const req = createRequire3(metaUrl);
|
|
2128
|
-
const tsxPkg = req.resolve("tsx/package.json");
|
|
2129
|
-
const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
|
|
2130
|
-
if (existsSync5(tsxLoader))
|
|
2131
|
-
return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
|
|
2132
|
-
} catch {}
|
|
2133
|
-
return ["--import", "tsx", entrypoint];
|
|
2134
|
-
}
|
|
454
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2135
455
|
function isMainModule(metaUrl) {
|
|
2136
456
|
const entry = process.argv[1];
|
|
2137
457
|
if (!entry)
|
|
2138
458
|
return false;
|
|
2139
|
-
const modulePath =
|
|
459
|
+
const modulePath = fileURLToPath2(metaUrl);
|
|
2140
460
|
try {
|
|
2141
461
|
return realpathSync2(entry) === realpathSync2(modulePath);
|
|
2142
462
|
} catch {
|
|
@@ -2144,256 +464,82 @@ function isMainModule(metaUrl) {
|
|
|
2144
464
|
}
|
|
2145
465
|
}
|
|
2146
466
|
|
|
2147
|
-
// ../../src/indexer/index.ts
|
|
2148
|
-
init_graph();
|
|
2149
|
-
init_client();
|
|
2150
|
-
init_marketplace();
|
|
2151
|
-
init_publish_admission();
|
|
2152
|
-
init_domain();
|
|
2153
|
-
await init_orchestrator();
|
|
2154
|
-
init_sanitize();
|
|
2155
|
-
init_artifact();
|
|
2156
|
-
init_publish();
|
|
2157
|
-
init_settings();
|
|
2158
|
-
import { join as join8 } from "node:path";
|
|
2159
|
-
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join8(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
2160
|
-
var indexInFlight2 = new Map;
|
|
2161
|
-
var pendingIndexJobs2 = new Map;
|
|
2162
|
-
async function drainPendingIndexJobs() {
|
|
2163
|
-
let logged = false;
|
|
2164
|
-
while (indexInFlight2.size > 0) {
|
|
2165
|
-
const pending = [...indexInFlight2.values()];
|
|
2166
|
-
if (!logged) {
|
|
2167
|
-
console.error(`[capture-pipeline] draining ${pending.length} pending job(s)...`);
|
|
2168
|
-
logged = true;
|
|
2169
|
-
}
|
|
2170
|
-
await Promise.allSettled(pending);
|
|
2171
|
-
}
|
|
2172
|
-
console.error(`[capture-pipeline] all jobs drained`);
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
// ../../src/orchestrator/passive-publish.ts
|
|
2176
|
-
init_client();
|
|
2177
|
-
init_publish_admission();
|
|
2178
|
-
init_marketplace();
|
|
2179
|
-
init_artifact();
|
|
2180
|
-
init_publish();
|
|
2181
|
-
var passivePublishInFlight2 = new Map;
|
|
2182
|
-
async function drainPendingPassivePublishes() {
|
|
2183
|
-
const pending = [...passivePublishInFlight2.values()];
|
|
2184
|
-
if (pending.length === 0)
|
|
2185
|
-
return;
|
|
2186
|
-
console.log(`[publish] draining ${pending.length} pending passive publish(es)...`);
|
|
2187
|
-
await Promise.allSettled(pending);
|
|
2188
|
-
console.log(`[publish] all passive publishes drained`);
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
467
|
// ../../src/runtime/setup.ts
|
|
2192
|
-
init_paths();
|
|
2193
|
-
init_client2();
|
|
2194
|
-
init_logger();
|
|
2195
|
-
init_wallet();
|
|
2196
468
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2197
|
-
import { existsSync as
|
|
2198
|
-
import os4 from "node:os";
|
|
2199
|
-
import path7 from "node:path";
|
|
2200
|
-
|
|
2201
|
-
// ../../src/runtime/update-hints.ts
|
|
2202
|
-
init_paths();
|
|
2203
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
469
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2204
470
|
import os3 from "node:os";
|
|
2205
471
|
import path6 from "node:path";
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
472
|
+
|
|
473
|
+
// ../../src/kuri/client.ts
|
|
474
|
+
import { execFileSync, spawn as spawn2 } from "node:child_process";
|
|
475
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
476
|
+
import path5 from "node:path";
|
|
477
|
+
|
|
478
|
+
// ../../src/logger.ts
|
|
479
|
+
import path4 from "node:path";
|
|
480
|
+
import os2 from "node:os";
|
|
481
|
+
var LOG_DIR = path4.join(os2.homedir(), ".unbrowse", "logs");
|
|
482
|
+
|
|
483
|
+
// ../../src/kuri/client.ts
|
|
484
|
+
function kuriBinaryName() {
|
|
485
|
+
return process.platform === "win32" ? "kuri.exe" : "kuri";
|
|
2215
486
|
}
|
|
2216
|
-
function
|
|
2217
|
-
if (
|
|
2218
|
-
|
|
2219
|
-
|
|
487
|
+
function currentBundledKuriTarget() {
|
|
488
|
+
if (process.platform === "darwin" && process.arch === "arm64")
|
|
489
|
+
return "darwin-arm64";
|
|
490
|
+
if (process.platform === "darwin" && process.arch === "x64")
|
|
491
|
+
return "darwin-x64";
|
|
492
|
+
if (process.platform === "linux" && process.arch === "arm64")
|
|
493
|
+
return "linux-arm64";
|
|
494
|
+
if (process.platform === "linux" && process.arch === "x64")
|
|
495
|
+
return "linux-x64";
|
|
496
|
+
return null;
|
|
2220
497
|
}
|
|
2221
|
-
function
|
|
498
|
+
function resolveBinaryOnPath(name) {
|
|
499
|
+
const checker = process.platform === "win32" ? "where" : "which";
|
|
2222
500
|
try {
|
|
2223
|
-
|
|
501
|
+
const output = execFileSync(checker, [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
502
|
+
const match = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
503
|
+
return match || null;
|
|
2224
504
|
} catch {
|
|
2225
505
|
return null;
|
|
2226
506
|
}
|
|
2227
507
|
}
|
|
2228
|
-
function
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
function getInstallSourcePath() {
|
|
2234
|
-
return path6.join(getConfigDir2(), "install-source.json");
|
|
2235
|
-
}
|
|
2236
|
-
function detectRepoRoot(start2) {
|
|
2237
|
-
let dir = path6.resolve(start2);
|
|
2238
|
-
const root = path6.parse(dir).root;
|
|
2239
|
-
while (dir !== root) {
|
|
2240
|
-
if (existsSync8(path6.join(dir, ".git")))
|
|
2241
|
-
return dir;
|
|
2242
|
-
dir = path6.dirname(dir);
|
|
2243
|
-
}
|
|
2244
|
-
return;
|
|
2245
|
-
}
|
|
2246
|
-
function detectInstallMethod(packageRoot) {
|
|
2247
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "repo-clone")
|
|
2248
|
-
return "repo-clone";
|
|
2249
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
|
|
2250
|
-
return "npm-global";
|
|
2251
|
-
if (packageRoot.includes(`${path6.sep}node_modules${path6.sep}`))
|
|
2252
|
-
return "npm-global";
|
|
2253
|
-
return detectRepoRoot(packageRoot) ? "repo-clone" : "unknown";
|
|
2254
|
-
}
|
|
2255
|
-
function detectInstallHost(repoRoot) {
|
|
2256
|
-
const explicit = process.env.UNBROWSE_SETUP_HOST;
|
|
2257
|
-
if (explicit)
|
|
2258
|
-
return explicit;
|
|
2259
|
-
if (!repoRoot)
|
|
2260
|
-
return "unknown";
|
|
2261
|
-
const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
|
|
2262
|
-
if (repoRoot === path6.join(codexHome, "skills", "unbrowse"))
|
|
2263
|
-
return "codex";
|
|
2264
|
-
if (repoRoot === path6.join(getHomeDir(), ".claude", "skills", "unbrowse"))
|
|
2265
|
-
return "claude";
|
|
2266
|
-
if (repoRoot === path6.join(getHomeDir(), "unbrowse"))
|
|
2267
|
-
return "off";
|
|
2268
|
-
return "unknown";
|
|
2269
|
-
}
|
|
2270
|
-
function resolveInstallSource(metaUrl) {
|
|
2271
|
-
const packageRoot = getPackageRoot(metaUrl);
|
|
2272
|
-
const envRepoRoot = process.env.UNBROWSE_SETUP_ROOT || undefined;
|
|
2273
|
-
const repoRoot = envRepoRoot || detectRepoRoot(packageRoot);
|
|
2274
|
-
return {
|
|
2275
|
-
method: detectInstallMethod(packageRoot),
|
|
2276
|
-
host: detectInstallHost(repoRoot),
|
|
2277
|
-
package_root: packageRoot,
|
|
2278
|
-
repo_root: repoRoot,
|
|
2279
|
-
recorded_at: new Date().toISOString()
|
|
2280
|
-
};
|
|
2281
|
-
}
|
|
2282
|
-
function saveInstallSource(metaUrl) {
|
|
2283
|
-
const state = resolveInstallSource(metaUrl);
|
|
2284
|
-
writeJsonFile(getInstallSourcePath(), state);
|
|
2285
|
-
return state;
|
|
2286
|
-
}
|
|
2287
|
-
function loadInstallSource(metaUrl) {
|
|
2288
|
-
return readJsonFile(getInstallSourcePath()) ?? resolveInstallSource(metaUrl);
|
|
2289
|
-
}
|
|
2290
|
-
function commandIncludesHook(command, marker) {
|
|
2291
|
-
return typeof command === "string" && command.includes(marker);
|
|
2292
|
-
}
|
|
2293
|
-
function getCodexConfigPath() {
|
|
2294
|
-
const codexHome = process.env.CODEX_HOME || path6.join(getHomeDir(), ".codex");
|
|
2295
|
-
return path6.join(codexHome, "config.toml");
|
|
2296
|
-
}
|
|
2297
|
-
function getClaudeSettingsPath() {
|
|
2298
|
-
return path6.join(getHomeDir(), ".claude", "settings.json");
|
|
2299
|
-
}
|
|
2300
|
-
function getHookScriptPath(metaUrl) {
|
|
2301
|
-
return path6.join(getPackageRoot(metaUrl), "bin", "unbrowse-update-hint.mjs");
|
|
2302
|
-
}
|
|
2303
|
-
function ensureCodexHooksFeature(content) {
|
|
2304
|
-
if (/\bcodex_hooks\s*=\s*true\b/.test(content))
|
|
2305
|
-
return content;
|
|
2306
|
-
if (/\[features\]/.test(content)) {
|
|
2307
|
-
return content.replace(/\[features\]\r?\n/, (match) => `${match}codex_hooks = true
|
|
2308
|
-
`);
|
|
2309
|
-
}
|
|
2310
|
-
const prefix = content && !content.endsWith(`
|
|
2311
|
-
`) ? `
|
|
2312
|
-
` : "";
|
|
2313
|
-
return `${content}${prefix}[features]
|
|
2314
|
-
codex_hooks = true
|
|
2315
|
-
`;
|
|
508
|
+
function addCandidate(candidates, candidate) {
|
|
509
|
+
if (!candidate)
|
|
510
|
+
return;
|
|
511
|
+
if (!candidates.includes(candidate))
|
|
512
|
+
candidates.push(candidate);
|
|
2316
513
|
}
|
|
2317
|
-
function
|
|
2318
|
-
const
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
content = ensureCodexHooksFeature(content);
|
|
2328
|
-
if (!content.includes("unbrowse-update-hint.mjs")) {
|
|
2329
|
-
const command = `node "${hookScript}"`;
|
|
2330
|
-
const prefix = content && !content.endsWith(`
|
|
2331
|
-
`) ? `
|
|
2332
|
-
` : "";
|
|
2333
|
-
content += `${prefix}${CODEX_MARKER}
|
|
2334
|
-
[[hooks]]
|
|
2335
|
-
event = "SessionStart"
|
|
2336
|
-
command = ${JSON.stringify(command)}
|
|
2337
|
-
`;
|
|
2338
|
-
}
|
|
2339
|
-
if (content !== previous) {
|
|
2340
|
-
writeFileSync4(configPath, content, "utf8");
|
|
2341
|
-
return {
|
|
2342
|
-
host: "codex",
|
|
2343
|
-
action: fileExistsBefore ? "updated" : "installed",
|
|
2344
|
-
config_file: configPath
|
|
2345
|
-
};
|
|
2346
|
-
}
|
|
2347
|
-
return { host: "codex", action: "already-installed", config_file: configPath };
|
|
2348
|
-
} catch (error) {
|
|
2349
|
-
return {
|
|
2350
|
-
host: "codex",
|
|
2351
|
-
action: "failed",
|
|
2352
|
-
config_file: configPath,
|
|
2353
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2354
|
-
};
|
|
2355
|
-
}
|
|
514
|
+
function getKuriSourceCandidates() {
|
|
515
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
516
|
+
const candidates = [];
|
|
517
|
+
addCandidate(candidates, path5.join(packageRoot, "vendor", "kuri-src"));
|
|
518
|
+
addCandidate(candidates, path5.join(packageRoot, "submodules", "kuri"));
|
|
519
|
+
if (process.env.KURI_PATH)
|
|
520
|
+
addCandidate(candidates, process.env.KURI_PATH);
|
|
521
|
+
if (process.env.HOME)
|
|
522
|
+
addCandidate(candidates, path5.join(process.env.HOME, "kuri"));
|
|
523
|
+
return candidates;
|
|
2356
524
|
}
|
|
2357
|
-
function
|
|
2358
|
-
const
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
const settings = readJsonFile(settingsPath) ?? {};
|
|
2367
|
-
settings.hooks ??= {};
|
|
2368
|
-
settings.hooks.SessionStart ??= [];
|
|
2369
|
-
const existing = settings.hooks.SessionStart.some((entry) => Array.isArray(entry.hooks) && entry.hooks.some((hook) => commandIncludesHook(hook.command, "unbrowse-update-hint.mjs")));
|
|
2370
|
-
if (!existing) {
|
|
2371
|
-
settings.hooks.SessionStart.push({
|
|
2372
|
-
hooks: [{ type: "command", command }]
|
|
2373
|
-
});
|
|
2374
|
-
writeJsonFile(settingsPath, settings);
|
|
2375
|
-
return {
|
|
2376
|
-
host: "claude",
|
|
2377
|
-
action: fileExistsBefore ? "updated" : "installed",
|
|
2378
|
-
config_file: settingsPath
|
|
2379
|
-
};
|
|
2380
|
-
}
|
|
2381
|
-
return { host: "claude", action: "already-installed", config_file: settingsPath };
|
|
2382
|
-
} catch (error) {
|
|
2383
|
-
return {
|
|
2384
|
-
host: "claude",
|
|
2385
|
-
action: "failed",
|
|
2386
|
-
config_file: settingsPath,
|
|
2387
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2388
|
-
};
|
|
525
|
+
function getKuriBinaryCandidates() {
|
|
526
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
527
|
+
const binaryName = kuriBinaryName();
|
|
528
|
+
const target = currentBundledKuriTarget();
|
|
529
|
+
const candidates = [];
|
|
530
|
+
if (target)
|
|
531
|
+
addCandidate(candidates, path5.join(packageRoot, "vendor", "kuri", target, binaryName));
|
|
532
|
+
for (const sourceDir of getKuriSourceCandidates()) {
|
|
533
|
+
addCandidate(candidates, path5.join(sourceDir, "zig-out", "bin", binaryName));
|
|
2389
534
|
}
|
|
535
|
+
addCandidate(candidates, resolveBinaryOnPath("kuri"));
|
|
536
|
+
return candidates;
|
|
2390
537
|
}
|
|
2391
|
-
function
|
|
2392
|
-
if (process.env.
|
|
2393
|
-
return
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
return configuredHosts.map((host) => host === "codex" ? writeCodexHook(metaUrl) : writeClaudeHook(metaUrl));
|
|
538
|
+
function findKuriBinary() {
|
|
539
|
+
if (process.env.KURI_BIN)
|
|
540
|
+
return process.env.KURI_BIN;
|
|
541
|
+
const candidates = getKuriBinaryCandidates();
|
|
542
|
+
return candidates.find((candidate) => existsSync4(candidate)) ?? candidates[0] ?? kuriBinaryName();
|
|
2397
543
|
}
|
|
2398
544
|
|
|
2399
545
|
// ../../src/runtime/setup.ts
|
|
@@ -2416,18 +562,18 @@ function detectPackageManagers() {
|
|
|
2416
562
|
}
|
|
2417
563
|
function resolveConfigHome() {
|
|
2418
564
|
if (process.platform === "win32") {
|
|
2419
|
-
return process.env.APPDATA ||
|
|
565
|
+
return process.env.APPDATA || path6.join(os3.homedir(), "AppData", "Roaming");
|
|
2420
566
|
}
|
|
2421
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
567
|
+
return process.env.XDG_CONFIG_HOME || path6.join(os3.homedir(), ".config");
|
|
2422
568
|
}
|
|
2423
569
|
function getOpenCodeGlobalCommandsDir() {
|
|
2424
|
-
return
|
|
570
|
+
return path6.join(resolveConfigHome(), "opencode", "commands");
|
|
2425
571
|
}
|
|
2426
572
|
function getOpenCodeProjectCommandsDir(cwd) {
|
|
2427
|
-
return
|
|
573
|
+
return path6.join(cwd, ".opencode", "commands");
|
|
2428
574
|
}
|
|
2429
575
|
function detectOpenCode(cwd) {
|
|
2430
|
-
return hasBinary("opencode") ||
|
|
576
|
+
return hasBinary("opencode") || existsSync5(path6.join(resolveConfigHome(), "opencode")) || existsSync5(path6.join(cwd, ".opencode"));
|
|
2431
577
|
}
|
|
2432
578
|
function renderOpenCodeCommand() {
|
|
2433
579
|
return `---
|
|
@@ -2455,26 +601,26 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
2455
601
|
if (scope === "auto" && !detected) {
|
|
2456
602
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
2457
603
|
}
|
|
2458
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" :
|
|
604
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync5(path6.join(cwd, ".opencode")) ? "project" : "global";
|
|
2459
605
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
2460
|
-
const commandFile =
|
|
606
|
+
const commandFile = path6.join(ensureDir(commandsDir), "unbrowse.md");
|
|
2461
607
|
const content = renderOpenCodeCommand();
|
|
2462
|
-
const
|
|
2463
|
-
|
|
2464
|
-
|
|
608
|
+
const action = existsSync5(commandFile) ? "updated" : "installed";
|
|
609
|
+
mkdirSync4(path6.dirname(commandFile), { recursive: true });
|
|
610
|
+
writeFileSync3(commandFile, content);
|
|
2465
611
|
return {
|
|
2466
612
|
detected: detected || scope !== "auto",
|
|
2467
|
-
action
|
|
613
|
+
action,
|
|
2468
614
|
scope: resolvedScope,
|
|
2469
615
|
command_file: commandFile
|
|
2470
616
|
};
|
|
2471
617
|
}
|
|
2472
618
|
async function ensureBrowserEngineInstalled() {
|
|
2473
619
|
const binary = findKuriBinary();
|
|
2474
|
-
if (
|
|
620
|
+
if (existsSync5(binary)) {
|
|
2475
621
|
return { installed: true, action: "already-installed" };
|
|
2476
622
|
}
|
|
2477
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) =>
|
|
623
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync5(path6.join(candidate, "build.zig")));
|
|
2478
624
|
if (!sourceDir) {
|
|
2479
625
|
return {
|
|
2480
626
|
installed: false,
|
|
@@ -2496,7 +642,7 @@ async function ensureBrowserEngineInstalled() {
|
|
|
2496
642
|
timeout: 300000
|
|
2497
643
|
});
|
|
2498
644
|
const builtBinary = findKuriBinary();
|
|
2499
|
-
if (
|
|
645
|
+
if (existsSync5(builtBinary)) {
|
|
2500
646
|
return {
|
|
2501
647
|
installed: true,
|
|
2502
648
|
action: "installed",
|
|
@@ -2515,220 +661,18 @@ async function ensureBrowserEngineInstalled() {
|
|
|
2515
661
|
}
|
|
2516
662
|
async function runSetup(options) {
|
|
2517
663
|
const cwd = options?.cwd || process.cwd();
|
|
2518
|
-
const installSource = saveInstallSource(import.meta.url);
|
|
2519
|
-
const hostEnv = detectHostEnvironment();
|
|
2520
|
-
log("setup", `detected host environment: ${hostEnv}`);
|
|
2521
664
|
const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
|
|
2522
|
-
const walletCheck = checkWalletConfigured();
|
|
2523
|
-
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
2524
|
-
const lobsterInstalled = hasBinary("lobstercash") || existsSync9(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
2525
|
-
if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
|
|
2526
|
-
console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
|
|
2527
|
-
try {
|
|
2528
|
-
execFileSync2("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
2529
|
-
stdio: "inherit",
|
|
2530
|
-
timeout: 60000
|
|
2531
|
-
});
|
|
2532
|
-
const recheck = checkWalletConfigured();
|
|
2533
|
-
if (recheck.configured) {
|
|
2534
|
-
console.log(`[unbrowse] wallet configured (${recheck.provider})`);
|
|
2535
|
-
}
|
|
2536
|
-
} catch {
|
|
2537
|
-
console.warn("[unbrowse] Crossmint lobster.cash setup failed or was skipped — continuing without wallet");
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
const finalWalletCheck = checkWalletConfigured();
|
|
2541
|
-
const wallet = {
|
|
2542
|
-
...finalWalletCheck,
|
|
2543
|
-
lobster_installed: lobsterInstalled,
|
|
2544
|
-
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 ? "Crossmint lobster.cash is installed but not paired. Pair it now so this wallet address becomes your contributor payout target and your paid-route spending wallet. Run: npx @crossmint/lobster-cli setup" : "No wallet configured. Recommended for new installs: set up Crossmint lobster.cash so contributor payouts have a destination address and paid-route spending can clear automatically. Without it you stay in free indexing mode only.",
|
|
2545
|
-
install_hint: finalWalletCheck.configured ? undefined : lobsterInstalled ? "npx @crossmint/lobster-cli setup" : "npx @crossmint/lobster-cli setup"
|
|
2546
|
-
};
|
|
2547
665
|
return {
|
|
2548
666
|
os: {
|
|
2549
667
|
platform: process.platform,
|
|
2550
|
-
release:
|
|
668
|
+
release: os3.release(),
|
|
2551
669
|
arch: process.arch
|
|
2552
670
|
},
|
|
2553
|
-
host_environment: hostEnv,
|
|
2554
671
|
package_managers: detectPackageManagers(),
|
|
2555
672
|
browser_engine: browser,
|
|
2556
|
-
opencode: writeOpenCodeCommand(options?.opencode ?? "auto", cwd)
|
|
2557
|
-
update_hints: configureUpdateHintHooks(import.meta.url, installSource),
|
|
2558
|
-
wallet
|
|
2559
|
-
};
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
// ../../src/runtime/update-hints.ts
|
|
2563
|
-
init_paths();
|
|
2564
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2565
|
-
import os5 from "node:os";
|
|
2566
|
-
import path8 from "node:path";
|
|
2567
|
-
var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
|
|
2568
|
-
var DEFAULT_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
|
|
2569
|
-
function getHomeDir2() {
|
|
2570
|
-
return process.env.HOME || os5.homedir();
|
|
2571
|
-
}
|
|
2572
|
-
function getConfigDir3() {
|
|
2573
|
-
if (process.env.UNBROWSE_CONFIG_DIR)
|
|
2574
|
-
return process.env.UNBROWSE_CONFIG_DIR;
|
|
2575
|
-
return path8.join(getHomeDir2(), ".unbrowse");
|
|
2576
|
-
}
|
|
2577
|
-
function ensureDir3(dir) {
|
|
2578
|
-
if (!existsSync10(dir))
|
|
2579
|
-
mkdirSync7(dir, { recursive: true });
|
|
2580
|
-
return dir;
|
|
2581
|
-
}
|
|
2582
|
-
function readJsonFile2(file) {
|
|
2583
|
-
try {
|
|
2584
|
-
return JSON.parse(readFileSync7(file, "utf8"));
|
|
2585
|
-
} catch {
|
|
2586
|
-
return null;
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
function writeJsonFile2(file, value) {
|
|
2590
|
-
ensureDir3(path8.dirname(file));
|
|
2591
|
-
writeFileSync6(file, `${JSON.stringify(value, null, 2)}
|
|
2592
|
-
`);
|
|
2593
|
-
}
|
|
2594
|
-
function getInstallSourcePath2() {
|
|
2595
|
-
return path8.join(getConfigDir3(), "install-source.json");
|
|
2596
|
-
}
|
|
2597
|
-
function getUpdateCheckStatePath() {
|
|
2598
|
-
return path8.join(getConfigDir3(), "update-check.json");
|
|
2599
|
-
}
|
|
2600
|
-
function detectRepoRoot2(start2) {
|
|
2601
|
-
let dir = path8.resolve(start2);
|
|
2602
|
-
const root = path8.parse(dir).root;
|
|
2603
|
-
while (dir !== root) {
|
|
2604
|
-
if (existsSync10(path8.join(dir, ".git")))
|
|
2605
|
-
return dir;
|
|
2606
|
-
dir = path8.dirname(dir);
|
|
2607
|
-
}
|
|
2608
|
-
return;
|
|
2609
|
-
}
|
|
2610
|
-
function detectInstallMethod2(packageRoot) {
|
|
2611
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "repo-clone")
|
|
2612
|
-
return "repo-clone";
|
|
2613
|
-
if (process.env.UNBROWSE_SETUP_METHOD === "npm-global")
|
|
2614
|
-
return "npm-global";
|
|
2615
|
-
if (packageRoot.includes(`${path8.sep}node_modules${path8.sep}`))
|
|
2616
|
-
return "npm-global";
|
|
2617
|
-
return detectRepoRoot2(packageRoot) ? "repo-clone" : "unknown";
|
|
2618
|
-
}
|
|
2619
|
-
function detectInstallHost2(repoRoot) {
|
|
2620
|
-
const explicit = process.env.UNBROWSE_SETUP_HOST;
|
|
2621
|
-
if (explicit)
|
|
2622
|
-
return explicit;
|
|
2623
|
-
if (!repoRoot)
|
|
2624
|
-
return "unknown";
|
|
2625
|
-
const codexHome = process.env.CODEX_HOME || path8.join(getHomeDir2(), ".codex");
|
|
2626
|
-
if (repoRoot === path8.join(codexHome, "skills", "unbrowse"))
|
|
2627
|
-
return "codex";
|
|
2628
|
-
if (repoRoot === path8.join(getHomeDir2(), ".claude", "skills", "unbrowse"))
|
|
2629
|
-
return "claude";
|
|
2630
|
-
if (repoRoot === path8.join(getHomeDir2(), "unbrowse"))
|
|
2631
|
-
return "off";
|
|
2632
|
-
return "unknown";
|
|
2633
|
-
}
|
|
2634
|
-
function getInstalledVersion(metaUrl) {
|
|
2635
|
-
const packageRoot = getPackageRoot(metaUrl);
|
|
2636
|
-
try {
|
|
2637
|
-
const pkg = JSON.parse(readFileSync7(path8.join(packageRoot, "package.json"), "utf8"));
|
|
2638
|
-
return pkg.version ?? "unknown";
|
|
2639
|
-
} catch {
|
|
2640
|
-
return "unknown";
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
function resolveInstallSource2(metaUrl) {
|
|
2644
|
-
const packageRoot = getPackageRoot(metaUrl);
|
|
2645
|
-
const envRepoRoot = process.env.UNBROWSE_SETUP_ROOT || undefined;
|
|
2646
|
-
const repoRoot = envRepoRoot || detectRepoRoot2(packageRoot);
|
|
2647
|
-
return {
|
|
2648
|
-
method: detectInstallMethod2(packageRoot),
|
|
2649
|
-
host: detectInstallHost2(repoRoot),
|
|
2650
|
-
package_root: packageRoot,
|
|
2651
|
-
repo_root: repoRoot,
|
|
2652
|
-
recorded_at: new Date().toISOString()
|
|
2653
|
-
};
|
|
2654
|
-
}
|
|
2655
|
-
function loadInstallSource2(metaUrl) {
|
|
2656
|
-
return readJsonFile2(getInstallSourcePath2()) ?? resolveInstallSource2(metaUrl);
|
|
2657
|
-
}
|
|
2658
|
-
function compareSemver(a, b) {
|
|
2659
|
-
const parse = (value) => value.split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
2660
|
-
const left = parse(a);
|
|
2661
|
-
const right = parse(b);
|
|
2662
|
-
const max = Math.max(left.length, right.length);
|
|
2663
|
-
for (let i = 0;i < max; i++) {
|
|
2664
|
-
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
2665
|
-
if (diff !== 0)
|
|
2666
|
-
return diff;
|
|
2667
|
-
}
|
|
2668
|
-
return 0;
|
|
2669
|
-
}
|
|
2670
|
-
async function fetchLatestVersion() {
|
|
2671
|
-
try {
|
|
2672
|
-
const res = await fetch("https://registry.npmjs.org/unbrowse/latest", {
|
|
2673
|
-
signal: AbortSignal.timeout(8000),
|
|
2674
|
-
headers: { Accept: "application/json" }
|
|
2675
|
-
});
|
|
2676
|
-
if (!res.ok)
|
|
2677
|
-
return null;
|
|
2678
|
-
const body = await res.json();
|
|
2679
|
-
return typeof body.version === "string" && body.version.trim() ? body.version.trim() : null;
|
|
2680
|
-
} catch {
|
|
2681
|
-
return null;
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
|
-
function getUpdateIntervalMs() {
|
|
2685
|
-
const value = Number.parseInt(process.env.UNBROWSE_UPDATE_CHECK_INTERVAL_MS ?? "", 10);
|
|
2686
|
-
return Number.isFinite(value) && value > 0 ? value : DEFAULT_INTERVAL_MS2;
|
|
2687
|
-
}
|
|
2688
|
-
function buildUpgradeCommand(install) {
|
|
2689
|
-
if (install.method === "repo-clone" && install.repo_root) {
|
|
2690
|
-
const host = install.host === "unknown" || install.host === "auto" ? "off" : install.host;
|
|
2691
|
-
return `cd ${install.repo_root} && git pull --ff-only && ./setup --host ${host}`;
|
|
2692
|
-
}
|
|
2693
|
-
return `curl -fsSL ${INSTALL_SCRIPT_URL} | bash`;
|
|
2694
|
-
}
|
|
2695
|
-
async function checkForUpdates(metaUrl, options) {
|
|
2696
|
-
const installed = getInstalledVersion(metaUrl);
|
|
2697
|
-
const install = loadInstallSource2(metaUrl);
|
|
2698
|
-
const statePath = getUpdateCheckStatePath();
|
|
2699
|
-
const state = readJsonFile2(statePath) ?? {};
|
|
2700
|
-
const checkedAt = new Date().toISOString();
|
|
2701
|
-
const intervalMs = getUpdateIntervalMs();
|
|
2702
|
-
const lastChecked = state.latest_checked_at ? Date.parse(state.latest_checked_at) : Number.NaN;
|
|
2703
|
-
const useCache = !options?.force && !!state.latest_version && Number.isFinite(lastChecked) && Date.now() - lastChecked < intervalMs;
|
|
2704
|
-
const latest = useCache ? state.latest_version ?? null : await fetchLatestVersion();
|
|
2705
|
-
if (!useCache) {
|
|
2706
|
-
writeJsonFile2(statePath, {
|
|
2707
|
-
...state,
|
|
2708
|
-
checked_at: checkedAt,
|
|
2709
|
-
latest_version: latest ?? undefined,
|
|
2710
|
-
latest_checked_at: checkedAt
|
|
2711
|
-
});
|
|
2712
|
-
}
|
|
2713
|
-
return {
|
|
2714
|
-
installed,
|
|
2715
|
-
latest,
|
|
2716
|
-
has_update: !!latest && compareSemver(latest, installed) > 0,
|
|
2717
|
-
install,
|
|
2718
|
-
command: buildUpgradeCommand(install),
|
|
2719
|
-
checked_at: checkedAt,
|
|
2720
|
-
cached: useCache
|
|
673
|
+
opencode: writeOpenCodeCommand(options?.opencode ?? "auto", cwd)
|
|
2721
674
|
};
|
|
2722
675
|
}
|
|
2723
|
-
function recordUpdateHint(latestVersion) {
|
|
2724
|
-
const statePath = getUpdateCheckStatePath();
|
|
2725
|
-
const state = readJsonFile2(statePath) ?? {};
|
|
2726
|
-
writeJsonFile2(statePath, {
|
|
2727
|
-
...state,
|
|
2728
|
-
notified_version: latestVersion,
|
|
2729
|
-
notified_at: new Date().toISOString()
|
|
2730
|
-
});
|
|
2731
|
-
}
|
|
2732
676
|
|
|
2733
677
|
// ../../src/cli.ts
|
|
2734
678
|
loadEnv({ quiet: true });
|
|
@@ -2758,28 +702,14 @@ function parseArgs(argv) {
|
|
|
2758
702
|
}
|
|
2759
703
|
return { command, args: positional, flags };
|
|
2760
704
|
}
|
|
2761
|
-
async function api2(method,
|
|
2762
|
-
|
|
2763
|
-
let requestBody = body;
|
|
2764
|
-
if (method === "GET" && body && typeof body === "object") {
|
|
2765
|
-
const params = new URLSearchParams;
|
|
2766
|
-
for (const [key, value] of Object.entries(body)) {
|
|
2767
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
2768
|
-
params.set(key, String(value));
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
const query = params.toString();
|
|
2772
|
-
if (query)
|
|
2773
|
-
target += `${target.includes("?") ? "&" : "?"}${query}`;
|
|
2774
|
-
requestBody = undefined;
|
|
2775
|
-
}
|
|
2776
|
-
const res = await fetch(target, {
|
|
705
|
+
async function api2(method, path7, body) {
|
|
706
|
+
const res = await fetch(`${BASE_URL}${path7}`, {
|
|
2777
707
|
method,
|
|
2778
708
|
headers: {
|
|
2779
|
-
...
|
|
709
|
+
...body ? { "Content-Type": "application/json" } : {},
|
|
2780
710
|
"x-unbrowse-client-id": CLI_CLIENT_ID
|
|
2781
711
|
},
|
|
2782
|
-
body:
|
|
712
|
+
body: body ? JSON.stringify(body) : undefined
|
|
2783
713
|
});
|
|
2784
714
|
if (!res.ok && res.headers.get("content-type")?.includes("json")) {
|
|
2785
715
|
return res.json();
|
|
@@ -2801,23 +731,6 @@ function info(msg) {
|
|
|
2801
731
|
process.stderr.write(`[unbrowse] ${msg}
|
|
2802
732
|
`);
|
|
2803
733
|
}
|
|
2804
|
-
function resolveResultError(result) {
|
|
2805
|
-
return result.result?.error ?? result.error;
|
|
2806
|
-
}
|
|
2807
|
-
function resolveLoginUrl(result, fallbackUrl) {
|
|
2808
|
-
return result.result?.login_url ?? fallbackUrl ?? "";
|
|
2809
|
-
}
|
|
2810
|
-
function hasIndexingFallback(result) {
|
|
2811
|
-
return result.result?.indexing_fallback_available === true;
|
|
2812
|
-
}
|
|
2813
|
-
function isResolveSuccessResult(result) {
|
|
2814
|
-
const resultObj = result.result;
|
|
2815
|
-
if (resolveResultError(result))
|
|
2816
|
-
return false;
|
|
2817
|
-
if (resultObj?.status === "browse_session_open")
|
|
2818
|
-
return false;
|
|
2819
|
-
return !!result.result || Array.isArray(result.available_endpoints);
|
|
2820
|
-
}
|
|
2821
734
|
async function withPendingNotice(promise, message, delayMs = 3000) {
|
|
2822
735
|
let done = false;
|
|
2823
736
|
const timer = setTimeout(() => {
|
|
@@ -2839,6 +752,174 @@ function normalizeSetupScope(value) {
|
|
|
2839
752
|
return normalized;
|
|
2840
753
|
return "auto";
|
|
2841
754
|
}
|
|
755
|
+
function buildEntityIndex(items) {
|
|
756
|
+
const index = new Map;
|
|
757
|
+
for (const item of items) {
|
|
758
|
+
if (item != null && typeof item === "object") {
|
|
759
|
+
const urn = item.entityUrn;
|
|
760
|
+
if (typeof urn === "string")
|
|
761
|
+
index.set(urn, item);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return index;
|
|
765
|
+
}
|
|
766
|
+
function detectEntityIndex(data) {
|
|
767
|
+
if (data == null || typeof data !== "object")
|
|
768
|
+
return null;
|
|
769
|
+
let best = null;
|
|
770
|
+
const check = (arr) => {
|
|
771
|
+
if (arr.length < 2)
|
|
772
|
+
return;
|
|
773
|
+
const sample = arr.slice(0, 10);
|
|
774
|
+
const withUrn = sample.filter((i) => i != null && typeof i === "object" && typeof i.entityUrn === "string").length;
|
|
775
|
+
if (withUrn >= sample.length * 0.5 && (!best || arr.length > best.length)) {
|
|
776
|
+
best = arr;
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
const obj = data;
|
|
780
|
+
for (const val of Object.values(obj)) {
|
|
781
|
+
if (Array.isArray(val)) {
|
|
782
|
+
check(val);
|
|
783
|
+
} else if (val != null && typeof val === "object" && !Array.isArray(val)) {
|
|
784
|
+
for (const nested of Object.values(val)) {
|
|
785
|
+
if (Array.isArray(nested))
|
|
786
|
+
check(nested);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return best ? buildEntityIndex(best) : null;
|
|
791
|
+
}
|
|
792
|
+
function resolvePath(obj, path7, entityIndex) {
|
|
793
|
+
if (!path7 || obj == null)
|
|
794
|
+
return obj;
|
|
795
|
+
const segments = path7.split(".");
|
|
796
|
+
let cur = obj;
|
|
797
|
+
for (let i = 0;i < segments.length; i++) {
|
|
798
|
+
if (cur == null)
|
|
799
|
+
return;
|
|
800
|
+
const seg = segments[i];
|
|
801
|
+
if (seg.endsWith("[]")) {
|
|
802
|
+
const key = seg.slice(0, -2);
|
|
803
|
+
const arr = key ? cur[key] : cur;
|
|
804
|
+
if (!Array.isArray(arr))
|
|
805
|
+
return;
|
|
806
|
+
const remaining = segments.slice(i + 1).join(".");
|
|
807
|
+
if (!remaining)
|
|
808
|
+
return arr;
|
|
809
|
+
return arr.flatMap((item) => {
|
|
810
|
+
const v = resolvePath(item, remaining, entityIndex);
|
|
811
|
+
return v === undefined ? [] : Array.isArray(v) ? v : [v];
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
const rec = cur;
|
|
815
|
+
let val = rec[seg];
|
|
816
|
+
if (val == null && entityIndex) {
|
|
817
|
+
const ref = rec[`*${seg}`];
|
|
818
|
+
if (typeof ref === "string") {
|
|
819
|
+
val = entityIndex.get(ref);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
cur = val;
|
|
823
|
+
}
|
|
824
|
+
return cur;
|
|
825
|
+
}
|
|
826
|
+
function extractFields(data, fields, entityIndex) {
|
|
827
|
+
if (data == null)
|
|
828
|
+
return data;
|
|
829
|
+
function mapItem(item) {
|
|
830
|
+
const out = {};
|
|
831
|
+
for (const f of fields) {
|
|
832
|
+
const colonIdx = f.indexOf(":");
|
|
833
|
+
const alias = colonIdx >= 0 ? f.slice(0, colonIdx) : f.split(".").pop();
|
|
834
|
+
const path7 = colonIdx >= 0 ? f.slice(colonIdx + 1) : f;
|
|
835
|
+
const resolved = resolvePath(item, path7, entityIndex ?? undefined) ?? [];
|
|
836
|
+
out[alias] = Array.isArray(resolved) ? resolved.length === 0 ? null : resolved.length === 1 ? resolved[0] : resolved : resolved;
|
|
837
|
+
}
|
|
838
|
+
return out;
|
|
839
|
+
}
|
|
840
|
+
function hasValue(v) {
|
|
841
|
+
if (v == null)
|
|
842
|
+
return false;
|
|
843
|
+
if (Array.isArray(v))
|
|
844
|
+
return v.length > 0;
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
if (Array.isArray(data)) {
|
|
848
|
+
return data.map(mapItem).filter((row) => Object.values(row).some(hasValue));
|
|
849
|
+
}
|
|
850
|
+
return mapItem(data);
|
|
851
|
+
}
|
|
852
|
+
function hasMeaningfulValue(value) {
|
|
853
|
+
if (value == null)
|
|
854
|
+
return false;
|
|
855
|
+
if (typeof value === "string")
|
|
856
|
+
return value.trim().length > 0;
|
|
857
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
858
|
+
return true;
|
|
859
|
+
if (Array.isArray(value))
|
|
860
|
+
return value.some((item) => hasMeaningfulValue(item));
|
|
861
|
+
if (typeof value === "object")
|
|
862
|
+
return Object.values(value).some((item) => hasMeaningfulValue(item));
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
function isPlainRecord(value) {
|
|
866
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
867
|
+
}
|
|
868
|
+
function isScalarLike(value) {
|
|
869
|
+
if (value == null)
|
|
870
|
+
return false;
|
|
871
|
+
if (typeof value === "string")
|
|
872
|
+
return value.trim().length > 0;
|
|
873
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
874
|
+
return true;
|
|
875
|
+
if (Array.isArray(value)) {
|
|
876
|
+
return value.length > 0 && value.every((item) => item == null || typeof item === "string" || typeof item === "number" || typeof item === "boolean");
|
|
877
|
+
}
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
function looksStructuredForDirectOutput(value) {
|
|
881
|
+
if (Array.isArray(value)) {
|
|
882
|
+
const sample = value.filter(isPlainRecord).slice(0, 3);
|
|
883
|
+
if (sample.length === 0)
|
|
884
|
+
return false;
|
|
885
|
+
const simpleRows = sample.filter((row) => {
|
|
886
|
+
const keys2 = Object.keys(row);
|
|
887
|
+
const scalarFields2 = Object.values(row).filter(isScalarLike).length;
|
|
888
|
+
return keys2.length > 0 && keys2.length <= 20 && scalarFields2 >= 2;
|
|
889
|
+
});
|
|
890
|
+
return simpleRows.length >= Math.ceil(sample.length / 2);
|
|
891
|
+
}
|
|
892
|
+
if (!isPlainRecord(value))
|
|
893
|
+
return false;
|
|
894
|
+
const keys = Object.keys(value);
|
|
895
|
+
if (keys.length === 0 || keys.length > 20)
|
|
896
|
+
return false;
|
|
897
|
+
const scalarFields = Object.values(value).filter(isScalarLike).length;
|
|
898
|
+
return scalarFields >= 2;
|
|
899
|
+
}
|
|
900
|
+
function applyTransforms(result, flags) {
|
|
901
|
+
let data = result;
|
|
902
|
+
const entityIndex = detectEntityIndex(result);
|
|
903
|
+
const pathFlag = flags.path;
|
|
904
|
+
if (pathFlag) {
|
|
905
|
+
data = resolvePath(data, pathFlag, entityIndex);
|
|
906
|
+
if (data === undefined) {
|
|
907
|
+
process.stderr.write(`[unbrowse] warning: --path "${pathFlag}" resolved to undefined. Check path against response structure.
|
|
908
|
+
`);
|
|
909
|
+
return [];
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const extractFlag = flags.extract;
|
|
913
|
+
if (extractFlag) {
|
|
914
|
+
const fields = extractFlag.split(",").map((f) => f.trim());
|
|
915
|
+
data = extractFields(data, fields, entityIndex);
|
|
916
|
+
}
|
|
917
|
+
const limitFlag = flags.limit;
|
|
918
|
+
if (limitFlag && Array.isArray(data)) {
|
|
919
|
+
data = data.slice(0, Number(limitFlag));
|
|
920
|
+
}
|
|
921
|
+
return data;
|
|
922
|
+
}
|
|
2842
923
|
function slimTrace(obj) {
|
|
2843
924
|
const trace = obj.trace;
|
|
2844
925
|
const out = {
|
|
@@ -2852,487 +933,155 @@ function slimTrace(obj) {
|
|
|
2852
933
|
...trace.schema_backfilled ? { schema_backfilled: true } : {}
|
|
2853
934
|
} : undefined
|
|
2854
935
|
};
|
|
2855
|
-
if (typeof obj.error === "string")
|
|
2856
|
-
out.error = obj.error;
|
|
2857
|
-
if (typeof obj.message === "string")
|
|
2858
|
-
out.message = obj.message;
|
|
2859
|
-
if (typeof obj.hint === "string")
|
|
2860
|
-
out.hint = obj.hint;
|
|
2861
|
-
if (typeof obj.login_url === "string")
|
|
2862
|
-
out.login_url = obj.login_url;
|
|
2863
|
-
if (typeof obj.provider === "string")
|
|
2864
|
-
out.provider = obj.provider;
|
|
2865
936
|
if ("result" in obj)
|
|
2866
937
|
out.result = obj.result;
|
|
2867
|
-
if (obj.
|
|
2868
|
-
out.
|
|
2869
|
-
if (obj.impact)
|
|
2870
|
-
out.impact = obj.impact;
|
|
2871
|
-
if (obj.next_actions)
|
|
2872
|
-
out.next_actions = obj.next_actions;
|
|
2873
|
-
if (obj.next_step)
|
|
2874
|
-
out.next_step = obj.next_step;
|
|
2875
|
-
if (obj.source)
|
|
2876
|
-
out.source = obj.source;
|
|
2877
|
-
if (obj.skill)
|
|
2878
|
-
out.skill = obj.skill;
|
|
938
|
+
if (obj.extraction_hints)
|
|
939
|
+
out.extraction_hints = obj.extraction_hints;
|
|
2879
940
|
return out;
|
|
2880
941
|
}
|
|
2881
|
-
function
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
if (
|
|
2887
|
-
return
|
|
2888
|
-
|
|
942
|
+
function wrapWithHints(obj) {
|
|
943
|
+
const hints = obj.extraction_hints;
|
|
944
|
+
if (!hints)
|
|
945
|
+
return obj;
|
|
946
|
+
const resultStr = JSON.stringify(obj.result ?? "");
|
|
947
|
+
if (resultStr.length < 2000)
|
|
948
|
+
return obj;
|
|
949
|
+
const trace = obj.trace;
|
|
950
|
+
return {
|
|
951
|
+
trace: trace ? {
|
|
952
|
+
trace_id: trace.trace_id,
|
|
953
|
+
skill_id: trace.skill_id,
|
|
954
|
+
endpoint_id: trace.endpoint_id,
|
|
955
|
+
success: trace.success,
|
|
956
|
+
status_code: trace.status_code
|
|
957
|
+
} : undefined,
|
|
958
|
+
_response_too_large: `${resultStr.length} bytes \u2014 use extraction flags below to get structured data`,
|
|
959
|
+
extraction_hints: hints
|
|
960
|
+
};
|
|
2889
961
|
}
|
|
2890
|
-
function
|
|
2891
|
-
const
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
const tokensSavedPct = typeof impact.tokens_saved_pct === "number" ? impact.tokens_saved_pct : 0;
|
|
2898
|
-
const browserAvoided = impact.browser_avoided === true;
|
|
2899
|
-
if (timeSavedMs <= 0 && tokensSaved <= 0 && !browserAvoided)
|
|
2900
|
-
return;
|
|
2901
|
-
const parts = [];
|
|
2902
|
-
if (timeSavedMs > 0)
|
|
2903
|
-
parts.push(`${formatSavedDuration(timeSavedMs)} saved (${timeSavedPct}% faster)`);
|
|
2904
|
-
if (tokensSaved > 0)
|
|
2905
|
-
parts.push(`${tokensSaved.toLocaleString("en-US")} tokens saved (${tokensSavedPct}% less context)`);
|
|
2906
|
-
if (browserAvoided)
|
|
2907
|
-
parts.push("browser avoided");
|
|
2908
|
-
info(parts.join(" \u2022 "));
|
|
962
|
+
function schemaOnly(obj) {
|
|
963
|
+
const trace = obj.trace;
|
|
964
|
+
return {
|
|
965
|
+
trace: trace ? { trace_id: trace.trace_id, skill_id: trace.skill_id, endpoint_id: trace.endpoint_id, success: trace.success } : undefined,
|
|
966
|
+
extraction_hints: obj.extraction_hints ?? null,
|
|
967
|
+
response_schema: obj.response_schema ?? null
|
|
968
|
+
};
|
|
2909
969
|
}
|
|
2910
|
-
function
|
|
2911
|
-
const
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
970
|
+
function autoExtractOrWrap(obj) {
|
|
971
|
+
const hints = obj.extraction_hints;
|
|
972
|
+
const resultStr = JSON.stringify(obj.result ?? "");
|
|
973
|
+
if (resultStr.length < 2000)
|
|
974
|
+
return obj;
|
|
975
|
+
if (looksStructuredForDirectOutput(obj.result)) {
|
|
976
|
+
return slimTrace({ ...obj, extraction_hints: undefined, response_schema: undefined });
|
|
977
|
+
}
|
|
978
|
+
if (!hints)
|
|
979
|
+
return obj;
|
|
980
|
+
if (hints.confidence === "high") {
|
|
981
|
+
const syntheticFlags = {};
|
|
982
|
+
if (hints.path)
|
|
983
|
+
syntheticFlags.path = hints.path;
|
|
984
|
+
if (hints.fields.length > 0)
|
|
985
|
+
syntheticFlags.extract = hints.fields.join(",");
|
|
986
|
+
syntheticFlags.limit = "20";
|
|
987
|
+
const extracted = applyTransforms(obj.result, syntheticFlags);
|
|
988
|
+
if (!hasMeaningfulValue(extracted))
|
|
989
|
+
return wrapWithHints(obj);
|
|
990
|
+
const slimmed = slimTrace({ ...obj, result: extracted });
|
|
991
|
+
slimmed._auto_extracted = {
|
|
992
|
+
applied: hints.cli_args,
|
|
993
|
+
confidence: hints.confidence,
|
|
994
|
+
all_fields: hints.schema_tree,
|
|
995
|
+
note: "Auto-extracted using response_schema. Add/remove fields with --extract, change array with --path, or use --raw for full response."
|
|
996
|
+
};
|
|
997
|
+
return slimmed;
|
|
2920
998
|
}
|
|
999
|
+
return wrapWithHints(obj);
|
|
2921
1000
|
}
|
|
2922
1001
|
async function cmdHealth(flags) {
|
|
2923
1002
|
output(await api2("GET", "/health"), !!flags.pretty);
|
|
2924
1003
|
}
|
|
2925
|
-
function telemetryDomainFromInput(domain, url) {
|
|
2926
|
-
if (domain?.trim())
|
|
2927
|
-
return domain.trim().replace(/^www\./, "");
|
|
2928
|
-
if (!url?.trim())
|
|
2929
|
-
return null;
|
|
2930
|
-
try {
|
|
2931
|
-
return new URL(url).hostname.replace(/^www\./, "");
|
|
2932
|
-
} catch {
|
|
2933
|
-
return null;
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
1004
|
async function cmdResolve(flags) {
|
|
2937
1005
|
const intent = flags.intent;
|
|
2938
1006
|
if (!intent)
|
|
2939
1007
|
die("--intent is required");
|
|
2940
|
-
const
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
});
|
|
2947
|
-
await recordFunnelTelemetryEvent("resolve_started", {
|
|
2948
|
-
source: "cli",
|
|
2949
|
-
hostType,
|
|
2950
|
-
properties: {
|
|
2951
|
-
command: "resolve",
|
|
2952
|
-
intent,
|
|
2953
|
-
domain: telemetryDomainFromInput(flags.domain, flags.url),
|
|
2954
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
2955
|
-
has_url: typeof flags.url === "string",
|
|
2956
|
-
has_domain: typeof flags.domain === "string",
|
|
2957
|
-
auto_execute: !!flags.execute
|
|
2958
|
-
}
|
|
2959
|
-
});
|
|
2960
|
-
try {
|
|
2961
|
-
let execBody = function(endpointId) {
|
|
2962
|
-
return {
|
|
2963
|
-
params: { endpoint_id: endpointId, ...extraParams },
|
|
2964
|
-
intent,
|
|
2965
|
-
projection: { raw: true },
|
|
2966
|
-
...flags["confirm-third-party-terms"] ? { confirm_third_party_terms: true } : {}
|
|
2967
|
-
};
|
|
2968
|
-
}, endpointNeedsThirdPartyTermsConfirmation = function(endpoint) {
|
|
2969
|
-
return endpoint.requires_third_party_terms_confirmation === true;
|
|
2970
|
-
}, resolveSkillId = function() {
|
|
2971
|
-
return result.skill?.skill_id ?? result.skill_id;
|
|
2972
|
-
};
|
|
2973
|
-
const body = { intent };
|
|
2974
|
-
const url = flags.url;
|
|
2975
|
-
const domain = flags.domain;
|
|
2976
|
-
const explicitEndpointId = flags["endpoint-id"];
|
|
2977
|
-
const autoExecute = !!flags.execute;
|
|
2978
|
-
const extraParams = flags.params ? JSON.parse(flags.params) : {};
|
|
2979
|
-
if (url) {
|
|
2980
|
-
body.params = { url };
|
|
2981
|
-
body.context = { url };
|
|
2982
|
-
}
|
|
2983
|
-
if (domain) {
|
|
2984
|
-
body.context = { ...body.context ?? {}, domain };
|
|
2985
|
-
}
|
|
2986
|
-
if (explicitEndpointId) {
|
|
2987
|
-
body.params = { ...body.params ?? {}, endpoint_id: explicitEndpointId };
|
|
2988
|
-
}
|
|
2989
|
-
if (flags.params) {
|
|
2990
|
-
body.params = { ...body.params ?? {}, ...extraParams };
|
|
2991
|
-
}
|
|
2992
|
-
if (flags["dry-run"])
|
|
2993
|
-
body.dry_run = true;
|
|
2994
|
-
if (flags["confirm-third-party-terms"])
|
|
2995
|
-
body.confirm_third_party_terms = true;
|
|
2996
|
-
if (flags["force-capture"])
|
|
2997
|
-
body.force_capture = true;
|
|
2998
|
-
body.projection = { raw: true };
|
|
2999
|
-
const startedAt = Date.now();
|
|
3000
|
-
async function resolveOnce(message = "Still working. First-time capture/indexing for a site can take 20-80s. Waiting is usually better than falling back.") {
|
|
3001
|
-
return withPendingNotice(api2("POST", "/v1/intent/resolve", body), message);
|
|
3002
|
-
}
|
|
3003
|
-
let result = await resolveOnce();
|
|
3004
|
-
let attemptedForceCapture = !!body.force_capture;
|
|
3005
|
-
let attemptedCookieImport = false;
|
|
3006
|
-
let attemptedInteractiveLogin = false;
|
|
3007
|
-
while (true) {
|
|
3008
|
-
const resultError = resolveResultError(result);
|
|
3009
|
-
if (resultError === "payment_required" && hasIndexingFallback(result) && url && !attemptedForceCapture) {
|
|
3010
|
-
attemptedForceCapture = true;
|
|
3011
|
-
body.force_capture = true;
|
|
3012
|
-
info("Marketplace search is paid here. Falling back to free live capture on the exact URL...");
|
|
3013
|
-
result = await resolveOnce("Running free live capture...");
|
|
3014
|
-
continue;
|
|
3015
|
-
}
|
|
3016
|
-
if (resultError === "auth_required") {
|
|
3017
|
-
const loginUrl = resolveLoginUrl(result, url);
|
|
3018
|
-
if (!loginUrl)
|
|
3019
|
-
break;
|
|
3020
|
-
if (!attemptedCookieImport) {
|
|
3021
|
-
attemptedCookieImport = true;
|
|
3022
|
-
info("Site requires authentication. Trying browser cookie import first...");
|
|
3023
|
-
const stealResult = await api2("POST", "/v1/auth/steal", { url: loginUrl });
|
|
3024
|
-
const cookiesStored = typeof stealResult.cookies_stored === "number" ? stealResult.cookies_stored : Number(stealResult.cookies_stored ?? 0);
|
|
3025
|
-
if (stealResult.success === true && cookiesStored > 0) {
|
|
3026
|
-
info(`Imported ${cookiesStored} browser cookies. Retrying...`);
|
|
3027
|
-
result = await resolveOnce("Retrying after browser cookie import...");
|
|
3028
|
-
continue;
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
if (!attemptedInteractiveLogin) {
|
|
3032
|
-
attemptedInteractiveLogin = true;
|
|
3033
|
-
info("Site requires authentication. Opening browser for login...");
|
|
3034
|
-
const loginResult = await api2("POST", "/v1/auth/login", { url: loginUrl });
|
|
3035
|
-
if (loginResult.error || loginResult.success === false) {
|
|
3036
|
-
const message = typeof loginResult.error === "string" ? loginResult.error : "interactive login did not produce a reusable session";
|
|
3037
|
-
throw new Error(`Login failed: ${message}. Run: unbrowse login --url "${loginUrl}"`);
|
|
3038
|
-
}
|
|
3039
|
-
info("Login complete. Retrying...");
|
|
3040
|
-
result = await resolveOnce("Retrying after login...");
|
|
3041
|
-
continue;
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
break;
|
|
3045
|
-
}
|
|
3046
|
-
if (explicitEndpointId && result.available_endpoints) {
|
|
3047
|
-
const skillId = resolveSkillId();
|
|
3048
|
-
if (skillId) {
|
|
3049
|
-
result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(explicitEndpointId)), "Executing selected endpoint...");
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
3052
|
-
if (autoExecute && result.available_endpoints && !result.result) {
|
|
3053
|
-
const endpoints = result.available_endpoints;
|
|
3054
|
-
const skillId = resolveSkillId();
|
|
3055
|
-
if (skillId && endpoints.length > 0) {
|
|
3056
|
-
const bestEndpoint = endpoints[0];
|
|
3057
|
-
if (endpointNeedsThirdPartyTermsConfirmation(bestEndpoint) && !flags["confirm-third-party-terms"]) {
|
|
3058
|
-
info(`Auto-execute skipped: ${bestEndpoint.description ?? bestEndpoint.endpoint_id} requires explicit third-party terms confirmation` + (typeof bestEndpoint.third_party_terms_policy_domain === "string" ? ` for ${bestEndpoint.third_party_terms_policy_domain}` : "") + ". Re-run with --confirm-third-party-terms only after the user explicitly confirms.");
|
|
3059
|
-
output(result, !!flags.pretty);
|
|
3060
|
-
return;
|
|
3061
|
-
}
|
|
3062
|
-
info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
|
|
3063
|
-
result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
const resultObj = result.result;
|
|
3067
|
-
if (resultObj?.status === "browse_session_open") {
|
|
3068
|
-
info(`No cached API. Browser session open on ${resultObj.domain ?? resultObj.url}.`);
|
|
3069
|
-
info(`Preferred flow: snap -> click/fill/eval -> submit -> sync -> close.`);
|
|
3070
|
-
info(`Use these commands to get your data:`);
|
|
3071
|
-
const commands = resultObj.commands ?? [
|
|
3072
|
-
resultObj.session_id ? `unbrowse snap --session ${resultObj.session_id} --filter interactive` : "unbrowse snap --filter interactive",
|
|
3073
|
-
resultObj.session_id ? `unbrowse click --session ${resultObj.session_id} <ref>` : "unbrowse click <ref>",
|
|
3074
|
-
resultObj.session_id ? `unbrowse fill --session ${resultObj.session_id} <ref> <value>` : "unbrowse fill <ref> <value>",
|
|
3075
|
-
resultObj.session_id ? `unbrowse submit --session ${resultObj.session_id} --wait-for "/next-step"` : 'unbrowse submit --wait-for "/next-step"',
|
|
3076
|
-
resultObj.session_id ? `unbrowse sync --session ${resultObj.session_id}` : "unbrowse sync",
|
|
3077
|
-
resultObj.session_id ? `unbrowse close --session ${resultObj.session_id}` : "unbrowse close"
|
|
3078
|
-
];
|
|
3079
|
-
for (const cmd of commands)
|
|
3080
|
-
info(` ${cmd}`);
|
|
3081
|
-
info(`For JS-heavy forms: prefer real date/time clicks first, inspect hidden inputs with eval when needed, then submit.`);
|
|
3082
|
-
info(`All traffic is being passively captured. Run "unbrowse close" when done.`);
|
|
3083
|
-
output(slimTrace(result), !!flags.pretty);
|
|
3084
|
-
return;
|
|
3085
|
-
}
|
|
3086
|
-
if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
|
|
3087
|
-
info("Live capture finished. Future runs against this site should be much faster.");
|
|
3088
|
-
}
|
|
3089
|
-
if (isResolveSuccessResult(result)) {
|
|
3090
|
-
await recordFunnelTelemetryEvent("resolve_completed", {
|
|
3091
|
-
source: "cli",
|
|
3092
|
-
hostType,
|
|
3093
|
-
properties: {
|
|
3094
|
-
command: "resolve",
|
|
3095
|
-
intent,
|
|
3096
|
-
domain: telemetryDomainFromInput(domain, url),
|
|
3097
|
-
url: url ?? null,
|
|
3098
|
-
source: result.source,
|
|
3099
|
-
auto_execute: autoExecute,
|
|
3100
|
-
explicit_endpoint: explicitEndpointId ?? null
|
|
3101
|
-
}
|
|
3102
|
-
});
|
|
3103
|
-
}
|
|
3104
|
-
result = slimTrace(result);
|
|
3105
|
-
emitImpactSummary(result);
|
|
3106
|
-
emitNextActionSummary(result);
|
|
3107
|
-
const skill = result.skill;
|
|
3108
|
-
const trace = result.trace;
|
|
3109
|
-
if (skill?.skill_id && trace) {
|
|
3110
|
-
result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
|
|
3111
|
-
}
|
|
3112
|
-
output(result, !!flags.pretty);
|
|
3113
|
-
} catch (error) {
|
|
3114
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3115
|
-
await recordFunnelTelemetryEvent("resolve_failed", {
|
|
3116
|
-
source: "cli",
|
|
3117
|
-
hostType,
|
|
3118
|
-
properties: {
|
|
3119
|
-
command: "resolve",
|
|
3120
|
-
intent,
|
|
3121
|
-
domain: telemetryDomainFromInput(flags.domain, flags.url),
|
|
3122
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
3123
|
-
failure_stage: "resolve",
|
|
3124
|
-
failure_reason: message
|
|
3125
|
-
}
|
|
3126
|
-
});
|
|
3127
|
-
throw error;
|
|
1008
|
+
const body = { intent };
|
|
1009
|
+
const url = flags.url;
|
|
1010
|
+
const domain = flags.domain;
|
|
1011
|
+
if (url) {
|
|
1012
|
+
body.params = { url };
|
|
1013
|
+
body.context = { url };
|
|
3128
1014
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
const segments = path9.split(/\./).flatMap((s) => {
|
|
3132
|
-
const m = s.match(/^(.+)\[\]$/);
|
|
3133
|
-
return m ? [m[1], "[]"] : [s];
|
|
3134
|
-
});
|
|
3135
|
-
let values = [data];
|
|
3136
|
-
for (const seg of segments) {
|
|
3137
|
-
if (values.length === 0)
|
|
3138
|
-
return [];
|
|
3139
|
-
if (seg === "[]") {
|
|
3140
|
-
values = values.flatMap((v) => Array.isArray(v) ? v : [v]);
|
|
3141
|
-
continue;
|
|
3142
|
-
}
|
|
3143
|
-
values = values.flatMap((v) => {
|
|
3144
|
-
if (v == null)
|
|
3145
|
-
return [];
|
|
3146
|
-
if (Array.isArray(v)) {
|
|
3147
|
-
return v.map((item) => item?.[seg]).filter((x) => x !== undefined);
|
|
3148
|
-
}
|
|
3149
|
-
if (typeof v === "object") {
|
|
3150
|
-
const val = v[seg];
|
|
3151
|
-
return val !== undefined ? [val] : [];
|
|
3152
|
-
}
|
|
3153
|
-
return [];
|
|
3154
|
-
});
|
|
1015
|
+
if (domain) {
|
|
1016
|
+
body.context = { ...body.context ?? {}, domain };
|
|
3155
1017
|
}
|
|
3156
|
-
|
|
3157
|
-
}
|
|
3158
|
-
function resolveDotPath(obj, path9) {
|
|
3159
|
-
let cur = obj;
|
|
3160
|
-
for (const key of path9.split(".")) {
|
|
3161
|
-
if (cur == null || typeof cur !== "object")
|
|
3162
|
-
return;
|
|
3163
|
-
cur = cur[key];
|
|
1018
|
+
if (flags["endpoint-id"]) {
|
|
1019
|
+
body.params = { ...body.params ?? {}, endpoint_id: flags["endpoint-id"] };
|
|
3164
1020
|
}
|
|
3165
|
-
|
|
3166
|
-
}
|
|
3167
|
-
function applyExtract(items, extractSpec) {
|
|
3168
|
-
const fields = extractSpec.split(",").map((f) => {
|
|
3169
|
-
const colon = f.indexOf(":");
|
|
3170
|
-
if (colon > 0)
|
|
3171
|
-
return { alias: f.slice(0, colon), path: f.slice(colon + 1) };
|
|
3172
|
-
return { alias: f, path: f };
|
|
3173
|
-
});
|
|
3174
|
-
return items.map((item) => {
|
|
3175
|
-
const row = {};
|
|
3176
|
-
let hasValue = false;
|
|
3177
|
-
for (const { alias, path: path9 } of fields) {
|
|
3178
|
-
const val = resolveDotPath(item, path9);
|
|
3179
|
-
row[alias] = val ?? null;
|
|
3180
|
-
if (val != null)
|
|
3181
|
-
hasValue = true;
|
|
3182
|
-
}
|
|
3183
|
-
return hasValue ? row : null;
|
|
3184
|
-
}).filter((row) => row !== null);
|
|
3185
|
-
}
|
|
3186
|
-
function schemaOf(value, depth = 4) {
|
|
3187
|
-
if (value == null)
|
|
3188
|
-
return "null";
|
|
3189
|
-
if (Array.isArray(value)) {
|
|
3190
|
-
if (value.length === 0)
|
|
3191
|
-
return ["unknown"];
|
|
3192
|
-
return [schemaOf(value[0], depth - 1)];
|
|
1021
|
+
if (flags.params) {
|
|
1022
|
+
body.params = { ...body.params ?? {}, ...JSON.parse(flags.params) };
|
|
3193
1023
|
}
|
|
3194
|
-
if (
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
}
|
|
3201
|
-
|
|
1024
|
+
if (flags["dry-run"])
|
|
1025
|
+
body.dry_run = true;
|
|
1026
|
+
if (flags["force-capture"])
|
|
1027
|
+
body.force_capture = true;
|
|
1028
|
+
const hasTransforms = !!(flags.path || flags.extract);
|
|
1029
|
+
if (flags.raw || hasTransforms)
|
|
1030
|
+
body.projection = { raw: true };
|
|
1031
|
+
const startedAt = Date.now();
|
|
1032
|
+
let result = await withPendingNotice(api2("POST", "/v1/intent/resolve", body), "Still working. First-time capture/indexing for a site can take 20-80s. Waiting is usually better than falling back.");
|
|
1033
|
+
if (flags.schema) {
|
|
1034
|
+
output(schemaOnly(result), !!flags.pretty);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (hasTransforms && result.result != null) {
|
|
1038
|
+
result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
|
|
1039
|
+
} else if (!flags.raw && result.result != null) {
|
|
1040
|
+
result = autoExtractOrWrap(result);
|
|
3202
1041
|
}
|
|
3203
|
-
|
|
1042
|
+
const skill = result.skill;
|
|
1043
|
+
const trace = result.trace;
|
|
1044
|
+
if (skill?.skill_id && trace) {
|
|
1045
|
+
result._feedback = `unbrowse feedback --skill ${skill.skill_id} --endpoint ${trace.endpoint_id || "?"} --rating <1-5>`;
|
|
1046
|
+
}
|
|
1047
|
+
if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
|
|
1048
|
+
info("Live capture finished. Future runs against this site should be much faster.");
|
|
1049
|
+
}
|
|
1050
|
+
output(result, !!flags.pretty);
|
|
3204
1051
|
}
|
|
3205
1052
|
async function cmdExecute(flags) {
|
|
3206
1053
|
const skillId = flags.skill;
|
|
3207
1054
|
if (!skillId)
|
|
3208
1055
|
die("--skill is required");
|
|
3209
|
-
const
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
}
|
|
3227
|
-
});
|
|
3228
|
-
try {
|
|
3229
|
-
const body = { params: {} };
|
|
3230
|
-
if (flags.endpoint) {
|
|
3231
|
-
body.params.endpoint_id = flags.endpoint;
|
|
3232
|
-
}
|
|
3233
|
-
if (flags.params) {
|
|
3234
|
-
body.params = { ...body.params, ...JSON.parse(flags.params) };
|
|
3235
|
-
}
|
|
3236
|
-
if (flags.url) {
|
|
3237
|
-
body.context_url = flags.url;
|
|
3238
|
-
body.params.url = flags.url;
|
|
3239
|
-
}
|
|
3240
|
-
if (flags.intent)
|
|
3241
|
-
body.intent = flags.intent;
|
|
3242
|
-
if (flags["dry-run"])
|
|
3243
|
-
body.dry_run = true;
|
|
3244
|
-
if (flags["confirm-unsafe"])
|
|
3245
|
-
body.confirm_unsafe = true;
|
|
3246
|
-
if (flags["confirm-third-party-terms"])
|
|
3247
|
-
body.confirm_third_party_terms = true;
|
|
1056
|
+
const body = { params: {} };
|
|
1057
|
+
if (flags.endpoint) {
|
|
1058
|
+
body.params.endpoint_id = flags.endpoint;
|
|
1059
|
+
}
|
|
1060
|
+
if (flags.params) {
|
|
1061
|
+
body.params = { ...body.params, ...JSON.parse(flags.params) };
|
|
1062
|
+
}
|
|
1063
|
+
if (flags.url)
|
|
1064
|
+
body.context_url = flags.url;
|
|
1065
|
+
if (flags.intent)
|
|
1066
|
+
body.intent = flags.intent;
|
|
1067
|
+
if (flags["dry-run"])
|
|
1068
|
+
body.dry_run = true;
|
|
1069
|
+
if (flags["confirm-unsafe"])
|
|
1070
|
+
body.confirm_unsafe = true;
|
|
1071
|
+
const hasTransforms = !!(flags.path || flags.extract);
|
|
1072
|
+
if (flags.raw || hasTransforms)
|
|
3248
1073
|
body.projection = { raw: true };
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
3259
|
-
skill_id: skillId,
|
|
3260
|
-
endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
|
|
3261
|
-
}
|
|
3262
|
-
});
|
|
3263
|
-
}
|
|
3264
|
-
result = slimTrace(result);
|
|
3265
|
-
emitImpactSummary(result);
|
|
3266
|
-
emitNextActionSummary(result);
|
|
3267
|
-
const pathFlag = flags.path;
|
|
3268
|
-
const extractFlag = flags.extract;
|
|
3269
|
-
const limitFlag = flags.limit ? Number(flags.limit) : undefined;
|
|
3270
|
-
const schemaFlag = !!flags.schema;
|
|
3271
|
-
const rawFlag = !!flags.raw;
|
|
3272
|
-
if (schemaFlag && !rawFlag) {
|
|
3273
|
-
const data = result.result;
|
|
3274
|
-
output({
|
|
3275
|
-
trace: result.trace,
|
|
3276
|
-
schema: schemaOf(data),
|
|
3277
|
-
...result.impact ? { impact: result.impact } : {},
|
|
3278
|
-
...result.next_actions ? { next_actions: result.next_actions } : {}
|
|
3279
|
-
}, !!flags.pretty);
|
|
3280
|
-
return;
|
|
3281
|
-
}
|
|
3282
|
-
if (!rawFlag && (pathFlag || extractFlag || limitFlag)) {
|
|
3283
|
-
const data = pathFlag ? drillPath(result.result, pathFlag) : result.result;
|
|
3284
|
-
const items = Array.isArray(data) ? data : data != null ? [data] : [];
|
|
3285
|
-
const extracted = extractFlag ? applyExtract(items, extractFlag) : items;
|
|
3286
|
-
const limited = limitFlag ? extracted.slice(0, limitFlag) : extracted;
|
|
3287
|
-
const trace = result.trace;
|
|
3288
|
-
const out = {
|
|
3289
|
-
trace: result.trace,
|
|
3290
|
-
data: limited,
|
|
3291
|
-
count: limited.length,
|
|
3292
|
-
...result.impact ? { impact: result.impact } : {},
|
|
3293
|
-
...result.next_actions ? { next_actions: result.next_actions } : {}
|
|
3294
|
-
};
|
|
3295
|
-
if (trace?.skill_id && trace?.endpoint_id && limited.length > 0) {
|
|
3296
|
-
out._review_hint = `After presenting results, improve this endpoint's description with what it returns plus any audience/eligibility/pricing/validity caveats: unbrowse review --skill ${trace.skill_id} --endpoints '[{"endpoint_id":"${trace.endpoint_id}","description":"DESCRIBE WHAT THIS RETURNS AND ANY IMPORTANT CONSTRAINTS","action_kind":"ACTION","resource_kind":"RESOURCE"}]'`;
|
|
3297
|
-
}
|
|
3298
|
-
output(out, !!flags.pretty);
|
|
3299
|
-
return;
|
|
3300
|
-
}
|
|
3301
|
-
if (!rawFlag && !pathFlag && !extractFlag && !schemaFlag) {
|
|
3302
|
-
const raw = JSON.stringify(result.result);
|
|
3303
|
-
if (raw && raw.length > 2048) {
|
|
3304
|
-
const schema = schemaOf(result.result);
|
|
3305
|
-
output({
|
|
3306
|
-
trace: result.trace,
|
|
3307
|
-
...result.impact ? { impact: result.impact } : {},
|
|
3308
|
-
...result.next_actions ? { next_actions: result.next_actions } : {},
|
|
3309
|
-
extraction_hints: {
|
|
3310
|
-
message: "Response is large. Use --path/--extract/--limit to filter, or --schema to see structure, or --raw for full response.",
|
|
3311
|
-
schema_tree: schema,
|
|
3312
|
-
response_bytes: raw.length
|
|
3313
|
-
}
|
|
3314
|
-
}, !!flags.pretty);
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
output(result, !!flags.pretty);
|
|
3319
|
-
} catch (error) {
|
|
3320
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3321
|
-
await recordFunnelTelemetryEvent("resolve_failed", {
|
|
3322
|
-
source: "cli",
|
|
3323
|
-
hostType,
|
|
3324
|
-
properties: {
|
|
3325
|
-
command: "execute",
|
|
3326
|
-
intent: typeof flags.intent === "string" ? flags.intent : null,
|
|
3327
|
-
domain: telemetryDomainFromInput(undefined, flags.url),
|
|
3328
|
-
url: typeof flags.url === "string" ? flags.url : null,
|
|
3329
|
-
skill_id: skillId,
|
|
3330
|
-
failure_stage: "execute",
|
|
3331
|
-
failure_reason: message
|
|
3332
|
-
}
|
|
3333
|
-
});
|
|
3334
|
-
throw error;
|
|
1074
|
+
let result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, body), "Still working. This endpoint may require browser replay or first-time auth/capture setup.");
|
|
1075
|
+
if (flags.schema) {
|
|
1076
|
+
output(schemaOnly(result), !!flags.pretty);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
if (hasTransforms && result.result != null) {
|
|
1080
|
+
result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
|
|
1081
|
+
} else if (!flags.raw && result.result != null) {
|
|
1082
|
+
result = autoExtractOrWrap(result);
|
|
3335
1083
|
}
|
|
1084
|
+
output(result, !!flags.pretty);
|
|
3336
1085
|
}
|
|
3337
1086
|
async function cmdFeedback(flags) {
|
|
3338
1087
|
const skillId = flags.skill;
|
|
@@ -3351,71 +1100,6 @@ async function cmdFeedback(flags) {
|
|
|
3351
1100
|
body.diagnostics = JSON.parse(flags.diagnostics);
|
|
3352
1101
|
output(await api2("POST", "/v1/feedback", body), !!flags.pretty);
|
|
3353
1102
|
}
|
|
3354
|
-
async function cmdReview(flags) {
|
|
3355
|
-
const skillId = flags.skill;
|
|
3356
|
-
if (!skillId)
|
|
3357
|
-
die("--skill is required");
|
|
3358
|
-
const endpointsJson = flags.endpoints;
|
|
3359
|
-
if (!endpointsJson)
|
|
3360
|
-
die("--endpoints is required (JSON array of {endpoint_id, description?, action_kind?, resource_kind?})");
|
|
3361
|
-
const endpoints = JSON.parse(endpointsJson);
|
|
3362
|
-
if (!Array.isArray(endpoints) || endpoints.length === 0)
|
|
3363
|
-
die("--endpoints must be a non-empty JSON array");
|
|
3364
|
-
output(await api2("POST", `/v1/skills/${skillId}/review`, { endpoints }), !!flags.pretty);
|
|
3365
|
-
}
|
|
3366
|
-
function parseCsvFlag(value) {
|
|
3367
|
-
if (typeof value !== "string")
|
|
3368
|
-
return;
|
|
3369
|
-
const parts = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
3370
|
-
return parts.length > 0 ? parts : [];
|
|
3371
|
-
}
|
|
3372
|
-
async function cmdIndex(flags) {
|
|
3373
|
-
const skillId = flags.skill;
|
|
3374
|
-
if (!skillId)
|
|
3375
|
-
die("--skill is required");
|
|
3376
|
-
output(await api2("POST", `/v1/skills/${skillId}/index`, {}), !!flags.pretty);
|
|
3377
|
-
}
|
|
3378
|
-
async function cmdPublish(flags) {
|
|
3379
|
-
const skillId = flags.skill;
|
|
3380
|
-
if (!skillId)
|
|
3381
|
-
die("--skill is required");
|
|
3382
|
-
const endpointsJson = flags.endpoints;
|
|
3383
|
-
const confirmPublish = flags["confirm-publish"] === true;
|
|
3384
|
-
if (endpointsJson) {
|
|
3385
|
-
const endpoints = JSON.parse(endpointsJson);
|
|
3386
|
-
if (!Array.isArray(endpoints) || endpoints.length === 0)
|
|
3387
|
-
die("--endpoints must be a non-empty JSON array");
|
|
3388
|
-
output(await api2("POST", `/v1/skills/${skillId}/publish`, {
|
|
3389
|
-
endpoints,
|
|
3390
|
-
...confirmPublish ? { confirm_publish: true } : {}
|
|
3391
|
-
}), !!flags.pretty);
|
|
3392
|
-
} else {
|
|
3393
|
-
output(await api2("POST", `/v1/skills/${skillId}/publish`, {
|
|
3394
|
-
...confirmPublish ? { confirm_publish: true } : {}
|
|
3395
|
-
}), !!flags.pretty);
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
async function cmdSettings(flags) {
|
|
3399
|
-
const body = {};
|
|
3400
|
-
if (typeof flags["auto-publish"] === "string") {
|
|
3401
|
-
const normalized = String(flags["auto-publish"]).trim().toLowerCase();
|
|
3402
|
-
if (normalized !== "on" && normalized !== "off")
|
|
3403
|
-
die("--auto-publish must be on or off");
|
|
3404
|
-
body.auto_publish_checkpoints = normalized === "on";
|
|
3405
|
-
}
|
|
3406
|
-
const blacklist = parseCsvFlag(flags["publish-blacklist"]);
|
|
3407
|
-
if (blacklist)
|
|
3408
|
-
body.publish_domain_blacklist = blacklist;
|
|
3409
|
-
const promptlist = parseCsvFlag(flags["publish-promptlist"]);
|
|
3410
|
-
if (promptlist)
|
|
3411
|
-
body.publish_domain_promptlist = promptlist;
|
|
3412
|
-
if (flags["clear-publish-blacklist"] === true)
|
|
3413
|
-
body.clear_publish_domain_blacklist = true;
|
|
3414
|
-
if (flags["clear-publish-promptlist"] === true)
|
|
3415
|
-
body.clear_publish_domain_promptlist = true;
|
|
3416
|
-
const hasMutation = Object.keys(body).length > 0;
|
|
3417
|
-
output(await api2(hasMutation ? "POST" : "GET", "/v1/settings", hasMutation ? body : undefined), !!flags.pretty);
|
|
3418
|
-
}
|
|
3419
1103
|
async function cmdLogin(flags) {
|
|
3420
1104
|
const url = flags.url;
|
|
3421
1105
|
if (!url)
|
|
@@ -3431,72 +1115,16 @@ async function cmdSkill(args, flags) {
|
|
|
3431
1115
|
die("skill <id> or --id required");
|
|
3432
1116
|
output(await api2("GET", `/v1/skills/${id}`), !!flags.pretty);
|
|
3433
1117
|
}
|
|
3434
|
-
async function cmdCleanupStale(flags) {
|
|
3435
|
-
const body = {};
|
|
3436
|
-
if (typeof flags.skill === "string")
|
|
3437
|
-
body.skill_id = flags.skill;
|
|
3438
|
-
if (typeof flags.domain === "string")
|
|
3439
|
-
body.domain = flags.domain;
|
|
3440
|
-
if (typeof flags.limit === "string")
|
|
3441
|
-
body.limit = Number(flags.limit);
|
|
3442
|
-
output(await withPendingNotice(api2("POST", "/v1/stale/cleanup", body), "Cleaning stale endpoints..."), !!flags.pretty);
|
|
3443
|
-
}
|
|
3444
1118
|
async function cmdSearch(flags) {
|
|
3445
1119
|
const intent = flags.intent;
|
|
3446
1120
|
if (!intent)
|
|
3447
1121
|
die("--intent is required");
|
|
3448
1122
|
const domain = flags.domain;
|
|
3449
|
-
const
|
|
1123
|
+
const path7 = domain ? "/v1/search/domain" : "/v1/search";
|
|
3450
1124
|
const body = { intent, k: Number(flags.k) || 5 };
|
|
3451
1125
|
if (domain)
|
|
3452
1126
|
body.domain = domain;
|
|
3453
|
-
|
|
3454
|
-
await ensureCliInstallTracked(hostType);
|
|
3455
|
-
await recordFunnelTelemetryEvent("cli_invoked", {
|
|
3456
|
-
source: "cli",
|
|
3457
|
-
hostType,
|
|
3458
|
-
properties: { command: "search" }
|
|
3459
|
-
});
|
|
3460
|
-
await recordFunnelTelemetryEvent("search_started", {
|
|
3461
|
-
source: "cli",
|
|
3462
|
-
hostType,
|
|
3463
|
-
properties: {
|
|
3464
|
-
command: "search",
|
|
3465
|
-
intent,
|
|
3466
|
-
domain: domain ?? null,
|
|
3467
|
-
k: body.k
|
|
3468
|
-
}
|
|
3469
|
-
});
|
|
3470
|
-
try {
|
|
3471
|
-
const result = await api2("POST", path9, body);
|
|
3472
|
-
const results = Array.isArray(result.results) ? result.results : [];
|
|
3473
|
-
await recordFunnelTelemetryEvent("search_completed", {
|
|
3474
|
-
source: "cli",
|
|
3475
|
-
hostType,
|
|
3476
|
-
properties: {
|
|
3477
|
-
command: "search",
|
|
3478
|
-
intent,
|
|
3479
|
-
domain: domain ?? null,
|
|
3480
|
-
k: body.k,
|
|
3481
|
-
result_count: results.length
|
|
3482
|
-
}
|
|
3483
|
-
});
|
|
3484
|
-
output(result, !!flags.pretty);
|
|
3485
|
-
} catch (error) {
|
|
3486
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3487
|
-
await recordFunnelTelemetryEvent("search_failed", {
|
|
3488
|
-
source: "cli",
|
|
3489
|
-
hostType,
|
|
3490
|
-
properties: {
|
|
3491
|
-
command: "search",
|
|
3492
|
-
intent,
|
|
3493
|
-
domain: domain ?? null,
|
|
3494
|
-
failure_stage: "search",
|
|
3495
|
-
failure_reason: message
|
|
3496
|
-
}
|
|
3497
|
-
});
|
|
3498
|
-
throw error;
|
|
3499
|
-
}
|
|
1127
|
+
output(await api2("POST", path7, body), !!flags.pretty);
|
|
3500
1128
|
}
|
|
3501
1129
|
async function cmdSessions(flags) {
|
|
3502
1130
|
const domain = flags.domain;
|
|
@@ -3506,13 +1134,6 @@ async function cmdSessions(flags) {
|
|
|
3506
1134
|
output(await api2("GET", `/v1/sessions/${domain}?limit=${limit}`), !!flags.pretty);
|
|
3507
1135
|
}
|
|
3508
1136
|
async function cmdSetup(flags) {
|
|
3509
|
-
const hostType = detectTelemetryHostType();
|
|
3510
|
-
await ensureCliInstallTracked(hostType);
|
|
3511
|
-
await recordFunnelTelemetryEvent("cli_invoked", {
|
|
3512
|
-
source: "setup",
|
|
3513
|
-
hostType,
|
|
3514
|
-
properties: { command: "setup" }
|
|
3515
|
-
});
|
|
3516
1137
|
info("Running setup checks");
|
|
3517
1138
|
const report = await runSetup({
|
|
3518
1139
|
cwd: process.cwd(),
|
|
@@ -3527,30 +1148,6 @@ async function cmdSetup(flags) {
|
|
|
3527
1148
|
if (report.opencode.action === "installed" || report.opencode.action === "updated") {
|
|
3528
1149
|
info(`Open Code command installed at ${report.opencode.command_file}`);
|
|
3529
1150
|
}
|
|
3530
|
-
for (const hook of report.update_hints) {
|
|
3531
|
-
if (hook.action === "installed" || hook.action === "updated") {
|
|
3532
|
-
info(`${hook.host} update hint hook ${hook.action} at ${hook.config_file}`);
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
await recordInstallTelemetryEvent("setup", {
|
|
3536
|
-
hostType,
|
|
3537
|
-
status: report.browser_engine.action === "failed" ? "failed" : "installed",
|
|
3538
|
-
properties: {
|
|
3539
|
-
browser_engine_action: report.browser_engine.action,
|
|
3540
|
-
opencode_action: report.opencode.action,
|
|
3541
|
-
no_start: !!flags["no-start"],
|
|
3542
|
-
skip_browser: !!flags["skip-browser"]
|
|
3543
|
-
}
|
|
3544
|
-
});
|
|
3545
|
-
await recordFunnelTelemetryEvent("setup_completed", {
|
|
3546
|
-
source: "setup",
|
|
3547
|
-
hostType,
|
|
3548
|
-
properties: {
|
|
3549
|
-
browser_engine_action: report.browser_engine.action,
|
|
3550
|
-
opencode_action: report.opencode.action,
|
|
3551
|
-
no_start: !!flags["no-start"]
|
|
3552
|
-
}
|
|
3553
|
-
});
|
|
3554
1151
|
if (flags["no-start"]) {
|
|
3555
1152
|
report.server = { started: false, skipped: true, base_url: BASE_URL };
|
|
3556
1153
|
output(report, true);
|
|
@@ -3564,23 +1161,8 @@ async function cmdSetup(flags) {
|
|
|
3564
1161
|
try {
|
|
3565
1162
|
await ensureLocalServer(BASE_URL, false, import.meta.url);
|
|
3566
1163
|
report.server = { started: true, base_url: BASE_URL };
|
|
3567
|
-
await recordFunnelTelemetryEvent("server_autostart_succeeded", {
|
|
3568
|
-
source: "setup",
|
|
3569
|
-
hostType,
|
|
3570
|
-
properties: {
|
|
3571
|
-
base_url: BASE_URL
|
|
3572
|
-
}
|
|
3573
|
-
});
|
|
3574
1164
|
} catch (error) {
|
|
3575
1165
|
const message = error instanceof Error ? error.message : String(error);
|
|
3576
|
-
await recordFunnelTelemetryEvent("server_autostart_failed", {
|
|
3577
|
-
source: "setup",
|
|
3578
|
-
hostType,
|
|
3579
|
-
properties: {
|
|
3580
|
-
failure_stage: "server_autostart",
|
|
3581
|
-
failure_reason: message
|
|
3582
|
-
}
|
|
3583
|
-
});
|
|
3584
1166
|
report.server = { started: false, error: message, base_url: BASE_URL };
|
|
3585
1167
|
output(report, true);
|
|
3586
1168
|
process.exit(1);
|
|
@@ -3592,40 +1174,15 @@ async function cmdSetup(flags) {
|
|
|
3592
1174
|
var CLI_REFERENCE = {
|
|
3593
1175
|
commands: [
|
|
3594
1176
|
{ name: "health", usage: "", desc: "Server health check" },
|
|
3595
|
-
{ name: "mcp", usage: "[--no-auto-start]", desc: "Run the stdio MCP server" },
|
|
3596
1177
|
{ name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
|
|
3597
|
-
{ name: "upgrade", usage: "", desc: "Check latest release and print the right upgrade command" },
|
|
3598
1178
|
{ name: "resolve", usage: '--intent "..." --url "..." [opts]', desc: "Resolve intent \u2192 search/capture/execute" },
|
|
3599
1179
|
{ name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
|
|
3600
1180
|
{ name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
|
|
3601
|
-
{ name: "review", usage: "--skill ID --endpoints '[...]'", desc: "Push reviewed descriptions/metadata back to skill" },
|
|
3602
|
-
{ name: "index", usage: "--skill ID", desc: "Recompute local graph/contracts/export from cached skill state only" },
|
|
3603
|
-
{ name: "publish", usage: "--skill ID [--confirm-publish] [--endpoints '[...]']", desc: "Re-index locally, then publish/share from cached skill state; without endpoints returns review metadata first" },
|
|
3604
|
-
{ name: "settings", usage: "[--auto-publish on|off] [--publish-blacklist domains] [--publish-promptlist domains]", desc: "Show or update local capture/publish policy settings" },
|
|
3605
1181
|
{ name: "login", usage: '--url "..."', desc: "Interactive browser login" },
|
|
3606
1182
|
{ name: "skills", usage: "", desc: "List all skills" },
|
|
3607
1183
|
{ name: "skill", usage: "<id>", desc: "Get skill details" },
|
|
3608
|
-
{ name: "cleanup-stale", usage: "[--skill ID] [--domain host] [--limit N]", desc: "Verify skills and evict stale cached endpoints" },
|
|
3609
1184
|
{ name: "search", usage: '--intent "..." [--domain "..."]', desc: "Search marketplace" },
|
|
3610
|
-
{ name: "sessions", usage: '--domain "..." [--limit N]', desc: "Debug session logs" }
|
|
3611
|
-
{ name: "go", usage: "<url> [--session id]", desc: "Open a live Kuri browser tab for capture-first workflows" },
|
|
3612
|
-
{ name: "submit", usage: "[--session id] [--form-selector sel] [--submit-selector sel] [--wait-for hint] [--assist-site-state]", desc: "Submit current form. Thin browser-native proxy by default; site-state assist and same-origin rehydrate are explicit opt-ins" },
|
|
3613
|
-
{ name: "snap", usage: "[--session id] [--filter interactive]", desc: "A11y snapshot with @eN refs" },
|
|
3614
|
-
{ name: "click", usage: "[--session id] <ref>", desc: "Click element by ref (e.g. e5)" },
|
|
3615
|
-
{ name: "fill", usage: "[--session id] <ref> <value>", desc: "Fill input by ref" },
|
|
3616
|
-
{ name: "type", usage: "<text>", desc: "Type text with key events" },
|
|
3617
|
-
{ name: "press", usage: "<key>", desc: "Press key (Enter, Tab, Escape)" },
|
|
3618
|
-
{ name: "select", usage: "<ref> <value>", desc: "Select option by ref" },
|
|
3619
|
-
{ name: "scroll", usage: "[up|down|left|right]", desc: "Scroll the page" },
|
|
3620
|
-
{ name: "screenshot", usage: "[--session id]", desc: "Capture screenshot (base64 PNG)" },
|
|
3621
|
-
{ name: "text", usage: "[--session id]", desc: "Get page text content" },
|
|
3622
|
-
{ name: "markdown", usage: "[--session id]", desc: "Get page as Markdown" },
|
|
3623
|
-
{ name: "cookies", usage: "[--session id]", desc: "Get page cookies" },
|
|
3624
|
-
{ name: "eval", usage: "[--session id] <expression>", desc: "Evaluate JavaScript" },
|
|
3625
|
-
{ name: "back", usage: "[--session id]", desc: "Navigate back" },
|
|
3626
|
-
{ name: "forward", usage: "[--session id]", desc: "Navigate forward" },
|
|
3627
|
-
{ name: "sync", usage: "[--session id]", desc: "Checkpoint current capture, keep tab open, queue background index + publish" },
|
|
3628
|
-
{ name: "close", usage: "[--session id]", desc: "Checkpoint capture, queue background index + publish, then close browse session" }
|
|
1185
|
+
{ name: "sessions", usage: '--domain "..." [--limit N]', desc: "Debug session logs" }
|
|
3629
1186
|
],
|
|
3630
1187
|
globalFlags: [
|
|
3631
1188
|
{ flag: "--pretty", desc: "Indented JSON output" },
|
|
@@ -3646,23 +1203,11 @@ var CLI_REFERENCE = {
|
|
|
3646
1203
|
],
|
|
3647
1204
|
examples: [
|
|
3648
1205
|
"unbrowse setup",
|
|
3649
|
-
"unbrowse mcp",
|
|
3650
|
-
'unbrowse resolve --intent "top stories" --url "https://news.ycombinator.com" --execute',
|
|
3651
1206
|
'unbrowse resolve --intent "get timeline" --url "https://x.com"',
|
|
3652
|
-
'unbrowse go "https://www.mandai.com/en/ticketing/admission-and-rides/parks-selection.html"',
|
|
3653
|
-
"unbrowse snap --filter interactive",
|
|
3654
|
-
'unbrowse submit --wait-for "/time-selection.html"',
|
|
3655
|
-
"unbrowse sync",
|
|
3656
1207
|
"unbrowse execute --skill abc --endpoint def --pretty",
|
|
3657
|
-
|
|
3658
|
-
'unbrowse execute --skill abc --endpoint def --path "data.
|
|
3659
|
-
"unbrowse feedback --skill abc --endpoint def --rating 5"
|
|
3660
|
-
`unbrowse review --skill abc --endpoints '[{"endpoint_id":"def","description":"..."}]'`,
|
|
3661
|
-
"unbrowse index --skill abc --pretty",
|
|
3662
|
-
"unbrowse publish --skill abc --pretty",
|
|
3663
|
-
"unbrowse publish --skill abc --confirm-publish --pretty",
|
|
3664
|
-
'unbrowse settings --auto-publish off --publish-blacklist "linkedin.com,x.com" --publish-promptlist "github.com" --pretty',
|
|
3665
|
-
`unbrowse publish --skill abc --endpoints '[{"endpoint_id":"def","description":"Search court judgments by keywords","action_kind":"search","resource_kind":"judgment"}]'`
|
|
1208
|
+
'unbrowse execute --skill abc --endpoint def --extract "user,text,likes" --limit 10',
|
|
1209
|
+
'unbrowse execute --skill abc --endpoint def --path "data.included[]" --extract "name:actor.name,text:commentary.text" --limit 20',
|
|
1210
|
+
"unbrowse feedback --skill abc --endpoint def --rating 5"
|
|
3666
1211
|
]
|
|
3667
1212
|
};
|
|
3668
1213
|
function printHelp() {
|
|
@@ -3688,378 +1233,10 @@ function printHelp() {
|
|
|
3688
1233
|
for (const e of r.examples) {
|
|
3689
1234
|
lines.push(` ${e}`);
|
|
3690
1235
|
}
|
|
3691
|
-
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; keep traversal browser-native; opt into same-origin rehydrate only for explicit replay/recovery debugging", " 5. sync -> checkpoint the current step and queue background index + publish", " 6. close -> final checkpoint + queue background index + publish, then close the session");
|
|
3692
|
-
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.");
|
|
3693
1236
|
lines.push("");
|
|
3694
1237
|
process.stderr.write(lines.join(`
|
|
3695
1238
|
`));
|
|
3696
1239
|
}
|
|
3697
|
-
async function cmdStatus(flags) {
|
|
3698
|
-
const healthy = await fetch(`${BASE_URL}/health`, { signal: AbortSignal.timeout(2000) }).then((r) => r.ok).catch(() => false);
|
|
3699
|
-
const versionInfo = checkServerVersion(BASE_URL, import.meta.url);
|
|
3700
|
-
output({
|
|
3701
|
-
server: healthy ? "running" : "stopped",
|
|
3702
|
-
url: BASE_URL,
|
|
3703
|
-
...versionInfo ?? {}
|
|
3704
|
-
}, !!flags.pretty);
|
|
3705
|
-
}
|
|
3706
|
-
async function cmdRestart(flags) {
|
|
3707
|
-
info("Restarting server...");
|
|
3708
|
-
await restartServer(BASE_URL, import.meta.url);
|
|
3709
|
-
info("Server restarted.");
|
|
3710
|
-
await cmdStatus(flags);
|
|
3711
|
-
}
|
|
3712
|
-
function cmdStop(flags) {
|
|
3713
|
-
const stopped = stopServer(BASE_URL);
|
|
3714
|
-
if (stopped)
|
|
3715
|
-
info("Server stopped.");
|
|
3716
|
-
else
|
|
3717
|
-
info("No server running.");
|
|
3718
|
-
}
|
|
3719
|
-
async function cmdUpgrade(flags) {
|
|
3720
|
-
const hintOnly = !!flags["hint-only"];
|
|
3721
|
-
if (!hintOnly)
|
|
3722
|
-
info("Checking for updates...");
|
|
3723
|
-
try {
|
|
3724
|
-
const result = await checkForUpdates(import.meta.url, { force: !hintOnly });
|
|
3725
|
-
if (!result.latest) {
|
|
3726
|
-
if (!hintOnly)
|
|
3727
|
-
info("Could not check for updates right now.");
|
|
3728
|
-
return;
|
|
3729
|
-
}
|
|
3730
|
-
if (!result.has_update) {
|
|
3731
|
-
if (!hintOnly)
|
|
3732
|
-
info(`Already at latest version: ${result.installed}`);
|
|
3733
|
-
return;
|
|
3734
|
-
}
|
|
3735
|
-
info(`Update available: ${result.installed} -> ${result.latest}`);
|
|
3736
|
-
info(`Run: ${result.command}`);
|
|
3737
|
-
if (!hintOnly) {
|
|
3738
|
-
info("Tip: `unbrowse setup` now installs session-start update hints for Codex and Claude when those hosts are present.");
|
|
3739
|
-
}
|
|
3740
|
-
recordUpdateHint(result.latest);
|
|
3741
|
-
} catch (err) {
|
|
3742
|
-
if (!hintOnly)
|
|
3743
|
-
info(`Could not check for updates: ${err.message}`);
|
|
3744
|
-
}
|
|
3745
|
-
}
|
|
3746
|
-
async function cmdMcp(flags) {
|
|
3747
|
-
const entrypoint = resolveSiblingEntrypoint2(import.meta.url, "mcp");
|
|
3748
|
-
const child = spawn3(process.execPath, [...runtimeArgsForEntrypoint2(import.meta.url, entrypoint), ...flags["no-auto-start"] ? ["--no-auto-start"] : []], {
|
|
3749
|
-
cwd: process.cwd(),
|
|
3750
|
-
stdio: "inherit",
|
|
3751
|
-
env: {
|
|
3752
|
-
...process.env,
|
|
3753
|
-
MCP_SERVER_MODE: "1"
|
|
3754
|
-
}
|
|
3755
|
-
});
|
|
3756
|
-
const code = await new Promise((resolve, reject) => {
|
|
3757
|
-
child.once("error", reject);
|
|
3758
|
-
child.once("exit", (exitCode, signal) => {
|
|
3759
|
-
if (signal) {
|
|
3760
|
-
process.kill(process.pid, signal);
|
|
3761
|
-
return;
|
|
3762
|
-
}
|
|
3763
|
-
resolve(exitCode ?? 1);
|
|
3764
|
-
});
|
|
3765
|
-
});
|
|
3766
|
-
if (code !== 0)
|
|
3767
|
-
process.exit(code);
|
|
3768
|
-
}
|
|
3769
|
-
async function cmdSiteHelp(pack, flags) {
|
|
3770
|
-
if (flags.deps) {
|
|
3771
|
-
const graph = buildDepsGraph(pack);
|
|
3772
|
-
output({ site: pack.site, tasks: graph }, !!flags.pretty);
|
|
3773
|
-
return;
|
|
3774
|
-
}
|
|
3775
|
-
if (flags.plan) {
|
|
3776
|
-
const taskNames = flags.plan.split(",").map((s) => s.trim());
|
|
3777
|
-
const waves = planExecution(pack, taskNames);
|
|
3778
|
-
output({ site: pack.site, waves }, !!flags.pretty);
|
|
3779
|
-
return;
|
|
3780
|
-
}
|
|
3781
|
-
const lines = [
|
|
3782
|
-
`unbrowse ${pack.site} \u2014 ${pack.description}`,
|
|
3783
|
-
"",
|
|
3784
|
-
"Tasks:"
|
|
3785
|
-
];
|
|
3786
|
-
for (const t of pack.tasks) {
|
|
3787
|
-
const aliases = t.match.length > 1 ? ` (${t.match.slice(1).join(", ")})` : "";
|
|
3788
|
-
const auth = t.needs_auth ? " [auth]" : "";
|
|
3789
|
-
lines.push(` ${t.match[0]}${aliases}${auth} ${t.description}`);
|
|
3790
|
-
}
|
|
3791
|
-
lines.push("", "Examples:", ` unbrowse ${pack.site} login`, ` unbrowse ${pack.site} ${pack.tasks.find((t) => t.match[0] !== "login")?.match[0] || "help"} --pretty`, ` unbrowse ${pack.site} --batch ${pack.tasks.filter((t) => t.parallel_safe).map((t) => t.match[0]).join(",")}`, ` unbrowse ${pack.site} help --deps`, ` unbrowse ${pack.site} help --plan feed,notifications`, "", "Flags: --pretty --raw --path --extract --limit --force-capture --dry-run --batch --deps --plan");
|
|
3792
|
-
process.stderr.write(lines.join(`
|
|
3793
|
-
`) + `
|
|
3794
|
-
`);
|
|
3795
|
-
}
|
|
3796
|
-
async function cmdSiteLogin(pack, flags) {
|
|
3797
|
-
const result = await api2("POST", "/v1/auth/login", { url: pack.login_url });
|
|
3798
|
-
const deps = buildDepsMetadata(pack, "login");
|
|
3799
|
-
output({ ...result, _deps: deps, _shortcut: `${pack.site} login` }, !!flags.pretty);
|
|
3800
|
-
}
|
|
3801
|
-
async function cmdSiteTask(pack, taskName, flags) {
|
|
3802
|
-
const task = findTask(pack, taskName);
|
|
3803
|
-
if (!task) {
|
|
3804
|
-
info(`Unknown task "${taskName}" for ${pack.site}. Run: unbrowse ${pack.site} help`);
|
|
3805
|
-
process.exit(1);
|
|
3806
|
-
}
|
|
3807
|
-
const body = {
|
|
3808
|
-
intent: task.intent,
|
|
3809
|
-
params: { url: task.url },
|
|
3810
|
-
context: { url: task.url }
|
|
3811
|
-
};
|
|
3812
|
-
if (flags.params) {
|
|
3813
|
-
body.params = { ...body.params, ...JSON.parse(flags.params) };
|
|
3814
|
-
}
|
|
3815
|
-
if (flags["dry-run"])
|
|
3816
|
-
body.dry_run = true;
|
|
3817
|
-
if (flags["force-capture"])
|
|
3818
|
-
body.force_capture = true;
|
|
3819
|
-
body.projection = { raw: true };
|
|
3820
|
-
const startedAt = Date.now();
|
|
3821
|
-
let result = await withPendingNotice(api2("POST", "/v1/intent/resolve", body), "Still working. First-time capture/indexing for a site can take 20-80s.");
|
|
3822
|
-
if (result && typeof result === "object" && result.error === "auth_required") {
|
|
3823
|
-
info(`Authentication required. Run: unbrowse ${pack.site} login`);
|
|
3824
|
-
const deps2 = buildDepsMetadata(pack, taskName);
|
|
3825
|
-
output({ ...result, _deps: { ...deps2, requires: ["login"] }, _next: [`unbrowse ${pack.site} login`] }, !!flags.pretty);
|
|
3826
|
-
process.exit(2);
|
|
3827
|
-
}
|
|
3828
|
-
result = slimTrace(result);
|
|
3829
|
-
const deps = buildDepsMetadata(pack, taskName);
|
|
3830
|
-
result._deps = deps;
|
|
3831
|
-
result._shortcut = `${pack.site} ${taskName}`;
|
|
3832
|
-
if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
|
|
3833
|
-
info("Live capture finished. Future runs should be much faster.");
|
|
3834
|
-
}
|
|
3835
|
-
output(result, !!flags.pretty);
|
|
3836
|
-
}
|
|
3837
|
-
async function cmdSiteBatch(pack, batchArg, flags) {
|
|
3838
|
-
const taskNames = batchArg.split(",").map((s) => s.trim());
|
|
3839
|
-
const waves = planExecution(pack, taskNames);
|
|
3840
|
-
const results = { site: pack.site, waves: [], _deps: { parallel_safe: true } };
|
|
3841
|
-
const waveResults = [];
|
|
3842
|
-
for (const wave of waves) {
|
|
3843
|
-
const waveStart = Date.now();
|
|
3844
|
-
const promises = wave.commands.map(async (cmd) => {
|
|
3845
|
-
const parts = cmd.split(" ");
|
|
3846
|
-
const task = parts[parts.length - 1];
|
|
3847
|
-
const taskDef = findTask(pack, task);
|
|
3848
|
-
if (!taskDef)
|
|
3849
|
-
return { task, error: "unknown task" };
|
|
3850
|
-
if (task === "login") {
|
|
3851
|
-
return { task, result: await api2("POST", "/v1/auth/login", { url: pack.login_url }) };
|
|
3852
|
-
}
|
|
3853
|
-
const body = {
|
|
3854
|
-
intent: taskDef.intent,
|
|
3855
|
-
params: { url: taskDef.url },
|
|
3856
|
-
context: { url: taskDef.url }
|
|
3857
|
-
};
|
|
3858
|
-
if (flags["force-capture"])
|
|
3859
|
-
body.force_capture = true;
|
|
3860
|
-
body.projection = { raw: true };
|
|
3861
|
-
const res = await api2("POST", "/v1/intent/resolve", body);
|
|
3862
|
-
return { task, result: slimTrace(res) };
|
|
3863
|
-
});
|
|
3864
|
-
const waveResult = await Promise.all(promises);
|
|
3865
|
-
waveResults.push({
|
|
3866
|
-
wave: wave.wave,
|
|
3867
|
-
reason: wave.reason,
|
|
3868
|
-
elapsed_ms: Date.now() - waveStart,
|
|
3869
|
-
tasks: waveResult
|
|
3870
|
-
});
|
|
3871
|
-
}
|
|
3872
|
-
results.waves = waveResults;
|
|
3873
|
-
output(results, !!flags.pretty);
|
|
3874
|
-
}
|
|
3875
|
-
async function cmdGo(args, flags) {
|
|
3876
|
-
const url = args[0] ?? flags.url;
|
|
3877
|
-
if (!url)
|
|
3878
|
-
die("Usage: unbrowse go <url>");
|
|
3879
|
-
output(await api2("POST", "/v1/browse/go", {
|
|
3880
|
-
url,
|
|
3881
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3882
|
-
}), !!flags.pretty);
|
|
3883
|
-
}
|
|
3884
|
-
async function cmdSubmit(flags) {
|
|
3885
|
-
const body = {};
|
|
3886
|
-
if (typeof flags.session === "string")
|
|
3887
|
-
body.session_id = flags.session;
|
|
3888
|
-
if (typeof flags["form-selector"] === "string")
|
|
3889
|
-
body.form_selector = flags["form-selector"];
|
|
3890
|
-
if (typeof flags["submit-selector"] === "string")
|
|
3891
|
-
body.submit_selector = flags["submit-selector"];
|
|
3892
|
-
if (typeof flags["wait-for"] === "string")
|
|
3893
|
-
body.wait_for = flags["wait-for"];
|
|
3894
|
-
if (typeof flags["timeout-ms"] === "string")
|
|
3895
|
-
body.timeout_ms = Number(flags["timeout-ms"]);
|
|
3896
|
-
if (flags["assist-site-state"] !== undefined) {
|
|
3897
|
-
body.assist_site_state = flags["assist-site-state"] !== "false";
|
|
3898
|
-
}
|
|
3899
|
-
if (flags["same-origin-fetch-fallback"] !== undefined) {
|
|
3900
|
-
body.same_origin_fetch_fallback = flags["same-origin-fetch-fallback"] !== "false";
|
|
3901
|
-
}
|
|
3902
|
-
output(await api2("POST", "/v1/browse/submit", body), !!flags.pretty);
|
|
3903
|
-
}
|
|
3904
|
-
async function cmdSnap(flags) {
|
|
3905
|
-
const filter = flags.filter;
|
|
3906
|
-
const result = await api2("POST", "/v1/browse/snap", {
|
|
3907
|
-
...filter ? { filter } : {},
|
|
3908
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3909
|
-
});
|
|
3910
|
-
if (result.snapshot && !flags.pretty) {
|
|
3911
|
-
console.log(result.snapshot);
|
|
3912
|
-
} else {
|
|
3913
|
-
output(result, !!flags.pretty);
|
|
3914
|
-
}
|
|
3915
|
-
}
|
|
3916
|
-
async function cmdClick(args, flags) {
|
|
3917
|
-
const ref = args[0];
|
|
3918
|
-
if (!ref)
|
|
3919
|
-
die("Usage: unbrowse click <ref>");
|
|
3920
|
-
output(await api2("POST", "/v1/browse/click", {
|
|
3921
|
-
ref,
|
|
3922
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3923
|
-
}), false);
|
|
3924
|
-
}
|
|
3925
|
-
async function cmdFill(args, flags) {
|
|
3926
|
-
const ref = args[0];
|
|
3927
|
-
const value = args.slice(1).join(" ");
|
|
3928
|
-
if (!ref || !value)
|
|
3929
|
-
die("Usage: unbrowse fill <ref> <value>");
|
|
3930
|
-
output(await api2("POST", "/v1/browse/fill", {
|
|
3931
|
-
ref,
|
|
3932
|
-
value,
|
|
3933
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3934
|
-
}), false);
|
|
3935
|
-
}
|
|
3936
|
-
async function cmdType(args, flags) {
|
|
3937
|
-
const text = args.join(" ");
|
|
3938
|
-
if (!text)
|
|
3939
|
-
die("Usage: unbrowse type <text>");
|
|
3940
|
-
output(await api2("POST", "/v1/browse/type", {
|
|
3941
|
-
text,
|
|
3942
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3943
|
-
}), false);
|
|
3944
|
-
}
|
|
3945
|
-
async function cmdPress(args, flags) {
|
|
3946
|
-
const key = args[0];
|
|
3947
|
-
if (!key)
|
|
3948
|
-
die("Usage: unbrowse press <key>");
|
|
3949
|
-
output(await api2("POST", "/v1/browse/press", {
|
|
3950
|
-
key,
|
|
3951
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3952
|
-
}), false);
|
|
3953
|
-
}
|
|
3954
|
-
async function cmdSelect(args, flags) {
|
|
3955
|
-
const ref = args[0];
|
|
3956
|
-
const value = args.slice(1).join(" ");
|
|
3957
|
-
if (!ref || !value)
|
|
3958
|
-
die("Usage: unbrowse select <ref> <value>");
|
|
3959
|
-
output(await api2("POST", "/v1/browse/select", {
|
|
3960
|
-
ref,
|
|
3961
|
-
value,
|
|
3962
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3963
|
-
}), false);
|
|
3964
|
-
}
|
|
3965
|
-
async function cmdScroll(args, flags) {
|
|
3966
|
-
const direction = args[0] ?? "down";
|
|
3967
|
-
output(await api2("POST", "/v1/browse/scroll", {
|
|
3968
|
-
direction,
|
|
3969
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
3970
|
-
}), false);
|
|
3971
|
-
}
|
|
3972
|
-
async function cmdScreenshot(flags) {
|
|
3973
|
-
output(await api2("GET", "/v1/browse/screenshot", typeof flags.session === "string" ? { session_id: flags.session } : undefined), !!flags.pretty);
|
|
3974
|
-
}
|
|
3975
|
-
async function cmdText(flags) {
|
|
3976
|
-
const result = await api2("GET", "/v1/browse/text", typeof flags.session === "string" ? { session_id: flags.session } : undefined);
|
|
3977
|
-
if (result.text && !flags.pretty) {
|
|
3978
|
-
console.log(result.text);
|
|
3979
|
-
} else {
|
|
3980
|
-
output(result, !!flags.pretty);
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
async function cmdMarkdown(flags) {
|
|
3984
|
-
const result = await api2("GET", "/v1/browse/markdown", typeof flags.session === "string" ? { session_id: flags.session } : undefined);
|
|
3985
|
-
if (result.markdown && !flags.pretty) {
|
|
3986
|
-
console.log(result.markdown);
|
|
3987
|
-
} else {
|
|
3988
|
-
output(result, !!flags.pretty);
|
|
3989
|
-
}
|
|
3990
|
-
}
|
|
3991
|
-
async function cmdCookies(flags) {
|
|
3992
|
-
output(await api2("GET", "/v1/browse/cookies", typeof flags.session === "string" ? { session_id: flags.session } : undefined), !!flags.pretty);
|
|
3993
|
-
}
|
|
3994
|
-
async function cmdEval(args, flags) {
|
|
3995
|
-
const expression = args.join(" ");
|
|
3996
|
-
if (!expression)
|
|
3997
|
-
die("Usage: unbrowse eval <expression>");
|
|
3998
|
-
output(await api2("POST", "/v1/browse/eval", {
|
|
3999
|
-
expression,
|
|
4000
|
-
...typeof flags.session === "string" ? { session_id: flags.session } : {}
|
|
4001
|
-
}), !!flags.pretty);
|
|
4002
|
-
}
|
|
4003
|
-
async function cmdBack(flags) {
|
|
4004
|
-
output(await api2("POST", "/v1/browse/back", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
|
|
4005
|
-
}
|
|
4006
|
-
async function cmdForward(flags) {
|
|
4007
|
-
output(await api2("POST", "/v1/browse/forward", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
|
|
4008
|
-
}
|
|
4009
|
-
async function cmdSync(flags) {
|
|
4010
|
-
output(await api2("POST", "/v1/browse/sync", typeof flags.session === "string" ? { session_id: flags.session } : undefined), !!flags.pretty);
|
|
4011
|
-
}
|
|
4012
|
-
async function cmdClose(flags) {
|
|
4013
|
-
output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
|
|
4014
|
-
}
|
|
4015
|
-
async function cmdConnectChrome() {
|
|
4016
|
-
const { execSync, spawn: spawnProc } = __require("child_process");
|
|
4017
|
-
try {
|
|
4018
|
-
const res = await fetch("http://127.0.0.1:9222/json/version", { signal: AbortSignal.timeout(1000) });
|
|
4019
|
-
if (res.ok) {
|
|
4020
|
-
const data = await res.json();
|
|
4021
|
-
if (!data["User-Agent"]?.includes("Headless")) {
|
|
4022
|
-
console.log("Your Chrome is already connected with CDP on port 9222.");
|
|
4023
|
-
console.log("Browse commands will use your real browser with all your sessions.");
|
|
4024
|
-
return;
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
4027
|
-
} catch {}
|
|
4028
|
-
try {
|
|
4029
|
-
execSync("pkill -f kuri/chrome-profile", { stdio: "ignore" });
|
|
4030
|
-
} catch {}
|
|
4031
|
-
console.log("Quitting Chrome to relaunch with remote debugging...");
|
|
4032
|
-
if (process.platform === "darwin") {
|
|
4033
|
-
try {
|
|
4034
|
-
execSync('osascript -e "quit app \\"Google Chrome\\""', { stdio: "ignore", timeout: 5000 });
|
|
4035
|
-
} catch {}
|
|
4036
|
-
} else {
|
|
4037
|
-
try {
|
|
4038
|
-
execSync("pkill -f chrome", { stdio: "ignore" });
|
|
4039
|
-
} catch {}
|
|
4040
|
-
}
|
|
4041
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
4042
|
-
console.log("Launching Chrome with remote debugging on port 9222...");
|
|
4043
|
-
if (process.platform === "darwin") {
|
|
4044
|
-
spawnProc("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", ["--remote-debugging-port=9222", "--no-first-run", "--no-default-browser-check"], { stdio: "ignore", detached: true }).unref();
|
|
4045
|
-
} else {
|
|
4046
|
-
spawnProc("google-chrome", ["--remote-debugging-port=9222"], { stdio: "ignore", detached: true }).unref();
|
|
4047
|
-
}
|
|
4048
|
-
const deadline = Date.now() + 15000;
|
|
4049
|
-
while (Date.now() < deadline) {
|
|
4050
|
-
try {
|
|
4051
|
-
const res = await fetch("http://127.0.0.1:9222/json/version", { signal: AbortSignal.timeout(500) });
|
|
4052
|
-
if (res.ok) {
|
|
4053
|
-
console.log("Connected. Your real Chrome is now available for browse commands.");
|
|
4054
|
-
console.log("All your logged-in sessions (LinkedIn, X, etc.) will work.");
|
|
4055
|
-
console.log('Run: unbrowse go "https://linkedin.com/feed/"');
|
|
4056
|
-
return;
|
|
4057
|
-
}
|
|
4058
|
-
} catch {}
|
|
4059
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
4060
|
-
}
|
|
4061
|
-
console.error("Could not connect to Chrome. Make sure all Chrome windows are closed and try again.");
|
|
4062
|
-
}
|
|
4063
1240
|
async function main() {
|
|
4064
1241
|
const { command, args, flags } = parseArgs(process.argv);
|
|
4065
1242
|
const noAutoStart = !!flags["no-auto-start"];
|
|
@@ -4071,88 +1248,10 @@ async function main() {
|
|
|
4071
1248
|
await cmdSetup(flags);
|
|
4072
1249
|
return;
|
|
4073
1250
|
}
|
|
4074
|
-
if (command === "mcp")
|
|
4075
|
-
return cmdMcp(flags);
|
|
4076
|
-
if (command === "status")
|
|
4077
|
-
return cmdStatus(flags);
|
|
4078
|
-
if (command === "stop") {
|
|
4079
|
-
cmdStop(flags);
|
|
4080
|
-
return;
|
|
4081
|
-
}
|
|
4082
|
-
if (command === "restart")
|
|
4083
|
-
return cmdRestart(flags);
|
|
4084
|
-
if (command === "upgrade" || command === "update")
|
|
4085
|
-
return cmdUpgrade(flags);
|
|
4086
|
-
if (command === "connect-chrome")
|
|
4087
|
-
return cmdConnectChrome();
|
|
4088
|
-
const KNOWN_COMMANDS = new Set([
|
|
4089
|
-
"health",
|
|
4090
|
-
"mcp",
|
|
4091
|
-
"setup",
|
|
4092
|
-
"resolve",
|
|
4093
|
-
"execute",
|
|
4094
|
-
"exec",
|
|
4095
|
-
"feedback",
|
|
4096
|
-
"fb",
|
|
4097
|
-
"review",
|
|
4098
|
-
"index",
|
|
4099
|
-
"publish",
|
|
4100
|
-
"settings",
|
|
4101
|
-
"login",
|
|
4102
|
-
"skills",
|
|
4103
|
-
"skill",
|
|
4104
|
-
"cleanup-stale",
|
|
4105
|
-
"search",
|
|
4106
|
-
"sessions",
|
|
4107
|
-
"status",
|
|
4108
|
-
"stop",
|
|
4109
|
-
"restart",
|
|
4110
|
-
"upgrade",
|
|
4111
|
-
"update",
|
|
4112
|
-
"go",
|
|
4113
|
-
"submit",
|
|
4114
|
-
"snap",
|
|
4115
|
-
"click",
|
|
4116
|
-
"fill",
|
|
4117
|
-
"type",
|
|
4118
|
-
"press",
|
|
4119
|
-
"select",
|
|
4120
|
-
"scroll",
|
|
4121
|
-
"screenshot",
|
|
4122
|
-
"text",
|
|
4123
|
-
"markdown",
|
|
4124
|
-
"cookies",
|
|
4125
|
-
"eval",
|
|
4126
|
-
"back",
|
|
4127
|
-
"forward",
|
|
4128
|
-
"sync",
|
|
4129
|
-
"close",
|
|
4130
|
-
"connect-chrome"
|
|
4131
|
-
]);
|
|
4132
|
-
if (!KNOWN_COMMANDS.has(command)) {
|
|
4133
|
-
const pack = findSitePack(command);
|
|
4134
|
-
if (pack) {
|
|
4135
|
-
await ensureLocalServer(BASE_URL, noAutoStart, import.meta.url);
|
|
4136
|
-
const taskName = args[0];
|
|
4137
|
-
if (!taskName || taskName === "help") {
|
|
4138
|
-
return cmdSiteHelp(pack, flags);
|
|
4139
|
-
}
|
|
4140
|
-
if (taskName === "login") {
|
|
4141
|
-
return cmdSiteLogin(pack, flags);
|
|
4142
|
-
}
|
|
4143
|
-
const batchArg = flags.batch;
|
|
4144
|
-
if (batchArg) {
|
|
4145
|
-
return cmdSiteBatch(pack, batchArg, flags);
|
|
4146
|
-
}
|
|
4147
|
-
return cmdSiteTask(pack, taskName, flags);
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
1251
|
await ensureLocalServer(BASE_URL, noAutoStart, import.meta.url);
|
|
4151
1252
|
switch (command) {
|
|
4152
1253
|
case "health":
|
|
4153
1254
|
return cmdHealth(flags);
|
|
4154
|
-
case "mcp":
|
|
4155
|
-
return cmdMcp(flags);
|
|
4156
1255
|
case "setup":
|
|
4157
1256
|
return cmdSetup(flags);
|
|
4158
1257
|
case "resolve":
|
|
@@ -4163,64 +1262,16 @@ async function main() {
|
|
|
4163
1262
|
case "feedback":
|
|
4164
1263
|
case "fb":
|
|
4165
1264
|
return cmdFeedback(flags);
|
|
4166
|
-
case "review":
|
|
4167
|
-
return cmdReview(flags);
|
|
4168
|
-
case "index":
|
|
4169
|
-
return cmdIndex(flags);
|
|
4170
|
-
case "publish":
|
|
4171
|
-
return cmdPublish(flags);
|
|
4172
|
-
case "settings":
|
|
4173
|
-
return cmdSettings(flags);
|
|
4174
1265
|
case "login":
|
|
4175
1266
|
return cmdLogin(flags);
|
|
4176
1267
|
case "skills":
|
|
4177
1268
|
return cmdSkills(flags);
|
|
4178
1269
|
case "skill":
|
|
4179
1270
|
return cmdSkill(args, flags);
|
|
4180
|
-
case "cleanup-stale":
|
|
4181
|
-
return cmdCleanupStale(flags);
|
|
4182
1271
|
case "search":
|
|
4183
1272
|
return cmdSearch(flags);
|
|
4184
1273
|
case "sessions":
|
|
4185
1274
|
return cmdSessions(flags);
|
|
4186
|
-
case "go":
|
|
4187
|
-
return cmdGo(args, flags);
|
|
4188
|
-
case "submit":
|
|
4189
|
-
return cmdSubmit(flags);
|
|
4190
|
-
case "snap":
|
|
4191
|
-
return cmdSnap(flags);
|
|
4192
|
-
case "click":
|
|
4193
|
-
return cmdClick(args, flags);
|
|
4194
|
-
case "fill":
|
|
4195
|
-
return cmdFill(args, flags);
|
|
4196
|
-
case "type":
|
|
4197
|
-
return cmdType(args, flags);
|
|
4198
|
-
case "press":
|
|
4199
|
-
return cmdPress(args, flags);
|
|
4200
|
-
case "select":
|
|
4201
|
-
return cmdSelect(args, flags);
|
|
4202
|
-
case "scroll":
|
|
4203
|
-
return cmdScroll(args, flags);
|
|
4204
|
-
case "screenshot":
|
|
4205
|
-
return cmdScreenshot(flags);
|
|
4206
|
-
case "text":
|
|
4207
|
-
return cmdText(flags);
|
|
4208
|
-
case "markdown":
|
|
4209
|
-
return cmdMarkdown(flags);
|
|
4210
|
-
case "cookies":
|
|
4211
|
-
return cmdCookies(flags);
|
|
4212
|
-
case "eval":
|
|
4213
|
-
return cmdEval(args, flags);
|
|
4214
|
-
case "back":
|
|
4215
|
-
return cmdBack(flags);
|
|
4216
|
-
case "forward":
|
|
4217
|
-
return cmdForward(flags);
|
|
4218
|
-
case "sync":
|
|
4219
|
-
return cmdSync(flags);
|
|
4220
|
-
case "close":
|
|
4221
|
-
return cmdClose(flags);
|
|
4222
|
-
case "connect-chrome":
|
|
4223
|
-
return cmdConnectChrome();
|
|
4224
1275
|
default:
|
|
4225
1276
|
info(`Unknown command: ${command}`);
|
|
4226
1277
|
printHelp();
|
|
@@ -4228,7 +1279,7 @@ async function main() {
|
|
|
4228
1279
|
}
|
|
4229
1280
|
}
|
|
4230
1281
|
if (isMainModule(import.meta.url)) {
|
|
4231
|
-
main().
|
|
1282
|
+
main().catch((err) => {
|
|
4232
1283
|
die(err.message);
|
|
4233
1284
|
});
|
|
4234
1285
|
}
|