@raysonmeng/agentbridge 0.1.5 → 0.1.7
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +55 -9
- package/README.zh-CN.md +39 -4
- package/dist/cli.js +4242 -464
- package/dist/daemon.js +4634 -0
- package/package.json +18 -5
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/README.md +2 -2
- package/plugins/agentbridge/scripts/health-check.sh +22 -3
- package/plugins/agentbridge/scripts/plugin-update-notice.mjs +73 -0
- package/plugins/agentbridge/server/bridge-server.js +1247 -235
- package/plugins/agentbridge/server/daemon.js +2897 -432
- package/scripts/install-safety.cjs +209 -0
- package/scripts/postinstall.cjs +129 -9
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared install-time safety checks.
|
|
5
|
+
*
|
|
6
|
+
* This intentionally stays CommonJS so both the ESM local installer and npm's
|
|
7
|
+
* CommonJS postinstall hook can use the same stop/verify behavior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { spawnSync } = require("node:child_process");
|
|
11
|
+
const { existsSync, readFileSync, statSync } = require("node:fs");
|
|
12
|
+
const path = require("node:path");
|
|
13
|
+
|
|
14
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
15
|
+
|
|
16
|
+
const REQUIRED_ARTIFACTS = Object.freeze([
|
|
17
|
+
"dist/cli.js",
|
|
18
|
+
"dist/daemon.js",
|
|
19
|
+
".claude-plugin/marketplace.json",
|
|
20
|
+
"plugins/agentbridge/.claude-plugin/plugin.json",
|
|
21
|
+
"plugins/agentbridge/.mcp.json",
|
|
22
|
+
"plugins/agentbridge/README.md",
|
|
23
|
+
"plugins/agentbridge/commands/init.md",
|
|
24
|
+
"plugins/agentbridge/hooks/hooks.json",
|
|
25
|
+
"plugins/agentbridge/scripts/health-check.sh",
|
|
26
|
+
"plugins/agentbridge/scripts/plugin-update-notice.mjs",
|
|
27
|
+
"plugins/agentbridge/server/bridge-server.js",
|
|
28
|
+
"plugins/agentbridge/server/daemon.js",
|
|
29
|
+
"package.json",
|
|
30
|
+
"README.md",
|
|
31
|
+
"scripts/install-safety.cjs",
|
|
32
|
+
"scripts/postinstall.cjs",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
function quote(arg) {
|
|
36
|
+
return /^[A-Za-z0-9_@%+=:,./<>-]+$/.test(arg) ? arg : JSON.stringify(arg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function commandLine(cmd, args) {
|
|
40
|
+
return [cmd, ...args].map(quote).join(" ");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fail(message, details = []) {
|
|
44
|
+
process.stderr.write(`install-safety: ${message}\n`);
|
|
45
|
+
for (const detail of details) {
|
|
46
|
+
process.stderr.write(` - ${detail}\n`);
|
|
47
|
+
}
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readPackageJson() {
|
|
52
|
+
return JSON.parse(readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf-8"));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function requiredPackagePaths() {
|
|
56
|
+
const pkg = readPackageJson();
|
|
57
|
+
const binTargets = Object.values(pkg.bin ?? {});
|
|
58
|
+
return [...new Set([...REQUIRED_ARTIFACTS, ...binTargets])];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildStopCommand() {
|
|
62
|
+
const sourceCli = path.join(PACKAGE_ROOT, "src", "cli.ts");
|
|
63
|
+
const bundledCli = path.join(PACKAGE_ROOT, "dist", "cli.js");
|
|
64
|
+
if (existsSync(sourceCli)) return ["bun", ["run", "src/cli.ts", "kill", "--all"]];
|
|
65
|
+
return ["bun", ["run", bundledCli, "kill", "--all"]];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function agentBridgeInstallEnv(baseEnv = process.env) {
|
|
69
|
+
const env = { ...baseEnv };
|
|
70
|
+
for (const key of Object.keys(env)) {
|
|
71
|
+
if (key.startsWith("AGENTBRIDGE_")) delete env[key];
|
|
72
|
+
}
|
|
73
|
+
delete env.CODEX_WS_PORT;
|
|
74
|
+
delete env.CODEX_PROXY_PORT;
|
|
75
|
+
return env;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stopRunningAgentBridge(options = {}) {
|
|
79
|
+
const { dryRun = false, bestEffort = false } = options;
|
|
80
|
+
const [cmd, args] = buildStopCommand();
|
|
81
|
+
if (dryRun) {
|
|
82
|
+
process.stdout.write(`$ ${commandLine(cmd, args)} # stop running AgentBridge daemons/TUIs\n`);
|
|
83
|
+
return { status: 0 };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
process.stdout.write(`$ ${commandLine(cmd, args)}\n`);
|
|
87
|
+
const res = spawnSync(cmd, args, {
|
|
88
|
+
cwd: PACKAGE_ROOT,
|
|
89
|
+
env: agentBridgeInstallEnv(),
|
|
90
|
+
stdio: "inherit",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (res.error || res.status !== 0) {
|
|
94
|
+
const message = res.error
|
|
95
|
+
? `failed to start stop command: ${res.error.message}`
|
|
96
|
+
: `stop command exited with ${res.status}`;
|
|
97
|
+
if (bestEffort) {
|
|
98
|
+
process.stdout.write(`install-safety: ${message}; continuing because this hook is best-effort\n`);
|
|
99
|
+
return res;
|
|
100
|
+
}
|
|
101
|
+
fail(message);
|
|
102
|
+
}
|
|
103
|
+
return res;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function verifyBuiltArtifacts() {
|
|
107
|
+
const missing = [];
|
|
108
|
+
const empty = [];
|
|
109
|
+
const notExecutable = [];
|
|
110
|
+
const pkg = readPackageJson();
|
|
111
|
+
const required = requiredPackagePaths();
|
|
112
|
+
const binTargets = new Set(Object.values(pkg.bin ?? {}));
|
|
113
|
+
|
|
114
|
+
for (const rel of required) {
|
|
115
|
+
const absolute = path.join(PACKAGE_ROOT, rel);
|
|
116
|
+
if (!existsSync(absolute)) {
|
|
117
|
+
missing.push(rel);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const stat = statSync(absolute);
|
|
121
|
+
if (stat.size <= 0) empty.push(rel);
|
|
122
|
+
if (binTargets.has(rel) && (stat.mode & 0o111) === 0) {
|
|
123
|
+
notExecutable.push(rel);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const problems = [
|
|
128
|
+
...missing.map((rel) => `missing: ${rel}`),
|
|
129
|
+
...empty.map((rel) => `empty: ${rel}`),
|
|
130
|
+
...notExecutable.map((rel) => `not executable: ${rel}`),
|
|
131
|
+
];
|
|
132
|
+
if (problems.length > 0) {
|
|
133
|
+
fail("built artifact verification failed", problems);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
process.stdout.write(`install-safety: verified ${required.length} built artifact(s)\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function verifyTarball(tarballPath) {
|
|
140
|
+
if (!tarballPath) fail("verify-tarball requires a tarball path");
|
|
141
|
+
if (!existsSync(tarballPath)) fail(`tarball does not exist: ${tarballPath}`);
|
|
142
|
+
|
|
143
|
+
const res = spawnSync("tar", ["-tf", tarballPath], {
|
|
144
|
+
encoding: "utf-8",
|
|
145
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
146
|
+
});
|
|
147
|
+
if (res.error) fail(`failed to inspect tarball: ${res.error.message}`);
|
|
148
|
+
if (res.status !== 0) {
|
|
149
|
+
fail(`tar -tf failed with ${res.status}`, [res.stderr?.trim()].filter(Boolean));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const packed = new Set(
|
|
153
|
+
res.stdout
|
|
154
|
+
.split("\n")
|
|
155
|
+
.map((line) => line.trim())
|
|
156
|
+
.filter(Boolean)
|
|
157
|
+
.map((line) => line.replace(/^package\//, "")),
|
|
158
|
+
);
|
|
159
|
+
const required = requiredPackagePaths();
|
|
160
|
+
const missing = required.filter((rel) => !packed.has(rel));
|
|
161
|
+
if (missing.length > 0) {
|
|
162
|
+
fail("packed tarball is missing required artifact(s)", missing);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
process.stdout.write(`install-safety: verified ${required.length} artifact(s) in ${tarballPath}\n`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function usage() {
|
|
169
|
+
process.stderr.write(`Usage:
|
|
170
|
+
node scripts/install-safety.cjs stop-running [--dry-run] [--best-effort]
|
|
171
|
+
node scripts/install-safety.cjs verify-built
|
|
172
|
+
node scripts/install-safety.cjs verify-tarball <tarball>
|
|
173
|
+
`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function main() {
|
|
177
|
+
const [command, ...args] = process.argv.slice(2);
|
|
178
|
+
if (command === "stop-running") {
|
|
179
|
+
stopRunningAgentBridge({
|
|
180
|
+
dryRun: args.includes("--dry-run"),
|
|
181
|
+
bestEffort: args.includes("--best-effort"),
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (command === "verify-built") {
|
|
186
|
+
verifyBuiltArtifacts();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (command === "verify-tarball") {
|
|
190
|
+
verifyTarball(args[0]);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
usage();
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (require.main === module) {
|
|
198
|
+
main();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
REQUIRED_ARTIFACTS,
|
|
203
|
+
agentBridgeInstallEnv,
|
|
204
|
+
commandLine,
|
|
205
|
+
requiredPackagePaths,
|
|
206
|
+
stopRunningAgentBridge,
|
|
207
|
+
verifyBuiltArtifacts,
|
|
208
|
+
verifyTarball,
|
|
209
|
+
};
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -1,18 +1,86 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* postinstall
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* postinstall: verify Bun, register marketplace, install plugin.
|
|
5
|
+
* Runs after `npm install -g @raysonmeng/agentbridge`.
|
|
6
|
+
*
|
|
7
|
+
* All steps are best-effort — a failure here does not block the npm install.
|
|
8
|
+
* Users can always fall back to `abg init` for manual setup.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
const { execFileSync } = require("child_process");
|
|
12
|
+
const { existsSync } = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const { stopRunningAgentBridge } = require("./install-safety.cjs");
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
17
|
+
const MARKETPLACE_NAME = "agentbridge";
|
|
18
|
+
const PLUGIN_NAME = "agentbridge";
|
|
19
|
+
// Every external command gets a hard timeout: postinstall runs inside the
|
|
20
|
+
// user's `npm install`, and a hung `claude` CLI (auth prompt, broken update)
|
|
21
|
+
// previously hung the whole install with it. Timeouts fall through to the
|
|
22
|
+
// existing warn-and-continue paths (`abg init` is always the fallback).
|
|
23
|
+
const VERSION_PROBE_TIMEOUT_MS = 10_000;
|
|
24
|
+
const PLUGIN_STEP_TIMEOUT_MS = 30_000;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Declarative opt-out of the marketplace/plugin registration steps (CI image
|
|
28
|
+
* builds, air-gapped installs) — symmetric with AGENTBRIDGE_POSTINSTALL_STOP.
|
|
29
|
+
*/
|
|
30
|
+
function skipPluginRegistration(env = process.env) {
|
|
31
|
+
return env.AGENTBRIDGE_POSTINSTALL_PLUGIN === "0";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Decide whether postinstall should stop ALL running AgentBridge pairs.
|
|
35
|
+
*
|
|
36
|
+
* Stop-the-world is destructive (it kills every running daemon/TUI), so it is
|
|
37
|
+
* gated to INTENTIONAL global self-installs only. The intentional installer
|
|
38
|
+
* (scripts/install-global.mjs) already calls install-safety stop-running
|
|
39
|
+
* directly, so postinstall only needs to react to an explicit global signal.
|
|
40
|
+
*
|
|
41
|
+
* Pure + injectable for unit testing.
|
|
42
|
+
*
|
|
43
|
+
* @param {{ env?: NodeJS.ProcessEnv, hasSourceCli?: boolean }} [opts]
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
function shouldStopRunningDaemons({
|
|
47
|
+
env = process.env,
|
|
48
|
+
hasSourceCli = existsSync(path.join(PACKAGE_ROOT, "src", "cli.ts")),
|
|
49
|
+
} = {}) {
|
|
50
|
+
// hasSourceCli is accepted for symmetry/future use; intentionally unused so
|
|
51
|
+
// that a packed (no-src) install no longer triggers stop-the-world on its own.
|
|
52
|
+
void hasSourceCli;
|
|
53
|
+
if (env.AGENTBRIDGE_POSTINSTALL_STOP === "0") return false;
|
|
54
|
+
if (env.AGENTBRIDGE_POSTINSTALL_STOP === "1") return true;
|
|
55
|
+
if (env.npm_config_global === "true") return true;
|
|
56
|
+
if (env.npm_config_location === "global") return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function runPostinstall() {
|
|
61
|
+
const dryRun = process.argv.includes("--dry-run");
|
|
62
|
+
|
|
63
|
+
if (dryRun) {
|
|
64
|
+
stopRunningAgentBridge({ dryRun: true });
|
|
65
|
+
console.log("$ claude --version");
|
|
66
|
+
console.log(`$ claude plugin marketplace add ${PACKAGE_ROOT}`);
|
|
67
|
+
console.log(`$ claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME}`);
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Step 1: Check Bun
|
|
72
|
+
let bunOk = false;
|
|
73
|
+
try {
|
|
74
|
+
const version = execFileSync("bun", ["--version"], { encoding: "utf-8", timeout: VERSION_PROBE_TIMEOUT_MS }).trim();
|
|
75
|
+
console.log(`\x1b[32m✔\x1b[0m AgentBridge: Bun ${version} detected.`);
|
|
76
|
+
bunOk = true;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// A timeout means bun exists but hung — "install Bun" advice would be
|
|
79
|
+
// misleading there; tell the truth and point at the abg init fallback.
|
|
80
|
+
if (e && e.code === "ETIMEDOUT") {
|
|
81
|
+
console.warn(`\x1b[33m⚠\x1b[0m AgentBridge: \`bun --version\` timed out — run \`abg init\` after the install completes.`);
|
|
82
|
+
} else {
|
|
83
|
+
console.warn(`
|
|
16
84
|
\x1b[33m⚠ AgentBridge requires Bun (v1.0+) as its runtime.\x1b[0m
|
|
17
85
|
|
|
18
86
|
The CLI was installed, but it won't work without Bun.
|
|
@@ -22,6 +90,58 @@ Install Bun with:
|
|
|
22
90
|
|
|
23
91
|
Then restart your terminal and run:
|
|
24
92
|
|
|
25
|
-
abg
|
|
93
|
+
abg init
|
|
26
94
|
`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Step 2: Register marketplace + install plugin (requires Claude Code)
|
|
99
|
+
if (bunOk && !skipPluginRegistration()) {
|
|
100
|
+
if (shouldStopRunningDaemons()) {
|
|
101
|
+
try {
|
|
102
|
+
stopRunningAgentBridge({ bestEffort: true });
|
|
103
|
+
} catch {
|
|
104
|
+
console.log(`\x1b[33m⚠\x1b[0m AgentBridge: could not stop running daemons — run \`abg kill --all\` before relying on this install.`);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.log(`\x1b[33m⚠\x1b[0m AgentBridge: not an explicit global self-install — leaving running daemons untouched (use \`abg kill --all\` or install-global to stop).`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
execFileSync("claude", ["--version"], { encoding: "utf-8", timeout: VERSION_PROBE_TIMEOUT_MS });
|
|
112
|
+
} catch {
|
|
113
|
+
console.log(`\x1b[33m⚠\x1b[0m AgentBridge: Claude Code not found — skipping plugin install.`);
|
|
114
|
+
console.log(` After installing Claude Code, run: abg init`);
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
execFileSync("claude", ["plugin", "marketplace", "add", PACKAGE_ROOT], {
|
|
120
|
+
stdio: "pipe",
|
|
121
|
+
timeout: PLUGIN_STEP_TIMEOUT_MS,
|
|
122
|
+
});
|
|
123
|
+
console.log(`\x1b[32m✔\x1b[0m AgentBridge: Marketplace registered.`);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.log(`\x1b[33m⚠\x1b[0m AgentBridge: Marketplace registration failed — run \`abg init\` to retry.`);
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
execFileSync("claude", ["plugin", "install", `${PLUGIN_NAME}@${MARKETPLACE_NAME}`], {
|
|
131
|
+
stdio: "pipe",
|
|
132
|
+
timeout: PLUGIN_STEP_TIMEOUT_MS,
|
|
133
|
+
});
|
|
134
|
+
console.log(`\x1b[32m✔\x1b[0m AgentBridge: Plugin installed. Run \`abg claude\` to start.`);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.log(`\x1b[33m⚠\x1b[0m AgentBridge: Plugin install failed — run \`abg init\` to retry.`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = { shouldStopRunningDaemons, skipPluginRegistration };
|
|
142
|
+
|
|
143
|
+
// Only run install side effects when invoked directly (not when require()'d
|
|
144
|
+
// from a unit test).
|
|
145
|
+
if (require.main === module) {
|
|
146
|
+
runPostinstall();
|
|
27
147
|
}
|