@agentbridgehq/agentbridge 0.1.0

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 ADDED
@@ -0,0 +1,28 @@
1
+ # agentbridge
2
+
3
+ The supply chain for agent extensions — install any agent plugin into any agent
4
+ client.
5
+
6
+ ```bash
7
+ npm install -g @agentbridgehq/agentbridge
8
+ agentbridge clients
9
+ ```
10
+
11
+ **Node is a dependency of installing, never of running.** This package downloads
12
+ a static Go binary for your platform on install and verifies its SHA-256 against
13
+ the release's signed checksum file before writing anything. After that, Node is
14
+ not involved: the command you run is the binary.
15
+
16
+ That verification is not optional. `npm` postinstall scripts are a well-worn
17
+ supply-chain vector, and a tool whose whole argument is about knowing where your
18
+ plugins came from cannot have an installer that downloads a binary and trusts it.
19
+
20
+ Other ways to install, both of which also verify:
21
+
22
+ ```bash
23
+ brew install agentbridge/tap/agentbridge
24
+ curl -fsSL https://raw.githubusercontent.com/agentbridge/agentbridge/main/install.sh | sh
25
+ ```
26
+
27
+ Source, documentation and issues:
28
+ https://github.com/agentbridgehq/agentbridge
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Thin shim: run the downloaded binary and pass everything through.
5
+ //
6
+ // Node is a dependency of *installing*, never of running. The binary is a
7
+ // static executable, so once it is in place this shim only forwards stdio,
8
+ // arguments and the exit code — which matters because agentbridge is itself
9
+ // launched by agent clients as an MCP server wrapper (`agentbridge run`), where
10
+ // anything that mangles signals or exit codes would be a real fault.
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const { spawnSync } = require('child_process');
15
+ const { binaryPath } = require('../platform');
16
+
17
+ // Asked of platform.js rather than rebuilt here: this shim is itself called
18
+ // agentbridge and lives in bin/, so a path computed independently is one
19
+ // mistake away from resolving to this file and spawning itself.
20
+ const binary = binaryPath(path.join(__dirname, '..'), process.platform);
21
+
22
+ if (!fs.existsSync(binary)) {
23
+ process.stderr.write(
24
+ 'agentbridge: the binary is missing. Reinstall with `npm install -g agentbridge`,\n' +
25
+ 'or install another way: https://github.com/agentbridgehq/agentbridge\n'
26
+ );
27
+ process.exit(1);
28
+ }
29
+
30
+ const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' });
31
+
32
+ if (result.error) {
33
+ process.stderr.write(`agentbridge: ${result.error.message}\n`);
34
+ process.exit(1);
35
+ }
36
+ // A process killed by a signal has no exit code; report it the way a shell
37
+ // would rather than exiting 0 and claiming success.
38
+ process.exit(result.status === null ? 1 : result.status);
package/install.js ADDED
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ // Downloads the agentbridge binary for this platform.
4
+ //
5
+ // The package ships no binaries. Publishing six platforms' worth would make the
6
+ // tarball enormous for every user, and the alternative — optional
7
+ // platform-specific packages — multiplies the number of things that must be
8
+ // published in lockstep. Fetching one at install time keeps the package small
9
+ // and the release process single-artifact.
10
+ //
11
+ // The checksum is verified before anything is written. npm postinstall scripts
12
+ // are a well-worn supply-chain vector, and a tool that argues about the
13
+ // provenance of plugins cannot have an installer that downloads a binary and
14
+ // trusts it.
15
+
16
+ const fs = require('fs');
17
+ const os = require('os');
18
+ const path = require('path');
19
+ const zlib = require('zlib');
20
+ const crypto = require('crypto');
21
+ const { execFileSync } = require('child_process');
22
+ const { artifactFor, binaryPath } = require('./platform');
23
+
24
+ const REPO = 'agentbridgehq/agentbridge';
25
+ const VERSION = require('./package.json').version;
26
+ const BASE =
27
+ process.env.AGENTBRIDGE_BASE_URL ||
28
+ `https://github.com/${REPO}/releases/download/v${VERSION}`;
29
+
30
+ async function fetchBuffer(url) {
31
+ const res = await fetch(url, { redirect: 'follow' });
32
+ if (!res.ok) {
33
+ throw new Error(`${url}: HTTP ${res.status}`);
34
+ }
35
+ return Buffer.from(await res.arrayBuffer());
36
+ }
37
+
38
+ function sha256(buf) {
39
+ return crypto.createHash('sha256').update(buf).digest('hex');
40
+ }
41
+
42
+ // verifyChecksum refuses anything the checksums file does not list, rather than
43
+ // skipping verification when the entry is absent — an unlisted artifact is the
44
+ // case this check exists to catch.
45
+ function verifyChecksum(name, buf, checksums) {
46
+ const line = checksums
47
+ .split('\n')
48
+ .map((l) => l.trim())
49
+ .find((l) => l.endsWith(` ${name}`) || l.endsWith(` ${name}`));
50
+
51
+ if (!line) {
52
+ throw new Error(`checksums.txt does not list ${name}; refusing to install`);
53
+ }
54
+
55
+ const expected = line.split(/\s+/)[0];
56
+ const actual = sha256(buf);
57
+ if (expected !== actual) {
58
+ throw new Error(
59
+ `checksum mismatch for ${name}\n expected ${expected}\n actual ${actual}\nDo not use this download.`
60
+ );
61
+ }
62
+ }
63
+
64
+ // extract pulls the single binary out of the archive.
65
+ //
66
+ // tar and unzip are invoked rather than depended on: adding an archive library
67
+ // would put third-party code in the install path of a security tool for the
68
+ // sake of one file.
69
+ function extract(archivePath, artifact, destDir) {
70
+ if (archivePath.endsWith('.zip')) {
71
+ execFileSync('unzip', ['-o', '-q', archivePath, artifact.binary, '-d', destDir], {
72
+ stdio: 'inherit',
73
+ });
74
+ return;
75
+ }
76
+ execFileSync('tar', ['-xzf', archivePath, '-C', destDir, artifact.binary], {
77
+ stdio: 'inherit',
78
+ });
79
+ }
80
+
81
+ async function main() {
82
+ const artifact = artifactFor(VERSION, process.platform, process.arch);
83
+ const target = binaryPath(__dirname, process.platform);
84
+ const destDir = path.dirname(target);
85
+
86
+ if (fs.existsSync(target)) {
87
+ return;
88
+ }
89
+
90
+ process.stderr.write(`agentbridge v${VERSION} (${artifact.os}/${artifact.arch})\n`);
91
+
92
+ const [archive, checksums] = await Promise.all([
93
+ fetchBuffer(`${BASE}/${artifact.name}`),
94
+ fetchBuffer(`${BASE}/checksums.txt`).then((b) => b.toString('utf8')),
95
+ ]);
96
+
97
+ verifyChecksum(artifact.name, archive, checksums);
98
+ process.stderr.write(' checksum ok\n');
99
+
100
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'agentbridge-'));
101
+ try {
102
+ const archivePath = path.join(tmp, artifact.name);
103
+ fs.writeFileSync(archivePath, archive);
104
+
105
+ fs.mkdirSync(destDir, { recursive: true });
106
+ extract(archivePath, artifact, destDir);
107
+ fs.chmodSync(target, 0o755);
108
+
109
+ process.stderr.write(` installed ${target}\n`);
110
+ } finally {
111
+ fs.rmSync(tmp, { recursive: true, force: true });
112
+ }
113
+ }
114
+
115
+ main().catch((err) => {
116
+ process.stderr.write(`\nagentbridge install failed: ${err.message}\n\n`);
117
+ process.stderr.write(
118
+ 'Install another way instead:\n' +
119
+ ' brew install agentbridge/tap/agentbridge\n' +
120
+ ` curl -fsSL https://raw.githubusercontent.com/${REPO}/main/install.sh | sh\n`
121
+ );
122
+ process.exit(1);
123
+ });
124
+
125
+ module.exports = { verifyChecksum, sha256 };
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@agentbridgehq/agentbridge",
3
+ "version": "0.1.0",
4
+ "description": "The supply chain for agent extensions — install any agent plugin into any agent client",
5
+ "license": "Apache-2.0",
6
+ "author": "Masih Moloodian <masihmoloodian@gmail.com>",
7
+ "homepage": "https://github.com/agentbridgehq/agentbridge",
8
+ "repository": { "type": "git", "url": "git+https://github.com/agentbridgehq/agentbridge.git" },
9
+ "keywords": ["agent", "plugins", "mcp", "agent-plugins", "skills", "cli"],
10
+ "bin": { "agentbridge": "bin/agentbridge" },
11
+ "files": ["bin/", "install.js", "platform.js", "README.md"],
12
+ "scripts": {
13
+ "postinstall": "node install.js",
14
+ "test": "node --test"
15
+ },
16
+ "engines": { "node": ">=18" }
17
+ }
package/platform.js ADDED
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ // Mapping from Node's platform names to the release artifacts.
6
+ //
7
+ // Separated from the installer so it can be tested without a network, because
8
+ // this is where a distribution bug hides best: a wrong mapping produces a
9
+ // confident download of a binary for the wrong architecture, and the failure
10
+ // surfaces much later as "exec format error" with nothing pointing back here.
11
+
12
+ const PLATFORMS = {
13
+ darwin: 'darwin',
14
+ linux: 'linux',
15
+ win32: 'windows',
16
+ };
17
+
18
+ const ARCHS = {
19
+ x64: 'amd64',
20
+ arm64: 'arm64',
21
+ };
22
+
23
+ /**
24
+ * Resolve the release artifact for a platform and architecture.
25
+ *
26
+ * @param {string} version release version, with or without a leading "v"
27
+ * @param {string} platform Node's process.platform
28
+ * @param {string} arch Node's process.arch
29
+ */
30
+ function artifactFor(version, platform, arch) {
31
+ const os = PLATFORMS[platform];
32
+ const goarch = ARCHS[arch];
33
+
34
+ if (!os || !goarch) {
35
+ // Naming both the unsupported pair and the supported set turns a dead end
36
+ // into something the reader can act on.
37
+ throw new Error(
38
+ `agentbridge does not publish a binary for ${platform}/${arch}.\n` +
39
+ `Supported: ${supportedPairs().join(', ')}.\n` +
40
+ `Build from source instead: https://github.com/agentbridgehq/agentbridge`
41
+ );
42
+ }
43
+
44
+ const stripped = String(version).replace(/^v/, '');
45
+ const ext = os === 'windows' ? 'zip' : 'tar.gz';
46
+
47
+ return {
48
+ os,
49
+ arch: goarch,
50
+ // Must match archives.name_template in .goreleaser.yaml. A drift test in
51
+ // the Go suite keeps the two from separating.
52
+ name: `agentbridge_${stripped}_${os}_${goarch}.${ext}`,
53
+ binary: os === 'windows' ? 'agentbridge.exe' : 'agentbridge',
54
+ };
55
+ }
56
+
57
+ function supportedPairs() {
58
+ const out = [];
59
+ for (const platform of Object.keys(PLATFORMS)) {
60
+ for (const arch of Object.keys(ARCHS)) {
61
+ out.push(`${platform}/${arch}`);
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+
67
+ // binaryPath is where the downloaded executable lives, and it is deliberately
68
+ // not bin/.
69
+ //
70
+ // bin/agentbridge is the shim npm links onto the PATH, and it is shipped in the
71
+ // package. Downloading the real binary to that same name meant three things
72
+ // claimed one path: the installer's "already present?" check saw the shim and
73
+ // skipped the download, and the shim then found "the binary" — itself — and
74
+ // spawned it, recursing until something ran out. `npm i -g @agentbridgehq/agentbridge`
75
+ // produced a command that hung on first use.
76
+ //
77
+ // The shim and the installer both ask this function, so the two cannot drift
78
+ // apart again.
79
+ function binaryPath(packageRoot, platform) {
80
+ const name = platform === 'win32' ? 'agentbridge.exe' : 'agentbridge';
81
+ return path.join(packageRoot, 'vendor', name);
82
+ }
83
+
84
+ module.exports = { artifactFor, supportedPairs, binaryPath, PLATFORMS, ARCHS };