@bivy/bivy 0.18.9-staging.3 → 0.18.9-staging.4

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
@@ -282,7 +282,7 @@ curl -fsSL https://bivy.sh/install.sh | bash
282
282
 
283
283
  Setup helps you choose an agent and configure remote access. Existing agents
284
284
  keep their command, login, and configuration. The installer may use `sudo` to
285
- install missing Node.js or Debian/Ubuntu build tools, but never for `npm install`. To inspect it first,
285
+ install a missing Node.js, but never for `npm install`. To inspect it first,
286
286
  download it with `curl -fsSL https://bivy.sh/install.sh -o install.sh`.
287
287
 
288
288
  Already have Node.js and want to avoid sudo?
package/bin/bivy.mjs CHANGED
@@ -160,6 +160,7 @@ const relaySetupEntry = path.join(repoRoot, packaged ? "dist/relay-setup.js" : "
160
160
  // artifact (src/ is not packaged), so resolve it the same packaged-aware way as
161
161
  // the runtime entries and import it dynamically at call time.
162
162
  const hostedEndpointsEntry = path.join(repoRoot, packaged ? "dist/hosted-endpoints.mjs" : "src/hosted-endpoints.mjs");
163
+ const agentBridgesEntry = path.join(repoRoot, packaged ? "dist/agent-bridges.mjs" : "src/agent-bridges.mjs");
163
164
  const githubConnectEntry = path.join(repoRoot, packaged ? "dist/github-connect-repo.js" : "src/github-connect-repo.ts");
164
165
  const githubAppConnectEntry = path.join(repoRoot, packaged ? "dist/github-app-connect.js" : "src/github-app-connect.ts");
165
166
  const githubAppSyncEntry = path.join(repoRoot, packaged ? "dist/github-app-sync-cli.js" : "src/github-app-sync-cli.ts");
@@ -185,6 +186,27 @@ async function getHostedEndpoints() {
185
186
  return _hostedEndpoints();
186
187
  }
187
188
 
189
+ // Agent SDK bridges (Claude, Pi) install on demand into the data dir, not with
190
+ // Bivy — the daemon and this CLI share src/agent-bridges.mjs for it.
191
+ function loadAgentBridges() {
192
+ return import(pathToFileURL(agentBridgesEntry).href);
193
+ }
194
+
195
+ /** Install any bridge packages the given agents need. True when all are present. */
196
+ async function ensureAgentBridges(agentIds) {
197
+ const bridges = await loadAgentBridges();
198
+ const missing = [...new Set(agentIds.flatMap((id) => bridges.missingBridges(id)))];
199
+ if (!missing.length) return true;
200
+ console.log(c.dim(`Installing agent bridge ${missing.join(", ")} into ${bridges.bridgesDir()}…`));
201
+ try {
202
+ await bridges.installBridges(missing, { stdio: "inherit" });
203
+ return true;
204
+ } catch (error) {
205
+ console.error(c.red(error instanceof Error ? error.message : String(error)));
206
+ return false;
207
+ }
208
+ }
209
+
188
210
  const SERVICE_LABEL = "dev.bivy";
189
211
  const SERVICE_UNIT = "bivy.service";
190
212
 
@@ -587,14 +609,10 @@ async function ensureDeps() {
587
609
  ));
588
610
  return false;
589
611
  }
590
- if (process.platform === "linux" && (!commandExists("make") || !commandExists("g++") || !commandExists("python3"))) {
591
- console.error(c.yellow("Build tools are missing. On Ubuntu/Debian run: sudo apt-get update && sudo apt-get install -y build-essential python3"));
592
- console.error(c.dim("The node-pty terminal dependency needs these tools if no prebuilt binary matches this machine."));
593
- }
594
612
  console.log(c.dim(`Installing dependencies (${cmd} ${args.join(" ")})…`));
595
613
  const code = await run(cmd, args, { cwd: repoRoot });
596
614
  if (code !== 0 || !fs.existsSync(dependencyMarker)) {
597
- console.error(c.red(`${cmd} install failed. Install Node.js 20+ and, if native dependencies failed, build tools (make/g++/python3), then try again.`));
615
+ console.error(c.red(`${cmd} install failed. Install Node.js 20+, then try again.`));
598
616
  return false;
599
617
  }
600
618
  return true;
@@ -628,28 +646,6 @@ function nodePackageLoadable(packageName) {
628
646
  return runQuiet(nodeBin, ["-e", "require(require.resolve(process.argv[1], { paths: [process.argv[2]] }))", packageName, repoRoot]).code === 0;
629
647
  }
630
648
 
631
- async function ensureNodePackage(packageName) {
632
- if (nodePackageInstalled(packageName)) {
633
- console.log(c.green(` ✓ Found Bivy bridge package ${packageName}`));
634
- return true;
635
- }
636
- // Add with the package manager that owns this tree. Running `npm install` in a
637
- // pnpm workspace would write a competing package-lock.json and a hoisted
638
- // node_modules over pnpm's symlink layout, leaving the checkout in a state
639
- // neither tool resolves correctly.
640
- const usesPnpm = fs.existsSync(path.join(repoRoot, "pnpm-lock.yaml"));
641
- const [cmd, baseArgs] = usesPnpm
642
- ? [commandExists("pnpm") ? "pnpm" : "corepack", commandExists("pnpm") ? ["add"] : ["pnpm", "add"]]
643
- : ["npm", ["install", "--no-audit", "--no-fund"]];
644
- if (!commandExists(cmd)) {
645
- console.error(c.red(`${cmd} is required to install ${packageName}.`));
646
- return false;
647
- }
648
- console.log(c.dim(`Installing Bivy bridge package ${packageName}…`));
649
- const code = await run(cmd, [...baseArgs, packageName], { cwd: repoRoot });
650
- return code === 0 && nodePackageInstalled(packageName);
651
- }
652
-
653
649
  const userLocalPrefix = process.env.BIVY_NPM_GLOBAL_PREFIX || path.join(os.homedir(), ".local");
654
650
 
655
651
  async function ensureNpmCommand(command, packageName, label) {
@@ -689,10 +685,13 @@ const KNOWN_AGENT_INSTALLS = [
689
685
  { command: "gemini", npmPackage: "@google/gemini-cli", label: "Gemini CLI" },
690
686
  ];
691
687
 
692
- async function ensureKnownAgents() {
688
+ async function ensureKnownAgents({ bridgesOnly = false } = {}) {
693
689
  if (process.env.BIVY_SKIP_AGENT_PREINSTALL === "1") return true;
690
+ const { AGENT_BRIDGES } = await loadAgentBridges();
691
+ const bridges = await ensureAgentBridges(Object.keys(AGENT_BRIDGES));
692
+ if (bridgesOnly) return bridges;
694
693
  console.log(c.dim("Ensuring known agent integrations are installed…"));
695
- const results = [await ensureNodePackage("@anthropic-ai/claude-agent-sdk")];
694
+ const results = [bridges];
696
695
  for (const agent of KNOWN_AGENT_INSTALLS) {
697
696
  results.push(
698
697
  agent.pythonPackage
@@ -707,11 +706,15 @@ async function ensureKnownAgents() {
707
706
 
708
707
  async function ensureSetupAgent(choice) {
709
708
  if (!choice) return true;
710
- if (choice.runtimeId === "pi") return ensureNpmCommand("pi", "@earendil-works/pi-coding-agent", "Pi");
709
+ if (choice.runtimeId === "pi") {
710
+ const bridge = await ensureAgentBridges(["pi"]);
711
+ const cli = await ensureNpmCommand("pi", "@earendil-works/pi-coding-agent", "Pi");
712
+ return bridge && cli;
713
+ }
711
714
  if (choice.runtimeId === "claude-code-sdk") {
712
- const sdk = await ensureNodePackage("@anthropic-ai/claude-agent-sdk");
715
+ const bridge = await ensureAgentBridges(["claude-code-sdk"]);
713
716
  const cli = await ensureNpmCommand("claude", "@anthropic-ai/claude-code", "Claude Code");
714
- return sdk && cli;
717
+ return bridge && cli;
715
718
  }
716
719
  if (choice.runtimeId === "codex-approvals") return ensureNpmCommand("codex", "@openai/codex", "Codex");
717
720
  if (choice.runtimeId === "opencode") return ensureNpmCommand("opencode", "opencode-ai/opencode", "OpenCode");
@@ -4462,12 +4465,13 @@ async function cmdDoctor(args = []) {
4462
4465
 
4463
4466
  console.log(c.bold("\n Bivy doctor\n"));
4464
4467
  console.log(` ${mark(hasSupportedNode())} Node ${process.version}${hasSupportedNode() ? "" : c.dim(" (needs >= 20.0.0)")}`);
4465
- const ptyInstalled = nodePackageInstalled("node-pty");
4466
- const ptyUsable = ptyInstalled && nodePackageLoadable("node-pty");
4467
- console.log(` ${mark(ptyUsable, true)} terminal PTY ${ptyUsable ? c.green("available") : ptyInstalled ? c.yellow("installed but not loadable — reinstall after installing build tools") : c.yellow("dependency missing — reinstall Bivy; interactive terminals unavailable")}`);
4468
- const claudeBridge = nodePackageInstalled("@anthropic-ai/claude-agent-sdk");
4468
+ const ptyInstalled = nodePackageInstalled("@lydell/node-pty");
4469
+ const ptyUsable = ptyInstalled && nodePackageLoadable("@lydell/node-pty");
4470
+ console.log(` ${mark(ptyUsable, true)} terminal PTY ${ptyUsable ? c.green("available") : ptyInstalled ? c.yellow(`installed but not loadable — no prebuilt binary for ${process.platform}-${process.arch}, or npm ran with --omit=optional`) : c.yellow("dependency missing — reinstall Bivy; interactive terminals unavailable")}`);
4471
+ const { bridgeInstalled } = await loadAgentBridges();
4472
+ const claudeBridge = bridgeInstalled("@anthropic-ai/claude-agent-sdk");
4469
4473
  console.log(` ${mark(claudeBridge, true)} Claude bridge ${claudeBridge ? c.green("installed") : c.dim("optional — installed on first Claude setup/use")}`);
4470
- const piBridge = nodePackageInstalled("@earendil-works/pi-coding-agent");
4474
+ const piBridge = bridgeInstalled("@earendil-works/pi-coding-agent");
4471
4475
  const piNodeOk = nodeAtLeast(22, 19);
4472
4476
  console.log(` ${mark(piBridge && piNodeOk, true)} Pi bridge ${piBridge ? (piNodeOk ? c.green("installed") : c.yellow("installed but needs Node >=22.19")) : c.dim("optional — installed only if you choose Pi")}`);
4473
4477
  console.log(` ${mark(commandExists("git"), true)} git${commandExists("git") ? "" : c.dim(" (recommended for repo-backed sessions)")}`);
@@ -5549,11 +5553,11 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
5549
5553
  case "agents:install":
5550
5554
  case "runtimes:install":
5551
5555
  if (args.includes("-h") || args.includes("--help")) {
5552
- console.log(`Usage: bivy agents:install\n\nInstall known agent integrations (${KNOWN_AGENT_INSTALLS.map((a) => a.label).join(", ")}).`);
5556
+ console.log(`Usage: bivy agents:install [--bridges]\n\nInstall known agent integrations (${KNOWN_AGENT_INSTALLS.map((a) => a.label).join(", ")}).\n\n --bridges only install the SDK bridges Bivy loads for Claude Code and Pi\n (they otherwise install on first use)`);
5553
5557
  break;
5554
5558
  }
5555
5559
  if (!(await ensureDeps())) process.exit(1);
5556
- await ensureKnownAgents();
5560
+ if (!(await ensureKnownAgents({ bridgesOnly: args.includes("--bridges") })) && args.includes("--bridges")) process.exit(1);
5557
5561
  break;
5558
5562
  case "open": {
5559
5563
  if (args.includes("-h") || args.includes("--help")) {
@@ -0,0 +1,26 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ export interface AgentBridge {
4
+ command: string;
5
+ packages: string[];
6
+ }
7
+
8
+ export declare const AGENT_BRIDGES: Record<string, AgentBridge>;
9
+
10
+ export interface ResolvedBridge {
11
+ dir: string;
12
+ version: string;
13
+ source: "bivy" | "bridges";
14
+ }
15
+
16
+ export declare function bridgeVersions(): Record<string, string>;
17
+ export declare function bridgesDir(env?: Record<string, string | undefined>): string;
18
+ export declare function resolveBridge(name: string, dir?: string): ResolvedBridge | undefined;
19
+ export declare function bridgeInstalled(name: string, dir?: string): boolean;
20
+ export declare function missingBridges(agentId: string, dir?: string): string[];
21
+ export declare function bridgesToSync(options?: { dir?: string; commandExists?: (command: string) => boolean }): string[];
22
+ export declare function installBridges(
23
+ names: string[],
24
+ options?: { dir?: string; stdio?: "inherit" | "pipe" },
25
+ ): Promise<{ installed: string[]; output: string }>;
26
+ export declare function enableBridgeResolution(dir?: string): void;
@@ -0,0 +1,271 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ // Agent bridges: the SDK packages Bivy loads in-process to drive an agent
4
+ // (Claude's Agent SDK, Pi's coding-agent library).
5
+ //
6
+ // They are large (hundreds of MB together) and a node usually needs one, so
7
+ // they are not installed with Bivy. They install on first use into a directory
8
+ // next to the node's state, which survives `bivy update` replacing the package
9
+ // directory, and resolve from there whenever Bivy's own node_modules lacks
10
+ // them. In a dev checkout the workspace installs them as optionalDependencies,
11
+ // so normal resolution finds them first and the bridge directory is unused.
12
+ //
13
+ // Plain JS (with a .d.mts) so the CLI in bin/ and the daemon share one copy.
14
+ import fs from "node:fs";
15
+ import module from "node:module";
16
+ import path from "node:path";
17
+ import { spawn } from "node:child_process";
18
+ import { fileURLToPath, pathToFileURL } from "node:url";
19
+
20
+ // src/ in a checkout, dist/ in a release: either way the parent is the package root.
21
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
22
+
23
+ /**
24
+ * Which bridge packages each agent loads, and the CLI whose presence means the
25
+ * agent is in use on this node. Adding a bridge is a row here plus a pinned
26
+ * entry in package.json's optionalDependencies (published as `agentBridges`).
27
+ */
28
+ export const AGENT_BRIDGES = {
29
+ "claude-code-sdk": { command: "claude", packages: ["@anthropic-ai/claude-agent-sdk"] },
30
+ pi: { command: "pi", packages: ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai"] },
31
+ };
32
+
33
+ function readJson(file) {
34
+ try {
35
+ return JSON.parse(fs.readFileSync(file, "utf8"));
36
+ } catch {
37
+ return undefined;
38
+ }
39
+ }
40
+
41
+ function bivyManifest() {
42
+ return readJson(path.join(packageRoot, "package.json")) ?? {};
43
+ }
44
+
45
+ /** Pinned bridge versions: `agentBridges` in a release, optionalDependencies in a checkout. */
46
+ export function bridgeVersions() {
47
+ const pkg = bivyManifest();
48
+ return { ...(pkg.agentBridges ?? pkg.optionalDependencies ?? {}) };
49
+ }
50
+
51
+ /** Where bridges install: $BIVY_BRIDGES_DIR, else `bridges/` in the node's data dir. */
52
+ export function bridgesDir(env = process.env) {
53
+ if (env.BIVY_BRIDGES_DIR) return path.resolve(env.BIVY_BRIDGES_DIR);
54
+ const dataDir = env.BIVY_DATA_DIR ? path.resolve(env.BIVY_DATA_DIR) : path.join(packageRoot, ".bivy");
55
+ return path.join(dataDir, "bridges");
56
+ }
57
+
58
+ /** "@scope/name/sub" -> "@scope/name"; "name/sub" -> "name". */
59
+ function packageNameOf(specifier) {
60
+ const parts = specifier.split("/");
61
+ return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
62
+ }
63
+
64
+ function packageAt(dir) {
65
+ const manifest = readJson(path.join(dir, "package.json"));
66
+ return manifest ? { dir, version: manifest.version } : undefined;
67
+ }
68
+
69
+ /** Find `name` the way Node's resolver walks node_modules upward from `from`. */
70
+ function findUpward(name, from) {
71
+ for (let dir = from; ; dir = path.dirname(dir)) {
72
+ const found = packageAt(path.join(dir, "node_modules", name));
73
+ if (found) return found;
74
+ if (path.dirname(dir) === dir) return undefined;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * npm writes node_modules/.package-lock.json when an install finishes, and we
80
+ * write package.json just before starting one. An interrupted install (the
81
+ * daemon stopped mid-way) leaves half-extracted packages, so trust the
82
+ * directory only when that lockfile is newer than the manifest.
83
+ */
84
+ function installComplete(dir) {
85
+ try {
86
+ return fs.statSync(path.join(dir, "node_modules", ".package-lock.json")).mtimeMs >= fs.statSync(path.join(dir, "package.json")).mtimeMs;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Where a bridge package resolves from, if anywhere: Bivy's own dependency
94
+ * tree first (a checkout), then the bridge directory.
95
+ */
96
+ export function resolveBridge(name, dir = bridgesDir()) {
97
+ const own = findUpward(name, packageRoot);
98
+ if (own) return { ...own, source: "bivy" };
99
+ if (!installComplete(dir)) return undefined;
100
+ const bridged = packageAt(path.join(dir, "node_modules", name));
101
+ return bridged ? { ...bridged, source: "bridges" } : undefined;
102
+ }
103
+
104
+ export function bridgeInstalled(name, dir) {
105
+ return resolveBridge(name, dir) !== undefined;
106
+ }
107
+
108
+ /** Bridge packages `agentId` needs that are not resolvable yet (none for agents without a bridge). */
109
+ export function missingBridges(agentId, dir) {
110
+ return (AGENT_BRIDGES[agentId]?.packages ?? []).filter((name) => !bridgeInstalled(name, dir));
111
+ }
112
+
113
+ function onPath(command, env = process.env) {
114
+ const exts = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD").split(";") : [""];
115
+ for (const dir of (env.PATH ?? "").split(path.delimiter)) {
116
+ for (const ext of exts) {
117
+ try {
118
+ fs.accessSync(path.join(dir, command + ext), fs.constants.X_OK);
119
+ return true;
120
+ } catch {}
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Bridge packages that should be (re)installed now: every package already in
128
+ * the bridge directory or belonging to an agent whose CLI is on PATH, that is
129
+ * either missing or not at Bivy's pinned version. Empty means nothing to do,
130
+ * and computing it touches no network.
131
+ */
132
+ export function bridgesToSync({ dir = bridgesDir(), commandExists = onPath } = {}) {
133
+ const versions = bridgeVersions();
134
+ const wanted = new Set(Object.keys(readJson(path.join(dir, "package.json"))?.dependencies ?? {}));
135
+ for (const bridge of Object.values(AGENT_BRIDGES)) {
136
+ if (commandExists(bridge.command)) bridge.packages.forEach((name) => wanted.add(name));
137
+ }
138
+ return [...wanted].filter((name) => {
139
+ if (!versions[name]) return false;
140
+ const found = resolveBridge(name, dir);
141
+ return !found || (found.source === "bridges" && found.version !== versions[name]);
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Replace nested copies that a bridge's own npm-shrinkwrap pins, which npm
147
+ * `overrides` cannot reach. Driven by the nested overrides in package.json
148
+ * (`"parent": { "child": "version" }`), so a new pin is data, not code.
149
+ */
150
+ function applyNestedOverrides(dir) {
151
+ for (const [parent, pins] of Object.entries(bivyManifest().overrides ?? {})) {
152
+ if (!pins || typeof pins !== "object") continue;
153
+ const parentDir = path.join(dir, "node_modules", parent);
154
+ for (const [child, version] of Object.entries(pins)) {
155
+ const target = path.join(parentDir, "node_modules", child);
156
+ const nested = packageAt(target);
157
+ if (!nested || nested.version === version) continue;
158
+ const source = [packageAt(path.join(dir, "node_modules", child)), findUpward(child, packageRoot)]
159
+ .find((candidate) => candidate?.version === version);
160
+ if (!source) throw new Error(`Security patch source ${child}@${version} is missing`);
161
+ fs.rmSync(target, { recursive: true, force: true });
162
+ fs.cpSync(source.dir, target, { recursive: true });
163
+ }
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Install `names` (at Bivy's pinned versions) into the bridge directory. The
169
+ * directory is an ordinary npm project that keeps earlier bridges and carries
170
+ * Bivy's security overrides. Optional dependencies are omitted: bridges run as
171
+ * libraries against the operator's own agent CLI, never their bundled binaries.
172
+ *
173
+ * `stdio: "inherit"` streams npm's output (CLI); otherwise it is captured.
174
+ * Installs in one process run one at a time, since they share a directory.
175
+ */
176
+ let installQueue = Promise.resolve();
177
+ export function installBridges(names, options = {}) {
178
+ const run = installQueue.then(() => installNow(names, options));
179
+ installQueue = run.catch(() => {});
180
+ return run;
181
+ }
182
+
183
+ function installNow(names, { dir = bridgesDir(), stdio = "pipe" } = {}) {
184
+ const versions = bridgeVersions();
185
+ const unknown = names.filter((name) => !versions[name]);
186
+ if (unknown.length) return Promise.reject(new Error(`Not an agent bridge: ${unknown.join(", ")}`));
187
+
188
+ fs.mkdirSync(dir, { recursive: true });
189
+ // npm does not repair half-extracted packages, so an interrupted earlier
190
+ // install is discarded rather than installed over.
191
+ if (!installComplete(dir)) fs.rmSync(path.join(dir, "node_modules"), { recursive: true, force: true });
192
+ const manifestPath = path.join(dir, "package.json");
193
+ const previous = readJson(manifestPath)?.dependencies ?? {};
194
+ const dependencies = { ...previous };
195
+ for (const name of names) dependencies[name] = versions[name];
196
+ const manifest = { name: "bivy-agent-bridges", private: true, dependencies, overrides: bivyManifest().overrides ?? {} };
197
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
198
+
199
+ return new Promise((resolve, reject) => {
200
+ const child = spawn("npm", ["install", "--omit=optional", "--no-audit", "--no-fund"], {
201
+ cwd: dir,
202
+ stdio: stdio === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"],
203
+ });
204
+ let output = "";
205
+ const append = (chunk) => { output = (output + chunk.toString()).slice(-12000); };
206
+ child.stdout?.on("data", append);
207
+ child.stderr?.on("data", append);
208
+ child.on("error", reject);
209
+ child.on("close", (code) => {
210
+ if (code !== 0) {
211
+ reject(new Error(`npm could not install ${names.join(", ")} (exit ${code ?? "unknown"})${output ? `\n${output}` : ""}`));
212
+ return;
213
+ }
214
+ try {
215
+ applyNestedOverrides(dir);
216
+ resolve({ installed: names, output });
217
+ } catch (error) {
218
+ reject(error);
219
+ }
220
+ });
221
+ });
222
+ }
223
+
224
+ const NOT_FOUND = new Set(["ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND"]);
225
+
226
+ // Same fallback as the in-thread hook below, for Node versions that only have
227
+ // module.register (off-thread, async hooks loaded from this data: URL).
228
+ const ASYNC_HOOKS = `
229
+ let parentURL, names;
230
+ export function initialize(data) { parentURL = data.parentURL; names = new Set(data.names); }
231
+ const nameOf = ${packageNameOf.toString()};
232
+ export async function resolve(specifier, context, next) {
233
+ try { return await next(specifier, context); } catch (error) {
234
+ if (!${JSON.stringify([...NOT_FOUND])}.includes(error?.code) || !names.has(nameOf(specifier))) throw error;
235
+ try { return await next(specifier, { ...context, parentURL }); } catch { throw error; }
236
+ }
237
+ }`;
238
+
239
+ let resolutionEnabled = false;
240
+
241
+ /**
242
+ * Let `import`/`require` of a bridge package fall back to the bridge directory
243
+ * when Bivy's own node_modules lacks it. Idempotent; call before loading a
244
+ * bridge. Only bridge package names are redirected, and only after normal
245
+ * resolution fails, so a checkout's workspace copies always win.
246
+ */
247
+ export function enableBridgeResolution(dir = bridgesDir()) {
248
+ if (resolutionEnabled) return;
249
+ resolutionEnabled = true;
250
+ const parentURL = pathToFileURL(path.join(dir, "package.json")).href;
251
+ const names = Object.keys(bridgeVersions());
252
+ if (typeof module.registerHooks === "function") {
253
+ const bridges = new Set(names);
254
+ module.registerHooks({
255
+ resolve(specifier, context, next) {
256
+ try {
257
+ return next(specifier, context);
258
+ } catch (error) {
259
+ if (!NOT_FOUND.has(error?.code) || !bridges.has(packageNameOf(specifier))) throw error;
260
+ try {
261
+ return next(specifier, { ...context, parentURL });
262
+ } catch {
263
+ throw error;
264
+ }
265
+ }
266
+ },
267
+ });
268
+ } else if (typeof module.register === "function") {
269
+ module.register(`data:text/javascript,${encodeURIComponent(ASYNC_HOOKS)}`, { data: { parentURL, names } });
270
+ }
271
+ }
@@ -55,7 +55,7 @@ export function claudeCodeIntegration(origin) {
55
55
  notes: installed
56
56
  ? "Uses the Claude Code executable already on this node, including its native auth, configuration, and sessions. Set BIVY_CLAUDE_MODEL to pick a default model."
57
57
  : agentInstalled
58
- ? "The Claude Code agent is installed, but this Bivy distribution is missing its SDK bridge dependency."
58
+ ? "The Claude Code agent is installed; Bivy installs its SDK bridge when you start the first Claude session (or run 'bivy agents:install --bridges')."
59
59
  : "Install and sign in to Claude Code on this node; Bivy will connect to that existing agent.",
60
60
  install: installed || agentInstalled ? undefined : {
61
61
  label: "Install Claude Code",
@@ -13,7 +13,6 @@
13
13
  // * The SDK is loaded with a dynamic import so it stays an *optional*
14
14
  // dependency: a Bivy install only needs it when this runtime is selected.
15
15
  import { withSessionCredentials, credentialEnvFallback } from "../../credentials/session.js";
16
- import { createRequire } from "node:module";
17
16
  import { randomUUID } from "node:crypto";
18
17
  import { EventEmitter } from "node:events";
19
18
  import { withExactCapabilitySurface } from "../../runtime/types.js";
@@ -30,6 +29,7 @@ import { anthropicCredentialPreflight, describeAnthropicError, isAnthropicAuthEr
30
29
  import { toModelInfo as sharedToModelInfo } from "../../runtime/normalize.js";
31
30
  import { hasLiveProcessForCwd } from "../../runtime/native-process-scan.js";
32
31
  import { bivySessionEnv } from "../../runtime/session-env.js";
32
+ import { bridgeInstalled, enableBridgeResolution } from "../../agent-bridges.mjs";
33
33
  /** Binary names a live Claude Code process could be running under (see
34
34
  * native-process-scan.ts's best-effort cwd match). */
35
35
  const CLAUDE_BIN_NAMES = ["claude"];
@@ -190,20 +190,15 @@ export function claudeRuntimeFromEnv() {
190
190
  }
191
191
  /** True when `@anthropic-ai/claude-agent-sdk` is resolvable in this install. */
192
192
  export function claudeSdkInstalled() {
193
- try {
194
- createRequire(import.meta.url).resolve(SDK_PACKAGE);
195
- return true;
196
- }
197
- catch {
198
- return false;
199
- }
193
+ return bridgeInstalled(SDK_PACKAGE);
200
194
  }
201
195
  async function loadSdk() {
196
+ enableBridgeResolution();
202
197
  try {
203
198
  return await import(SDK_PACKAGE);
204
199
  }
205
200
  catch {
206
- throw new Error(`The claude-code-sdk runtime requires the "${SDK_PACKAGE}" package. Install it with: npm install ${SDK_PACKAGE}`);
201
+ throw new Error(`The claude-code-sdk runtime requires the "${SDK_PACKAGE}" bridge. Install it with: bivy agents:install --bridges`);
207
202
  }
208
203
  }
209
204
  /**
@@ -5,6 +5,7 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { defineAgentIntegration } from "../definition.js";
7
7
  import { PI_CAPABILITIES } from "./capabilities.js";
8
+ import { bridgeInstalled, enableBridgeResolution } from "../../agent-bridges.mjs";
8
9
  export const PI_TESTED_VERSION = "0.87.1";
9
10
  export function piAgentDir() {
10
11
  return process.env.PI_CODING_AGENT_DIR?.trim() || path.join(os.homedir(), ".pi", "agent");
@@ -18,6 +19,7 @@ export function piCommand() {
18
19
  * PI_CODING_AGENT_DIR (normally ~/.pi/agent). Takeover discovery must inspect
19
20
  * that native store rather than only the governed one. */
20
21
  export async function listNativePiSessions() {
22
+ enableBridgeResolution();
21
23
  const { SessionManager } = await import("@earendil-works/pi-coding-agent");
22
24
  // Do not pass the sessions root here. Pi's default store is split into one
23
25
  // encoded subdirectory per cwd, and SessionManager.listAll(customDir) scans
@@ -40,13 +42,7 @@ export function piCommandAvailable() {
40
42
  return resolveExecutable(piCommand()) !== null;
41
43
  }
42
44
  export function piBridgeInstalled() {
43
- try {
44
- import.meta.resolve("@earendil-works/pi-coding-agent");
45
- return true;
46
- }
47
- catch {
48
- return false;
49
- }
45
+ return bridgeInstalled("@earendil-works/pi-coding-agent");
50
46
  }
51
47
  function unsupportedNodeMessage() {
52
48
  return `Pi requires Node.js 22.19+ (found ${process.version}). Upgrade Node, or select another agent such as Claude Code/Codex/OpenCode.`;
@@ -67,6 +63,7 @@ export class LazyPiRuntime {
67
63
  async runtime() {
68
64
  if (!nodeSupportsPi())
69
65
  throw new Error(unsupportedNodeMessage());
66
+ enableBridgeResolution();
70
67
  this.inner ??= import("./runtime.js").then(({ PiRuntime }) => new PiRuntime(this.options));
71
68
  return this.inner;
72
69
  }
@@ -13,6 +13,7 @@
13
13
  // inference SDK, kept under src/runtime/ so the core daemon (src/server.ts) stays
14
14
  // free of any @earendil-works import.
15
15
  import { createPiModelRuntime } from "./pi-oauth.js";
16
+ import { enableBridgeResolution } from "../agent-bridges.mjs";
16
17
  function cleanSessionName(value) {
17
18
  return value
18
19
  .replace(/[\r\n"'`]/g, " ")
@@ -54,6 +55,7 @@ export async function suggestNameFromSelectedModel(opts) {
54
55
  if (!prompt)
55
56
  return undefined;
56
57
  try {
58
+ enableBridgeResolution();
57
59
  const [{ completeSimple }, { ModelRegistry }] = await Promise.all([
58
60
  import("@earendil-works/pi-ai/compat"),
59
61
  import("@earendil-works/pi-coding-agent"),
@@ -12,6 +12,7 @@ import path from "node:path";
12
12
  import { createCredentialVault } from "./credential-store.js";
13
13
  import { isNativeOAuthProvider } from "./oauth/model-oauth-providers.js";
14
14
  import { selectedCredentialStore } from "../credentials/selected-store.js";
15
+ import { enableBridgeResolution } from "../agent-bridges.mjs";
15
16
  /** Adapt Bivy's store to pi-ai's structurally-identical CredentialStore for injection. */
16
17
  export function piCredentialStore(store) {
17
18
  return store;
@@ -25,6 +26,7 @@ export function piCredentialStore(store) {
25
26
  */
26
27
  export async function createPiModelRuntime(opts) {
27
28
  const store = opts.store ?? createCredentialVault(opts.credsDir);
29
+ enableBridgeResolution();
28
30
  const { ModelRuntime } = await import("@earendil-works/pi-coding-agent");
29
31
  const selected = selectedCredentialStore(store, opts.credsDir, { workspace: opts.workspace, credentialLabels: opts.credentialLabels });
30
32
  for (const provider of Object.keys(opts.credentialLabels ?? {}))
package/dist/server.js CHANGED
@@ -12,6 +12,8 @@ import { fileURLToPath } from "node:url";
12
12
  import express from "express";
13
13
  import { rateLimit } from "express-rate-limit";
14
14
  import { WebSocketServer, WebSocket } from "ws";
15
+ import { bridgesToSync, installBridges, missingBridges } from "./agent-bridges.mjs";
16
+ import { resolveExecutable } from "./executable.js";
15
17
  import { listRuntimes, catalogRuntimes, agentInstallSpec, canonicalAgentId, invalidateCliProbeCache, pluginAgentConflictDiagnostics } from "./runtime/index.js";
16
18
  import { createRunPolicy } from "./policy/run-policy.js";
17
19
  import { DEFAULT_BACKOFF } from "./policy/ruleset.js";
@@ -764,6 +766,35 @@ function runInstallCommand(spec) {
764
766
  });
765
767
  });
766
768
  }
769
+ /**
770
+ * Install the SDK bridge packages `id` loads in-process, if any are missing
771
+ * (see src/agent-bridges.mjs). Bridges are not shipped with Bivy; the first
772
+ * session with an agent installs its bridge, announced like an agent install.
773
+ */
774
+ async function ensureAgentBridges(id) {
775
+ const missing = missingBridges(id);
776
+ if (!missing.length)
777
+ return;
778
+ broadcast({ type: "runtime.install.start", id, command: `npm install ${missing.join(" ")}` });
779
+ await installBridges(missing);
780
+ invalidateCliProbeCache();
781
+ }
782
+ /**
783
+ * At startup, bring bridges up to Bivy's pinned versions, and install them for
784
+ * agents whose CLI is on PATH, so the first session doesn't wait on npm. This
785
+ * is also how a node updated from a release that bundled bridges gets them
786
+ * back. A no-op without network access when nothing changed.
787
+ */
788
+ async function syncAgentBridges() {
789
+ const names = bridgesToSync({ commandExists: (command) => resolveExecutable(command) !== null });
790
+ if (!names.length)
791
+ return;
792
+ console.log(`[bridges] Installing agent bridges: ${names.join(", ")}`);
793
+ await installBridges(names);
794
+ invalidateCliProbeCache();
795
+ const runtimes = runtimeList(active?.runtimeId ?? defaultRuntimeId);
796
+ broadcast({ type: "runtime.updated", current: runtimeSummary(getRuntime(defaultRuntimeId)), runtimes });
797
+ }
767
798
  /**
768
799
  * Return an available runtime, auto-installing allowlisted optional adapters when
769
800
  * a normal session/select path asks for them. The explicit /api/runtimes/install
@@ -771,6 +802,9 @@ function runInstallCommand(spec) {
771
802
  * to press it before starting a regular managed session.
772
803
  */
773
804
  async function ensureRuntimeAvailable(requested, sandbox) {
805
+ const bridgeAgent = canonicalAgentId(requested ?? defaultRuntimeId);
806
+ if (bridgeAgent)
807
+ await ensureAgentBridges(bridgeAgent);
774
808
  try {
775
809
  return getRuntime(requested, sandbox);
776
810
  }
@@ -2593,6 +2627,9 @@ const RELAY_COMMANDS = {
2593
2627
  return;
2594
2628
  }
2595
2629
  try {
2630
+ // A missing bridge also reads as "not available"; install it first so an
2631
+ // agent whose CLI is already here isn't reinstalled for nothing.
2632
+ await ensureAgentBridges(spec.id);
2596
2633
  const before = runtimeList().find((runtime) => runtime.id === spec.id);
2597
2634
  if (before?.status !== "available")
2598
2635
  await runInstallCommand(spec);
@@ -11413,6 +11450,7 @@ const server = app.listen(port, host, async () => {
11413
11450
  console.log(`Agent data dir: ${piDir}`);
11414
11451
  console.log(`Workspace: ${defaultWorkspace}`);
11415
11452
  startRelayIfConfigured();
11453
+ void syncAgentBridges().catch((error) => console.warn("[bridges] Could not install agent bridges; they will install on first use:", error instanceof Error ? error.message : error));
11416
11454
  if (appGateway) {
11417
11455
  appGateway.server.on("error", (error) => { console.error("[apps] Preview gateway could not start:", error); shutdown("preview gateway failure"); });
11418
11456
  appGateway.server.listen(appPreviewPort, "127.0.0.1", () => console.log(`[apps] Preview gateway listening on 127.0.0.1:${appPreviewPort}`));
package/dist/terminal.js CHANGED
@@ -1,26 +1,24 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-only
2
2
  // Copyright (c) 2026 Petter André Sjulstad
3
3
  import { randomUUID } from "node:crypto";
4
- import fs from "node:fs";
5
4
  import { resolveExecutable } from "./executable.js";
6
5
  import { createRequire } from "node:module";
7
- import path from "node:path";
8
6
  import { depCacheEnv } from "./harness/dep-cache.js";
9
7
  let ptyModule;
10
8
  export function loadPty() {
11
9
  if (ptyModule)
12
10
  return ptyModule;
13
11
  if (ptyModule === null) {
14
- throw new Error("PTY support is unavailable because the node-pty dependency is not installed. Reinstall Bivy after installing build tools, or use governed chat/exec sessions instead of interactive terminals.");
12
+ throw new Error("PTY support is unavailable because the node-pty dependency is not installed. Reinstall Bivy, or use governed chat/exec sessions instead of interactive terminals.");
15
13
  }
16
14
  try {
17
- ptyModule = createRequire(import.meta.url)("node-pty");
15
+ ptyModule = createRequire(import.meta.url)("@lydell/node-pty");
18
16
  return ptyModule;
19
17
  }
20
18
  catch (error) {
21
19
  ptyModule = null;
22
20
  const detail = error instanceof Error && error.message ? ` (${error.message.split("\n")[0]})` : "";
23
- throw new Error(`PTY support is unavailable because the node-pty dependency could not be loaded${detail}. Reinstall Bivy after installing build tools, or use governed chat/exec sessions instead of interactive terminals.`);
21
+ throw new Error(`PTY support is unavailable because the node-pty dependency could not be loaded${detail}. Prebuilt terminals cover macOS, Windows and glibc Linux on x64/arm64; reinstall Bivy, or use governed chat/exec sessions instead of interactive terminals.`);
24
22
  }
25
23
  }
26
24
  /**
@@ -72,57 +70,6 @@ function defaultShell() {
72
70
  * PTY an unambiguous absolute path.
73
71
  */
74
72
  export { resolveExecutable } from "./executable.js";
75
- /**
76
- * Restore the execute bit on node-pty's bundled `spawn-helper` binary.
77
- *
78
- * On macOS/Linux, node-pty launches every PTY by exec'ing this helper. Its
79
- * prebuilds ship without the execute bit (mode 0664), and a fresh `npm install`
80
- * — exactly what `bivy update` runs — resets the bit. node-pty does not surface
81
- * this: its spawn prints the cryptic "posix_spawnp failed." into the terminal and
82
- * exits, so `bivy run <agent>` dies before the agent starts and keeps breaking
83
- * after every update until someone runs `chmod +x` by hand.
84
- *
85
- * Fix it once, up front, so runs survive updates and reinstalls automatically.
86
- * Best-effort and idempotent: if node-pty can't be resolved or the helper is
87
- * already executable, this is a no-op and node-pty reports its own errors.
88
- */
89
- let spawnHelperEnsured = false;
90
- function ensureSpawnHelperExecutable() {
91
- if (spawnHelperEnsured)
92
- return;
93
- spawnHelperEnsured = true;
94
- if (process.platform === "win32")
95
- return; // Windows has no spawn-helper.
96
- const makeExecutable = (file) => {
97
- try {
98
- const st = fs.statSync(file);
99
- if (!st.isFile() || (st.mode & 0o111) !== 0)
100
- return; // missing or already +x
101
- fs.chmodSync(file, st.mode | 0o755);
102
- }
103
- catch {
104
- // Missing candidate or read-only install — best-effort.
105
- }
106
- };
107
- try {
108
- const require = createRequire(import.meta.url);
109
- const root = path.dirname(require.resolve("node-pty/package.json"));
110
- // Cover both a from-source build and the prebuilt binaries node-pty ships.
111
- makeExecutable(path.join(root, "build", "Release", "spawn-helper"));
112
- const prebuilds = path.join(root, "prebuilds");
113
- try {
114
- for (const dir of fs.readdirSync(prebuilds)) {
115
- makeExecutable(path.join(prebuilds, dir, "spawn-helper"));
116
- }
117
- }
118
- catch {
119
- // No prebuilds directory — fine.
120
- }
121
- }
122
- catch {
123
- // Couldn't resolve node-pty; leave it to node-pty to report.
124
- }
125
- }
126
73
  export class TerminalManager {
127
74
  terminals = new Map();
128
75
  /** The number of live terminals (for tests / capacity reporting). */
@@ -158,10 +105,6 @@ export class TerminalManager {
158
105
  ? `Cannot run "${shell}": command not found or not executable on the node's PATH. Install it, or check the command.`
159
106
  : `Cannot start a terminal: no usable shell found (tried "${shell}"). Set the SHELL environment variable.`);
160
107
  }
161
- // node-pty exec's a bundled `spawn-helper` whose execute bit a fresh install
162
- // (e.g. `bivy update`) strips — restore it before spawning or this fails with
163
- // "posix_spawnp failed." See ensureSpawnHelperExecutable().
164
- ensureSpawnHelperExecutable();
165
108
  const pty = loadPty();
166
109
  const proc = pty.spawn(resolved, shellArgs, {
167
110
  name: "xterm-256color",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.18.9-staging.3",
3
+ "version": "0.18.9-staging.4",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",
@@ -34,16 +34,15 @@
34
34
  "bivy": "node bin/bivy.mjs",
35
35
  "start": "node dist/server.js",
36
36
  "dev": "node dist/server.js",
37
- "relay:setup": "node bin/bivy.mjs relay:setup",
38
- "postinstall": "node bin/repair-pty-permissions.mjs && node bin/patch-pi-dependencies.mjs"
37
+ "relay:setup": "node bin/bivy.mjs relay:setup"
39
38
  },
40
39
  "dependencies": {
40
+ "@lydell/node-pty": "1.2.0-beta.15",
41
41
  "@modelcontextprotocol/sdk": "1.30.0",
42
42
  "@novnc/novnc": "1.7.0",
43
43
  "brace-expansion": "5.0.12",
44
44
  "express": "^5.2.1",
45
45
  "express-rate-limit": "^8.7.0",
46
- "node-pty": "^1.1.0",
47
46
  "semver": "^7.7.2",
48
47
  "typebox": "^1.3.34",
49
48
  "undici": "8.10.2",
@@ -51,11 +50,6 @@
51
50
  "yaml": "^2.9.1",
52
51
  "zod": "^4.6.5"
53
52
  },
54
- "optionalDependencies": {
55
- "@anthropic-ai/claude-agent-sdk": "0.3.281",
56
- "@earendil-works/pi-ai": "0.87.1",
57
- "@earendil-works/pi-coding-agent": "0.87.1"
58
- },
59
53
  "overrides": {
60
54
  "fast-uri": "3.1.6",
61
55
  "hono": "4.13.5",
@@ -71,6 +65,11 @@
71
65
  "lodash-es": "4.18.1",
72
66
  "undici": "8.10.2"
73
67
  },
74
- "readme": "# Bivy\n\n[![npm](https://img.shields.io/npm/v/@bivy/bivy?color=2b6cb0&label=%40bivy%2Fbivy)](https://www.npmjs.com/package/@bivy/bivy)\n[![license: AGPL-3.0-only](https://img.shields.io/badge/license-AGPL--3.0--only-2b6cb0)](LICENSE)\n[![node](https://img.shields.io/badge/node-%E2%89%A520-2b6cb0)](https://nodejs.org)\n\n**Build, try, and review apps from your phone. Your coding agent and dev\nenvironment stay on your own machines.**\n\nBivy is an open-source remote development environment built around coding\nagents. Ask for a change, try the running app, and point to or draw on what\nneeds fixing. Send that feedback to the agent and try again—all in the same\nsession, without going back to your computer.\n\nLive previews bring your app to your phone or browser without deploying it to\nRender, building a Docker image, or setting up a separate preview server.\nShare a preview link so someone else can try it and leave notes, too.\n\nKeep Claude Code, Codex, Pi, OpenCode, or another supported agent. Keep your\nrepos, tools, databases, and development environment. Bivy connects them to\nchat, a terminal, app previews, and code review wherever you are. Start work\nyourself, or let an issue, CI failure, message, schedule, or webhook start it.\n\n**[Start free on Bivy Cloud](https://app.bivy.sh)** ·\n**[Quickstart](docs/quickstart.md)** ·\n**[Documentation](docs/README.md)** ·\n**[Self-host](docs/deploy-images.md)** ·\n**[Website](https://bivy.sh)**\n\n**Recommended:** sign in at [app.bivy.sh](https://app.bivy.sh), then copy your\npersonalized **Connect a Machine** command into a terminal on your Mac or Linux\ncomputer. It installs and enrolls the machine without another Bivy login. The\nbrowser connects automatically; choose a repository and send your first task\nright there.\n\nPrefer starting from the terminal?\n\n```bash\ncurl -fsSL https://bivy.sh/install.sh | bash # install + guided setup\ncd your-repo\nbivy run claude # or codex, pi, opencode\nbivy open # continue in the web app (needs remote setup)\n```\n\nBivy Cloud hosts the app, control plane, and relay—not the machines running your\nagents. Connect a Mac, Linux computer, or existing server and bring your own\nagent subscription, model API key, or local model. You can also self-host the\nentire remote-access stack.\n\n> **Bivy is 0.x software.** Claude Code, Codex, Pi, and OpenCode are the\n> release-tested paths. Credential sync, resume, handoffs, approvals, and\n> sandboxing depend on the runtime. See the\n> [runtime support matrix](docs/runtime-support-matrix.md).\n\n## Build → preview → mark up → iterate\n\nRemote chat lets you ask an agent for a change. Bivy also lets you **use what\nit built and show it what to fix**, without a separate deployment step.\n\n1. **Ask for a change.** The agent works in your repo with your existing tools\n and local services.\n2. **Try the app.** Open a live preview from the session. Use the running app,\n not just a screenshot of what the agent says it finished. Bivy can discover\n dev servers running in the session workspace; tap **Preview** to open one.\n3. **Mark up what needs work.** Point at an element or circle and draw on the\n preview. Add a note, then send the draft to the agent with the element and\n page context. Marked-up screenshots and before/after comparison are available\n when agent screenshots are enabled.\n4. **Iterate and review.** Try the next version, inspect the diff, review checks,\n and decide when the work is ready—not just when the agent stops.\n5. **Bring someone else into the review.** Copy a public preview link so they\n can use the app without a Bivy account and leave notes on specific elements.\n Bring their notes into the session when you're ready to act on them.\n\nOn Bivy Cloud, preview delivery is built in: no per-app domains, certificates,\npublic ports, or tunnels to configure. Self-hosters configure preview delivery\nonce for their Bivy deployment, not for every app. The app still needs its normal\nbuild or dev-server setup, and **the machine serving it must stay awake and\nonline**. These are development previews, not production hosting.\n\n**Share deliberately:** anyone with a preview link can use that app, including\nits live backend. Links expire after 24 hours and can be revoked sooner.\nReviewer notes aren't sent to the agent automatically; you send them or explicitly\nallow agent access. Preview traffic uses HTTPS through the preview relay, not\nsession end-to-end encryption; the relay operator can see it.\n\n[App previews, visual feedback, and sharing →](docs/apps.md)\n\n## Your agents and machines, one workspace\n\n| Capability | What it means for you |\n|---|---|\n| **One workspace, multiple agents** | Use different agents and models for different tasks without maintaining a separate workflow for each. |\n| **Your machines and environment** | Work beside your existing repos, dev servers, databases, private networks, toolchains, and GPUs. |\n| **Automations and triggers** | Let issues, failed CI, messages, schedules, and webhooks start work instead of copying requests into a chat. |\n| **Encrypted key and OAuth sync** | Reuse Bivy-managed provider credentials across enrolled machines and compatible runtimes, with less repeated setup. |\n| **Live sessions from anywhere** | Build and review from your phone, browser, or terminal; answer questions and approve supported actions in the same session. |\n| **Live app previews** | Try the running app without deploying it to a separate hosting service. |\n| **Visual feedback and sharing** | Point, draw, and send specific feedback to the agent; share a preview link for outside review. |\n| **Reviewable results** | Inspect the app, changes, declared checks, artifacts, and pull requests—not just an agent's claim that it finished. |\n| **Hosted convenience or self-hosting** | Use Bivy Cloud for managed remote access, or run the same open-source core yourself. |\n\n## One workflow, from trigger to review\n\n```text\nPrompt · GitHub issue · CI failure · Linear · Slack · Schedule · Webhook\n │\n ▼\n Choose machine + agent + model\n + supported credentials\n │\n ▼\n Live agent session\n Join · steer · approve · stop\n │\n ▼\n Try app · inspect changes · checks\n │\n ▼\n Mark up · send feedback · iterate\n │\n ▼\n Share preview · review PR\n```\n\nA **Machine** is a computer or server you connect. A **Session** is live agent\nwork on that machine. A **Run** is delegated background work that creates a\nsession and tracks its outcome. An **Automation** is a reusable definition that\ncreates runs when an event matches.\n\nManual and automated work use the same kind of live session. You can join a run\nwhen it needs help rather than wait for a black-box job to finish.\n\n### Work in the environment you already have\n\nA clean cloud sandbox isn't always enough. Your agent may need the database\nrunning on localhost, an uncommitted change, an internal API behind your VPN,\nor a model running on your GPU. Bivy runs the agent where those things already\nexist, subject to that machine's permissions and the runtime's protection.\n\nConnect several machines to the same account: a laptop for interactive work,\na Linux server for background jobs, or a GPU box for local inference. Choose\nthe machine for each session or pin it in an automation. Repository runs can\nuse isolated Git worktrees without rebuilding the whole development environment.\n\n**The execution machine must stay awake and online.** To close your laptop and\nleave work running, run the agent on a different, always-on machine.\n\n[Environment and multi-machine recipes →](docs/capability-recipes.md)\n\n### Use multiple agents, not multiple disconnected workflows\n\nRun Claude Code for one task, Codex for another, and Pi or OpenCode where they\nfit. Bivy supplies the shared session, remote-access, automation, and review\nsurfaces; your chosen agent still does the coding and uses your model provider.\n\n- Choose an agent and, where supported, a model for each session or run.\n- Import existing Claude Code and Codex sessions.\n- Fork or move work to another agent or machine when a different setup fits\n better. Continuation fidelity varies: some paths preserve native history,\n while others replay portable turns or seed the destination with context.\n- Use agent-native logins, Bivy-managed credentials, or local inference.\n Bivy's custom OpenAI-compatible endpoint registry currently feeds Pi;\n other agents may need their own provider configuration.\n- Register your own ACP or headless process agent with `bivy agent add`.\n\nBivy does not replace your agent, provide model inference, or make every agent's\nfeatures identical. Consult the [support matrix](docs/runtime-support-matrix.md)\nand [handoff recipes](docs/capability-recipes.md#fork-or-move-a-session).\n\n### Less signing in. Less copying secrets.\n\nBivy syncs **Bivy-managed API keys and supported OAuth credentials** across\nenrolled machines for compatible runtimes. Connect supported credentials once\nand reuse them where you run work, rather than manually distributing keys to\neach machine.\n\nFor ordinary account sync, credentials are encrypted on the node before upload.\nThe control plane stores ciphertext and wrapped-key metadata; enrolled nodes\nshare access by wrapping the vault key to one another. Bivy Cloud does not\nreceive plaintext credentials through this sync path.\n\nYou can also keep credentials local, use labeled keys and project presets, or\nreference environment variables and 1Password instead of embedding secrets in\nconfiguration:\n\n```bash\nbivy provider login\nbivy credentials add anthropic work\nbivy secrets ref github.repo-token op://Bivy/GitHub/repo-token\n```\n\n**Not every CLI login syncs.** Native agent logins may still be per-machine;\nGitHub App private-key sync is separately opt-in. If you lose every node and\ndevice able to unwrap a vault, you must sign in to providers again. Explicit\nhosted-provisioning custody grants are separate from ordinary encrypted sync.\n\n[Credential sync and runtime coverage →](docs/credential-sync.md) ·\n[Credentials guide →](docs/credentials-guide.md) ·\n[Key storage →](docs/key-management.md)\n\n### Let events start the work\n\nAutomations turn recurring or incoming work into sessions you can join,\nsupervise, and review. Choose the repository, machine, agent, model, approval\nmode, sandbox setting, and maximum attempts.\n\n| Trigger | Example workflow |\n|---|---|\n| **GitHub issues and mentions** | Label an issue `bivy` or `bivy/<machine>`, or mention your Bivy GitHub App, to work toward a pull request. |\n| **Failed CI** | Match a failed workflow, ask the agent to reproduce it, make a fix, and run the affected checks. |\n| **Linear** | Label an issue to start work without copying its description into an agent. |\n| **Slack** | Send a request from the conversation where the work came up. |\n| **Schedules** | Run a weekly dependency review, recurring maintenance, or a one-time task. |\n| **Signed webhooks** | Connect alerts, internal tools, or your own event sources. |\n\nConfigure automations in the app or version them with your repository in\n`.bivy/automations.yaml`:\n\n```bash\nbivy automation init\n# Edit the generated definition for your repository and workflow.\nbivy automation validate\nbivy automation test --event .bivy/events/failed-ci.yaml # supply a local event fixture\nbivy automation apply\n```\n\nOr delegate a one-off job without creating an automation:\n\n```bash\nbivy runs start \"Review outdated dependencies and propose a small, tested update.\"\nbivy runs wait <id>\n```\n\nRuns keep routing and lifecycle evidence, check results, and output references\nin a reviewable Receipt. For unattended issue work, Bivy runs declared repository\nchecks after the agent's turn; failed required checks fail the run even if the\nagent reports success. A completed process alone is not proof that the task\nsucceeded.\n\n[Automation recipes →](docs/capability-recipes.md#let-events-start-runs) ·\n[Automations as code →](docs/automations-as-code.md) ·\n[Run outcomes and reliability limits →](docs/automation-runs.md)\n\n### Start at your desk. Continue anywhere.\n\nOpen the same session in the browser, phone PWA, or terminal. Watch work live,\nanswer questions, approve supported tool calls, or stop the agent.\n\n- Send screenshots, images, logs, and other files from your phone.\n- Download reports and artifacts the agent creates.\n- Open [session apps](docs/apps.md) from chat or the **Apps** menu: live web\n previews, desktop app views, and CLI/TUI tools. Agents can publish views with\n `bivy app publish bivy.app.json` and create share links with `bivy app share`.\n- Use voice input and read-aloud where supported; provider-backed voice may\n send audio or text to the selected provider.\n- Keep a native terminal workflow or use structured chat, depending on the agent.\n\n```bash\nbivy run claude --no-follow # start without attaching\nbivy open # open the web app\nbivy resume # return to the session in your terminal\nbivy link # pair a device directly via QR\n```\n\nNo phone app installation is required. Open [app.bivy.sh](https://app.bivy.sh)\nin your browser; adding it to your home screen is optional.\n\n[Remote access →](docs/remote-access.md) ·\n[Voice, files, and terminal recipes →](docs/capability-recipes.md)\n\n## Get started\n\n### Install\n\nBivy supports **macOS and Linux with Node.js 20+**. The installer installs the\n`@bivy/bivy` package, runs guided setup, and starts a launchd or systemd service:\n\n```bash\ncurl -fsSL https://bivy.sh/install.sh | bash\n```\n\nSetup helps you choose an agent and configure remote access. Existing agents\nkeep their command, login, and configuration. The installer may use `sudo` to\ninstall missing Node.js or Debian/Ubuntu build tools, but never for `npm install`. To inspect it first,\ndownload it with `curl -fsSL https://bivy.sh/install.sh -o install.sh`.\n\nAlready have Node.js and want to avoid sudo?\n\n```bash\nnpm install -g @bivy/bivy\nbivy setup\n```\n\nThen try one small task:\n\n```bash\ncd your-repo\nbivy run claude\n# Ask: \"Explain this repo and make one small, safe improvement. Run the relevant checks.\"\nbivy open\n```\n\nOpen that same session on your phone while it runs. For a web app, ask the agent\nto start its dev server, then open **Apps → Running in this workspace → Preview**.\nTry a page, point to something you'd change, and send the feedback back to the\nagent. No separate preview deployment needed.\n\nOnce that works, share a preview for review, connect another machine, or add\nyour first automation.\n\n**Local-only works too.** `bivy run`, `bivy resume`, and `bivy sessions` need no\naccount or server. Choose **local only for now** during setup; use `bivy login`\nlater. Browser and phone access need a hosted or self-hosted control plane;\nthe node itself does not serve a web UI.\n\n[Full quickstart →](docs/quickstart.md) ·\n[Installer options, service management, and uninstall →](docs/install.md)\n\n### Choose hosted or self-hosted\n\n| Option | What you get |\n|---|---|\n| **Free Cloud — $0** | Every launch feature, including automations; 10 new remote sessions per rolling seven days. No credit card required. |\n| **Cloud — $15/month** | The same features with unlimited remote sessions. |\n| **Self-hosted Core** | Operate the app, control plane, and relay yourself, with no Bivy usage limits. |\n\nManual and automated sessions share the Cloud allowance. Resuming existing\nsessions and viewing history do not consume it. Agent subscriptions and model\nprovider charges are separate. See [current pricing](https://bivy.sh#pricing).\n\n**Self-host anywhere:** deploy the public control-plane (including the web app)\nand relay images with Postgres and [a small set of environment variables](docs/deploy-images.md).\nYour server or container platform handles HTTPS. Set up owner access in the\nbrowser—no SSH, external authentication provider, or Bivy Cloud account required.\nFor a bare VPS, the [Compose installer](docs/self-host-quickstart.md) automates the\nsame stack. These onboarding features require a release containing them.\n\nStart on Cloud and self-host later if you prefer. Deploy the stack, reconnect\nmachines with `bivy relay:setup`, and pair devices to your server. This is not a\none-click migration of your Cloud account; your local repos and agent\nconfiguration stay in place.\n\n```bash\nbivy relay:setup \\\n --control-plane https://bivy.example.com \\\n --relay wss://relay.example.com\n```\n\nSelf-hosting is community-supported: you own TLS, backups, upgrades, and\nhardening. Public multi-architecture images are available as\n`ghcr.io/bivysh/bivy-control-plane` and `ghcr.io/bivysh/bivy-relay`; pin a release\nversion or full commit SHA.\n\n[Deploy the images anywhere →](docs/deploy-images.md) ·\n[Optional VPS installer →](docs/self-host-quickstart.md) ·\n[Operations reference →](docs/self-host.md)\n\n## Architecture\n\nYour environment, with clear security boundaries:\n\n```text\nYour machine Hosted or self-hosted\n┌──────────────────────┐ ┌──────────────────────┐\n│ Node daemon │──outbound──▶│ Relay │\n│ Agents, repos, tools │ │ Encrypted frames │\n│ Local credentials │ └──────────┬───────────┘\n└──────────────────────┘ │\n Browser / phone\n + control plane\n (app, accounts, metadata)\n```\n\n- **Execution stays on your machine.** Bivy Cloud does not run your agents.\n Your model provider still sees whatever the agent sends it.\n- **Interactive session traffic is end-to-end encrypted** between the node and\n paired devices. The relay forwards opaque session frames; your node dials out,\n so no inbound public port is required.\n- **App previews have a separate security boundary.** Preview traffic uses HTTPS\n and an outbound tunnel, not session E2E encryption. The preview ingress/relay\n operator can see it. Public preview links grant anyone holding them access to\n that view and its live backend until expiry or revocation. See\n [preview security and sharing](docs/apps.md#runtime-and-security-boundaries).\n- **Ordinary credential sync uploads ciphertext, not plaintext keys.**\n Supported credentials and recovery limits are documented separately.\n- **Encryption is not universal across integrations.** Slack commands and\n generic webhook instructions reach the control plane in plaintext. Do not\n put secrets in them. Routing and bounded run metadata are also visible there.\n- **Device authorization matters.** QR pairing authorizes a device directly\n through the node. Hosted account pairing trusts the control plane to authorize\n devices and serve the web app that holds client keys.\n- **Bivy is not an OS-level sandbox.** The default approval mode is\n `autonomous`; protection depends on the runtime. Some agents enforce sandbox\n tiers, while process agents may run with your full user permissions.\n Heuristic tool checks help prevent accidents but are not isolation.\n\nReview the runtime's Protection label and configure approval/sandbox settings\nfor the task, especially before enabling unattended work.\n\n[Security model and known limitations →](docs/security-model.md) ·\n[Runtime protection matrix →](docs/runtime-support-matrix.md) ·\n[Configuration →](docs/configuration.md)\n\n## Agents and everyday commands\n\n**Claude Code, Codex, Pi, and OpenCode are release-tested.** Additional adapters\ninclude Gemini CLI, Qwen Code, Goose, Aider, Cline, Crush, Cursor, GitHub Copilot,\nGrok, Amp, Auggie, Droid, Continue, Kilo Code, and Rovo Dev. Installation,\nresume, model selection, and tool protection vary—see the\n[support matrix](docs/runtime-support-matrix.md) and [agent guides](docs/agents/README.md).\n\nRun an arbitrary command with `bivy run -- ./your-agent --flags`, register a\nreusable entry with `bivy agent add`, or package a declarative integration with\nexperimental [plugins](docs/plugins.md).\n\n```bash\nbivy run claude # launch a durable session; also codex, pi, opencode\nbivy sessions # list live and saved sessions\nbivy resume # resume the most recent session\nbivy open # open the web app (requires remote setup)\nbivy nodes # list connected account machines\nbivy runs list # inspect delegated work\nbivy automation init # scaffold repo-owned automations\nbivy provider login # connect supported model credentials\nbivy agent add # register an ACP or process agent\nbivy doctor # check installation and connectivity\nbivy logs -f # follow node logs\nbivy update # update and restart the service\n```\n\n`bivy update` uses your original installation method and waits for an active\nturn to finish before restarting. Use `--force` to skip that wait.\n\n[CLI reference →](docs/cli-reference.md) ·\n[Node and project configuration →](docs/config-as-code.md) ·\n[GitHub setup →](docs/github-setup.md) ·\n[Linear setup →](docs/linear-work-queue.md)\n\n## Development and contributions\n\n```bash\npnpm install\npnpm run dev # node daemon on http://localhost:4317\npnpm run dev:web # web client dev server\n```\n\n| Directory | Contents |\n|---|---|\n| `src/`, `bin/` | Node daemon, CLI, runtime adapters, sessions, approvals, secrets |\n| `packages/core/` | Shared protocol, pairing, and wire format |\n| `packages/web/`, `packages/ui/` | React PWA and shared design system |\n| `services/relay/` | Self-hostable encrypted relay |\n| `services/control-plane/` | Self-hostable control plane |\n| `deploy/` | Deployment examples |\n\n```bash\npnpm run typecheck\npnpm run typecheck:web\npnpm run lint\npnpm run test:unit\npnpm run test:core\npnpm run check:licenses\npnpm run check:secrets\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow. Releases\nare published from CI with provenance attestations; see\n[release verification](docs/releasing.md).\n\n**Found a security issue?** Use\n[GitHub private vulnerability reporting](https://github.com/bivysh/bivy/security/advisories/new),\nnot a public issue. See [SECURITY.md](SECURITY.md).\n\n### In development—not available at launch\n\nAutomatically provisioned, short-lived **ephemeral machines** are in development\nfor hosted and bring-your-own-cloud deployments. Neither path is ready or\nsupported for this launch. Use an existing computer or server you operate.\nExperimental provisioning has different credential-custody and encryption\nboundaries; see the [provisioning trust model](docs/hosted-provisioning-trust-model.md).\n\n## License\n\nEverything in this repository—node, CLI, web/PWA, relay, and control plane—is\nfree and open-source **AGPL-3.0-only Core**, with no Bivy usage limits. You may\nuse, modify, and self-host it under that license. If users interact with your\nmodified version over a network, section 13 requires you to offer its\ncorresponding source. See [LICENSE](LICENSE).\n\nBivy Cloud is the hosted operation of that stack plus billing and plans, in a\nseparate private repository. Contributions use the\n[DCO](CONTRIBUTING.md#certificate-of-origin); there is no CLA.\n",
75
- "readmeFilename": "README.md"
68
+ "readme": "# Bivy\n\n[![npm](https://img.shields.io/npm/v/@bivy/bivy?color=2b6cb0&label=%40bivy%2Fbivy)](https://www.npmjs.com/package/@bivy/bivy)\n[![license: AGPL-3.0-only](https://img.shields.io/badge/license-AGPL--3.0--only-2b6cb0)](LICENSE)\n[![node](https://img.shields.io/badge/node-%E2%89%A520-2b6cb0)](https://nodejs.org)\n\n**Build, try, and review apps from your phone. Your coding agent and dev\nenvironment stay on your own machines.**\n\nBivy is an open-source remote development environment built around coding\nagents. Ask for a change, try the running app, and point to or draw on what\nneeds fixing. Send that feedback to the agent and try again—all in the same\nsession, without going back to your computer.\n\nLive previews bring your app to your phone or browser without deploying it to\nRender, building a Docker image, or setting up a separate preview server.\nShare a preview link so someone else can try it and leave notes, too.\n\nKeep Claude Code, Codex, Pi, OpenCode, or another supported agent. Keep your\nrepos, tools, databases, and development environment. Bivy connects them to\nchat, a terminal, app previews, and code review wherever you are. Start work\nyourself, or let an issue, CI failure, message, schedule, or webhook start it.\n\n**[Start free on Bivy Cloud](https://app.bivy.sh)** ·\n**[Quickstart](docs/quickstart.md)** ·\n**[Documentation](docs/README.md)** ·\n**[Self-host](docs/deploy-images.md)** ·\n**[Website](https://bivy.sh)**\n\n**Recommended:** sign in at [app.bivy.sh](https://app.bivy.sh), then copy your\npersonalized **Connect a Machine** command into a terminal on your Mac or Linux\ncomputer. It installs and enrolls the machine without another Bivy login. The\nbrowser connects automatically; choose a repository and send your first task\nright there.\n\nPrefer starting from the terminal?\n\n```bash\ncurl -fsSL https://bivy.sh/install.sh | bash # install + guided setup\ncd your-repo\nbivy run claude # or codex, pi, opencode\nbivy open # continue in the web app (needs remote setup)\n```\n\nBivy Cloud hosts the app, control plane, and relay—not the machines running your\nagents. Connect a Mac, Linux computer, or existing server and bring your own\nagent subscription, model API key, or local model. You can also self-host the\nentire remote-access stack.\n\n> **Bivy is 0.x software.** Claude Code, Codex, Pi, and OpenCode are the\n> release-tested paths. Credential sync, resume, handoffs, approvals, and\n> sandboxing depend on the runtime. See the\n> [runtime support matrix](docs/runtime-support-matrix.md).\n\n## Build → preview → mark up → iterate\n\nRemote chat lets you ask an agent for a change. Bivy also lets you **use what\nit built and show it what to fix**, without a separate deployment step.\n\n1. **Ask for a change.** The agent works in your repo with your existing tools\n and local services.\n2. **Try the app.** Open a live preview from the session. Use the running app,\n not just a screenshot of what the agent says it finished. Bivy can discover\n dev servers running in the session workspace; tap **Preview** to open one.\n3. **Mark up what needs work.** Point at an element or circle and draw on the\n preview. Add a note, then send the draft to the agent with the element and\n page context. Marked-up screenshots and before/after comparison are available\n when agent screenshots are enabled.\n4. **Iterate and review.** Try the next version, inspect the diff, review checks,\n and decide when the work is ready—not just when the agent stops.\n5. **Bring someone else into the review.** Copy a public preview link so they\n can use the app without a Bivy account and leave notes on specific elements.\n Bring their notes into the session when you're ready to act on them.\n\nOn Bivy Cloud, preview delivery is built in: no per-app domains, certificates,\npublic ports, or tunnels to configure. Self-hosters configure preview delivery\nonce for their Bivy deployment, not for every app. The app still needs its normal\nbuild or dev-server setup, and **the machine serving it must stay awake and\nonline**. These are development previews, not production hosting.\n\n**Share deliberately:** anyone with a preview link can use that app, including\nits live backend. Links expire after 24 hours and can be revoked sooner.\nReviewer notes aren't sent to the agent automatically; you send them or explicitly\nallow agent access. Preview traffic uses HTTPS through the preview relay, not\nsession end-to-end encryption; the relay operator can see it.\n\n[App previews, visual feedback, and sharing →](docs/apps.md)\n\n## Your agents and machines, one workspace\n\n| Capability | What it means for you |\n|---|---|\n| **One workspace, multiple agents** | Use different agents and models for different tasks without maintaining a separate workflow for each. |\n| **Your machines and environment** | Work beside your existing repos, dev servers, databases, private networks, toolchains, and GPUs. |\n| **Automations and triggers** | Let issues, failed CI, messages, schedules, and webhooks start work instead of copying requests into a chat. |\n| **Encrypted key and OAuth sync** | Reuse Bivy-managed provider credentials across enrolled machines and compatible runtimes, with less repeated setup. |\n| **Live sessions from anywhere** | Build and review from your phone, browser, or terminal; answer questions and approve supported actions in the same session. |\n| **Live app previews** | Try the running app without deploying it to a separate hosting service. |\n| **Visual feedback and sharing** | Point, draw, and send specific feedback to the agent; share a preview link for outside review. |\n| **Reviewable results** | Inspect the app, changes, declared checks, artifacts, and pull requests—not just an agent's claim that it finished. |\n| **Hosted convenience or self-hosting** | Use Bivy Cloud for managed remote access, or run the same open-source core yourself. |\n\n## One workflow, from trigger to review\n\n```text\nPrompt · GitHub issue · CI failure · Linear · Slack · Schedule · Webhook\n │\n ▼\n Choose machine + agent + model\n + supported credentials\n │\n ▼\n Live agent session\n Join · steer · approve · stop\n │\n ▼\n Try app · inspect changes · checks\n │\n ▼\n Mark up · send feedback · iterate\n │\n ▼\n Share preview · review PR\n```\n\nA **Machine** is a computer or server you connect. A **Session** is live agent\nwork on that machine. A **Run** is delegated background work that creates a\nsession and tracks its outcome. An **Automation** is a reusable definition that\ncreates runs when an event matches.\n\nManual and automated work use the same kind of live session. You can join a run\nwhen it needs help rather than wait for a black-box job to finish.\n\n### Work in the environment you already have\n\nA clean cloud sandbox isn't always enough. Your agent may need the database\nrunning on localhost, an uncommitted change, an internal API behind your VPN,\nor a model running on your GPU. Bivy runs the agent where those things already\nexist, subject to that machine's permissions and the runtime's protection.\n\nConnect several machines to the same account: a laptop for interactive work,\na Linux server for background jobs, or a GPU box for local inference. Choose\nthe machine for each session or pin it in an automation. Repository runs can\nuse isolated Git worktrees without rebuilding the whole development environment.\n\n**The execution machine must stay awake and online.** To close your laptop and\nleave work running, run the agent on a different, always-on machine.\n\n[Environment and multi-machine recipes →](docs/capability-recipes.md)\n\n### Use multiple agents, not multiple disconnected workflows\n\nRun Claude Code for one task, Codex for another, and Pi or OpenCode where they\nfit. Bivy supplies the shared session, remote-access, automation, and review\nsurfaces; your chosen agent still does the coding and uses your model provider.\n\n- Choose an agent and, where supported, a model for each session or run.\n- Import existing Claude Code and Codex sessions.\n- Fork or move work to another agent or machine when a different setup fits\n better. Continuation fidelity varies: some paths preserve native history,\n while others replay portable turns or seed the destination with context.\n- Use agent-native logins, Bivy-managed credentials, or local inference.\n Bivy's custom OpenAI-compatible endpoint registry currently feeds Pi;\n other agents may need their own provider configuration.\n- Register your own ACP or headless process agent with `bivy agent add`.\n\nBivy does not replace your agent, provide model inference, or make every agent's\nfeatures identical. Consult the [support matrix](docs/runtime-support-matrix.md)\nand [handoff recipes](docs/capability-recipes.md#fork-or-move-a-session).\n\n### Less signing in. Less copying secrets.\n\nBivy syncs **Bivy-managed API keys and supported OAuth credentials** across\nenrolled machines for compatible runtimes. Connect supported credentials once\nand reuse them where you run work, rather than manually distributing keys to\neach machine.\n\nFor ordinary account sync, credentials are encrypted on the node before upload.\nThe control plane stores ciphertext and wrapped-key metadata; enrolled nodes\nshare access by wrapping the vault key to one another. Bivy Cloud does not\nreceive plaintext credentials through this sync path.\n\nYou can also keep credentials local, use labeled keys and project presets, or\nreference environment variables and 1Password instead of embedding secrets in\nconfiguration:\n\n```bash\nbivy provider login\nbivy credentials add anthropic work\nbivy secrets ref github.repo-token op://Bivy/GitHub/repo-token\n```\n\n**Not every CLI login syncs.** Native agent logins may still be per-machine;\nGitHub App private-key sync is separately opt-in. If you lose every node and\ndevice able to unwrap a vault, you must sign in to providers again. Explicit\nhosted-provisioning custody grants are separate from ordinary encrypted sync.\n\n[Credential sync and runtime coverage →](docs/credential-sync.md) ·\n[Credentials guide →](docs/credentials-guide.md) ·\n[Key storage →](docs/key-management.md)\n\n### Let events start the work\n\nAutomations turn recurring or incoming work into sessions you can join,\nsupervise, and review. Choose the repository, machine, agent, model, approval\nmode, sandbox setting, and maximum attempts.\n\n| Trigger | Example workflow |\n|---|---|\n| **GitHub issues and mentions** | Label an issue `bivy` or `bivy/<machine>`, or mention your Bivy GitHub App, to work toward a pull request. |\n| **Failed CI** | Match a failed workflow, ask the agent to reproduce it, make a fix, and run the affected checks. |\n| **Linear** | Label an issue to start work without copying its description into an agent. |\n| **Slack** | Send a request from the conversation where the work came up. |\n| **Schedules** | Run a weekly dependency review, recurring maintenance, or a one-time task. |\n| **Signed webhooks** | Connect alerts, internal tools, or your own event sources. |\n\nConfigure automations in the app or version them with your repository in\n`.bivy/automations.yaml`:\n\n```bash\nbivy automation init\n# Edit the generated definition for your repository and workflow.\nbivy automation validate\nbivy automation test --event .bivy/events/failed-ci.yaml # supply a local event fixture\nbivy automation apply\n```\n\nOr delegate a one-off job without creating an automation:\n\n```bash\nbivy runs start \"Review outdated dependencies and propose a small, tested update.\"\nbivy runs wait <id>\n```\n\nRuns keep routing and lifecycle evidence, check results, and output references\nin a reviewable Receipt. For unattended issue work, Bivy runs declared repository\nchecks after the agent's turn; failed required checks fail the run even if the\nagent reports success. A completed process alone is not proof that the task\nsucceeded.\n\n[Automation recipes →](docs/capability-recipes.md#let-events-start-runs) ·\n[Automations as code →](docs/automations-as-code.md) ·\n[Run outcomes and reliability limits →](docs/automation-runs.md)\n\n### Start at your desk. Continue anywhere.\n\nOpen the same session in the browser, phone PWA, or terminal. Watch work live,\nanswer questions, approve supported tool calls, or stop the agent.\n\n- Send screenshots, images, logs, and other files from your phone.\n- Download reports and artifacts the agent creates.\n- Open [session apps](docs/apps.md) from chat or the **Apps** menu: live web\n previews, desktop app views, and CLI/TUI tools. Agents can publish views with\n `bivy app publish bivy.app.json` and create share links with `bivy app share`.\n- Use voice input and read-aloud where supported; provider-backed voice may\n send audio or text to the selected provider.\n- Keep a native terminal workflow or use structured chat, depending on the agent.\n\n```bash\nbivy run claude --no-follow # start without attaching\nbivy open # open the web app\nbivy resume # return to the session in your terminal\nbivy link # pair a device directly via QR\n```\n\nNo phone app installation is required. Open [app.bivy.sh](https://app.bivy.sh)\nin your browser; adding it to your home screen is optional.\n\n[Remote access →](docs/remote-access.md) ·\n[Voice, files, and terminal recipes →](docs/capability-recipes.md)\n\n## Get started\n\n### Install\n\nBivy supports **macOS and Linux with Node.js 20+**. The installer installs the\n`@bivy/bivy` package, runs guided setup, and starts a launchd or systemd service:\n\n```bash\ncurl -fsSL https://bivy.sh/install.sh | bash\n```\n\nSetup helps you choose an agent and configure remote access. Existing agents\nkeep their command, login, and configuration. The installer may use `sudo` to\ninstall a missing Node.js, but never for `npm install`. To inspect it first,\ndownload it with `curl -fsSL https://bivy.sh/install.sh -o install.sh`.\n\nAlready have Node.js and want to avoid sudo?\n\n```bash\nnpm install -g @bivy/bivy\nbivy setup\n```\n\nThen try one small task:\n\n```bash\ncd your-repo\nbivy run claude\n# Ask: \"Explain this repo and make one small, safe improvement. Run the relevant checks.\"\nbivy open\n```\n\nOpen that same session on your phone while it runs. For a web app, ask the agent\nto start its dev server, then open **Apps → Running in this workspace → Preview**.\nTry a page, point to something you'd change, and send the feedback back to the\nagent. No separate preview deployment needed.\n\nOnce that works, share a preview for review, connect another machine, or add\nyour first automation.\n\n**Local-only works too.** `bivy run`, `bivy resume`, and `bivy sessions` need no\naccount or server. Choose **local only for now** during setup; use `bivy login`\nlater. Browser and phone access need a hosted or self-hosted control plane;\nthe node itself does not serve a web UI.\n\n[Full quickstart →](docs/quickstart.md) ·\n[Installer options, service management, and uninstall →](docs/install.md)\n\n### Choose hosted or self-hosted\n\n| Option | What you get |\n|---|---|\n| **Free Cloud — $0** | Every launch feature, including automations; 10 new remote sessions per rolling seven days. No credit card required. |\n| **Cloud — $15/month** | The same features with unlimited remote sessions. |\n| **Self-hosted Core** | Operate the app, control plane, and relay yourself, with no Bivy usage limits. |\n\nManual and automated sessions share the Cloud allowance. Resuming existing\nsessions and viewing history do not consume it. Agent subscriptions and model\nprovider charges are separate. See [current pricing](https://bivy.sh#pricing).\n\n**Self-host anywhere:** deploy the public control-plane (including the web app)\nand relay images with Postgres and [a small set of environment variables](docs/deploy-images.md).\nYour server or container platform handles HTTPS. Set up owner access in the\nbrowser—no SSH, external authentication provider, or Bivy Cloud account required.\nFor a bare VPS, the [Compose installer](docs/self-host-quickstart.md) automates the\nsame stack. These onboarding features require a release containing them.\n\nStart on Cloud and self-host later if you prefer. Deploy the stack, reconnect\nmachines with `bivy relay:setup`, and pair devices to your server. This is not a\none-click migration of your Cloud account; your local repos and agent\nconfiguration stay in place.\n\n```bash\nbivy relay:setup \\\n --control-plane https://bivy.example.com \\\n --relay wss://relay.example.com\n```\n\nSelf-hosting is community-supported: you own TLS, backups, upgrades, and\nhardening. Public multi-architecture images are available as\n`ghcr.io/bivysh/bivy-control-plane` and `ghcr.io/bivysh/bivy-relay`; pin a release\nversion or full commit SHA.\n\n[Deploy the images anywhere →](docs/deploy-images.md) ·\n[Optional VPS installer →](docs/self-host-quickstart.md) ·\n[Operations reference →](docs/self-host.md)\n\n## Architecture\n\nYour environment, with clear security boundaries:\n\n```text\nYour machine Hosted or self-hosted\n┌──────────────────────┐ ┌──────────────────────┐\n│ Node daemon │──outbound──▶│ Relay │\n│ Agents, repos, tools │ │ Encrypted frames │\n│ Local credentials │ └──────────┬───────────┘\n└──────────────────────┘ │\n Browser / phone\n + control plane\n (app, accounts, metadata)\n```\n\n- **Execution stays on your machine.** Bivy Cloud does not run your agents.\n Your model provider still sees whatever the agent sends it.\n- **Interactive session traffic is end-to-end encrypted** between the node and\n paired devices. The relay forwards opaque session frames; your node dials out,\n so no inbound public port is required.\n- **App previews have a separate security boundary.** Preview traffic uses HTTPS\n and an outbound tunnel, not session E2E encryption. The preview ingress/relay\n operator can see it. Public preview links grant anyone holding them access to\n that view and its live backend until expiry or revocation. See\n [preview security and sharing](docs/apps.md#runtime-and-security-boundaries).\n- **Ordinary credential sync uploads ciphertext, not plaintext keys.**\n Supported credentials and recovery limits are documented separately.\n- **Encryption is not universal across integrations.** Slack commands and\n generic webhook instructions reach the control plane in plaintext. Do not\n put secrets in them. Routing and bounded run metadata are also visible there.\n- **Device authorization matters.** QR pairing authorizes a device directly\n through the node. Hosted account pairing trusts the control plane to authorize\n devices and serve the web app that holds client keys.\n- **Bivy is not an OS-level sandbox.** The default approval mode is\n `autonomous`; protection depends on the runtime. Some agents enforce sandbox\n tiers, while process agents may run with your full user permissions.\n Heuristic tool checks help prevent accidents but are not isolation.\n\nReview the runtime's Protection label and configure approval/sandbox settings\nfor the task, especially before enabling unattended work.\n\n[Security model and known limitations →](docs/security-model.md) ·\n[Runtime protection matrix →](docs/runtime-support-matrix.md) ·\n[Configuration →](docs/configuration.md)\n\n## Agents and everyday commands\n\n**Claude Code, Codex, Pi, and OpenCode are release-tested.** Additional adapters\ninclude Gemini CLI, Qwen Code, Goose, Aider, Cline, Crush, Cursor, GitHub Copilot,\nGrok, Amp, Auggie, Droid, Continue, Kilo Code, and Rovo Dev. Installation,\nresume, model selection, and tool protection vary—see the\n[support matrix](docs/runtime-support-matrix.md) and [agent guides](docs/agents/README.md).\n\nRun an arbitrary command with `bivy run -- ./your-agent --flags`, register a\nreusable entry with `bivy agent add`, or package a declarative integration with\nexperimental [plugins](docs/plugins.md).\n\n```bash\nbivy run claude # launch a durable session; also codex, pi, opencode\nbivy sessions # list live and saved sessions\nbivy resume # resume the most recent session\nbivy open # open the web app (requires remote setup)\nbivy nodes # list connected account machines\nbivy runs list # inspect delegated work\nbivy automation init # scaffold repo-owned automations\nbivy provider login # connect supported model credentials\nbivy agent add # register an ACP or process agent\nbivy doctor # check installation and connectivity\nbivy logs -f # follow node logs\nbivy update # update and restart the service\n```\n\n`bivy update` uses your original installation method and waits for an active\nturn to finish before restarting. Use `--force` to skip that wait.\n\n[CLI reference →](docs/cli-reference.md) ·\n[Node and project configuration →](docs/config-as-code.md) ·\n[GitHub setup →](docs/github-setup.md) ·\n[Linear setup →](docs/linear-work-queue.md)\n\n## Development and contributions\n\n```bash\npnpm install\npnpm run dev # node daemon on http://localhost:4317\npnpm run dev:web # web client dev server\n```\n\n| Directory | Contents |\n|---|---|\n| `src/`, `bin/` | Node daemon, CLI, runtime adapters, sessions, approvals, secrets |\n| `packages/core/` | Shared protocol, pairing, and wire format |\n| `packages/web/`, `packages/ui/` | React PWA and shared design system |\n| `services/relay/` | Self-hostable encrypted relay |\n| `services/control-plane/` | Self-hostable control plane |\n| `deploy/` | Deployment examples |\n\n```bash\npnpm run typecheck\npnpm run typecheck:web\npnpm run lint\npnpm run test:unit\npnpm run test:core\npnpm run check:licenses\npnpm run check:secrets\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow. Releases\nare published from CI with provenance attestations; see\n[release verification](docs/releasing.md).\n\n**Found a security issue?** Use\n[GitHub private vulnerability reporting](https://github.com/bivysh/bivy/security/advisories/new),\nnot a public issue. See [SECURITY.md](SECURITY.md).\n\n### In development—not available at launch\n\nAutomatically provisioned, short-lived **ephemeral machines** are in development\nfor hosted and bring-your-own-cloud deployments. Neither path is ready or\nsupported for this launch. Use an existing computer or server you operate.\nExperimental provisioning has different credential-custody and encryption\nboundaries; see the [provisioning trust model](docs/hosted-provisioning-trust-model.md).\n\n## License\n\nEverything in this repository—node, CLI, web/PWA, relay, and control plane—is\nfree and open-source **AGPL-3.0-only Core**, with no Bivy usage limits. You may\nuse, modify, and self-host it under that license. If users interact with your\nmodified version over a network, section 13 requires you to offer its\ncorresponding source. See [LICENSE](LICENSE).\n\nBivy Cloud is the hosted operation of that stack plus billing and plans, in a\nseparate private repository. Contributions use the\n[DCO](CONTRIBUTING.md#certificate-of-origin); there is no CLA.\n",
69
+ "readmeFilename": "README.md",
70
+ "agentBridges": {
71
+ "@anthropic-ai/claude-agent-sdk": "0.3.281",
72
+ "@earendil-works/pi-ai": "0.87.1",
73
+ "@earendil-works/pi-coding-agent": "0.87.1"
74
+ }
76
75
  }
@@ -1,56 +0,0 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: AGPL-3.0-only
3
- // Copyright (c) 2026 Petter André Sjulstad
4
- /**
5
- * pi-coding-agent 0.82.1 publishes an npm-shrinkwrap that pins vulnerable
6
- * transitive packages below its own node_modules. npm overrides update the outer
7
- * lockfile/audit result but do not replace those shrinkwrapped files during
8
- * installation. Replace the affected nested packages with direct, exact patched
9
- * dependencies until pi publishes a corrected shrinkwrap.
10
- *
11
- * This matters for the PUBLISHED package, which users install with npm. It is a
12
- * no-op in this repo's own dev install: pnpm ignores a dependency's shrinkwrap
13
- * and resolves everything through pnpm-lock.yaml, so the vulnerable nested
14
- * copies are never created and the loop below finds nothing to replace. Either
15
- * way scripts/check-security-pins.mjs asserts the end state.
16
- */
17
- import fs from "node:fs";
18
- import path from "node:path";
19
- import { fileURLToPath } from "node:url";
20
-
21
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
22
-
23
- // npm-global installs usually nest dependencies under bivy, while npm/npx may
24
- // hoist them to an ancestor node_modules. Support both layouts.
25
- function findDependency(relativePath) {
26
- let dir = root;
27
- while (true) {
28
- const candidate = path.join(dir, "node_modules", relativePath);
29
- if (fs.existsSync(candidate)) return candidate;
30
- const parent = path.dirname(dir);
31
- if (parent === dir) return undefined;
32
- dir = parent;
33
- }
34
- }
35
-
36
- const piPackage = findDependency(path.join("@earendil-works", "pi-coding-agent"));
37
- if (!piPackage) process.exit(0);
38
-
39
- const patches = [
40
- { name: "brace-expansion", version: "5.0.12" },
41
- { name: "undici", version: "8.10.2" },
42
- ];
43
-
44
- for (const patch of patches) {
45
- const source = findDependency(patch.name);
46
- const target = path.join(piPackage, "node_modules", patch.name);
47
- if (!fs.existsSync(target)) continue;
48
- if (!source) throw new Error(`Security patch source ${patch.name}@${patch.version} is missing`);
49
- const sourcePackage = JSON.parse(fs.readFileSync(path.join(source, "package.json"), "utf8"));
50
- if (sourcePackage.version !== patch.version) {
51
- throw new Error(`Refusing dependency patch from unexpected ${patch.name} ${sourcePackage.version}`);
52
- }
53
- fs.rmSync(target, { recursive: true, force: true });
54
- fs.cpSync(source, target, { recursive: true });
55
- console.log(`Patched pi-coding-agent's nested ${patch.name} to ${patch.version}`);
56
- }
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: AGPL-3.0-only
3
- // Copyright (c) 2026 Petter André Sjulstad
4
- // node-pty 1.1.0's npm tarball ships the Darwin spawn-helper with mode 0644.
5
- // Loading pty.node succeeds, but opening any terminal fails with posix_spawnp.
6
- // Restore executable bits at install time, until upstream fixes the archive.
7
- import fs from "node:fs";
8
- import path from "node:path";
9
- import { createRequire } from "node:module";
10
- import { fileURLToPath } from "node:url";
11
-
12
- export function repairPtyPermissions(root, platform = process.platform, arch = process.arch) {
13
- if (platform !== "darwin") return;
14
- // resolve() handles both nested global installs and npm's hoisted layout.
15
- const require = createRequire(path.join(root, "package.json"));
16
- const ptyRoot = path.dirname(require.resolve("node-pty/package.json"));
17
- // node-pty can load a source build or a shipped prebuild. Only change its
18
- // known helper files, never recurse through arbitrary dependency executables.
19
- for (const dir of ["build/Release", "build/Debug", `prebuilds/${platform}-${arch}`]) {
20
- const helper = path.join(ptyRoot, dir, "spawn-helper");
21
- let stat;
22
- try { stat = fs.lstatSync(helper); }
23
- catch (error) {
24
- if (error.code === "ENOENT") continue;
25
- throw error;
26
- }
27
- if (!stat.isFile() || (stat.mode & 0o111) === 0o111) continue;
28
- fs.chmodSync(helper, (stat.mode & 0o777) | 0o111);
29
- console.log(`Restored executable permissions on node-pty/${dir}/spawn-helper`);
30
- }
31
- }
32
-
33
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
34
- repairPtyPermissions(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
35
- }