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.
Files changed (90) hide show
  1. package/README.md +10 -127
  2. package/dist/cli.js +493 -3442
  3. package/package.json +6 -15
  4. package/runtime-src/api/routes.ts +29 -1338
  5. package/runtime-src/auth/index.ts +33 -296
  6. package/runtime-src/capture/index.ts +123 -466
  7. package/runtime-src/cli.ts +374 -1288
  8. package/runtime-src/client/index.ts +30 -696
  9. package/runtime-src/execution/index.ts +143 -694
  10. package/runtime-src/graph/index.ts +10 -128
  11. package/runtime-src/intent-match.ts +27 -27
  12. package/runtime-src/kuri/client.ts +242 -1210
  13. package/runtime-src/orchestrator/index.ts +499 -2753
  14. package/runtime-src/reverse-engineer/index.ts +22 -239
  15. package/runtime-src/runtime/local-server.ts +22 -238
  16. package/runtime-src/runtime/paths.ts +5 -9
  17. package/runtime-src/runtime/setup.ts +1 -56
  18. package/runtime-src/server.ts +11 -11
  19. package/runtime-src/transform/schema-hints.ts +358 -0
  20. package/runtime-src/types/skill.ts +2 -488
  21. package/runtime-src/verification/index.ts +14 -35
  22. package/runtime-src/version.ts +8 -68
  23. package/vendor/kuri/darwin-arm64/kuri +0 -0
  24. package/vendor/kuri/darwin-x64/kuri +0 -0
  25. package/vendor/kuri/linux-arm64/kuri +0 -0
  26. package/vendor/kuri/linux-x64/kuri +0 -0
  27. package/SKILL.md +0 -754
  28. package/bin/unbrowse-update-hint.mjs +0 -22
  29. package/bin/unbrowse-wrapper.mjs +0 -107
  30. package/bin/unbrowse.js +0 -37
  31. package/dist/mcp.js +0 -1796
  32. package/runtime-src/agent-outcome.ts +0 -166
  33. package/runtime-src/analytics-session.ts +0 -55
  34. package/runtime-src/api/browse-index.ts +0 -254
  35. package/runtime-src/api/browse-session.ts +0 -648
  36. package/runtime-src/api/browse-submit-prereqs.ts +0 -48
  37. package/runtime-src/api/browse-submit.ts +0 -1184
  38. package/runtime-src/auth/runtime.ts +0 -116
  39. package/runtime-src/browser/index.ts +0 -643
  40. package/runtime-src/browser/types.ts +0 -41
  41. package/runtime-src/build-info.generated.ts +0 -4
  42. package/runtime-src/capture/prefetch.ts +0 -122
  43. package/runtime-src/capture/rsc.ts +0 -45
  44. package/runtime-src/cli/shortcuts.ts +0 -273
  45. package/runtime-src/client/graph-client.ts +0 -99
  46. package/runtime-src/execution/robots.ts +0 -167
  47. package/runtime-src/execution/search-forms.ts +0 -188
  48. package/runtime-src/graph/planner.ts +0 -411
  49. package/runtime-src/graph/session.ts +0 -294
  50. package/runtime-src/graph/trace-store.ts +0 -136
  51. package/runtime-src/indexer/index.ts +0 -441
  52. package/runtime-src/mcp.ts +0 -1522
  53. package/runtime-src/orchestrator/browser-agent.ts +0 -374
  54. package/runtime-src/orchestrator/dag-advisor.ts +0 -59
  55. package/runtime-src/orchestrator/dag-feedback.ts +0 -256
  56. package/runtime-src/orchestrator/first-pass-action.ts +0 -403
  57. package/runtime-src/orchestrator/passive-publish.ts +0 -182
  58. package/runtime-src/orchestrator/timing-economics.ts +0 -80
  59. package/runtime-src/payments/cascade.ts +0 -137
  60. package/runtime-src/payments/index.ts +0 -268
  61. package/runtime-src/payments/wallet.ts +0 -98
  62. package/runtime-src/publish/sanitize.ts +0 -197
  63. package/runtime-src/publish-admission.ts +0 -279
  64. package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
  65. package/runtime-src/router.ts +0 -17
  66. package/runtime-src/routing-telemetry.ts +0 -395
  67. package/runtime-src/runtime/browser-access.ts +0 -11
  68. package/runtime-src/runtime/browser-auth.ts +0 -12
  69. package/runtime-src/runtime/browser-host.ts +0 -48
  70. package/runtime-src/runtime/lifecycle.ts +0 -17
  71. package/runtime-src/runtime/supervisor.ts +0 -69
  72. package/runtime-src/runtime/update-hints.ts +0 -351
  73. package/runtime-src/settings.ts +0 -221
  74. package/runtime-src/single-binary.ts +0 -141
  75. package/runtime-src/site-policy.ts +0 -54
  76. package/runtime-src/stale-cleanup-runner.ts +0 -144
  77. package/runtime-src/stale-cleanup.ts +0 -133
  78. package/runtime-src/telemetry-attribution.ts +0 -120
  79. package/runtime-src/telemetry.ts +0 -253
  80. package/runtime-src/verification/auth-gate.ts +0 -8
  81. package/runtime-src/verification/candidates.ts +0 -27
  82. package/runtime-src/verification/matrix.ts +0 -30
  83. package/runtime-src/workflow/artifact.ts +0 -161
  84. package/runtime-src/workflow/compile.ts +0 -808
  85. package/runtime-src/workflow/publish.ts +0 -205
  86. package/runtime-src/workflow/runtime.ts +0 -213
  87. package/scripts/postinstall.mjs +0 -105
  88. package/scripts/release-assets.mjs +0 -24
  89. package/scripts/verify-release-assets.mjs +0 -39
  90. 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
- init_version();
1175
- init_cascade();
1176
- init_wallet();
1177
- init_telemetry_attribution();
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 ? join3(homedir2(), ".unbrowse", "profiles", PROFILE_NAME) : join3(homedir2(), ".unbrowse");
20
+ return PROFILE_NAME ? join(homedir(), ".unbrowse", "profiles", PROFILE_NAME) : join(homedir(), ".unbrowse");
1206
21
  }
1207
22
  function getConfigPath() {
1208
- return join3(getConfigDir(), "config.json");
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 (existsSync3(configPath)) {
1227
- return JSON.parse(readFileSync3(configPath, "utf-8"));
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 (!existsSync3(configDir))
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 apiRequest(method, path, body, opts) {
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(), opts?.timeoutMs ?? API_TIMEOUT_MS);
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(options) {
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 true;
228
+ return;
1589
229
  }
1590
230
  if (config?.tos_accepted_version === tosInfo.version) {
1591
- return true;
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
- if (exitOnFailure)
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
- const accepted2 = await checkTosStatus({ exitOnFailure });
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
- if (exitOnFailure)
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 wallet = getLocalWalletContext();
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
- if (exitOnFailure)
1678
- process.exit(1);
294
+ process.exit(1);
1679
295
  }
1680
296
  }
1681
- async function getMyProfile() {
1682
- return api("GET", "/v1/agents/me", undefined);
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
- async function syncAgentWallet(wallet = getLocalWalletContext()) {
1685
- if (!wallet.wallet_address)
1686
- return;
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
- // ../../src/cli/shortcuts.ts
1695
- var linkedin = {
1696
- site: "linkedin",
1697
- aliases: ["linkedin", "li"],
1698
- domain: "www.linkedin.com",
1699
- login_url: "https://www.linkedin.com/login",
1700
- description: "LinkedIn professional network",
1701
- tasks: [
1702
- {
1703
- match: ["login"],
1704
- intent: "login",
1705
- url: "https://www.linkedin.com/login",
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 findTask(pack, taskName) {
1807
- const lower = taskName.toLowerCase();
1808
- return pack.tasks.find((t) => t.match.includes(lower));
337
+ function getUnbrowseHome() {
338
+ return path.join(os.homedir(), ".unbrowse");
1809
339
  }
1810
- function buildDepsGraph(pack) {
1811
- const graph = {};
1812
- for (const task of pack.tasks) {
1813
- graph[task.match[0]] = {
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 planExecution(pack, taskNames) {
1822
- const waves = [];
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 buildDepsMetadata(pack, taskName) {
1879
- const task = findTask(pack, taskName);
1880
- if (!task)
1881
- return { requires: [], enables: [], parallel_safe: true };
1882
- return {
1883
- requires: task.requires,
1884
- enables: task.enables,
1885
- parallel_safe: task.parallel_safe
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
- init_paths();
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
- if (!res.ok)
1903
- return null;
1904
- return await res.json();
368
+ return res.ok;
1905
369
  } catch {
1906
- return null;
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(readFileSync4(pidFile, "utf-8"));
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
- const installedVersion = getVersion(metaUrl);
2011
- const initialHealth = await fetchServerHealth(baseUrl);
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
- for (let attempt = 0;attempt <= MAX_RESTART_ATTEMPTS; attempt++) {
2054
- spawnServer(baseUrl, metaUrl, pidFile, attempt);
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
- throw new Error(`Server failed to start after ${MAX_RESTART_ATTEMPTS + 1} attempts. Check ${logFile}`);
2073
- }
2074
- function checkServerVersion(baseUrl, metaUrl, healthOverride) {
2075
- const pidFile = getServerPidFile(baseUrl);
2076
- const state = readPidState(pidFile);
2077
- if (!state)
2078
- return null;
2079
- const installed = getVersion(metaUrl);
2080
- const running = healthOverride?.runningVersion ?? state.version ?? "unknown";
2081
- const runningCodeHash = healthOverride?.runningCodeHash ?? state.code_hash;
2082
- return {
2083
- running,
2084
- installed,
2085
- ...runningCodeHash ? { running_code_hash: runningCodeHash } : {},
2086
- installed_code_hash: CODE_HASH,
2087
- needs_restart: isServerVersionMismatch(running, installed, runningCodeHash, CODE_HASH)
2088
- };
2089
- }
2090
- function stopServer(baseUrl) {
2091
- const pidFile = getServerPidFile(baseUrl);
2092
- const state = readPidState(pidFile);
2093
- if (!state?.pid)
2094
- return false;
2095
- try {
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 existsSync5, mkdirSync as mkdirSync3, realpathSync as realpathSync2 } from "node:fs";
452
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, realpathSync as realpathSync2 } from "node:fs";
2114
453
  import path3 from "node:path";
2115
- import { createRequire as createRequire3 } from "node:module";
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 = fileURLToPath3(metaUrl);
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 existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
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
- var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
2207
- var CODEX_MARKER = "# Unbrowse update hints — managed by unbrowse setup";
2208
- function getHomeDir() {
2209
- return process.env.HOME || os3.homedir();
2210
- }
2211
- function getConfigDir2() {
2212
- if (process.env.UNBROWSE_CONFIG_DIR)
2213
- return process.env.UNBROWSE_CONFIG_DIR;
2214
- return path6.join(getHomeDir(), ".unbrowse");
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 ensureDir2(dir) {
2217
- if (!existsSync8(dir))
2218
- mkdirSync5(dir, { recursive: true });
2219
- return dir;
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 readJsonFile(file) {
498
+ function resolveBinaryOnPath(name) {
499
+ const checker = process.platform === "win32" ? "where" : "which";
2222
500
  try {
2223
- return JSON.parse(readFileSync6(file, "utf8"));
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 writeJsonFile(file, value) {
2229
- ensureDir2(path6.dirname(file));
2230
- writeFileSync4(file, `${JSON.stringify(value, null, 2)}
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 writeCodexHook(metaUrl) {
2318
- const configPath = getCodexConfigPath();
2319
- if (!existsSync8(path6.dirname(configPath))) {
2320
- return { host: "codex", action: "not-detected", config_file: configPath };
2321
- }
2322
- try {
2323
- const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2324
- const fileExistsBefore = existsSync8(configPath);
2325
- let content = fileExistsBefore ? readFileSync6(configPath, "utf8") : "";
2326
- const previous = content;
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 writeClaudeHook(metaUrl) {
2358
- const settingsPath = getClaudeSettingsPath();
2359
- if (!existsSync8(path6.dirname(settingsPath))) {
2360
- return { host: "claude", action: "not-detected", config_file: settingsPath };
2361
- }
2362
- try {
2363
- const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2364
- const command = `node "${hookScript}"`;
2365
- const fileExistsBefore = existsSync8(settingsPath);
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 configureUpdateHintHooks(metaUrl, install) {
2392
- if (process.env.UNBROWSE_DISABLE_UPDATE_HINTS === "1")
2393
- return [];
2394
- const source = install ?? loadInstallSource(metaUrl);
2395
- const configuredHosts = source.host === "codex" || source.host === "claude" ? [source.host] : ["codex", "claude"];
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 || path7.join(os4.homedir(), "AppData", "Roaming");
565
+ return process.env.APPDATA || path6.join(os3.homedir(), "AppData", "Roaming");
2420
566
  }
2421
- return process.env.XDG_CONFIG_HOME || path7.join(os4.homedir(), ".config");
567
+ return process.env.XDG_CONFIG_HOME || path6.join(os3.homedir(), ".config");
2422
568
  }
2423
569
  function getOpenCodeGlobalCommandsDir() {
2424
- return path7.join(resolveConfigHome(), "opencode", "commands");
570
+ return path6.join(resolveConfigHome(), "opencode", "commands");
2425
571
  }
2426
572
  function getOpenCodeProjectCommandsDir(cwd) {
2427
- return path7.join(cwd, ".opencode", "commands");
573
+ return path6.join(cwd, ".opencode", "commands");
2428
574
  }
2429
575
  function detectOpenCode(cwd) {
2430
- return hasBinary("opencode") || existsSync9(path7.join(resolveConfigHome(), "opencode")) || existsSync9(path7.join(cwd, ".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" : existsSync9(path7.join(cwd, ".opencode")) ? "project" : "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 = path7.join(ensureDir(commandsDir), "unbrowse.md");
606
+ const commandFile = path6.join(ensureDir(commandsDir), "unbrowse.md");
2461
607
  const content = renderOpenCodeCommand();
2462
- const action2 = existsSync9(commandFile) ? "updated" : "installed";
2463
- mkdirSync6(path7.dirname(commandFile), { recursive: true });
2464
- writeFileSync5(commandFile, content);
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: action2,
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 (existsSync9(binary)) {
620
+ if (existsSync5(binary)) {
2475
621
  return { installed: true, action: "already-installed" };
2476
622
  }
2477
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync9(path7.join(candidate, "build.zig")));
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 (existsSync9(builtBinary)) {
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: os4.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, path9, body) {
2762
- let target = `${BASE_URL}${path9}`;
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
- ...requestBody ? { "Content-Type": "application/json" } : {},
709
+ ...body ? { "Content-Type": "application/json" } : {},
2780
710
  "x-unbrowse-client-id": CLI_CLIENT_ID
2781
711
  },
2782
- body: requestBody ? JSON.stringify(requestBody) : undefined
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.available_endpoints)
2868
- out.available_endpoints = obj.available_endpoints;
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 formatSavedDuration(ms) {
2882
- if (ms >= 60000)
2883
- return `${(ms / 60000).toFixed(1)}m`;
2884
- if (ms >= 1e4)
2885
- return `${Math.round(ms / 1000)}s`;
2886
- if (ms >= 1000)
2887
- return `${(ms / 1000).toFixed(1)}s`;
2888
- return `${ms}ms`;
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 emitImpactSummary(result) {
2891
- const impact = result.impact;
2892
- if (!impact)
2893
- return;
2894
- const timeSavedMs = typeof impact.time_saved_ms === "number" ? impact.time_saved_ms : 0;
2895
- const tokensSaved = typeof impact.tokens_saved === "number" ? impact.tokens_saved : 0;
2896
- const timeSavedPct = typeof impact.time_saved_pct === "number" ? impact.time_saved_pct : 0;
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 emitNextActionSummary(result) {
2911
- const nextActions = Array.isArray(result.next_actions) ? result.next_actions : [];
2912
- if (nextActions.length === 0)
2913
- return;
2914
- info("Likely next actions:");
2915
- for (const action2 of nextActions.slice(0, 3)) {
2916
- const command = typeof action2.command === "string" ? action2.command : "";
2917
- const title = typeof action2.title === "string" ? action2.title : action2.endpoint_id ?? "next step";
2918
- const why = typeof action2.why === "string" ? action2.why : "";
2919
- info(` ${command || title}${why ? ` # ${why}` : ""}`);
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 hostType = detectTelemetryHostType();
2941
- await ensureCliInstallTracked(hostType);
2942
- await recordFunnelTelemetryEvent("cli_invoked", {
2943
- source: "cli",
2944
- hostType,
2945
- properties: { command: "resolve" }
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
- function drillPath(data, path9) {
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
- return values;
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
- return cur;
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 (typeof value === "object") {
3195
- if (depth <= 0)
3196
- return "object";
3197
- const out = {};
3198
- for (const [k, v] of Object.entries(value)) {
3199
- out[k] = schemaOf(v, depth - 1);
3200
- }
3201
- return out;
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
- return typeof value;
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 hostType = detectTelemetryHostType();
3210
- await ensureCliInstallTracked(hostType);
3211
- await recordFunnelTelemetryEvent("cli_invoked", {
3212
- source: "cli",
3213
- hostType,
3214
- properties: { command: "execute" }
3215
- });
3216
- await recordFunnelTelemetryEvent("resolve_started", {
3217
- source: "cli",
3218
- hostType,
3219
- properties: {
3220
- command: "execute",
3221
- intent: typeof flags.intent === "string" ? flags.intent : null,
3222
- domain: telemetryDomainFromInput(undefined, flags.url),
3223
- url: typeof flags.url === "string" ? flags.url : null,
3224
- skill_id: skillId,
3225
- endpoint_id: typeof flags.endpoint === "string" ? flags.endpoint : null
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
- 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.");
3250
- if (isResolveSuccessResult(result)) {
3251
- await recordFunnelTelemetryEvent("resolve_completed", {
3252
- source: "cli",
3253
- hostType,
3254
- properties: {
3255
- command: "execute",
3256
- intent: typeof flags.intent === "string" ? flags.intent : null,
3257
- domain: telemetryDomainFromInput(undefined, flags.url),
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 path9 = domain ? "/v1/search/domain" : "/v1/search";
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
- const hostType = detectTelemetryHostType();
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
- "unbrowse execute --skill abc --endpoint def --schema --pretty",
3658
- 'unbrowse execute --skill abc --endpoint def --path "data.items[]" --extract "name,url" --limit 10 --pretty',
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().then(() => Promise.all([drainPendingIndexJobs(), drainPendingPassivePublishes()])).catch((err) => {
1282
+ main().catch((err) => {
4232
1283
  die(err.message);
4233
1284
  });
4234
1285
  }