@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,117 @@
1
+ // SPDX-License-Identifier: FSL-1.1-ALv2
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { cowProvisionDeps } from "./worktree-provision.js";
8
+ const exec = promisify(execFile);
9
+ /** Slugify arbitrary text into a safe git branch / directory segment. */
10
+ export function branchSlug(input) {
11
+ return (input
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9._/-]+/g, "-")
14
+ .replace(/\/+/g, "-")
15
+ .replace(/\.{2,}/g, ".")
16
+ .replace(/-+/g, "-")
17
+ .replace(/\.lock$/g, "")
18
+ .replace(/^[-.]+|[-.]+$/g, "")
19
+ .slice(0, 60) || "task");
20
+ }
21
+ /** The toplevel of the git repo containing `dir`, or undefined if not a repo. */
22
+ export async function gitRepoRoot(dir) {
23
+ try {
24
+ const { stdout } = await exec("git", ["-C", dir, "rev-parse", "--show-toplevel"]);
25
+ return stdout.trim() || undefined;
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
31
+ async function currentRef(repoRoot) {
32
+ try {
33
+ const { stdout } = await exec("git", ["-C", repoRoot, "rev-parse", "--abbrev-ref", "HEAD"]);
34
+ const ref = stdout.trim();
35
+ return ref && ref !== "HEAD" ? ref : "HEAD";
36
+ }
37
+ catch {
38
+ return "HEAD";
39
+ }
40
+ }
41
+ /** Keep Bivy's worktree dir out of `git status` noise without committing a rule. */
42
+ function excludeMeshDir(repoRoot) {
43
+ try {
44
+ const excludePath = path.join(repoRoot, ".git", "info", "exclude");
45
+ const existing = fs.existsSync(excludePath) ? fs.readFileSync(excludePath, "utf8") : "";
46
+ if (!existing.split("\n").some((line) => line.trim() === ".bivy/")) {
47
+ fs.appendFileSync(excludePath, `${existing.endsWith("\n") || !existing ? "" : "\n"}.bivy/\n`);
48
+ }
49
+ }
50
+ catch {
51
+ // best effort
52
+ }
53
+ }
54
+ /**
55
+ * Create a worktree for `repoDir` on a new branch. The worktree lives under
56
+ * `<repoRoot>/.bivy/worktrees/<slug>` by default (excluded from git).
57
+ */
58
+ export async function createWorktree(opts) {
59
+ const repoRoot = await gitRepoRoot(opts.repoDir);
60
+ if (!repoRoot)
61
+ throw new Error(`Not a git repository: ${opts.repoDir}`);
62
+ const slug = branchSlug(opts.id);
63
+ const branch = opts.branch ?? `bivy/${slug}`;
64
+ const root = opts.root ?? path.join(repoRoot, ".bivy", "worktrees");
65
+ const wtPath = path.join(root, slug);
66
+ const base = opts.base ?? (await currentRef(repoRoot));
67
+ excludeMeshDir(repoRoot);
68
+ fs.mkdirSync(root, { recursive: true });
69
+ try {
70
+ await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, wtPath, base]);
71
+ }
72
+ catch (error) {
73
+ // The branch already exists (e.g. a GitHub-issue pickup whose branch was
74
+ // pushed on an earlier run, now re-triggered after the session closed). Adopt
75
+ // it instead of hard-failing, which previously bubbled up and silently marked
76
+ // the work item "done" with nothing happening. Clear any stale worktree dir
77
+ // first, then check the existing branch out into a fresh worktree.
78
+ const localExists = await refExists(repoRoot, `refs/heads/${branch}`);
79
+ const remoteExists = !localExists && (await refExists(repoRoot, `refs/remotes/origin/${branch}`));
80
+ if (!localExists && !remoteExists)
81
+ throw error;
82
+ await removeWorktree(repoRoot, wtPath);
83
+ fs.rmSync(wtPath, { recursive: true, force: true });
84
+ if (localExists) {
85
+ await exec("git", ["-C", repoRoot, "worktree", "add", wtPath, branch]);
86
+ }
87
+ else {
88
+ // Recreate the local branch from origin, then check it out in the worktree.
89
+ await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, wtPath, `origin/${branch}`]);
90
+ }
91
+ }
92
+ // Opportunistically reuse a sibling worktree's installed deps (node_modules,
93
+ // target, .venv) via copy-on-write when the filesystem supports it, so a
94
+ // second worktree of this repo costs ~its diff instead of a full re-install.
95
+ // Opt-in (BIVY_WORKTREE_COW_CLONE), CoW-only, lockfile-matched, best-effort.
96
+ cowProvisionDeps({ worktreePath: wtPath, worktreesRoot: root, log: (m) => console.log(m) });
97
+ return { path: wtPath, branch, repoRoot };
98
+ }
99
+ /** True if `ref` (a fully-qualified ref) resolves in the repo. */
100
+ async function refExists(repoRoot, ref) {
101
+ try {
102
+ await exec("git", ["-C", repoRoot, "rev-parse", "--verify", "--quiet", ref]);
103
+ return true;
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
109
+ /** Remove a worktree (and its branch is left intact for the PR). */
110
+ export async function removeWorktree(repoRoot, wtPath) {
111
+ try {
112
+ await exec("git", ["-C", repoRoot, "worktree", "remove", "--force", wtPath]);
113
+ }
114
+ catch {
115
+ // already gone / not a worktree — ignore
116
+ }
117
+ }
package/package.json CHANGED
@@ -1,16 +1,50 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.0.0",
4
- "description": "Placeholder for the distributable parts of Bivy. No functionality yet.",
5
- "license": "FSL-1.1-ALv2",
3
+ "version": "0.1.0-staging.2",
6
4
  "type": "module",
5
+ "license": "FSL-1.1-ALv2",
6
+ "description": "Run coding agents on machines you own. Source-available, self-hostable agent workspace.",
7
+ "homepage": "https://bivy.sh",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/bivysh/bivy.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/bivysh/bivy/issues"
14
+ },
7
15
  "engines": {
8
16
  "node": ">=22.19.0"
9
17
  },
18
+ "bin": {
19
+ "bivy": "bin/bivy.mjs"
20
+ },
10
21
  "publishConfig": {
11
22
  "access": "public"
12
23
  },
13
- "files": [
14
- "README.md"
15
- ]
24
+ "scripts": {
25
+ "setup": "node bin/bivy.mjs setup",
26
+ "bivy": "node bin/bivy.mjs",
27
+ "start": "node dist/server.js",
28
+ "dev": "node dist/server.js",
29
+ "relay:setup": "node bin/bivy.mjs relay:setup",
30
+ "postinstall": "node bin/patch-pi-dependencies.mjs"
31
+ },
32
+ "dependencies": {
33
+ "@anthropic-ai/claude-agent-sdk": "^0.3.199",
34
+ "@earendil-works/pi-ai": "0.82.1",
35
+ "@earendil-works/pi-coding-agent": "0.82.1",
36
+ "brace-expansion": "5.0.8",
37
+ "express": "^5.2.1",
38
+ "node-pty": "^1.1.0",
39
+ "typebox": "^1.3.6",
40
+ "ws": "^8.21.1"
41
+ },
42
+ "overrides": {
43
+ "@hono/node-server": "2.0.12",
44
+ "@modelcontextprotocol/sdk": "1.30.0",
45
+ "@earendil-works/pi-coding-agent": {
46
+ "brace-expansion": "5.0.8"
47
+ },
48
+ "brace-expansion": "5.0.8"
49
+ }
16
50
  }
package/public/qr.js ADDED
@@ -0,0 +1,464 @@
1
+ /*
2
+ * Minimal self-contained QR Code encoder (byte mode, ECC level L, versions
3
+ * 1-20) with mask selection and canvas rendering. No external dependencies,
4
+ * no network calls — important because we encode private LAN pairing URLs.
5
+ *
6
+ * Exposes: window.QRCode.toCanvas(canvasEl, text)
7
+ *
8
+ * Implements ISO/IEC 18004 essentials: GF(256) Reed-Solomon, function
9
+ * patterns, data placement, 8 data masks with penalty scoring, and BCH
10
+ * format/version information.
11
+ */
12
+ (function () {
13
+ "use strict";
14
+
15
+ // GF(256) tables, primitive polynomial 0x11d.
16
+ var EXP = new Array(512);
17
+ var LOG = new Array(256);
18
+ (function () {
19
+ var x = 1;
20
+ for (var i = 0; i < 255; i++) {
21
+ EXP[i] = x;
22
+ LOG[x] = i;
23
+ x <<= 1;
24
+ if (x & 0x100) x ^= 0x11d;
25
+ }
26
+ for (var j = 255; j < 512; j++) EXP[j] = EXP[j - 255];
27
+ })();
28
+
29
+ function gmul(a, b) {
30
+ if (a === 0 || b === 0) return 0;
31
+ return EXP[LOG[a] + LOG[b]];
32
+ }
33
+
34
+ // Reed-Solomon generator polynomial of given degree.
35
+ function rsGenerator(degree) {
36
+ var poly = [1];
37
+ for (var d = 0; d < degree; d++) {
38
+ var next = new Array(poly.length + 1).fill(0);
39
+ for (var i = 0; i < poly.length; i++) {
40
+ next[i] ^= poly[i];
41
+ next[i + 1] ^= gmul(poly[i], EXP[d]);
42
+ }
43
+ poly = next;
44
+ }
45
+ return poly;
46
+ }
47
+
48
+ function rsEncode(data, ecCount) {
49
+ // rsGenerator returns ecCount+1 coefficients with a leading 1 at [0].
50
+ // The remainder uses the non-leading coefficients (length ecCount).
51
+ var div = rsGenerator(ecCount).slice(1);
52
+ var res = new Array(ecCount).fill(0);
53
+ for (var i = 0; i < data.length; i++) {
54
+ var factor = data[i] ^ res[0];
55
+ res.shift();
56
+ res.push(0);
57
+ for (var j = 0; j < ecCount; j++) {
58
+ res[j] ^= gmul(div[j], factor);
59
+ }
60
+ }
61
+ return res;
62
+ }
63
+
64
+ // Per-version ECC L block structure:
65
+ // [ecPerBlock, blocks1, dataPerBlock1, blocks2, dataPerBlock2]
66
+ var ECL = {
67
+ 1: [7, 1, 19, 0, 0],
68
+ 2: [10, 1, 34, 0, 0],
69
+ 3: [15, 1, 55, 0, 0],
70
+ 4: [20, 1, 80, 0, 0],
71
+ 5: [26, 1, 108, 0, 0],
72
+ 6: [18, 2, 68, 0, 0],
73
+ 7: [20, 2, 78, 0, 0],
74
+ 8: [24, 2, 97, 0, 0],
75
+ 9: [30, 2, 116, 0, 0],
76
+ 10: [18, 2, 68, 2, 69],
77
+ 11: [20, 4, 81, 0, 0],
78
+ 12: [24, 2, 92, 2, 93],
79
+ 13: [26, 4, 107, 0, 0],
80
+ 14: [30, 3, 115, 1, 116],
81
+ 15: [22, 5, 87, 1, 88],
82
+ 16: [24, 5, 98, 1, 99],
83
+ 17: [28, 1, 107, 5, 108],
84
+ 18: [30, 5, 120, 1, 121],
85
+ 19: [28, 3, 113, 4, 114],
86
+ 20: [28, 3, 107, 5, 108],
87
+ };
88
+
89
+ var ALIGN = {
90
+ 1: [], 2: [6, 18], 3: [6, 22], 4: [6, 26], 5: [6, 30],
91
+ 6: [6, 34], 7: [6, 22, 38], 8: [6, 24, 42], 9: [6, 26, 46], 10: [6, 28, 50],
92
+ 11: [6, 30, 54], 12: [6, 32, 58], 13: [6, 34, 62],
93
+ 14: [6, 26, 46, 66], 15: [6, 26, 48, 70], 16: [6, 26, 50, 74],
94
+ 17: [6, 30, 54, 78], 18: [6, 30, 56, 82], 19: [6, 30, 58, 86],
95
+ 20: [6, 34, 62, 90],
96
+ };
97
+
98
+ function totalDataCodewords(v) {
99
+ var s = ECL[v];
100
+ return s[1] * s[2] + s[3] * s[4];
101
+ }
102
+
103
+ function charCountBits(v) {
104
+ return v <= 9 ? 8 : 16; // byte mode
105
+ }
106
+
107
+ function chooseVersion(byteLen) {
108
+ for (var v = 1; v <= 20; v++) {
109
+ var bits = 4 + charCountBits(v) + byteLen * 8;
110
+ if (Math.ceil(bits / 8) <= totalDataCodewords(v)) return v;
111
+ }
112
+ throw new Error("Data too large for supported QR versions (1-20)");
113
+ }
114
+
115
+ function encodeData(bytes, v) {
116
+ var bb = [];
117
+ function put(val, len) {
118
+ for (var i = len - 1; i >= 0; i--) bb.push((val >>> i) & 1);
119
+ }
120
+ put(0b0100, 4); // byte mode
121
+ put(bytes.length, charCountBits(v));
122
+ for (var i = 0; i < bytes.length; i++) put(bytes[i], 8);
123
+
124
+ var cap = totalDataCodewords(v) * 8;
125
+ // Terminator
126
+ for (var t = 0; t < 4 && bb.length < cap; t++) bb.push(0);
127
+ // Byte align
128
+ while (bb.length % 8 !== 0) bb.push(0);
129
+ // Pad bytes
130
+ var pads = [0xec, 0x11];
131
+ var pi = 0;
132
+ while (bb.length < cap) {
133
+ put(pads[pi % 2], 8);
134
+ pi++;
135
+ }
136
+ // To bytes
137
+ var codewords = [];
138
+ for (var b = 0; b < bb.length; b += 8) {
139
+ var byte = 0;
140
+ for (var k = 0; k < 8; k++) byte = (byte << 1) | bb[b + k];
141
+ codewords.push(byte);
142
+ }
143
+ return codewords;
144
+ }
145
+
146
+ function buildFinalSequence(codewords, v) {
147
+ var s = ECL[v];
148
+ var ecCount = s[0];
149
+ var blocks = [];
150
+ var idx = 0;
151
+ function take(n) {
152
+ var arr = codewords.slice(idx, idx + n);
153
+ idx += n;
154
+ return arr;
155
+ }
156
+ for (var b = 0; b < s[1]; b++) blocks.push(take(s[2]));
157
+ for (var b2 = 0; b2 < s[3]; b2++) blocks.push(take(s[4]));
158
+
159
+ var ecBlocks = blocks.map(function (data) {
160
+ return rsEncode(data, ecCount);
161
+ });
162
+
163
+ var maxData = Math.max.apply(null, blocks.map(function (x) { return x.length; }));
164
+ var result = [];
165
+ for (var i = 0; i < maxData; i++) {
166
+ for (var bl = 0; bl < blocks.length; bl++) {
167
+ if (i < blocks[bl].length) result.push(blocks[bl][i]);
168
+ }
169
+ }
170
+ for (var e = 0; e < ecCount; e++) {
171
+ for (var bl2 = 0; bl2 < ecBlocks.length; bl2++) {
172
+ result.push(ecBlocks[bl2][e]);
173
+ }
174
+ }
175
+ return result;
176
+ }
177
+
178
+ // --- Matrix construction ---------------------------------------------
179
+ function makeMatrix(size) {
180
+ var m = [];
181
+ var fn = [];
182
+ for (var r = 0; r < size; r++) {
183
+ m.push(new Array(size).fill(null));
184
+ fn.push(new Array(size).fill(false));
185
+ }
186
+ return { m: m, fn: fn, size: size };
187
+ }
188
+
189
+ function setFn(M, r, c, val) {
190
+ M.m[r][c] = val;
191
+ M.fn[r][c] = true;
192
+ }
193
+
194
+ function placeFinder(M, r, c) {
195
+ for (var dr = -1; dr <= 7; dr++) {
196
+ for (var dc = -1; dc <= 7; dc++) {
197
+ var rr = r + dr, cc = c + dc;
198
+ if (rr < 0 || cc < 0 || rr >= M.size || cc >= M.size) continue;
199
+ var inRing = dr >= 0 && dr <= 6 && dc >= 0 && dc <= 6;
200
+ var dark = inRing &&
201
+ (dr === 0 || dr === 6 || dc === 0 || dc === 6 ||
202
+ (dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4));
203
+ setFn(M, rr, cc, !!dark);
204
+ }
205
+ }
206
+ }
207
+
208
+ function placeAlignment(M, v) {
209
+ var pos = ALIGN[v];
210
+ for (var i = 0; i < pos.length; i++) {
211
+ for (var j = 0; j < pos.length; j++) {
212
+ var r = pos[i], c = pos[j];
213
+ // Skip if overlapping a finder pattern.
214
+ if ((r <= 7 && c <= 7) || (r <= 7 && c >= M.size - 8) || (r >= M.size - 8 && c <= 7)) continue;
215
+ for (var dr = -2; dr <= 2; dr++) {
216
+ for (var dc = -2; dc <= 2; dc++) {
217
+ var dark = Math.max(Math.abs(dr), Math.abs(dc)) !== 1;
218
+ setFn(M, r + dr, c + dc, dark);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ function placeTimingAndDark(M, v) {
226
+ for (var i = 8; i < M.size - 8; i++) {
227
+ if (M.fn[6][i]) {} else setFn(M, 6, i, i % 2 === 0);
228
+ if (M.fn[i][6]) {} else setFn(M, i, 6, i % 2 === 0);
229
+ }
230
+ setFn(M, 4 * v + 9, 8, true); // dark module
231
+ }
232
+
233
+ function reserveFormat(M) {
234
+ var size = M.size;
235
+ for (var i = 0; i <= 8; i++) {
236
+ if (!M.fn[8][i]) setFn(M, 8, i, false);
237
+ if (!M.fn[i][8]) setFn(M, i, 8, false);
238
+ }
239
+ for (var j = 0; j < 8; j++) {
240
+ if (!M.fn[8][size - 1 - j]) setFn(M, 8, size - 1 - j, false);
241
+ if (!M.fn[size - 1 - j][8]) setFn(M, size - 1 - j, 8, false);
242
+ }
243
+ }
244
+
245
+ function reserveVersion(M, v) {
246
+ if (v < 7) return;
247
+ var size = M.size;
248
+ for (var i = 0; i < 6; i++) {
249
+ for (var j = 0; j < 3; j++) {
250
+ setFn(M, i, size - 11 + j, false);
251
+ setFn(M, size - 11 + j, i, false);
252
+ }
253
+ }
254
+ }
255
+
256
+ function maskFn(mask, r, c) {
257
+ switch (mask) {
258
+ case 0: return (r + c) % 2 === 0;
259
+ case 1: return r % 2 === 0;
260
+ case 2: return c % 3 === 0;
261
+ case 3: return (r + c) % 3 === 0;
262
+ case 4: return (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0;
263
+ case 5: return ((r * c) % 2) + ((r * c) % 3) === 0;
264
+ case 6: return (((r * c) % 2) + ((r * c) % 3)) % 2 === 0;
265
+ case 7: return (((r + c) % 2) + ((r * c) % 3)) % 2 === 0;
266
+ }
267
+ return false;
268
+ }
269
+
270
+ function placeData(M, bits, mask) {
271
+ var size = M.size;
272
+ var idx = 0;
273
+ var up = true;
274
+ for (var col = size - 1; col > 0; col -= 2) {
275
+ if (col === 6) col = 5; // skip vertical timing column
276
+ for (var i = 0; i < size; i++) {
277
+ var row = up ? size - 1 - i : i;
278
+ for (var k = 0; k < 2; k++) {
279
+ var c = col - k;
280
+ if (M.fn[row][c]) continue;
281
+ var dark = idx < bits.length ? bits[idx] === 1 : false;
282
+ idx++;
283
+ if (maskFn(mask, row, c)) dark = !dark;
284
+ M.m[row][c] = dark;
285
+ }
286
+ }
287
+ up = !up;
288
+ }
289
+ }
290
+
291
+ function bchFormat(ecMaskValue) {
292
+ var d = ecMaskValue << 10;
293
+ var g = 0b10100110111;
294
+ for (var i = 14; i >= 10; i--) {
295
+ if ((d >> i) & 1) d ^= g << (i - 10);
296
+ }
297
+ return ((ecMaskValue << 10) | d) ^ 0b101010000010010;
298
+ }
299
+
300
+ function placeFormat(M, mask) {
301
+ var size = M.size;
302
+ var ecBits = 0b01; // level L
303
+ var bits = bchFormat((ecBits << 3) | mask); // 15 bits, bit 0 = LSB
304
+ function bit(i) { return ((bits >> i) & 1) ? true : false; }
305
+ // Copy 1 — vertical strip in column 8 (and bottom-left tail), per spec.
306
+ for (var i = 0; i < 15; i++) {
307
+ var b = bit(i);
308
+ if (i < 6) M.m[i][8] = b;
309
+ else if (i < 8) M.m[i + 1][8] = b; // skip horizontal timing at row 6
310
+ else M.m[size - 15 + i][8] = b;
311
+ }
312
+ // Copy 2 — horizontal strip in row 8 (and top-right tail), per spec.
313
+ for (var j = 0; j < 15; j++) {
314
+ var b2 = bit(j);
315
+ if (j < 8) M.m[8][size - 1 - j] = b2;
316
+ else if (j < 9) M.m[8][7] = b2; // skip vertical timing at col 6
317
+ else M.m[8][14 - j] = b2;
318
+ }
319
+ }
320
+
321
+ function bchVersion(v) {
322
+ var d = v << 12;
323
+ var g = 0b1111100100101;
324
+ for (var i = 17; i >= 12; i--) {
325
+ if ((d >> i) & 1) d ^= g << (i - 12);
326
+ }
327
+ return (v << 12) | d;
328
+ }
329
+
330
+ function placeVersion(M, v) {
331
+ if (v < 7) return;
332
+ var size = M.size;
333
+ var bits = bchVersion(v); // 18 bits
334
+ for (var i = 0; i < 18; i++) {
335
+ var bit = (bits >> i) & 1 ? true : false;
336
+ var r = Math.floor(i / 3);
337
+ var c = i % 3;
338
+ M.m[r][size - 11 + c] = bit;
339
+ M.m[size - 11 + c][r] = bit;
340
+ }
341
+ }
342
+
343
+ function penalty(M) {
344
+ var size = M.size, score = 0, r, c, i;
345
+ // Rule 1: runs of >=5 same color in rows/cols
346
+ for (r = 0; r < size; r++) {
347
+ var runC = 1;
348
+ for (c = 1; c < size; c++) {
349
+ if (M.m[r][c] === M.m[r][c - 1]) { runC++; if (runC === 5) score += 3; else if (runC > 5) score++; }
350
+ else runC = 1;
351
+ }
352
+ }
353
+ for (c = 0; c < size; c++) {
354
+ var runR = 1;
355
+ for (r = 1; r < size; r++) {
356
+ if (M.m[r][c] === M.m[r - 1][c]) { runR++; if (runR === 5) score += 3; else if (runR > 5) score++; }
357
+ else runR = 1;
358
+ }
359
+ }
360
+ // Rule 2: 2x2 blocks
361
+ for (r = 0; r < size - 1; r++) {
362
+ for (c = 0; c < size - 1; c++) {
363
+ var v = M.m[r][c];
364
+ if (v === M.m[r][c + 1] && v === M.m[r + 1][c] && v === M.m[r + 1][c + 1]) score += 3;
365
+ }
366
+ }
367
+ // Rule 3: finder-like patterns
368
+ var pat1 = [true, false, true, true, true, false, true, false, false, false, false];
369
+ var pat2 = [false, false, false, false, true, false, true, true, true, false, true];
370
+ function matches(arr, pat) {
371
+ for (var k = 0; k < pat.length; k++) if (arr[k] !== pat[k]) return false;
372
+ return true;
373
+ }
374
+ for (r = 0; r < size; r++) {
375
+ for (c = 0; c < size - 10; c++) {
376
+ var rowSeg = [], colSeg = [];
377
+ for (i = 0; i < 11; i++) { rowSeg.push(M.m[r][c + i]); colSeg.push(M.m[c + i][r]); }
378
+ if (matches(rowSeg, pat1) || matches(rowSeg, pat2)) score += 40;
379
+ if (matches(colSeg, pat1) || matches(colSeg, pat2)) score += 40;
380
+ }
381
+ }
382
+ // Rule 4: dark proportion
383
+ var dark = 0;
384
+ for (r = 0; r < size; r++) for (c = 0; c < size; c++) if (M.m[r][c]) dark++;
385
+ var ratio = (dark * 100) / (size * size);
386
+ var prev = Math.floor(Math.abs(ratio - 50) / 5);
387
+ score += prev * 10;
388
+ return score;
389
+ }
390
+
391
+ function buildBase(v) {
392
+ var size = 17 + 4 * v;
393
+ var M = makeMatrix(size);
394
+ placeFinder(M, 0, 0);
395
+ placeFinder(M, 0, size - 7);
396
+ placeFinder(M, size - 7, 0);
397
+ placeAlignment(M, v);
398
+ placeTimingAndDark(M, v);
399
+ reserveFormat(M);
400
+ reserveVersion(M, v);
401
+ return M;
402
+ }
403
+
404
+ function cloneModules(M) {
405
+ var copy = makeMatrix(M.size);
406
+ for (var r = 0; r < M.size; r++) {
407
+ for (var c = 0; c < M.size; c++) {
408
+ copy.m[r][c] = M.m[r][c];
409
+ copy.fn[r][c] = M.fn[r][c];
410
+ }
411
+ }
412
+ return copy;
413
+ }
414
+
415
+ function generate(text) {
416
+ var bytes = [];
417
+ var enc = unescape(encodeURIComponent(text)); // UTF-8
418
+ for (var i = 0; i < enc.length; i++) bytes.push(enc.charCodeAt(i) & 0xff);
419
+
420
+ var v = chooseVersion(bytes.length);
421
+ var codewords = encodeData(bytes, v);
422
+ var finalSeq = buildFinalSequence(codewords, v);
423
+ var bits = [];
424
+ for (var b = 0; b < finalSeq.length; b++) {
425
+ for (var k = 7; k >= 0; k--) bits.push((finalSeq[b] >> k) & 1);
426
+ }
427
+
428
+ var base = buildBase(v);
429
+ var best = null, bestScore = Infinity;
430
+ for (var mask = 0; mask < 8; mask++) {
431
+ var M = cloneModules(base);
432
+ placeData(M, bits, mask);
433
+ placeFormat(M, mask);
434
+ placeVersion(M, v);
435
+ var p = penalty(M);
436
+ if (p < bestScore) { bestScore = p; best = M; }
437
+ }
438
+ return best;
439
+ }
440
+
441
+ function toCanvas(canvas, text) {
442
+ var M = generate(text);
443
+ var size = M.size;
444
+ var quiet = 4;
445
+ var total = size + quiet * 2;
446
+ var px = Math.max(1, Math.floor((canvas.width || 220) / total));
447
+ var dim = px * total;
448
+ canvas.width = dim;
449
+ canvas.height = dim;
450
+ var ctx = canvas.getContext("2d");
451
+ ctx.fillStyle = "#ffffff";
452
+ ctx.fillRect(0, 0, dim, dim);
453
+ ctx.fillStyle = "#000000";
454
+ for (var r = 0; r < size; r++) {
455
+ for (var c = 0; c < size; c++) {
456
+ if (M.m[r][c]) {
457
+ ctx.fillRect((c + quiet) * px, (r + quiet) * px, px, px);
458
+ }
459
+ }
460
+ }
461
+ }
462
+
463
+ window.QRCode = { toCanvas: toCanvas, generate: generate };
464
+ })();