@bivy/bivy 0.0.0 → 0.1.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +265 -5
  3. package/bin/acp-shim.mjs +298 -0
  4. package/bin/agent-manifest.json +277 -0
  5. package/bin/bivy.mjs +4100 -0
  6. package/bin/codex-app-server-shim.mjs +447 -0
  7. package/bin/patch-pi-dependencies.mjs +44 -0
  8. package/bin/prune-sessions.mjs +52 -0
  9. package/bin/sessions-list.mjs +27 -0
  10. package/bin/shim-path.mjs +126 -0
  11. package/bin/uninstall-paths.mjs +48 -0
  12. package/dist/approval.js +87 -0
  13. package/dist/attach.js +248 -0
  14. package/dist/auth.js +258 -0
  15. package/dist/bivy-login.js +180 -0
  16. package/dist/browser-open.js +50 -0
  17. package/dist/control-plane-tasks.js +236 -0
  18. package/dist/data-dir.js +25 -0
  19. package/dist/device-registry.js +201 -0
  20. package/dist/e2e.js +70 -0
  21. package/dist/ephemeral-exec.js +109 -0
  22. package/dist/exec.js +209 -0
  23. package/dist/git-auth.js +155 -0
  24. package/dist/github-app-auth.js +107 -0
  25. package/dist/github-app-connect.js +235 -0
  26. package/dist/github-app-manifest.js +82 -0
  27. package/dist/github-app-sync-cli.js +93 -0
  28. package/dist/github-app-vault.js +106 -0
  29. package/dist/github-apps.js +121 -0
  30. package/dist/github-connect-repo.js +74 -0
  31. package/dist/github-device-auth.js +109 -0
  32. package/dist/github-tasks.js +650 -0
  33. package/dist/guard.js +109 -0
  34. package/dist/harness/cache-evict.js +88 -0
  35. package/dist/harness/checkpoint.js +0 -0
  36. package/dist/harness/cow-clone.js +84 -0
  37. package/dist/harness/dep-cache.js +78 -0
  38. package/dist/harness/disk-admission.js +46 -0
  39. package/dist/harness/egress.js +30 -0
  40. package/dist/harness/manager.js +97 -0
  41. package/dist/harness/mcp-config-formats.js +164 -0
  42. package/dist/harness/mcp-config.js +111 -0
  43. package/dist/harness/mcp-inject.js +134 -0
  44. package/dist/harness/mcp-proxy-cli.js +88 -0
  45. package/dist/harness/mcp-proxy.js +150 -0
  46. package/dist/harness/net-proxy.js +120 -0
  47. package/dist/harness/sandbox.js +96 -0
  48. package/dist/history-sync.js +26 -0
  49. package/dist/hosted-endpoints.d.mts +14 -0
  50. package/dist/hosted-endpoints.mjs +35 -0
  51. package/dist/identity.js +153 -0
  52. package/dist/integrations/index.js +4 -0
  53. package/dist/integrations/manager.js +279 -0
  54. package/dist/integrations/oauth.js +78 -0
  55. package/dist/integrations/registry.js +239 -0
  56. package/dist/integrations/store.js +54 -0
  57. package/dist/integrations/types.js +1 -0
  58. package/dist/linear-tasks.js +49 -0
  59. package/dist/metadata.js +226 -0
  60. package/dist/multiplexer.js +79 -0
  61. package/dist/native-pi.js +38 -0
  62. package/dist/node-stats.js +237 -0
  63. package/dist/pairing-crypto.js +105 -0
  64. package/dist/policy/conditions.js +103 -0
  65. package/dist/policy/policy-engine.js +20 -0
  66. package/dist/policy/risk.js +18 -0
  67. package/dist/policy/ruleset.js +113 -0
  68. package/dist/policy/run-policy.js +108 -0
  69. package/dist/policy/session-reroute.js +96 -0
  70. package/dist/pty-runner.py +95 -0
  71. package/dist/question.js +146 -0
  72. package/dist/redact.js +97 -0
  73. package/dist/relay-attach.js +345 -0
  74. package/dist/relay-chunk.js +73 -0
  75. package/dist/relay-cli-crypto.js +70 -0
  76. package/dist/relay-client.js +344 -0
  77. package/dist/relay-setup.js +262 -0
  78. package/dist/repo-workspace.js +208 -0
  79. package/dist/runtime/adoption.js +45 -0
  80. package/dist/runtime/agent-service-bin.js +149 -0
  81. package/dist/runtime/agent-service.js +439 -0
  82. package/dist/runtime/ansi.js +27 -0
  83. package/dist/runtime/anthropic-preflight.js +80 -0
  84. package/dist/runtime/claude-code.js +1364 -0
  85. package/dist/runtime/cli-parsers.js +647 -0
  86. package/dist/runtime/codex-auth.js +168 -0
  87. package/dist/runtime/codex-preflight.js +60 -0
  88. package/dist/runtime/codex-sessions.js +229 -0
  89. package/dist/runtime/control-plane-location.js +74 -0
  90. package/dist/runtime/credential-ingest.js +122 -0
  91. package/dist/runtime/credential-provisioning.js +79 -0
  92. package/dist/runtime/credential-store.js +435 -0
  93. package/dist/runtime/credentials.js +153 -0
  94. package/dist/runtime/host.js +153 -0
  95. package/dist/runtime/index.js +1548 -0
  96. package/dist/runtime/local-model-store.js +194 -0
  97. package/dist/runtime/location-registry.js +28 -0
  98. package/dist/runtime/model-catalog.js +97 -0
  99. package/dist/runtime/model-namer.js +85 -0
  100. package/dist/runtime/native-process-scan.js +102 -0
  101. package/dist/runtime/native-session-discovery.js +103 -0
  102. package/dist/runtime/normalize.js +75 -0
  103. package/dist/runtime/oauth/model-oauth-providers.js +75 -0
  104. package/dist/runtime/oauth/model-oauth.js +324 -0
  105. package/dist/runtime/opencode-preflight.js +55 -0
  106. package/dist/runtime/pi-auth.js +82 -0
  107. package/dist/runtime/pi-oauth.js +52 -0
  108. package/dist/runtime/pi-session-discovery.js +42 -0
  109. package/dist/runtime/pi.js +518 -0
  110. package/dist/runtime/process.js +499 -0
  111. package/dist/runtime/protocol.js +630 -0
  112. package/dist/runtime/remote.js +541 -0
  113. package/dist/runtime/rpc-protocol.js +56 -0
  114. package/dist/runtime/ruleset-store.js +117 -0
  115. package/dist/runtime/session-location.js +50 -0
  116. package/dist/runtime/types.js +17 -0
  117. package/dist/secrets-cli.js +134 -0
  118. package/dist/secrets.js +264 -0
  119. package/dist/server.js +9411 -0
  120. package/dist/session/bivy-session.js +1 -0
  121. package/dist/session/checkpoint-pack.js +133 -0
  122. package/dist/session/event-log.js +340 -0
  123. package/dist/session/fork-dirty.js +73 -0
  124. package/dist/session/fork-prereqs.js +61 -0
  125. package/dist/session/fork.js +57 -0
  126. package/dist/session/native-import.js +56 -0
  127. package/dist/session/reconnect.js +168 -0
  128. package/dist/session/replication-service.js +236 -0
  129. package/dist/session/replication.js +106 -0
  130. package/dist/session/replicator.js +140 -0
  131. package/dist/session/session-new-dedupe.js +42 -0
  132. package/dist/session/sibling-client.js +201 -0
  133. package/dist/session/transcript-merge.js +131 -0
  134. package/dist/session/transcript-normal.js +130 -0
  135. package/dist/session/workspace-context.js +1 -0
  136. package/dist/session-event-coalescer.js +50 -0
  137. package/dist/session-identity.js +34 -0
  138. package/dist/session-ref.js +65 -0
  139. package/dist/stt-cli.js +131 -0
  140. package/dist/stt.js +168 -0
  141. package/dist/terminal.js +409 -0
  142. package/dist/wire-format.js +67 -0
  143. package/dist/worktree-provision.js +118 -0
  144. package/dist/worktree.js +117 -0
  145. package/package.json +40 -6
  146. package/public/qr.js +464 -0
@@ -0,0 +1,74 @@
1
+ // SPDX-License-Identifier: FSL-1.1-ALv2
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { connectRepoScope, deviceFlowClientId } from "./github-device-auth.js";
7
+ import { SecretVault } from "./secrets.js";
8
+ import { openBrowser } from "./browser-open.js";
9
+ /**
10
+ * `bivy github:connect` — staged repo-scope authorization (C3).
11
+ *
12
+ * Runs GitHub's device flow ON THIS NODE to obtain a `repo`-scoped token (only
13
+ * now, when the user is connecting a repo to the work queue — not at sign-in),
14
+ * and stores it in Bivy's node secret vault, with `.bivy/cli.json`
15
+ * `env.BIVY_GITHUB_TOKEN` holding only a `secret://github.repo-token` reference
16
+ * so both foreground runs and the background service pick it up. The control plane is never
17
+ * involved, so it never sees the token.
18
+ */
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const repoRoot = path.resolve(__dirname, "..");
21
+ const appDir = process.env.BIVY_DATA_DIR ?? path.join(repoRoot, ".bivy");
22
+ const cliConfigPath = path.join(appDir, "cli.json");
23
+ function loadCliConfig() {
24
+ try {
25
+ return JSON.parse(fs.readFileSync(cliConfigPath, "utf8"));
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ function saveToken(token, repo) {
32
+ const config = loadCliConfig();
33
+ const env = (config.env && typeof config.env === "object" ? config.env : {});
34
+ new SecretVault(appDir).setLocal("github.repo-token", token, "GitHub repo/work-queue token");
35
+ env.BIVY_GITHUB_TOKEN = "secret://github.repo-token";
36
+ if (repo)
37
+ env.BIVY_GITHUB_REPO = repo;
38
+ config.env = env;
39
+ fs.mkdirSync(appDir, { recursive: true });
40
+ fs.writeFileSync(cliConfigPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
41
+ try {
42
+ fs.chmodSync(cliConfigPath, 0o600);
43
+ }
44
+ catch {
45
+ // best effort
46
+ }
47
+ }
48
+ async function main() {
49
+ const clientId = deviceFlowClientId();
50
+ if (!clientId) {
51
+ console.error("Set BIVY_GITHUB_OAUTH_CLIENT_ID to the GitHub OAuth app (with device flow enabled) before connecting a repo.");
52
+ process.exit(1);
53
+ }
54
+ const repoArg = process.argv.slice(2).find((a) => !a.startsWith("-"));
55
+ console.log("Connecting a GitHub repo grants Bivy push + pull-request access on this machine only.\n");
56
+ const token = await connectRepoScope(clientId, {
57
+ openBrowser,
58
+ onPrompt: ({ userCode, verificationUri }) => {
59
+ console.log(`Open ${verificationUri} and enter this code:\n\n ${userCode}\n`);
60
+ console.log("Waiting for you to authorize…");
61
+ },
62
+ });
63
+ saveToken(token, repoArg);
64
+ console.log(`\n✓ Authorized. Stored a repo-scoped token in Bivy's local secret vault.`);
65
+ console.log(` ${cliConfigPath} contains only a secret:// reference.`);
66
+ if (repoArg)
67
+ console.log(` Issue pickup is set for ${repoArg}. Restart the node to apply.`);
68
+ else
69
+ console.log(" Set BIVY_GITHUB_REPO (or re-run with owner/repo) to enable issue pickup.");
70
+ }
71
+ main().catch((error) => {
72
+ console.error(error instanceof Error ? error.message : String(error));
73
+ process.exit(1);
74
+ });
@@ -0,0 +1,109 @@
1
+ // SPDX-License-Identifier: FSL-1.1-ALv2
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ /**
4
+ * GitHub OAuth Device Flow for STAGED repo scope (C3).
5
+ *
6
+ * At sign-in the control plane asks GitHub for only `read:user user:email`
7
+ * (minimal scope — see services/control-plane C1). The broader `repo` scope
8
+ * (push branches, open PRs) is requested LATER and only when the user actually
9
+ * connects a repo to the work queue.
10
+ *
11
+ * Crucially this runs on the NODE and talks straight to GitHub's device-flow
12
+ * endpoints — the control plane is never in the loop, so it never sees the
13
+ * repo-scoped token (invariant #3: the control plane stores metadata only). The
14
+ * node keeps the token in its own config and uses it for issue→PR work.
15
+ *
16
+ * Device flow uses a PUBLIC client id (no client secret), which is exactly the
17
+ * right shape for an app the user installs. Set BIVY_GITHUB_OAUTH_CLIENT_ID to
18
+ * the OAuth app that has "Device flow" enabled.
19
+ */
20
+ const DEVICE_CODE_URL = "https://github.com/login/device/code";
21
+ const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
22
+ /** Default repo-connect scope: branch push + PRs + issue read/write. */
23
+ export const REPO_CONNECT_SCOPE = "repo";
24
+ /** Resolve the device-flow client id (public). Returns undefined if unset. */
25
+ export function deviceFlowClientId(env = process.env) {
26
+ return env.BIVY_GITHUB_OAUTH_CLIENT_ID?.trim() || undefined;
27
+ }
28
+ /** Step 1: ask GitHub for a device + user code. */
29
+ export async function requestDeviceCode(clientId, scope = REPO_CONNECT_SCOPE) {
30
+ const res = await fetch(DEVICE_CODE_URL, {
31
+ method: "POST",
32
+ headers: { accept: "application/json", "content-type": "application/json" },
33
+ body: JSON.stringify({ client_id: clientId, scope }),
34
+ });
35
+ if (!res.ok)
36
+ throw new Error(`GitHub device-code request failed (${res.status})`);
37
+ const data = (await res.json().catch(() => ({})));
38
+ if (!data.device_code || !data.user_code)
39
+ throw new Error("GitHub returned no device code (is device flow enabled on the OAuth app?)");
40
+ return {
41
+ deviceCode: String(data.device_code),
42
+ userCode: String(data.user_code),
43
+ verificationUri: String(data.verification_uri ?? "https://github.com/login/device"),
44
+ intervalSec: Math.max(Number(data.interval) || 5, 1),
45
+ expiresInSec: Number(data.expires_in) || 900,
46
+ };
47
+ }
48
+ /**
49
+ * Interpret a GitHub access-token poll response. Pure, so the (otherwise
50
+ * timing-dependent) state machine is unit-testable.
51
+ */
52
+ export function interpretTokenResponse(data) {
53
+ if (typeof data.access_token === "string" && data.access_token) {
54
+ return { status: "ok", token: data.access_token };
55
+ }
56
+ switch (data.error) {
57
+ case "authorization_pending":
58
+ return { status: "pending" };
59
+ case "slow_down":
60
+ return { status: "slow_down", intervalSec: Number(data.interval) || undefined };
61
+ case "access_denied":
62
+ return { status: "denied" };
63
+ case "expired_token":
64
+ return { status: "expired" };
65
+ default:
66
+ return { status: "error", error: String(data.error_description ?? data.error ?? "unknown error") };
67
+ }
68
+ }
69
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
70
+ /** Step 2: poll until the user authorizes (or the code expires). */
71
+ export async function pollForAccessToken(clientId, device, signal) {
72
+ let intervalMs = device.intervalSec * 1000;
73
+ const deadline = Date.now() + device.expiresInSec * 1000;
74
+ while (Date.now() < deadline) {
75
+ if (signal?.aborted)
76
+ throw new Error("Cancelled");
77
+ await sleep(intervalMs);
78
+ const res = await fetch(ACCESS_TOKEN_URL, {
79
+ method: "POST",
80
+ headers: { accept: "application/json", "content-type": "application/json" },
81
+ body: JSON.stringify({ client_id: clientId, device_code: device.deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }),
82
+ signal,
83
+ });
84
+ const data = (await res.json().catch(() => ({})));
85
+ const poll = interpretTokenResponse(data);
86
+ if (poll.status === "ok")
87
+ return poll.token;
88
+ if (poll.status === "slow_down")
89
+ intervalMs += (poll.intervalSec ? poll.intervalSec * 1000 : 5000);
90
+ else if (poll.status === "denied")
91
+ throw new Error("Authorization was denied.");
92
+ else if (poll.status === "expired")
93
+ throw new Error("The code expired before authorization. Try again.");
94
+ else if (poll.status === "error")
95
+ throw new Error(`GitHub authorization failed: ${poll.error}`);
96
+ // "pending": keep polling.
97
+ }
98
+ throw new Error("Timed out waiting for GitHub authorization.");
99
+ }
100
+ /**
101
+ * Full staged-scope connect: request a device code, prompt the user, and return
102
+ * the repo-scoped token. The caller persists it in the node's own config.
103
+ */
104
+ export async function connectRepoScope(clientId, cb) {
105
+ const device = await requestDeviceCode(clientId, REPO_CONNECT_SCOPE);
106
+ cb.openBrowser?.(device.verificationUri);
107
+ cb.onPrompt({ userCode: device.userCode, verificationUri: device.verificationUri });
108
+ return pollForAccessToken(clientId, device, cb.signal);
109
+ }