@binoctal/open-agents-bridge 0.5.0 → 0.6.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 (2) hide show
  1. package/install.js +81 -11
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -2,8 +2,11 @@ const https = require("https");
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
  const os = require("os");
5
+ const crypto = require("crypto");
5
6
  const { execSync } = require("child_process");
6
7
 
8
+ const PACKAGE_VERSION = require("./package.json").version;
9
+
7
10
  const REPO_OWNER = "binoctal";
8
11
  const REPO_NAME = "open-agents-bridge";
9
12
  const BINARY_NAME = "open-agents-bridge";
@@ -61,6 +64,66 @@ function fetchJSON(url) {
61
64
  });
62
65
  }
63
66
 
67
+ function fetchText(url) {
68
+ return new Promise((resolve, reject) => {
69
+ https
70
+ .get(url, { headers: { "User-Agent": "open-agents-bridge-npm" } }, (res) => {
71
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
72
+ return fetchText(res.headers.location).then(resolve, reject);
73
+ }
74
+ if (res.statusCode !== 200) {
75
+ return reject(new Error(`HTTP ${res.statusCode} from ${url}`));
76
+ }
77
+ let data = "";
78
+ res.on("data", (chunk) => (data += chunk));
79
+ res.on("end", () => resolve(data));
80
+ })
81
+ .on("error", reject);
82
+ });
83
+ }
84
+
85
+ function sha256(filePath) {
86
+ return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
87
+ }
88
+
89
+ // Verify the download against the release's own checksums.txt. A missing
90
+ // checksums.txt aborts rather than skipping the check: whoever can swap an
91
+ // asset can also make checksums.txt 404, so "skip when absent" verifies
92
+ // nothing at all. The bridge is a long-lived daemon that takes commands from
93
+ // a remote, and install time is the one cheap chance to confirm the bytes are
94
+ // the ones that were published.
95
+ async function verifyChecksum(release, asset, filePath) {
96
+ const checksums = release.assets.find((a) => a.name === "checksums.txt");
97
+ if (!checksums) {
98
+ fs.unlinkSync(filePath);
99
+ throw new Error(
100
+ `Release ${release.tag_name} has no checksums.txt; refusing to install an unverified binary`
101
+ );
102
+ }
103
+
104
+ const text = await fetchText(checksums.browser_download_url);
105
+ const line = text
106
+ .split("\n")
107
+ .map((l) => l.trim().split(/\s+/))
108
+ .find((parts) => parts[1] === asset.name || parts[1] === `*${asset.name}`);
109
+
110
+ if (!line) {
111
+ fs.unlinkSync(filePath);
112
+ throw new Error(`checksums.txt does not list ${asset.name}`);
113
+ }
114
+
115
+ const expected = line[0].toLowerCase();
116
+ const actual = sha256(filePath);
117
+ if (expected !== actual) {
118
+ fs.unlinkSync(filePath);
119
+ throw new Error(
120
+ `Checksum mismatch for ${asset.name}\n expected: ${expected}\n actual: ${actual}`
121
+ );
122
+ }
123
+
124
+ console.log(`Checksum verified (sha256 ${actual.slice(0, 16)}…)`);
125
+ }
126
+
64
127
  function downloadFile(url, destPath) {
65
128
  return new Promise((resolve, reject) => {
66
129
  const file = fs.createWriteStream(destPath);
@@ -94,18 +157,23 @@ async function install() {
94
157
 
95
158
  console.log(`Detecting platform: ${goos}/${goarch}`);
96
159
 
97
- // Get latest release from GitHub
98
- const apiUrl = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
99
- console.log("Fetching latest release...");
160
+ // Fetch the release matching THIS package's version, not "latest". Pulling
161
+ // latest means the npm version and the binary version have nothing to do
162
+ // with each other: a lockfile pins the package but not what the package
163
+ // downloads, so the same lockfile installs different binaries over time.
164
+ const tag = `v${PACKAGE_VERSION}`;
165
+ const apiUrl = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${tag}`;
166
+ console.log(`Fetching release ${tag}...`);
100
167
 
101
168
  let release;
102
169
  try {
103
170
  release = await fetchJSON(apiUrl);
104
171
  } catch (err) {
172
+ console.error(`Failed to fetch release ${tag} of ${REPO_OWNER}/${REPO_NAME}.`);
173
+ console.error(` Error: ${err.message}`);
105
174
  console.error(
106
- "Failed to fetch release info. If you are installing from source, build the binary manually."
175
+ " This package installs the binary built for its own version; it does not fall back to the latest release."
107
176
  );
108
- console.error(` Error: ${err.message}`);
109
177
  process.exit(1);
110
178
  }
111
179
 
@@ -133,9 +201,12 @@ async function install() {
133
201
 
134
202
  console.log(`Downloading ${asset.name}...`);
135
203
 
204
+ const downloadPath = path.join(binDir, asset.name);
205
+ await downloadFile(asset.browser_download_url, downloadPath);
206
+ await verifyChecksum(release, asset, downloadPath);
207
+
136
208
  if (asset.name.endsWith(".tar.gz")) {
137
- const tmpArchive = path.join(binDir, asset.name);
138
- await downloadFile(asset.browser_download_url, tmpArchive);
209
+ const tmpArchive = downloadPath;
139
210
 
140
211
  // Extract the binary from tarball
141
212
  try {
@@ -150,8 +221,7 @@ async function install() {
150
221
  }
151
222
  fs.unlinkSync(tmpArchive);
152
223
  } else if (asset.name.endsWith(".zip")) {
153
- const tmpArchive = path.join(binDir, asset.name);
154
- await downloadFile(asset.browser_download_url, tmpArchive);
224
+ const tmpArchive = downloadPath;
155
225
 
156
226
  // Extract using built-in or system unzip
157
227
  try {
@@ -167,8 +237,8 @@ async function install() {
167
237
  }
168
238
  fs.unlinkSync(tmpArchive);
169
239
  } else {
170
- // Plain binary
171
- await downloadFile(asset.browser_download_url, binaryPath);
240
+ // Plain binary — already downloaded and verified, just put it in place.
241
+ fs.renameSync(downloadPath, binaryPath);
172
242
  }
173
243
 
174
244
  // Make executable (unix)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@binoctal/open-agents-bridge",
3
- "version": "0.5.0",
3
+ "version": "0.6.2",
4
4
  "description": "Bridge CLI for Open Agents - connect your devices to the agent network",
5
5
  "bin": {
6
6
  "open-agents-bridge": "./run.js"