unbrowse 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +455 -96
- package/dist/index.js +2 -6
- package/dist/mcp.js +695 -46
- package/dist/server.js +25811 -0
- package/package.json +1 -2
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/manifest.json +7 -10
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/analytics-session.ts +0 -55
- package/runtime-src/api/browse-index.ts +0 -317
- package/runtime-src/api/browse-session.ts +0 -572
- package/runtime-src/api/browse-submit-prereqs.ts +0 -48
- package/runtime-src/api/browse-submit.ts +0 -1184
- package/runtime-src/api/routes.ts +0 -1823
- package/runtime-src/auth/browser-cookies.ts +0 -423
- package/runtime-src/auth/index.ts +0 -535
- package/runtime-src/auth/runtime.ts +0 -116
- package/runtime-src/browser/index.ts +0 -659
- package/runtime-src/browser/types.ts +0 -41
- package/runtime-src/build-info.generated.ts +0 -6
- package/runtime-src/capture/index.ts +0 -1794
- package/runtime-src/capture/prefetch.ts +0 -95
- package/runtime-src/capture/rsc.ts +0 -45
- package/runtime-src/cli/shortcuts.ts +0 -273
- package/runtime-src/cli.ts +0 -1572
- package/runtime-src/client/graph-client.ts +0 -100
- package/runtime-src/client/index.ts +0 -1425
- package/runtime-src/debug-trace.ts +0 -18
- package/runtime-src/domain.ts +0 -38
- package/runtime-src/execution/index.ts +0 -3397
- package/runtime-src/execution/retry.ts +0 -46
- package/runtime-src/execution/robots.ts +0 -167
- package/runtime-src/execution/search-forms.ts +0 -188
- package/runtime-src/extraction/index.ts +0 -1507
- package/runtime-src/foundry/publish-bundle.ts +0 -392
- package/runtime-src/graph/agent-augment.ts +0 -315
- package/runtime-src/graph/index.ts +0 -1524
- package/runtime-src/graph/local-fixtures.ts +0 -393
- package/runtime-src/graph/local-harness.ts +0 -646
- package/runtime-src/graph/planner.ts +0 -411
- package/runtime-src/graph/session.ts +0 -294
- package/runtime-src/graph/trace-store.ts +0 -136
- package/runtime-src/index.ts +0 -24
- package/runtime-src/indexer/index.ts +0 -465
- package/runtime-src/intent-match.ts +0 -1515
- package/runtime-src/kuri/client.ts +0 -1839
- package/runtime-src/logger.ts +0 -30
- package/runtime-src/marketplace/index.ts +0 -103
- package/runtime-src/mcp.ts +0 -1747
- package/runtime-src/orchestrator/browser-agent.ts +0 -374
- package/runtime-src/orchestrator/dag-advisor.ts +0 -59
- package/runtime-src/orchestrator/dag-feedback.ts +0 -257
- package/runtime-src/orchestrator/first-pass-action.ts +0 -403
- package/runtime-src/orchestrator/index.ts +0 -4480
- package/runtime-src/orchestrator/passive-publish.ts +0 -187
- package/runtime-src/orchestrator/timing-economics.ts +0 -80
- package/runtime-src/payments/cascade.ts +0 -137
- package/runtime-src/payments/index.ts +0 -270
- package/runtime-src/payments/lobster-pay.ts +0 -182
- package/runtime-src/payments/wallet.ts +0 -98
- package/runtime-src/publish/review-context.ts +0 -93
- package/runtime-src/publish/sanitize.ts +0 -197
- package/runtime-src/publish/schema-review.ts +0 -192
- package/runtime-src/publish-admission.ts +0 -388
- package/runtime-src/ratelimit/index.ts +0 -23
- package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
- package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
- package/runtime-src/reverse-engineer/index.ts +0 -1551
- package/runtime-src/router.ts +0 -17
- package/runtime-src/routing-telemetry.ts +0 -395
- package/runtime-src/runtime/browser-access.ts +0 -11
- package/runtime-src/runtime/browser-auth.ts +0 -12
- package/runtime-src/runtime/browser-host.ts +0 -48
- package/runtime-src/runtime/lifecycle.ts +0 -17
- package/runtime-src/runtime/local-server.ts +0 -311
- package/runtime-src/runtime/paths.ts +0 -99
- package/runtime-src/runtime/setup.ts +0 -251
- package/runtime-src/runtime/supervisor.ts +0 -69
- package/runtime-src/runtime/update-hints.ts +0 -351
- package/runtime-src/server.ts +0 -100
- package/runtime-src/session-logs.ts +0 -142
- package/runtime-src/settings.ts +0 -221
- package/runtime-src/single-binary.ts +0 -143
- package/runtime-src/site-policy.ts +0 -54
- package/runtime-src/stale-cleanup-runner.ts +0 -144
- package/runtime-src/stale-cleanup.ts +0 -133
- package/runtime-src/telemetry-attribution.ts +0 -120
- package/runtime-src/telemetry.ts +0 -253
- package/runtime-src/template-params.ts +0 -141
- package/runtime-src/transform/drift.ts +0 -60
- package/runtime-src/transform/index.ts +0 -277
- package/runtime-src/types/index.ts +0 -1
- package/runtime-src/types/skill.ts +0 -912
- package/runtime-src/vault/index.ts +0 -196
- package/runtime-src/verification/auth-gate.ts +0 -8
- package/runtime-src/verification/candidates.ts +0 -27
- package/runtime-src/verification/index.ts +0 -120
- package/runtime-src/verification/matrix.ts +0 -30
- package/runtime-src/version.ts +0 -148
- package/runtime-src/workflow/artifact.ts +0 -161
- package/runtime-src/workflow/compile.ts +0 -808
- package/runtime-src/workflow/publish.ts +0 -225
- package/runtime-src/workflow/runtime.ts +0 -213
- package/vendor/kuri/win-x64/kuri.exe +0 -0
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import { openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { ensureDir, getPackageRoot, getServerAutostartLogFile, getServerPidFile, resolveSiblingEntrypoint, runtimeArgsForEntrypoint } from "./paths.js";
|
|
5
|
-
import { LocalSupervisor } from "./supervisor.js";
|
|
6
|
-
import { CODE_HASH, PACKAGE_VERSION } from "../version.js";
|
|
7
|
-
|
|
8
|
-
type PidState = {
|
|
9
|
-
pid: number;
|
|
10
|
-
base_url: string;
|
|
11
|
-
started_at: string;
|
|
12
|
-
entrypoint: string;
|
|
13
|
-
version?: string;
|
|
14
|
-
code_hash?: string;
|
|
15
|
-
restart_count?: number;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type ServerHealth = {
|
|
19
|
-
status?: string;
|
|
20
|
-
package_version?: string;
|
|
21
|
-
code_hash?: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export function isServerVersionMismatch(
|
|
25
|
-
runningVersion: string | undefined,
|
|
26
|
-
installedVersion: string,
|
|
27
|
-
runningCodeHash?: string,
|
|
28
|
-
installedCodeHash?: string,
|
|
29
|
-
): boolean {
|
|
30
|
-
return (!!runningVersion && runningVersion !== installedVersion)
|
|
31
|
-
|| (!!runningCodeHash && !!installedCodeHash && runningCodeHash !== installedCodeHash);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function fetchServerHealth(baseUrl: string, timeoutMs = 2_000): Promise<ServerHealth | null> {
|
|
35
|
-
try {
|
|
36
|
-
const res = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
37
|
-
if (!res.ok) return null;
|
|
38
|
-
return await res.json() as ServerHealth;
|
|
39
|
-
} catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function isServerHealthy(baseUrl: string, timeoutMs = 2_000): Promise<boolean> {
|
|
45
|
-
return !!(await fetchServerHealth(baseUrl, timeoutMs));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function waitForHealthy(baseUrl: string, timeoutMs: number): Promise<boolean> {
|
|
49
|
-
const start = Date.now();
|
|
50
|
-
while (Date.now() - start < timeoutMs) {
|
|
51
|
-
if (await isServerHealthy(baseUrl)) return true;
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isPidAlive(pid: number): boolean {
|
|
58
|
-
try {
|
|
59
|
-
process.kill(pid, 0);
|
|
60
|
-
return true;
|
|
61
|
-
} catch {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function readPidState(pidFile: string): PidState | null {
|
|
67
|
-
try {
|
|
68
|
-
return JSON.parse(readFileSync(pidFile, "utf-8")) as PidState;
|
|
69
|
-
} catch {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function clearStalePidFile(pidFile: string): void {
|
|
75
|
-
try {
|
|
76
|
-
unlinkSync(pidFile);
|
|
77
|
-
} catch {
|
|
78
|
-
// ignore
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function deriveListenEnv(baseUrl: string): Record<string, string> {
|
|
83
|
-
const url = new URL(baseUrl);
|
|
84
|
-
const host = !url.hostname || url.hostname === "localhost" ? "127.0.0.1" : url.hostname;
|
|
85
|
-
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
86
|
-
return { HOST: host, PORT: port, UNBROWSE_URL: baseUrl };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function isCompiledBinary(): boolean {
|
|
90
|
-
return !!(process.versions.bun && !process.argv[1]?.match(/\.(ts|js|mjs)$/));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function getServerSpawnSpec(metaUrl: string, entrypoint = resolveSiblingEntrypoint(metaUrl, "index")): {
|
|
94
|
-
command: string;
|
|
95
|
-
args: string[];
|
|
96
|
-
cwd: string;
|
|
97
|
-
recordedEntrypoint: string;
|
|
98
|
-
} {
|
|
99
|
-
if (isCompiledBinary()) {
|
|
100
|
-
return {
|
|
101
|
-
command: process.execPath,
|
|
102
|
-
args: ["serve"],
|
|
103
|
-
cwd: process.cwd(),
|
|
104
|
-
recordedEntrypoint: `${process.execPath} serve`,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
command: process.execPath,
|
|
110
|
-
args: runtimeArgsForEntrypoint(metaUrl, entrypoint),
|
|
111
|
-
cwd: getPackageRoot(metaUrl),
|
|
112
|
-
recordedEntrypoint: entrypoint,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const MAX_RESTART_ATTEMPTS = 3;
|
|
117
|
-
const RESTART_BACKOFF_MS = 2_000;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Spawn the local server as a detached child process.
|
|
121
|
-
* Returns the PidState written to the pid file.
|
|
122
|
-
*/
|
|
123
|
-
function spawnServer(
|
|
124
|
-
baseUrl: string,
|
|
125
|
-
metaUrl: string,
|
|
126
|
-
pidFile: string,
|
|
127
|
-
restartCount = 0,
|
|
128
|
-
): PidState {
|
|
129
|
-
const entrypoint = resolveSiblingEntrypoint(metaUrl, "index");
|
|
130
|
-
const spawnSpec = getServerSpawnSpec(metaUrl, entrypoint);
|
|
131
|
-
const logFile = getServerAutostartLogFile();
|
|
132
|
-
ensureDir(path.dirname(logFile));
|
|
133
|
-
const logFd = openSync(logFile, "a");
|
|
134
|
-
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
135
|
-
cwd: spawnSpec.cwd,
|
|
136
|
-
detached: true,
|
|
137
|
-
stdio: ["ignore", logFd, logFd],
|
|
138
|
-
env: {
|
|
139
|
-
...process.env,
|
|
140
|
-
...deriveListenEnv(baseUrl),
|
|
141
|
-
UNBROWSE_NON_INTERACTIVE: process.env.UNBROWSE_NON_INTERACTIVE || "1",
|
|
142
|
-
UNBROWSE_TOS_ACCEPTED: process.env.UNBROWSE_TOS_ACCEPTED || "1",
|
|
143
|
-
UNBROWSE_PID_FILE: pidFile,
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
child.unref();
|
|
147
|
-
|
|
148
|
-
const state: PidState = {
|
|
149
|
-
pid: child.pid!,
|
|
150
|
-
base_url: baseUrl,
|
|
151
|
-
started_at: new Date().toISOString(),
|
|
152
|
-
entrypoint: spawnSpec.recordedEntrypoint,
|
|
153
|
-
version: PACKAGE_VERSION,
|
|
154
|
-
code_hash: CODE_HASH,
|
|
155
|
-
restart_count: restartCount,
|
|
156
|
-
};
|
|
157
|
-
writeFileSync(pidFile, JSON.stringify(state, null, 2));
|
|
158
|
-
return state;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Shared supervisor instance used for health checking. */
|
|
162
|
-
const supervisor = new LocalSupervisor();
|
|
163
|
-
|
|
164
|
-
export { supervisor };
|
|
165
|
-
|
|
166
|
-
export async function ensureLocalServer(baseUrl: string, noAutoStart: boolean, metaUrl: string): Promise<void> {
|
|
167
|
-
const installedVersion = PACKAGE_VERSION;
|
|
168
|
-
const initialHealth = await fetchServerHealth(baseUrl);
|
|
169
|
-
if (initialHealth) {
|
|
170
|
-
const runningVersion = initialHealth.package_version;
|
|
171
|
-
const runningCodeHash = initialHealth.code_hash;
|
|
172
|
-
if (isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, CODE_HASH)) {
|
|
173
|
-
const versionInfo = checkServerVersion(baseUrl, metaUrl, {
|
|
174
|
-
runningVersion,
|
|
175
|
-
runningCodeHash,
|
|
176
|
-
});
|
|
177
|
-
if (versionInfo?.needs_restart) {
|
|
178
|
-
stopServer(baseUrl);
|
|
179
|
-
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
180
|
-
} else {
|
|
181
|
-
throw new Error(
|
|
182
|
-
`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.`,
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
} else {
|
|
186
|
-
// Server already healthy — ensure supervisor state reflects this
|
|
187
|
-
if (!supervisor.isRunning()) await supervisor.start();
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const pidFile = getServerPidFile(baseUrl);
|
|
193
|
-
const existing = readPidState(pidFile);
|
|
194
|
-
|
|
195
|
-
if (existing?.pid && isPidAlive(existing.pid)) {
|
|
196
|
-
// Process alive but not healthy — wait then try supervisor restart
|
|
197
|
-
if (await waitForHealthy(baseUrl, 15_000)) {
|
|
198
|
-
if (!supervisor.isRunning()) await supervisor.start();
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
// Still unhealthy after wait — kill stale process and restart
|
|
202
|
-
try { process.kill(existing.pid, "SIGTERM"); } catch { /* ignore */ }
|
|
203
|
-
await new Promise((r) => setTimeout(r, 1_000));
|
|
204
|
-
clearStalePidFile(pidFile);
|
|
205
|
-
if (supervisor.isRunning()) await supervisor.stop();
|
|
206
|
-
} else if (existing) {
|
|
207
|
-
clearStalePidFile(pidFile);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (noAutoStart) {
|
|
211
|
-
throw new Error("Server not running and auto-start disabled (--no-auto-start).");
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Spawn with supervisor retry
|
|
215
|
-
for (let attempt = 0; attempt <= MAX_RESTART_ATTEMPTS; attempt++) {
|
|
216
|
-
spawnServer(baseUrl, metaUrl, pidFile, attempt);
|
|
217
|
-
|
|
218
|
-
if (await waitForHealthy(baseUrl, 30_000)) {
|
|
219
|
-
await supervisor.start();
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Failed to start — clear and retry with backoff
|
|
224
|
-
const state = readPidState(pidFile);
|
|
225
|
-
if (state?.pid) {
|
|
226
|
-
try { process.kill(state.pid, "SIGTERM"); } catch { /* ignore */ }
|
|
227
|
-
}
|
|
228
|
-
clearStalePidFile(pidFile);
|
|
229
|
-
|
|
230
|
-
if (attempt < MAX_RESTART_ATTEMPTS) {
|
|
231
|
-
const backoff = RESTART_BACKOFF_MS * (attempt + 1);
|
|
232
|
-
await new Promise((r) => setTimeout(r, backoff));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const logFile = getServerAutostartLogFile();
|
|
237
|
-
throw new Error(`Server failed to start after ${MAX_RESTART_ATTEMPTS + 1} attempts. Check ${logFile}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Check if the running server version matches the installed CLI version.
|
|
242
|
-
* Returns null if no server is running, or runtime/version details.
|
|
243
|
-
*/
|
|
244
|
-
export function checkServerVersion(
|
|
245
|
-
baseUrl: string,
|
|
246
|
-
metaUrl: string,
|
|
247
|
-
): {
|
|
248
|
-
running: string;
|
|
249
|
-
installed: string;
|
|
250
|
-
running_code_hash?: string;
|
|
251
|
-
installed_code_hash: string;
|
|
252
|
-
needs_restart: boolean;
|
|
253
|
-
} | null;
|
|
254
|
-
export function checkServerVersion(
|
|
255
|
-
baseUrl: string,
|
|
256
|
-
metaUrl: string,
|
|
257
|
-
healthOverride?: {
|
|
258
|
-
runningVersion?: string;
|
|
259
|
-
runningCodeHash?: string;
|
|
260
|
-
},
|
|
261
|
-
): {
|
|
262
|
-
running: string;
|
|
263
|
-
installed: string;
|
|
264
|
-
running_code_hash?: string;
|
|
265
|
-
installed_code_hash: string;
|
|
266
|
-
needs_restart: boolean;
|
|
267
|
-
} | null {
|
|
268
|
-
const pidFile = getServerPidFile(baseUrl);
|
|
269
|
-
const state = readPidState(pidFile);
|
|
270
|
-
if (!state) return null;
|
|
271
|
-
const installed = PACKAGE_VERSION;
|
|
272
|
-
const running = healthOverride?.runningVersion ?? state.version ?? "unknown";
|
|
273
|
-
const runningCodeHash = healthOverride?.runningCodeHash ?? state.code_hash;
|
|
274
|
-
return {
|
|
275
|
-
running,
|
|
276
|
-
installed,
|
|
277
|
-
...(runningCodeHash ? { running_code_hash: runningCodeHash } : {}),
|
|
278
|
-
installed_code_hash: CODE_HASH,
|
|
279
|
-
needs_restart: isServerVersionMismatch(running, installed, runningCodeHash, CODE_HASH),
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Stop the running server gracefully.
|
|
285
|
-
* Returns true if a server was stopped, false if none was running.
|
|
286
|
-
*/
|
|
287
|
-
export function stopServer(baseUrl: string): boolean {
|
|
288
|
-
const pidFile = getServerPidFile(baseUrl);
|
|
289
|
-
const state = readPidState(pidFile);
|
|
290
|
-
if (!state?.pid) return false;
|
|
291
|
-
try {
|
|
292
|
-
process.kill(state.pid, "SIGTERM");
|
|
293
|
-
clearStalePidFile(pidFile);
|
|
294
|
-
// Synchronously mark supervisor as stopped (fire-and-forget the async stop)
|
|
295
|
-
if (supervisor.isRunning()) void supervisor.stop();
|
|
296
|
-
return true;
|
|
297
|
-
} catch {
|
|
298
|
-
clearStalePidFile(pidFile);
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Restart the local server (stop + start).
|
|
305
|
-
* Used after CLI upgrades to pick up new code.
|
|
306
|
-
*/
|
|
307
|
-
export async function restartServer(baseUrl: string, metaUrl: string): Promise<void> {
|
|
308
|
-
stopServer(baseUrl);
|
|
309
|
-
await new Promise((r) => setTimeout(r, 1_000));
|
|
310
|
-
await ensureLocalServer(baseUrl, false, metaUrl);
|
|
311
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
-
|
|
7
|
-
export function getModuleDir(metaUrl: string): string {
|
|
8
|
-
return path.dirname(fileURLToPath(metaUrl));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function getPackageRoot(metaUrl: string): string {
|
|
12
|
-
if (process.env.UNBROWSE_PACKAGE_ROOT) return process.env.UNBROWSE_PACKAGE_ROOT;
|
|
13
|
-
let dir = getModuleDir(metaUrl);
|
|
14
|
-
const root = path.parse(dir).root;
|
|
15
|
-
while (dir !== root) {
|
|
16
|
-
if (existsSync(path.join(dir, "package.json"))) return dir;
|
|
17
|
-
dir = path.dirname(dir);
|
|
18
|
-
}
|
|
19
|
-
return getModuleDir(metaUrl);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function resolveSiblingEntrypoint(metaUrl: string, basename: string): string {
|
|
23
|
-
const file = fileURLToPath(metaUrl);
|
|
24
|
-
return path.join(path.dirname(file), `${basename}${path.extname(file) || ".js"}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function isBundledVirtualEntrypoint(entrypoint: string): boolean {
|
|
28
|
-
return entrypoint.startsWith("/$bunfs/");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function runtimeArgsForEntrypoint(metaUrl: string, entrypoint: string): string[] {
|
|
32
|
-
if (path.extname(entrypoint) !== ".ts") return [entrypoint];
|
|
33
|
-
if (process.versions.bun) return [entrypoint];
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const req = createRequire(metaUrl);
|
|
37
|
-
const tsxPkg = req.resolve("tsx/package.json");
|
|
38
|
-
const tsxLoader = path.join(path.dirname(tsxPkg), "dist", "loader.mjs");
|
|
39
|
-
if (existsSync(tsxLoader)) return ["--import", pathToFileURL(tsxLoader).href, entrypoint];
|
|
40
|
-
} catch {
|
|
41
|
-
// fall through to bare specifier
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return ["--import", "tsx", entrypoint];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function isMainModule(metaUrl: string): boolean {
|
|
48
|
-
const entry = process.argv[1];
|
|
49
|
-
if (!entry) return false;
|
|
50
|
-
|
|
51
|
-
const modulePath = fileURLToPath(metaUrl);
|
|
52
|
-
try {
|
|
53
|
-
return realpathSync(entry) === realpathSync(modulePath);
|
|
54
|
-
} catch {
|
|
55
|
-
return path.resolve(entry) === path.resolve(modulePath);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function getUnbrowseHome(): string {
|
|
60
|
-
return path.join(os.homedir(), ".unbrowse");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function ensureDir(dir: string): string {
|
|
64
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
65
|
-
return dir;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function getLogsDir(): string {
|
|
69
|
-
return ensureDir(path.join(getUnbrowseHome(), "logs"));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function getRunDir(): string {
|
|
73
|
-
return ensureDir(process.env.UNBROWSE_RUN_DIR || path.join(getUnbrowseHome(), "run"));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function sanitizeSegment(value: string): string {
|
|
77
|
-
return value.replace(/[^a-zA-Z0-9.-]+/g, "_");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function getServerPidFile(baseUrl: string): string {
|
|
81
|
-
const url = new URL(baseUrl);
|
|
82
|
-
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
83
|
-
const host = sanitizeSegment(url.hostname || "127.0.0.1");
|
|
84
|
-
return path.join(getRunDir(), `server-${host}-${port}.json`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function getServerAutostartLogFile(): string {
|
|
88
|
-
return path.join(getLogsDir(), "server-autostart.log");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function isManagedSkillInstall(metaUrl: string): boolean {
|
|
92
|
-
const managedDir = process.env.UNBROWSE_SKILL_DIR || path.join(os.homedir(), ".agents", "skills", "unbrowse");
|
|
93
|
-
const pkgRoot = getPackageRoot(metaUrl);
|
|
94
|
-
try {
|
|
95
|
-
return realpathSync(pkgRoot) === realpathSync(managedDir);
|
|
96
|
-
} catch {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { ensureDir } from "./paths.js";
|
|
6
|
-
import { findKuriBinary, getKuriSourceCandidates } from "../kuri/client.js";
|
|
7
|
-
import { detectHostEnvironment, type HostEnvironment } from "./browser-host.js";
|
|
8
|
-
import { log } from "../logger.js";
|
|
9
|
-
import { checkWalletConfigured, type WalletCheckResult } from "../payments/wallet.js";
|
|
10
|
-
import { configureUpdateHintHooks, saveInstallSource, type UpdateHookStatus } from "./update-hints.js";
|
|
11
|
-
|
|
12
|
-
export type SetupScope = "auto" | "global" | "project" | "off";
|
|
13
|
-
|
|
14
|
-
export type SetupReport = {
|
|
15
|
-
os: {
|
|
16
|
-
platform: NodeJS.Platform;
|
|
17
|
-
release: string;
|
|
18
|
-
arch: string;
|
|
19
|
-
};
|
|
20
|
-
host_environment: HostEnvironment;
|
|
21
|
-
package_managers: {
|
|
22
|
-
npm: boolean;
|
|
23
|
-
npx: boolean;
|
|
24
|
-
bun: boolean;
|
|
25
|
-
brew: boolean;
|
|
26
|
-
};
|
|
27
|
-
browser_engine: {
|
|
28
|
-
installed: boolean;
|
|
29
|
-
action: "already-installed" | "installed" | "failed" | "skipped";
|
|
30
|
-
message?: string;
|
|
31
|
-
};
|
|
32
|
-
opencode: {
|
|
33
|
-
detected: boolean;
|
|
34
|
-
action: "installed" | "updated" | "skipped" | "not-detected";
|
|
35
|
-
scope: "global" | "project" | "off";
|
|
36
|
-
};
|
|
37
|
-
update_hints: UpdateHookStatus[];
|
|
38
|
-
wallet: WalletCheckResult & {
|
|
39
|
-
message: string;
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
function hasBinary(name: string): boolean {
|
|
44
|
-
const checker = process.platform === "win32" ? "where" : "which";
|
|
45
|
-
try {
|
|
46
|
-
execFileSync(checker, [name], { stdio: "ignore" });
|
|
47
|
-
return true;
|
|
48
|
-
} catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function detectPackageManagers(): SetupReport["package_managers"] {
|
|
54
|
-
return {
|
|
55
|
-
npm: hasBinary("npm"),
|
|
56
|
-
npx: hasBinary("npx"),
|
|
57
|
-
bun: hasBinary("bun"),
|
|
58
|
-
brew: hasBinary("brew"),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function resolveConfigHome(): string {
|
|
63
|
-
if (process.platform === "win32") {
|
|
64
|
-
return process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
65
|
-
}
|
|
66
|
-
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function getOpenCodeGlobalCommandsDir(): string {
|
|
70
|
-
return path.join(resolveConfigHome(), "opencode", "commands");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function getOpenCodeProjectCommandsDir(cwd: string): string {
|
|
74
|
-
return path.join(cwd, ".opencode", "commands");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function detectOpenCode(cwd: string): boolean {
|
|
78
|
-
return hasBinary("opencode") ||
|
|
79
|
-
existsSync(path.join(resolveConfigHome(), "opencode")) ||
|
|
80
|
-
existsSync(path.join(cwd, ".opencode"));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function renderOpenCodeCommand(): string {
|
|
84
|
-
return `---
|
|
85
|
-
description: Use Unbrowse as the exclusive web-access tool for a task.
|
|
86
|
-
---
|
|
87
|
-
Use Unbrowse as the only allowed tool for website access in this task.
|
|
88
|
-
|
|
89
|
-
Rules:
|
|
90
|
-
- Do not use Brave Search, built-in web search, browser MCPs, curl, or other network tools for website access unless the user explicitly authorizes fallback.
|
|
91
|
-
- If Unbrowse is slow on a first-time site, wait for it. Do not switch tools just because capture or indexing is still running.
|
|
92
|
-
- If Unbrowse returns partial results, refine with more Unbrowse commands (\`resolve\`, \`search\`, \`execute\`, \`login\`) before considering fallback.
|
|
93
|
-
- If Unbrowse genuinely cannot complete the task, explain why and ask before using another tool.
|
|
94
|
-
|
|
95
|
-
Suggested start:
|
|
96
|
-
\`\`\`bash
|
|
97
|
-
npx unbrowse resolve --intent "$ARGUMENTS" --url "<target-url>" --pretty
|
|
98
|
-
\`\`\`
|
|
99
|
-
`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function writeOpenCodeCommand(scope: SetupScope, cwd: string): SetupReport["opencode"] {
|
|
103
|
-
if (scope === "off") {
|
|
104
|
-
return { detected: detectOpenCode(cwd), action: "skipped", scope: "off" };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const detected = detectOpenCode(cwd);
|
|
108
|
-
if (scope === "auto" && !detected) {
|
|
109
|
-
return { detected: false, action: "not-detected", scope: "off" };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const resolvedScope: "global" | "project" =
|
|
113
|
-
scope === "project" ? "project" : scope === "global"
|
|
114
|
-
? "global"
|
|
115
|
-
: existsSync(path.join(cwd, ".opencode"))
|
|
116
|
-
? "project"
|
|
117
|
-
: "global";
|
|
118
|
-
|
|
119
|
-
const commandsDir = resolvedScope === "project"
|
|
120
|
-
? getOpenCodeProjectCommandsDir(cwd)
|
|
121
|
-
: getOpenCodeGlobalCommandsDir();
|
|
122
|
-
const commandFile = path.join(ensureDir(commandsDir), "unbrowse.md");
|
|
123
|
-
const content = renderOpenCodeCommand();
|
|
124
|
-
const action = existsSync(commandFile) ? "updated" : "installed";
|
|
125
|
-
mkdirSync(path.dirname(commandFile), { recursive: true });
|
|
126
|
-
writeFileSync(commandFile, content);
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
detected: detected || scope !== "auto",
|
|
130
|
-
action,
|
|
131
|
-
scope: resolvedScope,
|
|
132
|
-
command_file: commandFile,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export async function ensureBrowserEngineInstalled(): Promise<SetupReport["browser_engine"]> {
|
|
137
|
-
const binary = findKuriBinary();
|
|
138
|
-
if (existsSync(binary)) {
|
|
139
|
-
return { installed: true, action: "already-installed" };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync(path.join(candidate, "build.zig")));
|
|
143
|
-
if (!sourceDir) {
|
|
144
|
-
return {
|
|
145
|
-
installed: false,
|
|
146
|
-
action: "failed",
|
|
147
|
-
message: `Kuri binary not found. Checked ${binary}`,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (!hasBinary("zig")) {
|
|
152
|
-
return {
|
|
153
|
-
installed: false,
|
|
154
|
-
action: "failed",
|
|
155
|
-
message: `Kuri source found at ${sourceDir}, but Zig is not installed`,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
execFileSync("zig", ["build", "-Doptimize=ReleaseFast"], {
|
|
161
|
-
cwd: sourceDir,
|
|
162
|
-
stdio: "inherit",
|
|
163
|
-
timeout: 300_000,
|
|
164
|
-
});
|
|
165
|
-
const builtBinary = findKuriBinary();
|
|
166
|
-
if (existsSync(builtBinary)) {
|
|
167
|
-
return {
|
|
168
|
-
installed: true,
|
|
169
|
-
action: "installed",
|
|
170
|
-
message: `Built Kuri from ${sourceDir}`,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
return {
|
|
174
|
-
installed: false,
|
|
175
|
-
action: "failed",
|
|
176
|
-
message: `Kuri build completed but ${builtBinary} was not created`,
|
|
177
|
-
};
|
|
178
|
-
} catch (error) {
|
|
179
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
-
return { installed: false, action: "failed", message };
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export async function runSetup(options?: {
|
|
185
|
-
cwd?: string;
|
|
186
|
-
opencode?: SetupScope;
|
|
187
|
-
installBrowser?: boolean;
|
|
188
|
-
}): Promise<SetupReport> {
|
|
189
|
-
const cwd = options?.cwd || process.cwd();
|
|
190
|
-
const installSource = saveInstallSource(import.meta.url);
|
|
191
|
-
|
|
192
|
-
const hostEnv = detectHostEnvironment();
|
|
193
|
-
log("setup", `detected host environment: ${hostEnv}`);
|
|
194
|
-
|
|
195
|
-
const browser = options?.installBrowser === false
|
|
196
|
-
? { installed: false, action: "skipped" as const }
|
|
197
|
-
: await ensureBrowserEngineInstalled();
|
|
198
|
-
const walletCheck = checkWalletConfigured();
|
|
199
|
-
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
200
|
-
const lobsterInstalled = hasBinary("lobstercash") ||
|
|
201
|
-
existsSync(path.join(os.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
202
|
-
|
|
203
|
-
// Auto-setup Crossmint lobster.cash if the wallet tooling is already present.
|
|
204
|
-
if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
|
|
205
|
-
console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
|
|
206
|
-
try {
|
|
207
|
-
execFileSync("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
208
|
-
stdio: "inherit",
|
|
209
|
-
timeout: 60_000,
|
|
210
|
-
});
|
|
211
|
-
// Re-check after setup
|
|
212
|
-
const recheck = checkWalletConfigured();
|
|
213
|
-
if (recheck.configured) {
|
|
214
|
-
console.log(`[unbrowse] wallet configured (${recheck.provider})`);
|
|
215
|
-
}
|
|
216
|
-
} catch {
|
|
217
|
-
console.warn("[unbrowse] Crossmint lobster.cash setup failed or was skipped — continuing without wallet");
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Re-check wallet state after potential setup
|
|
222
|
-
const finalWalletCheck = checkWalletConfigured();
|
|
223
|
-
const wallet = {
|
|
224
|
-
...finalWalletCheck,
|
|
225
|
-
lobster_installed: lobsterInstalled,
|
|
226
|
-
message: finalWalletCheck.configured
|
|
227
|
-
? `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.`
|
|
228
|
-
: lobsterInstalled
|
|
229
|
-
? "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"
|
|
230
|
-
: "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.",
|
|
231
|
-
install_hint: finalWalletCheck.configured
|
|
232
|
-
? undefined
|
|
233
|
-
: lobsterInstalled
|
|
234
|
-
? "npx @crossmint/lobster-cli setup"
|
|
235
|
-
: "npx @crossmint/lobster-cli setup",
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
os: {
|
|
240
|
-
platform: process.platform,
|
|
241
|
-
release: os.release(),
|
|
242
|
-
arch: process.arch,
|
|
243
|
-
},
|
|
244
|
-
host_environment: hostEnv,
|
|
245
|
-
package_managers: detectPackageManagers(),
|
|
246
|
-
browser_engine: browser,
|
|
247
|
-
opencode: writeOpenCodeCommand(options?.opencode ?? "auto", cwd),
|
|
248
|
-
update_hints: configureUpdateHintHooks(import.meta.url, installSource),
|
|
249
|
-
wallet,
|
|
250
|
-
};
|
|
251
|
-
}
|