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 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, installs browser assets, 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.
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
- - Installs browser assets needed for live capture.
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 as createRequire2 } from "node:module";
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 = createRequire2(metaUrl);
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 { createRequire as createRequire3 } from "node:module";
488
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
489
- import os2 from "node:os";
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
- var req = createRequire3(import.meta.url);
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
- execFileSync(checker, [name], { stdio: "ignore" });
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 || path4.join(os2.homedir(), "AppData", "Roaming");
565
+ return process.env.APPDATA || path6.join(os3.homedir(), "AppData", "Roaming");
512
566
  }
513
- return process.env.XDG_CONFIG_HOME || path4.join(os2.homedir(), ".config");
567
+ return process.env.XDG_CONFIG_HOME || path6.join(os3.homedir(), ".config");
514
568
  }
515
569
  function getOpenCodeGlobalCommandsDir() {
516
- return path4.join(resolveConfigHome(), "opencode", "commands");
570
+ return path6.join(resolveConfigHome(), "opencode", "commands");
517
571
  }
518
572
  function getOpenCodeProjectCommandsDir(cwd) {
519
- return path4.join(cwd, ".opencode", "commands");
573
+ return path6.join(cwd, ".opencode", "commands");
520
574
  }
521
575
  function detectOpenCode(cwd) {
522
- return hasBinary("opencode") || existsSync4(path4.join(resolveConfigHome(), "opencode")) || existsSync4(path4.join(cwd, ".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" : existsSync4(path4.join(cwd, ".opencode")) ? "project" : "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 = path4.join(ensureDir(commandsDir), "unbrowse.md");
606
+ const commandFile = path6.join(ensureDir(commandsDir), "unbrowse.md");
553
607
  const content = renderOpenCodeCommand();
554
- const action = existsSync4(commandFile) ? "updated" : "installed";
555
- mkdirSync4(path4.dirname(commandFile), { recursive: true });
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
- const { chromium } = await import("playwright-core");
567
- if (existsSync4(chromium.executablePath())) {
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
- return { installed: true, action: "installed" };
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: os2.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, path5, body) {
625
- const res = await fetch(`${BASE_URL}${path5}`, {
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, path5, entityIndex) {
712
- if (!path5 || obj == null)
792
+ function resolvePath(obj, path7, entityIndex) {
793
+ if (!path7 || obj == null)
713
794
  return obj;
714
- const segments = path5.split(".");
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 path5 = colonIdx >= 0 ? f.slice(colonIdx + 1) : f;
754
- const resolved = resolvePath(item, path5, entityIndex ?? undefined) ?? [];
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 path5 = domain ? "/v1/search/domain" : "/v1/search";
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", path5, body), !!flags.pretty);
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.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 kuriRepoPath = process.env.KURI_PATH ?? `${process.env.HOME}/kuri`;
134
- return `${kuriRepoPath}/zig-out/bin/kuri`;
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
- try {
132
- const { chromium } = await import("playwright-core");
133
- if (existsSync(chromium.executablePath())) {
134
- return { installed: true, action: "already-installed" };
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
- const agentBrowserBin = req.resolve("agent-browser/bin/agent-browser.js");
138
- execFileSync(process.execPath, [agentBrowserBin, "install"], {
151
+ try {
152
+ execFileSync("zig", ["build", "-Doptimize=ReleaseFast"], {
153
+ cwd: sourceDir,
139
154
  stdio: "inherit",
140
155
  timeout: 300_000,
141
156
  });
142
- return { installed: true, action: "installed" };
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