unbrowse 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -2
- package/dist/cli.js +133 -52
- package/package.json +2 -3
- package/runtime-src/kuri/client.ts +62 -4
- package/runtime-src/runtime/setup.ts +38 -11
- 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/README.md
CHANGED
|
@@ -15,7 +15,7 @@ One agent learns a site once. Every later agent gets the fast path.
|
|
|
15
15
|
npx unbrowse setup
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`npx unbrowse setup` downloads the CLI on demand,
|
|
18
|
+
`npx unbrowse setup` downloads the CLI on demand, verifies the bundled Kuri runtime, lets you register with an email-shaped display identity, registers the Open Code `/unbrowse` command when Open Code is detected, and starts the local server.
|
|
19
19
|
|
|
20
20
|
For daily use:
|
|
21
21
|
|
|
@@ -30,6 +30,25 @@ If your agent host uses skills:
|
|
|
30
30
|
npx skills add unbrowse-ai/unbrowse
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## Upgrading
|
|
34
|
+
|
|
35
|
+
Unbrowse no longer self-updates at runtime. If you already have Unbrowse installed, upgrade to the latest version after each release or the new flow may not work on your machine.
|
|
36
|
+
|
|
37
|
+
If you installed the CLI globally:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g unbrowse@latest
|
|
41
|
+
unbrowse setup
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If your agent host uses skills, rerun its skill install/update command too:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx skills add unbrowse-ai/unbrowse
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Need help or want release updates? Join the Discord: [discord.gg/VWugEeFNsG](https://discord.gg/VWugEeFNsG)
|
|
51
|
+
|
|
33
52
|
Every CLI command auto-starts the local server on `http://localhost:6969` by default. Override with `UNBROWSE_URL`, `PORT`, or `HOST`. On first startup it auto-registers as an agent with the marketplace and caches credentials in `~/.unbrowse/config.json`. `unbrowse setup` now prompts for an email-shaped identity first; headless setups can provide `UNBROWSE_AGENT_EMAIL`.
|
|
34
53
|
|
|
35
54
|
Works with Claude Code, Open Code, Cursor, Codex, Windsurf, and any agent host that can call a local CLI or skill.
|
|
@@ -37,7 +56,7 @@ Works with Claude Code, Open Code, Cursor, Codex, Windsurf, and any agent host t
|
|
|
37
56
|
## What setup does
|
|
38
57
|
|
|
39
58
|
- Checks local prerequisites for the npm/npx flow.
|
|
40
|
-
-
|
|
59
|
+
- Verifies the bundled Kuri binary, or builds it from the vendored Kuri source when working from repo source with Zig installed.
|
|
41
60
|
- Registers the Open Code `/unbrowse` command when Open Code is present.
|
|
42
61
|
- Starts the local Unbrowse server unless `--no-start` is passed.
|
|
43
62
|
|
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
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 __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
21
3
|
|
|
22
4
|
// ../../src/cli.ts
|
|
23
5
|
import { config as loadEnv } from "dotenv";
|
|
@@ -322,7 +304,7 @@ import { spawn } from "node:child_process";
|
|
|
322
304
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, realpathSync } from "node:fs";
|
|
323
305
|
import os from "node:os";
|
|
324
306
|
import path from "node:path";
|
|
325
|
-
import { createRequire
|
|
307
|
+
import { createRequire } from "node:module";
|
|
326
308
|
import { fileURLToPath } from "node:url";
|
|
327
309
|
function getModuleDir(metaUrl) {
|
|
328
310
|
return path.dirname(fileURLToPath(metaUrl));
|
|
@@ -344,7 +326,7 @@ function runtimeArgsForEntrypoint(metaUrl, entrypoint) {
|
|
|
344
326
|
if (process.versions.bun)
|
|
345
327
|
return [entrypoint];
|
|
346
328
|
try {
|
|
347
|
-
const req =
|
|
329
|
+
const req = createRequire(metaUrl);
|
|
348
330
|
const tsxPkg = req.resolve("tsx/package.json");
|
|
349
331
|
const tsxLoader = path.join(path.dirname(tsxPkg), "dist", "loader.mjs");
|
|
350
332
|
if (existsSync2(tsxLoader))
|
|
@@ -483,16 +465,88 @@ function isMainModule(metaUrl) {
|
|
|
483
465
|
}
|
|
484
466
|
|
|
485
467
|
// ../../src/runtime/setup.ts
|
|
486
|
-
import { execFileSync } from "node:child_process";
|
|
487
|
-
import {
|
|
488
|
-
import
|
|
489
|
-
import
|
|
468
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
469
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
470
|
+
import os3 from "node:os";
|
|
471
|
+
import path6 from "node:path";
|
|
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
|
|
490
479
|
import path4 from "node:path";
|
|
491
|
-
|
|
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";
|
|
486
|
+
}
|
|
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;
|
|
497
|
+
}
|
|
498
|
+
function resolveBinaryOnPath(name) {
|
|
499
|
+
const checker = process.platform === "win32" ? "where" : "which";
|
|
500
|
+
try {
|
|
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;
|
|
504
|
+
} catch {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function addCandidate(candidates, candidate) {
|
|
509
|
+
if (!candidate)
|
|
510
|
+
return;
|
|
511
|
+
if (!candidates.includes(candidate))
|
|
512
|
+
candidates.push(candidate);
|
|
513
|
+
}
|
|
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;
|
|
524
|
+
}
|
|
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));
|
|
534
|
+
}
|
|
535
|
+
addCandidate(candidates, resolveBinaryOnPath("kuri"));
|
|
536
|
+
return candidates;
|
|
537
|
+
}
|
|
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();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ../../src/runtime/setup.ts
|
|
492
546
|
function hasBinary(name) {
|
|
493
547
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
494
548
|
try {
|
|
495
|
-
|
|
549
|
+
execFileSync2(checker, [name], { stdio: "ignore" });
|
|
496
550
|
return true;
|
|
497
551
|
} catch {
|
|
498
552
|
return false;
|
|
@@ -508,18 +562,18 @@ function detectPackageManagers() {
|
|
|
508
562
|
}
|
|
509
563
|
function resolveConfigHome() {
|
|
510
564
|
if (process.platform === "win32") {
|
|
511
|
-
return process.env.APPDATA ||
|
|
565
|
+
return process.env.APPDATA || path6.join(os3.homedir(), "AppData", "Roaming");
|
|
512
566
|
}
|
|
513
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
567
|
+
return process.env.XDG_CONFIG_HOME || path6.join(os3.homedir(), ".config");
|
|
514
568
|
}
|
|
515
569
|
function getOpenCodeGlobalCommandsDir() {
|
|
516
|
-
return
|
|
570
|
+
return path6.join(resolveConfigHome(), "opencode", "commands");
|
|
517
571
|
}
|
|
518
572
|
function getOpenCodeProjectCommandsDir(cwd) {
|
|
519
|
-
return
|
|
573
|
+
return path6.join(cwd, ".opencode", "commands");
|
|
520
574
|
}
|
|
521
575
|
function detectOpenCode(cwd) {
|
|
522
|
-
return hasBinary("opencode") ||
|
|
576
|
+
return hasBinary("opencode") || existsSync5(path6.join(resolveConfigHome(), "opencode")) || existsSync5(path6.join(cwd, ".opencode"));
|
|
523
577
|
}
|
|
524
578
|
function renderOpenCodeCommand() {
|
|
525
579
|
return `---
|
|
@@ -547,12 +601,12 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
547
601
|
if (scope === "auto" && !detected) {
|
|
548
602
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
549
603
|
}
|
|
550
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" :
|
|
604
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync5(path6.join(cwd, ".opencode")) ? "project" : "global";
|
|
551
605
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
552
|
-
const commandFile =
|
|
606
|
+
const commandFile = path6.join(ensureDir(commandsDir), "unbrowse.md");
|
|
553
607
|
const content = renderOpenCodeCommand();
|
|
554
|
-
const action =
|
|
555
|
-
mkdirSync4(
|
|
608
|
+
const action = existsSync5(commandFile) ? "updated" : "installed";
|
|
609
|
+
mkdirSync4(path6.dirname(commandFile), { recursive: true });
|
|
556
610
|
writeFileSync3(commandFile, content);
|
|
557
611
|
return {
|
|
558
612
|
detected: detected || scope !== "auto",
|
|
@@ -562,17 +616,44 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
562
616
|
};
|
|
563
617
|
}
|
|
564
618
|
async function ensureBrowserEngineInstalled() {
|
|
619
|
+
const binary = findKuriBinary();
|
|
620
|
+
if (existsSync5(binary)) {
|
|
621
|
+
return { installed: true, action: "already-installed" };
|
|
622
|
+
}
|
|
623
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync5(path6.join(candidate, "build.zig")));
|
|
624
|
+
if (!sourceDir) {
|
|
625
|
+
return {
|
|
626
|
+
installed: false,
|
|
627
|
+
action: "failed",
|
|
628
|
+
message: `Kuri binary not found. Checked ${binary}`
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
if (!hasBinary("zig")) {
|
|
632
|
+
return {
|
|
633
|
+
installed: false,
|
|
634
|
+
action: "failed",
|
|
635
|
+
message: `Kuri source found at ${sourceDir}, but Zig is not installed`
|
|
636
|
+
};
|
|
637
|
+
}
|
|
565
638
|
try {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
return { installed: true, action: "already-installed" };
|
|
569
|
-
}
|
|
570
|
-
const agentBrowserBin = req.resolve("agent-browser/bin/agent-browser.js");
|
|
571
|
-
execFileSync(process.execPath, [agentBrowserBin, "install"], {
|
|
639
|
+
execFileSync2("zig", ["build", "-Doptimize=ReleaseFast"], {
|
|
640
|
+
cwd: sourceDir,
|
|
572
641
|
stdio: "inherit",
|
|
573
642
|
timeout: 300000
|
|
574
643
|
});
|
|
575
|
-
|
|
644
|
+
const builtBinary = findKuriBinary();
|
|
645
|
+
if (existsSync5(builtBinary)) {
|
|
646
|
+
return {
|
|
647
|
+
installed: true,
|
|
648
|
+
action: "installed",
|
|
649
|
+
message: `Built Kuri from ${sourceDir}`
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
installed: false,
|
|
654
|
+
action: "failed",
|
|
655
|
+
message: `Kuri build completed but ${builtBinary} was not created`
|
|
656
|
+
};
|
|
576
657
|
} catch (error) {
|
|
577
658
|
const message = error instanceof Error ? error.message : String(error);
|
|
578
659
|
return { installed: false, action: "failed", message };
|
|
@@ -584,7 +665,7 @@ async function runSetup(options) {
|
|
|
584
665
|
return {
|
|
585
666
|
os: {
|
|
586
667
|
platform: process.platform,
|
|
587
|
-
release:
|
|
668
|
+
release: os3.release(),
|
|
588
669
|
arch: process.arch
|
|
589
670
|
},
|
|
590
671
|
package_managers: detectPackageManagers(),
|
|
@@ -621,8 +702,8 @@ function parseArgs(argv) {
|
|
|
621
702
|
}
|
|
622
703
|
return { command, args: positional, flags };
|
|
623
704
|
}
|
|
624
|
-
async function api2(method,
|
|
625
|
-
const res = await fetch(`${BASE_URL}${
|
|
705
|
+
async function api2(method, path7, body) {
|
|
706
|
+
const res = await fetch(`${BASE_URL}${path7}`, {
|
|
626
707
|
method,
|
|
627
708
|
headers: {
|
|
628
709
|
...body ? { "Content-Type": "application/json" } : {},
|
|
@@ -708,10 +789,10 @@ function detectEntityIndex(data) {
|
|
|
708
789
|
}
|
|
709
790
|
return best ? buildEntityIndex(best) : null;
|
|
710
791
|
}
|
|
711
|
-
function resolvePath(obj,
|
|
712
|
-
if (!
|
|
792
|
+
function resolvePath(obj, path7, entityIndex) {
|
|
793
|
+
if (!path7 || obj == null)
|
|
713
794
|
return obj;
|
|
714
|
-
const segments =
|
|
795
|
+
const segments = path7.split(".");
|
|
715
796
|
let cur = obj;
|
|
716
797
|
for (let i = 0;i < segments.length; i++) {
|
|
717
798
|
if (cur == null)
|
|
@@ -750,8 +831,8 @@ function extractFields(data, fields, entityIndex) {
|
|
|
750
831
|
for (const f of fields) {
|
|
751
832
|
const colonIdx = f.indexOf(":");
|
|
752
833
|
const alias = colonIdx >= 0 ? f.slice(0, colonIdx) : f.split(".").pop();
|
|
753
|
-
const
|
|
754
|
-
const resolved = resolvePath(item,
|
|
834
|
+
const path7 = colonIdx >= 0 ? f.slice(colonIdx + 1) : f;
|
|
835
|
+
const resolved = resolvePath(item, path7, entityIndex ?? undefined) ?? [];
|
|
755
836
|
out[alias] = Array.isArray(resolved) ? resolved.length === 0 ? null : resolved.length === 1 ? resolved[0] : resolved : resolved;
|
|
756
837
|
}
|
|
757
838
|
return out;
|
|
@@ -1039,11 +1120,11 @@ async function cmdSearch(flags) {
|
|
|
1039
1120
|
if (!intent)
|
|
1040
1121
|
die("--intent is required");
|
|
1041
1122
|
const domain = flags.domain;
|
|
1042
|
-
const
|
|
1123
|
+
const path7 = domain ? "/v1/search/domain" : "/v1/search";
|
|
1043
1124
|
const body = { intent, k: Number(flags.k) || 5 };
|
|
1044
1125
|
if (domain)
|
|
1045
1126
|
body.domain = domain;
|
|
1046
|
-
output(await api2("POST",
|
|
1127
|
+
output(await api2("POST", path7, body), !!flags.pretty);
|
|
1047
1128
|
}
|
|
1048
1129
|
async function cmdSessions(flags) {
|
|
1049
1130
|
const domain = flags.domain;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unbrowse",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Reverse-engineer any website into reusable API skills. npm CLI + local engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"runtime-src",
|
|
12
|
+
"vendor/kuri",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
@@ -25,8 +26,6 @@
|
|
|
25
26
|
"cheerio": "^1.2.0",
|
|
26
27
|
"dotenv": "^17.3.1",
|
|
27
28
|
"nanoid": "^5.1.6",
|
|
28
|
-
"agent-browser": "^0.13.0",
|
|
29
|
-
"playwright-core": "^1.58.2",
|
|
30
29
|
"tsx": "^4.20.6",
|
|
31
30
|
"ws": "^8.19.0"
|
|
32
31
|
},
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
* All browser ops go through HTTP — no Playwright, no Node CDP bindings.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
11
|
+
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
12
14
|
import { log } from "../logger.js";
|
|
15
|
+
import { getPackageRoot } from "../runtime/paths.js";
|
|
13
16
|
|
|
14
17
|
const KURI_DEFAULT_PORT = 7700;
|
|
15
18
|
const KURI_STARTUP_TIMEOUT_MS = 10_000;
|
|
@@ -52,6 +55,58 @@ let kuriPort = KURI_DEFAULT_PORT;
|
|
|
52
55
|
let kuriCdpPort: number | null = null;
|
|
53
56
|
let kuriReady = false;
|
|
54
57
|
|
|
58
|
+
function kuriBinaryName(): string {
|
|
59
|
+
return process.platform === "win32" ? "kuri.exe" : "kuri";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function currentBundledKuriTarget(): string | null {
|
|
63
|
+
if (process.platform === "darwin" && process.arch === "arm64") return "darwin-arm64";
|
|
64
|
+
if (process.platform === "darwin" && process.arch === "x64") return "darwin-x64";
|
|
65
|
+
if (process.platform === "linux" && process.arch === "arm64") return "linux-arm64";
|
|
66
|
+
if (process.platform === "linux" && process.arch === "x64") return "linux-x64";
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveBinaryOnPath(name: string): string | null {
|
|
71
|
+
const checker = process.platform === "win32" ? "where" : "which";
|
|
72
|
+
try {
|
|
73
|
+
const output = execFileSync(checker, [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
74
|
+
const match = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
75
|
+
return match || null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function addCandidate(candidates: string[], candidate?: string | null): void {
|
|
82
|
+
if (!candidate) return;
|
|
83
|
+
if (!candidates.includes(candidate)) candidates.push(candidate);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getKuriSourceCandidates(): string[] {
|
|
87
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
88
|
+
const candidates: string[] = [];
|
|
89
|
+
addCandidate(candidates, path.join(packageRoot, "vendor", "kuri-src"));
|
|
90
|
+
addCandidate(candidates, path.join(packageRoot, "submodules", "kuri"));
|
|
91
|
+
if (process.env.KURI_PATH) addCandidate(candidates, process.env.KURI_PATH);
|
|
92
|
+
if (process.env.HOME) addCandidate(candidates, path.join(process.env.HOME, "kuri"));
|
|
93
|
+
return candidates;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getKuriBinaryCandidates(): string[] {
|
|
97
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
98
|
+
const binaryName = kuriBinaryName();
|
|
99
|
+
const target = currentBundledKuriTarget();
|
|
100
|
+
const candidates: string[] = [];
|
|
101
|
+
|
|
102
|
+
if (target) addCandidate(candidates, path.join(packageRoot, "vendor", "kuri", target, binaryName));
|
|
103
|
+
for (const sourceDir of getKuriSourceCandidates()) {
|
|
104
|
+
addCandidate(candidates, path.join(sourceDir, "zig-out", "bin", binaryName));
|
|
105
|
+
}
|
|
106
|
+
addCandidate(candidates, resolveBinaryOnPath("kuri"));
|
|
107
|
+
return candidates;
|
|
108
|
+
}
|
|
109
|
+
|
|
55
110
|
/** Try common CDP ports to find where Chrome is listening. */
|
|
56
111
|
async function discoverCdpPort(): Promise<void> {
|
|
57
112
|
const portsToTry = [9222, 9223, 9224, 9225];
|
|
@@ -128,10 +183,10 @@ async function kuriPost(path: string, params: Record<string, string>, body: unkn
|
|
|
128
183
|
}
|
|
129
184
|
|
|
130
185
|
/** Find the kuri binary — check env, then common build locations. */
|
|
131
|
-
function findKuriBinary(): string {
|
|
186
|
+
export function findKuriBinary(): string {
|
|
132
187
|
if (process.env.KURI_BIN) return process.env.KURI_BIN;
|
|
133
|
-
const
|
|
134
|
-
return
|
|
188
|
+
const candidates = getKuriBinaryCandidates();
|
|
189
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0] ?? kuriBinaryName();
|
|
135
190
|
}
|
|
136
191
|
|
|
137
192
|
/**
|
|
@@ -160,6 +215,9 @@ export async function start(port?: number): Promise<void> {
|
|
|
160
215
|
|
|
161
216
|
const binary = findKuriBinary();
|
|
162
217
|
log("kuri", `starting: ${binary} on port ${kuriPort}`);
|
|
218
|
+
if (!existsSync(binary)) {
|
|
219
|
+
throw new Error(`Kuri binary not found at ${binary}`);
|
|
220
|
+
}
|
|
163
221
|
|
|
164
222
|
// Check if Chrome is already running — if so, pass CDP_URL to connect
|
|
165
223
|
// If not, omit CDP_URL so Kuri launches its own managed Chrome
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
3
|
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { ensureDir } from "./paths.js";
|
|
7
|
-
|
|
8
|
-
const req = createRequire(import.meta.url);
|
|
6
|
+
import { findKuriBinary, getKuriSourceCandidates } from "../kuri/client.js";
|
|
9
7
|
|
|
10
8
|
export type SetupScope = "auto" | "global" | "project" | "off";
|
|
11
9
|
|
|
@@ -128,18 +126,47 @@ function writeOpenCodeCommand(scope: SetupScope, cwd: string): SetupReport["open
|
|
|
128
126
|
}
|
|
129
127
|
|
|
130
128
|
export async function ensureBrowserEngineInstalled(): Promise<SetupReport["browser_engine"]> {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
const binary = findKuriBinary();
|
|
130
|
+
if (existsSync(binary)) {
|
|
131
|
+
return { installed: true, action: "already-installed" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync(path.join(candidate, "build.zig")));
|
|
135
|
+
if (!sourceDir) {
|
|
136
|
+
return {
|
|
137
|
+
installed: false,
|
|
138
|
+
action: "failed",
|
|
139
|
+
message: `Kuri binary not found. Checked ${binary}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!hasBinary("zig")) {
|
|
144
|
+
return {
|
|
145
|
+
installed: false,
|
|
146
|
+
action: "failed",
|
|
147
|
+
message: `Kuri source found at ${sourceDir}, but Zig is not installed`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
136
150
|
|
|
137
|
-
|
|
138
|
-
execFileSync(
|
|
151
|
+
try {
|
|
152
|
+
execFileSync("zig", ["build", "-Doptimize=ReleaseFast"], {
|
|
153
|
+
cwd: sourceDir,
|
|
139
154
|
stdio: "inherit",
|
|
140
155
|
timeout: 300_000,
|
|
141
156
|
});
|
|
142
|
-
|
|
157
|
+
const builtBinary = findKuriBinary();
|
|
158
|
+
if (existsSync(builtBinary)) {
|
|
159
|
+
return {
|
|
160
|
+
installed: true,
|
|
161
|
+
action: "installed",
|
|
162
|
+
message: `Built Kuri from ${sourceDir}`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
installed: false,
|
|
167
|
+
action: "failed",
|
|
168
|
+
message: `Kuri build completed but ${builtBinary} was not created`,
|
|
169
|
+
};
|
|
143
170
|
} catch (error) {
|
|
144
171
|
const message = error instanceof Error ? error.message : String(error);
|
|
145
172
|
return { installed: false, action: "failed", message };
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|