@raysonmeng/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/dist/cli.js ADDED
@@ -0,0 +1,1158 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
5
+ var __returnValue = (v) => v;
6
+ function __exportSetter(name, newValue) {
7
+ this[name] = __returnValue.bind(null, newValue);
8
+ }
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true,
14
+ configurable: true,
15
+ set: __exportSetter.bind(all, name)
16
+ });
17
+ };
18
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
+
20
+ // src/config-service.ts
21
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
22
+ import { join } from "path";
23
+
24
+ class ConfigService {
25
+ configDir;
26
+ configPath;
27
+ collaborationPath;
28
+ constructor(projectRoot) {
29
+ const root = projectRoot ?? process.cwd();
30
+ this.configDir = join(root, CONFIG_DIR);
31
+ this.configPath = join(this.configDir, CONFIG_FILE);
32
+ this.collaborationPath = join(this.configDir, COLLABORATION_FILE);
33
+ }
34
+ hasConfig() {
35
+ return existsSync(this.configPath);
36
+ }
37
+ load() {
38
+ try {
39
+ const raw = readFileSync(this.configPath, "utf-8");
40
+ return JSON.parse(raw);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ loadOrDefault() {
46
+ return this.load() ?? structuredClone(DEFAULT_CONFIG);
47
+ }
48
+ save(config) {
49
+ this.ensureConfigDir();
50
+ writeFileSync(this.configPath, JSON.stringify(config, null, 2) + `
51
+ `, "utf-8");
52
+ }
53
+ loadCollaboration() {
54
+ try {
55
+ return readFileSync(this.collaborationPath, "utf-8");
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ saveCollaboration(content) {
61
+ this.ensureConfigDir();
62
+ writeFileSync(this.collaborationPath, content, "utf-8");
63
+ }
64
+ initDefaults() {
65
+ this.ensureConfigDir();
66
+ const created = [];
67
+ if (!existsSync(this.configPath)) {
68
+ this.save(DEFAULT_CONFIG);
69
+ created.push(this.configPath);
70
+ }
71
+ if (!existsSync(this.collaborationPath)) {
72
+ this.saveCollaboration(DEFAULT_COLLABORATION_MD);
73
+ created.push(this.collaborationPath);
74
+ }
75
+ return created;
76
+ }
77
+ get configFilePath() {
78
+ return this.configPath;
79
+ }
80
+ get collaborationFilePath() {
81
+ return this.collaborationPath;
82
+ }
83
+ ensureConfigDir() {
84
+ if (!existsSync(this.configDir)) {
85
+ mkdirSync(this.configDir, { recursive: true });
86
+ }
87
+ }
88
+ }
89
+ var DEFAULT_CONFIG, DEFAULT_COLLABORATION_MD = `# Collaboration Rules
90
+
91
+ ## Roles
92
+ - Claude: Reviewer, Planner, Hypothesis Challenger
93
+ - Codex: Implementer, Executor, Reproducer/Verifier
94
+
95
+ ## Thinking Patterns
96
+ - Analytical/review tasks: Independent Analysis & Convergence
97
+ - Implementation tasks: Architect -> Builder -> Critic
98
+ - Debugging tasks: Hypothesis -> Experiment -> Interpretation
99
+
100
+ ## Communication
101
+ - Use explicit phrases: "My independent view is:", "I agree on:", "I disagree on:", "Current consensus:"
102
+ - Tag messages with [IMPORTANT], [STATUS], or [FYI]
103
+
104
+ ## Review Process
105
+ - Cross-review: author never reviews their own code
106
+ - All changes go through feature/fix branches + PR
107
+ - Merge via squash merge
108
+
109
+ ## Custom Rules
110
+ <!-- Add your project-specific collaboration rules here -->
111
+ `, CONFIG_DIR = ".agentbridge", CONFIG_FILE = "config.json", COLLABORATION_FILE = "collaboration.md";
112
+ var init_config_service = __esm(() => {
113
+ DEFAULT_CONFIG = {
114
+ version: "1.0",
115
+ daemon: {
116
+ port: 4500,
117
+ proxyPort: 4501
118
+ },
119
+ agents: {
120
+ claude: {
121
+ role: "Reviewer, Planner",
122
+ mode: "push"
123
+ },
124
+ codex: {
125
+ role: "Implementer, Executor"
126
+ }
127
+ },
128
+ markers: ["IMPORTANT", "STATUS", "FYI"],
129
+ turnCoordination: {
130
+ attentionWindowSeconds: 15,
131
+ busyGuard: true
132
+ },
133
+ idleShutdownSeconds: 30
134
+ };
135
+ });
136
+
137
+ // src/cli/pkg-root.ts
138
+ import { dirname as dirname2, join as join2 } from "path";
139
+ import { existsSync as existsSync2 } from "fs";
140
+ import { execFileSync } from "child_process";
141
+ function findPackageRoot() {
142
+ let dir = import.meta.dir;
143
+ while (true) {
144
+ if (existsSync2(join2(dir, "package.json"))) {
145
+ return dir;
146
+ }
147
+ const parent = dirname2(dir);
148
+ if (parent === dir) {
149
+ throw new Error("Could not find package.json in any parent directory");
150
+ }
151
+ dir = parent;
152
+ }
153
+ }
154
+ function registerMarketplace(marketplaceRoot) {
155
+ execFileSync("claude", ["plugin", "marketplace", "add", marketplaceRoot], {
156
+ stdio: "inherit"
157
+ });
158
+ }
159
+ var init_pkg_root = () => {};
160
+
161
+ // src/cli/init.ts
162
+ var exports_init = {};
163
+ __export(exports_init, {
164
+ runInit: () => runInit,
165
+ compareVersions: () => compareVersions
166
+ });
167
+ import { execSync, execFileSync as execFileSync2 } from "child_process";
168
+ async function runInit() {
169
+ console.log(`AgentBridge Init
170
+ `);
171
+ console.log("Checking dependencies...");
172
+ checkBun();
173
+ checkClaude();
174
+ checkCodex();
175
+ console.log("");
176
+ console.log("Generating project config...");
177
+ const configService = new ConfigService;
178
+ const created = configService.initDefaults();
179
+ if (created.length > 0) {
180
+ for (const file of created) {
181
+ console.log(` Created: ${file}`);
182
+ }
183
+ } else {
184
+ console.log(" Project config already exists, skipping.");
185
+ }
186
+ console.log("");
187
+ console.log("Installing AgentBridge plugin...");
188
+ try {
189
+ registerMarketplace(findPackageRoot());
190
+ execFileSync2("claude", ["plugin", "install", `${PLUGIN_NAME}@${MARKETPLACE_NAME}`], {
191
+ stdio: "inherit"
192
+ });
193
+ console.log(" Plugin installed successfully.");
194
+ } catch {
195
+ console.log(" Plugin install skipped (marketplace registration or install failed).");
196
+ console.log(" You can install it later with:");
197
+ console.log(` abg dev # registers marketplace and installs plugin`);
198
+ }
199
+ console.log("");
200
+ console.log(`Setup complete!
201
+ `);
202
+ console.log("Next steps:");
203
+ console.log(" 1. If Claude Code is already running, execute /reload-plugins in your session");
204
+ console.log(" 2. Start Claude Code: agentbridge claude");
205
+ console.log(" 3. Start Codex TUI: agentbridge codex");
206
+ }
207
+ function checkBun() {
208
+ try {
209
+ const version = execSync("bun --version", { encoding: "utf-8" }).trim();
210
+ console.log(` bun: ${version}`);
211
+ } catch {
212
+ console.error(" ERROR: bun not found in PATH.");
213
+ console.error(" Install Bun: https://bun.sh");
214
+ process.exit(1);
215
+ }
216
+ }
217
+ function checkClaude() {
218
+ try {
219
+ const versionOutput = execSync("claude --version", { encoding: "utf-8" }).trim();
220
+ const match = versionOutput.match(/(\d+\.\d+\.\d+)/);
221
+ if (match) {
222
+ const version = match[1];
223
+ console.log(` claude: ${version}`);
224
+ if (compareVersions(version, MIN_CLAUDE_VERSION) < 0) {
225
+ console.error(` ERROR: Claude Code version ${version} is too old.`);
226
+ console.error(` Channels require >= ${MIN_CLAUDE_VERSION}.`);
227
+ console.error(" Update: npm update -g @anthropic-ai/claude-code");
228
+ process.exit(1);
229
+ }
230
+ } else {
231
+ console.log(` claude: ${versionOutput} (version check skipped)`);
232
+ }
233
+ } catch {
234
+ console.error(" ERROR: claude not found in PATH.");
235
+ console.error(" Install Claude Code: npm install -g @anthropic-ai/claude-code");
236
+ process.exit(1);
237
+ }
238
+ }
239
+ function checkCodex() {
240
+ try {
241
+ const version = execSync("codex --version", { encoding: "utf-8" }).trim();
242
+ console.log(` codex: ${version}`);
243
+ } catch {
244
+ console.error(" ERROR: codex not found in PATH.");
245
+ console.error(" Install Codex: https://github.com/openai/codex");
246
+ process.exit(1);
247
+ }
248
+ }
249
+ function compareVersions(a, b) {
250
+ const pa = a.split(".").map(Number);
251
+ const pb = b.split(".").map(Number);
252
+ for (let i = 0;i < 3; i++) {
253
+ const va = pa[i] ?? 0;
254
+ const vb = pb[i] ?? 0;
255
+ if (va < vb)
256
+ return -1;
257
+ if (va > vb)
258
+ return 1;
259
+ }
260
+ return 0;
261
+ }
262
+ var MIN_CLAUDE_VERSION = "2.1.80";
263
+ var init_init = __esm(() => {
264
+ init_config_service();
265
+ init_cli();
266
+ init_pkg_root();
267
+ });
268
+
269
+ // src/cli/dev.ts
270
+ var exports_dev = {};
271
+ __export(exports_dev, {
272
+ runDev: () => runDev
273
+ });
274
+ import { execFileSync as execFileSync3 } from "child_process";
275
+ import { resolve } from "path";
276
+ import { existsSync as existsSync3, cpSync, rmSync } from "fs";
277
+ import { homedir } from "os";
278
+ async function runDev() {
279
+ console.log(`AgentBridge Dev Setup
280
+ `);
281
+ const projectRoot = findPackageRoot();
282
+ const marketplacePath = resolve(projectRoot, ".claude-plugin", "marketplace.json");
283
+ const pluginDir = resolve(projectRoot, "plugins", "agentbridge");
284
+ const pluginManifest = resolve(pluginDir, ".claude-plugin", "plugin.json");
285
+ if (!existsSync3(pluginManifest)) {
286
+ console.error(` ERROR: Plugin manifest not found at ${pluginManifest}`);
287
+ console.error(" Run 'bun run build:plugin' first, or check your working tree.");
288
+ process.exit(1);
289
+ }
290
+ if (!existsSync3(marketplacePath)) {
291
+ console.error(` ERROR: Marketplace manifest not found at ${marketplacePath}`);
292
+ process.exit(1);
293
+ }
294
+ console.log(` Plugin source: ${pluginDir}`);
295
+ console.log(`
296
+ Registering local marketplace...`);
297
+ try {
298
+ registerMarketplace(projectRoot);
299
+ } catch (e) {
300
+ console.error(` ERROR: Failed to register marketplace: ${e.message}`);
301
+ process.exit(1);
302
+ }
303
+ const pluginRef = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
304
+ console.log(`
305
+ Installing plugin...`);
306
+ try {
307
+ const listOutput = execFileSync3("claude", ["plugin", "list"], { encoding: "utf-8" });
308
+ if (!listOutput.includes(pluginRef)) {
309
+ execFileSync3("claude", ["plugin", "install", pluginRef], { stdio: "inherit" });
310
+ } else {
311
+ console.log(` Plugin '${pluginRef}' already installed.`);
312
+ }
313
+ } catch (e) {
314
+ console.error(` ERROR: Failed to install plugin: ${e.message}`);
315
+ process.exit(1);
316
+ }
317
+ console.log(`
318
+ Syncing local plugin to cache...`);
319
+ const cacheDir = resolve(homedir(), ".claude", "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME);
320
+ if (existsSync3(cacheDir)) {
321
+ const versionDirs = Bun.spawnSync(["ls", cacheDir]).stdout.toString().trim().split(`
322
+ `).filter(Boolean);
323
+ for (const ver of versionDirs) {
324
+ const targetDir = resolve(cacheDir, ver);
325
+ rmSync(targetDir, { recursive: true, force: true });
326
+ cpSync(pluginDir, targetDir, { recursive: true });
327
+ console.log(` Synced to ${targetDir}`);
328
+ }
329
+ } else {
330
+ console.log(" Cache directory not found, plugin install should have created it.");
331
+ }
332
+ console.log(`
333
+ \u2705 Dev setup complete!
334
+ `);
335
+ console.log("Next steps:");
336
+ console.log(" agentbridge claude # Start Claude Code (plugin auto-loaded)");
337
+ console.log(" agentbridge codex # Start Codex TUI");
338
+ console.log("");
339
+ console.log("Code changed? Run 'agentbridge dev' again, then restart Claude Code or /reload-plugins.");
340
+ }
341
+ var init_dev = __esm(() => {
342
+ init_cli();
343
+ init_pkg_root();
344
+ });
345
+
346
+ // src/daemon-lifecycle.ts
347
+ import { spawn, execFileSync as execFileSync4 } from "child_process";
348
+ import { existsSync as existsSync4, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2, openSync, closeSync, constants } from "fs";
349
+ import { fileURLToPath } from "url";
350
+
351
+ class DaemonLifecycle {
352
+ stateDir;
353
+ controlPort;
354
+ log;
355
+ constructor(opts) {
356
+ this.stateDir = opts.stateDir;
357
+ this.controlPort = opts.controlPort;
358
+ this.log = opts.log;
359
+ }
360
+ get healthUrl() {
361
+ return `http://127.0.0.1:${this.controlPort}/healthz`;
362
+ }
363
+ get readyUrl() {
364
+ return `http://127.0.0.1:${this.controlPort}/readyz`;
365
+ }
366
+ get controlWsUrl() {
367
+ return `ws://127.0.0.1:${this.controlPort}/ws`;
368
+ }
369
+ async ensureRunning() {
370
+ if (await this.isHealthy()) {
371
+ await this.waitForReady();
372
+ return;
373
+ }
374
+ const existingPid = this.readPid();
375
+ if (existingPid) {
376
+ if (isProcessAlive(existingPid)) {
377
+ if (this.isDaemonProcess(existingPid)) {
378
+ try {
379
+ await this.waitForReady(12, 250);
380
+ return;
381
+ } catch {
382
+ throw new Error(`Found existing daemon process ${existingPid}, but control port ${this.controlPort} never became ready.`);
383
+ }
384
+ }
385
+ this.log(`Pid ${existingPid} is alive but not an AgentBridge daemon, removing stale pid file`);
386
+ }
387
+ this.removeStalePidFile();
388
+ }
389
+ const lockAcquired = this.acquireLock();
390
+ if (!lockAcquired) {
391
+ this.log("Another process is starting the daemon, waiting for readiness...");
392
+ await this.waitForReady();
393
+ return;
394
+ }
395
+ try {
396
+ this.launch();
397
+ await this.waitForReady();
398
+ } finally {
399
+ this.releaseLock();
400
+ }
401
+ }
402
+ async isHealthy() {
403
+ try {
404
+ const response = await fetch(this.healthUrl);
405
+ return response.ok;
406
+ } catch {
407
+ return false;
408
+ }
409
+ }
410
+ async waitForHealthy(maxRetries = 40, delayMs = 250) {
411
+ for (let attempt = 0;attempt < maxRetries; attempt++) {
412
+ if (await this.isHealthy())
413
+ return;
414
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
415
+ }
416
+ throw new Error(`Timed out waiting for AgentBridge daemon health on ${this.healthUrl}`);
417
+ }
418
+ async isReady() {
419
+ try {
420
+ const response = await fetch(this.readyUrl);
421
+ return response.ok;
422
+ } catch {
423
+ return false;
424
+ }
425
+ }
426
+ async waitForReady(maxRetries = 40, delayMs = 250) {
427
+ for (let attempt = 0;attempt < maxRetries; attempt++) {
428
+ if (await this.isReady())
429
+ return;
430
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
431
+ }
432
+ throw new Error(`Timed out waiting for AgentBridge daemon readiness on ${this.readyUrl}`);
433
+ }
434
+ readStatus() {
435
+ try {
436
+ const raw = readFileSync2(this.stateDir.statusFile, "utf-8");
437
+ return JSON.parse(raw);
438
+ } catch {
439
+ return null;
440
+ }
441
+ }
442
+ writeStatus(status) {
443
+ this.stateDir.ensure();
444
+ writeFileSync2(this.stateDir.statusFile, JSON.stringify(status, null, 2) + `
445
+ `, "utf-8");
446
+ }
447
+ readPid() {
448
+ try {
449
+ const raw = readFileSync2(this.stateDir.pidFile, "utf-8").trim();
450
+ if (!raw)
451
+ return null;
452
+ const pid = Number.parseInt(raw, 10);
453
+ return Number.isFinite(pid) ? pid : null;
454
+ } catch {
455
+ return null;
456
+ }
457
+ }
458
+ writePid(pid) {
459
+ this.stateDir.ensure();
460
+ writeFileSync2(this.stateDir.pidFile, `${pid ?? process.pid}
461
+ `, "utf-8");
462
+ }
463
+ removePidFile() {
464
+ try {
465
+ unlinkSync(this.stateDir.pidFile);
466
+ } catch {}
467
+ }
468
+ removeStatusFile() {
469
+ try {
470
+ unlinkSync(this.stateDir.statusFile);
471
+ } catch {}
472
+ }
473
+ markKilled() {
474
+ this.stateDir.ensure();
475
+ writeFileSync2(this.stateDir.killedFile, `${Date.now()}
476
+ `, "utf-8");
477
+ }
478
+ clearKilled() {
479
+ try {
480
+ unlinkSync(this.stateDir.killedFile);
481
+ } catch {}
482
+ }
483
+ wasKilled() {
484
+ return existsSync4(this.stateDir.killedFile);
485
+ }
486
+ launch() {
487
+ this.stateDir.ensure();
488
+ this.log(`Launching detached daemon on control port ${this.controlPort}`);
489
+ const daemonProc = spawn(process.execPath, ["run", DAEMON_PATH], {
490
+ cwd: process.cwd(),
491
+ env: {
492
+ ...process.env,
493
+ AGENTBRIDGE_CONTROL_PORT: String(this.controlPort),
494
+ AGENTBRIDGE_STATE_DIR: this.stateDir.dir
495
+ },
496
+ detached: true,
497
+ stdio: "ignore"
498
+ });
499
+ daemonProc.unref();
500
+ }
501
+ removeStalePidFile() {
502
+ this.log("Removing stale pid file");
503
+ this.removePidFile();
504
+ }
505
+ acquireLock(depth = 0) {
506
+ if (depth > 1) {
507
+ this.log("Lock acquisition failed after retry, proceeding without lock");
508
+ return true;
509
+ }
510
+ this.stateDir.ensure();
511
+ try {
512
+ const fd = openSync(this.stateDir.lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
513
+ writeFileSync2(fd, `${process.pid}
514
+ `);
515
+ closeSync(fd);
516
+ return true;
517
+ } catch (err) {
518
+ if (err.code === "EEXIST") {
519
+ try {
520
+ const holderPid = Number.parseInt(readFileSync2(this.stateDir.lockFile, "utf-8").trim(), 10);
521
+ if (Number.isFinite(holderPid) && !isProcessAlive(holderPid)) {
522
+ this.log(`Stale lock file from dead process ${holderPid}, removing`);
523
+ this.releaseLock();
524
+ return this.acquireLock(depth + 1);
525
+ }
526
+ } catch {
527
+ this.log("Cannot read lock file, removing stale lock");
528
+ this.releaseLock();
529
+ return this.acquireLock(depth + 1);
530
+ }
531
+ return false;
532
+ }
533
+ this.log(`Warning: could not acquire startup lock: ${err.message}`);
534
+ return true;
535
+ }
536
+ }
537
+ releaseLock() {
538
+ try {
539
+ unlinkSync(this.stateDir.lockFile);
540
+ } catch {}
541
+ }
542
+ async kill(gracefulTimeoutMs = 3000) {
543
+ const pid = this.readPid();
544
+ if (!pid) {
545
+ this.log("No daemon pid file found");
546
+ this.cleanup();
547
+ return false;
548
+ }
549
+ if (!isProcessAlive(pid)) {
550
+ this.log(`Daemon pid ${pid} is not alive, cleaning up stale files`);
551
+ this.cleanup();
552
+ return false;
553
+ }
554
+ if (!this.isDaemonProcess(pid)) {
555
+ this.log(`Pid ${pid} is alive but is NOT an AgentBridge daemon \u2014 refusing to kill. Cleaning up stale pid file.`);
556
+ this.cleanup();
557
+ return false;
558
+ }
559
+ this.log(`Sending SIGTERM to daemon pid ${pid}`);
560
+ try {
561
+ process.kill(pid, "SIGTERM");
562
+ } catch {
563
+ this.cleanup();
564
+ return false;
565
+ }
566
+ const deadline = Date.now() + gracefulTimeoutMs;
567
+ while (Date.now() < deadline) {
568
+ if (!isProcessAlive(pid)) {
569
+ this.log(`Daemon pid ${pid} stopped gracefully`);
570
+ this.cleanup();
571
+ return true;
572
+ }
573
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
574
+ }
575
+ this.log(`Daemon pid ${pid} did not stop gracefully, sending SIGKILL`);
576
+ try {
577
+ process.kill(pid, "SIGKILL");
578
+ } catch {}
579
+ this.cleanup();
580
+ return true;
581
+ }
582
+ isDaemonProcess(pid) {
583
+ try {
584
+ const cmd = execFileSync4("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
585
+ return cmd.includes("daemon") && (cmd.includes("agentbridge") || cmd.includes("agent_bridge"));
586
+ } catch {
587
+ return false;
588
+ }
589
+ }
590
+ cleanup() {
591
+ this.removePidFile();
592
+ this.removeStatusFile();
593
+ this.releaseLock();
594
+ }
595
+ }
596
+ function isProcessAlive(pid) {
597
+ try {
598
+ process.kill(pid, 0);
599
+ return true;
600
+ } catch {
601
+ return false;
602
+ }
603
+ }
604
+ var DAEMON_ENTRY, DAEMON_PATH;
605
+ var init_daemon_lifecycle = __esm(() => {
606
+ DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY ?? "./daemon.ts";
607
+ DAEMON_PATH = fileURLToPath(new URL(DAEMON_ENTRY, import.meta.url));
608
+ });
609
+
610
+ // src/state-dir.ts
611
+ import { mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
612
+ import { join as join3 } from "path";
613
+ import { homedir as homedir2, platform } from "os";
614
+
615
+ class StateDirResolver {
616
+ stateDir;
617
+ constructor(envOverride) {
618
+ const override = envOverride ?? process.env.AGENTBRIDGE_STATE_DIR;
619
+ if (override) {
620
+ this.stateDir = override;
621
+ } else if (platform() === "darwin") {
622
+ this.stateDir = join3(homedir2(), "Library", "Application Support", "AgentBridge");
623
+ } else {
624
+ const xdgState = process.env.XDG_STATE_HOME ?? join3(homedir2(), ".local", "state");
625
+ this.stateDir = join3(xdgState, "agentbridge");
626
+ }
627
+ }
628
+ ensure() {
629
+ if (!existsSync5(this.stateDir)) {
630
+ mkdirSync2(this.stateDir, { recursive: true });
631
+ }
632
+ }
633
+ get dir() {
634
+ return this.stateDir;
635
+ }
636
+ get pidFile() {
637
+ return join3(this.stateDir, "daemon.pid");
638
+ }
639
+ get tuiPidFile() {
640
+ return join3(this.stateDir, "codex-tui.pid");
641
+ }
642
+ get lockFile() {
643
+ return join3(this.stateDir, "daemon.lock");
644
+ }
645
+ get statusFile() {
646
+ return join3(this.stateDir, "status.json");
647
+ }
648
+ get portsFile() {
649
+ return join3(this.stateDir, "ports.json");
650
+ }
651
+ get logFile() {
652
+ return join3(this.stateDir, "agentbridge.log");
653
+ }
654
+ get killedFile() {
655
+ return join3(this.stateDir, "killed");
656
+ }
657
+ }
658
+ var init_state_dir = () => {};
659
+
660
+ // src/cli/claude.ts
661
+ var exports_claude = {};
662
+ __export(exports_claude, {
663
+ runClaude: () => runClaude,
664
+ checkOwnedFlagConflicts: () => checkOwnedFlagConflicts
665
+ });
666
+ import { spawn as spawn2 } from "child_process";
667
+ async function runClaude(args) {
668
+ checkOwnedFlagConflicts(args, "agentbridge claude", OWNED_FLAGS);
669
+ const stateDir = new StateDirResolver;
670
+ const controlPort = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
671
+ const lifecycle = new DaemonLifecycle({
672
+ stateDir,
673
+ controlPort,
674
+ log: (msg) => console.error(`[agentbridge] ${msg}`)
675
+ });
676
+ lifecycle.clearKilled();
677
+ const channelEntry = `plugin:${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
678
+ const fullArgs = [
679
+ "--dangerously-load-development-channels",
680
+ channelEntry,
681
+ ...args
682
+ ];
683
+ const child = spawn2("claude", fullArgs, {
684
+ stdio: "inherit",
685
+ env: process.env
686
+ });
687
+ child.on("exit", (code) => {
688
+ process.exit(code ?? 0);
689
+ });
690
+ child.on("error", (err) => {
691
+ if (err.code === "ENOENT") {
692
+ console.error("Error: claude not found in PATH.");
693
+ console.error("Install Claude Code: npm install -g @anthropic-ai/claude-code");
694
+ process.exit(1);
695
+ }
696
+ console.error(`Error starting Claude Code: ${err.message}`);
697
+ process.exit(1);
698
+ });
699
+ }
700
+ function checkOwnedFlagConflicts(args, commandName, ownedFlags) {
701
+ for (const flag of ownedFlags) {
702
+ if (args.some((a) => a === flag || a.startsWith(`${flag}=`))) {
703
+ console.error(`Error: "${flag}" is automatically set by ${commandName}.`);
704
+ console.error("");
705
+ console.error("AgentBridge automatically injects these flags:");
706
+ for (const f of ownedFlags) {
707
+ console.error(` ${f}`);
708
+ }
709
+ console.error("");
710
+ const nativeCmd = commandName.includes("codex") ? "codex" : "claude";
711
+ console.error("If you need full control over these flags, use the native command directly:");
712
+ console.error(` ${nativeCmd} [your flags here]`);
713
+ process.exit(1);
714
+ }
715
+ }
716
+ }
717
+ var OWNED_FLAGS;
718
+ var init_claude = __esm(() => {
719
+ init_cli();
720
+ init_daemon_lifecycle();
721
+ init_state_dir();
722
+ OWNED_FLAGS = ["--channels", "--dangerously-load-development-channels"];
723
+ });
724
+
725
+ // src/cli/codex.ts
726
+ var exports_codex = {};
727
+ __export(exports_codex, {
728
+ runCodex: () => runCodex
729
+ });
730
+ import { spawn as spawn3, execSync as execSync2 } from "child_process";
731
+ import { openSync as openSync2, writeSync, closeSync as closeSync2, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
732
+ async function runCodex(args) {
733
+ checkOwnedFlagConflicts(args, "agentbridge codex", OWNED_FLAGS2);
734
+ for (let i = 0;i < args.length; i++) {
735
+ if (args[i] === "--enable" && args[i + 1] === "tui_app_server") {
736
+ console.error(`Error: "--enable tui_app_server" is automatically set by agentbridge codex.`);
737
+ console.error("");
738
+ console.error("If you need full control over these flags, use the native command directly:");
739
+ console.error(" codex [your flags here]");
740
+ process.exit(1);
741
+ }
742
+ if (args[i] === "--enable=tui_app_server") {
743
+ console.error(`Error: "--enable=tui_app_server" is automatically set by agentbridge codex.`);
744
+ console.error("");
745
+ console.error("If you need full control over these flags, use the native command directly:");
746
+ console.error(" codex [your flags here]");
747
+ process.exit(1);
748
+ }
749
+ }
750
+ const stateDir = new StateDirResolver;
751
+ const configService = new ConfigService;
752
+ const config = configService.loadOrDefault();
753
+ const controlPort = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
754
+ const lifecycle = new DaemonLifecycle({
755
+ stateDir,
756
+ controlPort,
757
+ log: (msg) => console.error(`[agentbridge] ${msg}`)
758
+ });
759
+ console.error("[agentbridge] Ensuring daemon is running...");
760
+ try {
761
+ lifecycle.clearKilled();
762
+ await lifecycle.ensureRunning();
763
+ console.error("[agentbridge] Daemon is ready.");
764
+ } catch (err) {
765
+ console.error(`[agentbridge] Failed to start daemon: ${err.message}`);
766
+ console.error("[agentbridge] Try: agentbridge kill && agentbridge claude");
767
+ process.exit(1);
768
+ }
769
+ let proxyUrl;
770
+ const status = lifecycle.readStatus();
771
+ if (status?.proxyUrl) {
772
+ proxyUrl = status.proxyUrl;
773
+ } else {
774
+ proxyUrl = `ws://127.0.0.1:${config.daemon.proxyPort}`;
775
+ console.error(`[agentbridge] No daemon status found, using config default: ${proxyUrl}`);
776
+ }
777
+ try {
778
+ await waitForProxyReady(proxyUrl);
779
+ } catch (err) {
780
+ console.error(`[agentbridge] ${err.message}`);
781
+ process.exit(1);
782
+ }
783
+ console.log(`Connecting Codex TUI to AgentBridge at ${proxyUrl}...`);
784
+ let savedStty = null;
785
+ if (process.stdin.isTTY) {
786
+ try {
787
+ savedStty = execSync2("stty -g", { encoding: "utf-8", stdio: ["inherit", "pipe", "pipe"] }).trim();
788
+ } catch {}
789
+ }
790
+ function restoreTerminal() {
791
+ if (savedStty && process.stdin.isTTY) {
792
+ try {
793
+ execSync2(`stty ${savedStty}`, { stdio: ["inherit", "ignore", "ignore"] });
794
+ } catch {
795
+ try {
796
+ execSync2("stty sane", { stdio: ["inherit", "ignore", "ignore"] });
797
+ } catch {}
798
+ }
799
+ }
800
+ let ttyFd = null;
801
+ try {
802
+ ttyFd = openSync2("/dev/tty", "w");
803
+ } catch {
804
+ if (process.stdout.isTTY) {
805
+ ttyFd = 1;
806
+ }
807
+ }
808
+ if (ttyFd !== null) {
809
+ const sequences = [
810
+ "\x1B[<u",
811
+ "\x1B[?2004l",
812
+ "\x1B[?1004l",
813
+ "\x1B[?1049l",
814
+ "\x1B[?25h",
815
+ "\x1B[0m"
816
+ ];
817
+ for (const seq of sequences) {
818
+ try {
819
+ writeSync(ttyFd, seq);
820
+ } catch {}
821
+ }
822
+ if (ttyFd !== 1) {
823
+ try {
824
+ closeSync2(ttyFd);
825
+ } catch {}
826
+ }
827
+ }
828
+ }
829
+ const fullArgs = [
830
+ "--enable",
831
+ "tui_app_server",
832
+ "--remote",
833
+ proxyUrl,
834
+ ...args
835
+ ];
836
+ const child = spawn3("codex", fullArgs, {
837
+ stdio: "inherit",
838
+ env: process.env
839
+ });
840
+ if (typeof child.pid === "number") {
841
+ writeFileSync3(stateDir.tuiPidFile, `${child.pid}
842
+ `, "utf-8");
843
+ }
844
+ let cleanedTuiPid = false;
845
+ function cleanupTuiPidFile() {
846
+ if (cleanedTuiPid)
847
+ return;
848
+ cleanedTuiPid = true;
849
+ try {
850
+ unlinkSync2(stateDir.tuiPidFile);
851
+ } catch {}
852
+ }
853
+ process.on("exit", () => {
854
+ restoreTerminal();
855
+ cleanupTuiPidFile();
856
+ });
857
+ process.on("SIGINT", () => {
858
+ restoreTerminal();
859
+ cleanupTuiPidFile();
860
+ process.exit(130);
861
+ });
862
+ process.on("SIGTERM", () => {
863
+ restoreTerminal();
864
+ cleanupTuiPidFile();
865
+ process.exit(143);
866
+ });
867
+ child.on("exit", (code) => {
868
+ cleanupTuiPidFile();
869
+ process.exit(code ?? 0);
870
+ });
871
+ child.on("error", (err) => {
872
+ cleanupTuiPidFile();
873
+ if (err.code === "ENOENT") {
874
+ console.error("Error: codex not found in PATH.");
875
+ console.error("Install Codex: https://github.com/openai/codex");
876
+ process.exit(1);
877
+ }
878
+ console.error(`Error starting Codex: ${err.message}`);
879
+ process.exit(1);
880
+ });
881
+ }
882
+ function proxyHealthUrl(proxyUrl) {
883
+ const url = new URL(proxyUrl);
884
+ url.protocol = url.protocol === "wss:" ? "https:" : "http:";
885
+ url.pathname = "/healthz";
886
+ url.search = "";
887
+ url.hash = "";
888
+ return url.toString();
889
+ }
890
+ async function waitForProxyReady(proxyUrl, maxRetries = 20, delayMs = 100) {
891
+ const healthUrl = proxyHealthUrl(proxyUrl);
892
+ for (let attempt = 0;attempt < maxRetries; attempt++) {
893
+ try {
894
+ const response = await fetch(healthUrl);
895
+ if (response.ok) {
896
+ return;
897
+ }
898
+ } catch {}
899
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
900
+ }
901
+ throw new Error(`Timed out waiting for Codex proxy readiness on ${healthUrl}`);
902
+ }
903
+ var OWNED_FLAGS2;
904
+ var init_codex = __esm(() => {
905
+ init_state_dir();
906
+ init_config_service();
907
+ init_daemon_lifecycle();
908
+ init_claude();
909
+ OWNED_FLAGS2 = ["--remote"];
910
+ });
911
+
912
+ // src/cli/kill.ts
913
+ var exports_kill = {};
914
+ __export(exports_kill, {
915
+ runKill: () => runKill
916
+ });
917
+ import { execFileSync as execFileSync5 } from "child_process";
918
+ import { readFileSync as readFileSync3, unlinkSync as unlinkSync3 } from "fs";
919
+ async function runKill() {
920
+ console.log(`AgentBridge Kill \u2014 stopping daemon and managed Codex TUI
921
+ `);
922
+ const stateDir = new StateDirResolver;
923
+ const controlPort = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
924
+ const lifecycle = new DaemonLifecycle({
925
+ stateDir,
926
+ controlPort,
927
+ log: (msg) => console.log(` ${msg}`)
928
+ });
929
+ lifecycle.markKilled();
930
+ const tuiKilled = await killManagedCodexTui(stateDir, (msg) => console.log(` ${msg}`));
931
+ const killed = await lifecycle.kill();
932
+ if (killed || tuiKilled) {
933
+ console.log(`
934
+ AgentBridge stopped.`);
935
+ console.log("Please restart Claude Code (`agentbridge claude`), switch to a new conversation, or run `/resume` to fully disconnect.");
936
+ } else {
937
+ console.log(`
938
+ No running AgentBridge daemon or managed Codex TUI found.`);
939
+ console.log("Stale state files cleaned up (if any).");
940
+ }
941
+ }
942
+ async function killManagedCodexTui(stateDir, log, gracefulTimeoutMs = 3000) {
943
+ const pid = readTuiPid(stateDir);
944
+ if (!pid) {
945
+ log("No Codex TUI pid file found");
946
+ removeTuiPidFile(stateDir);
947
+ return false;
948
+ }
949
+ if (!isProcessAlive(pid)) {
950
+ log(`Codex TUI pid ${pid} is not alive, cleaning up stale pid file`);
951
+ removeTuiPidFile(stateDir);
952
+ return false;
953
+ }
954
+ if (!isManagedCodexTuiProcess(pid)) {
955
+ log(`Pid ${pid} is alive but is NOT a managed AgentBridge Codex TUI \u2014 refusing to kill. Cleaning up stale pid file.`);
956
+ removeTuiPidFile(stateDir);
957
+ return false;
958
+ }
959
+ log(`Sending SIGTERM to Codex TUI pid ${pid}`);
960
+ try {
961
+ process.kill(pid, "SIGTERM");
962
+ } catch {
963
+ removeTuiPidFile(stateDir);
964
+ return false;
965
+ }
966
+ const deadline = Date.now() + gracefulTimeoutMs;
967
+ while (Date.now() < deadline) {
968
+ if (!isProcessAlive(pid)) {
969
+ log(`Codex TUI pid ${pid} stopped gracefully`);
970
+ removeTuiPidFile(stateDir);
971
+ return true;
972
+ }
973
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
974
+ }
975
+ log(`Codex TUI pid ${pid} did not stop gracefully, sending SIGKILL`);
976
+ try {
977
+ process.kill(pid, "SIGKILL");
978
+ } catch {}
979
+ removeTuiPidFile(stateDir);
980
+ return true;
981
+ }
982
+ function readTuiPid(stateDir) {
983
+ try {
984
+ const raw = readFileSync3(stateDir.tuiPidFile, "utf-8").trim();
985
+ if (!raw)
986
+ return null;
987
+ const pid = Number.parseInt(raw, 10);
988
+ return Number.isFinite(pid) ? pid : null;
989
+ } catch {
990
+ return null;
991
+ }
992
+ }
993
+ function removeTuiPidFile(stateDir) {
994
+ try {
995
+ unlinkSync3(stateDir.tuiPidFile);
996
+ } catch {}
997
+ }
998
+ function isManagedCodexTuiProcess(pid) {
999
+ try {
1000
+ const cmd = execFileSync5("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
1001
+ return cmd.includes("codex") && cmd.includes("--enable") && cmd.includes("tui_app_server") && cmd.includes("--remote");
1002
+ } catch {
1003
+ return false;
1004
+ }
1005
+ }
1006
+ var init_kill = __esm(() => {
1007
+ init_state_dir();
1008
+ init_daemon_lifecycle();
1009
+ });
1010
+
1011
+ // package.json
1012
+ var require_package = __commonJS((exports, module) => {
1013
+ module.exports = {
1014
+ name: "@raysonmeng/agentbridge",
1015
+ version: "0.1.0",
1016
+ description: "Bridge between Claude Code and Codex \u2014 bidirectional agent communication via MCP Channel + JSON-RPC",
1017
+ type: "module",
1018
+ bin: {
1019
+ agentbridge: "dist/cli.js",
1020
+ abg: "dist/cli.js"
1021
+ },
1022
+ files: [
1023
+ "dist/",
1024
+ "plugins/",
1025
+ ".claude-plugin/",
1026
+ "scripts/postinstall.cjs",
1027
+ "README.md",
1028
+ "LICENSE"
1029
+ ],
1030
+ scripts: {
1031
+ start: "bun run src/bridge.ts",
1032
+ "build:cli": "mkdir -p dist && bun build src/cli.ts --outfile dist/cli.js --target bun && chmod +x dist/cli.js",
1033
+ "build:plugin": "mkdir -p plugins/agentbridge/server && bun build src/bridge.ts --outfile plugins/agentbridge/server/bridge-server.js --target bun && bun build src/daemon.ts --outfile plugins/agentbridge/server/daemon.js --target bun",
1034
+ postinstall: "node scripts/postinstall.cjs",
1035
+ prepublishOnly: "bun run build:cli && bun run build:plugin",
1036
+ "validate:plugin": "claude plugin validate plugins/agentbridge && claude plugin validate .claude-plugin/marketplace.json",
1037
+ test: "bun test src",
1038
+ typecheck: "tsc --noEmit",
1039
+ "validate:plugin-versions": "bun scripts/check-plugin-versions.js",
1040
+ check: "tsc --noEmit && bun test src && bun run build:plugin && bun scripts/check-plugin-versions.js"
1041
+ },
1042
+ repository: {
1043
+ type: "git",
1044
+ url: "https://github.com/raysonmeng/agent-bridge.git"
1045
+ },
1046
+ homepage: "https://github.com/raysonmeng/agent-bridge#readme",
1047
+ bugs: {
1048
+ url: "https://github.com/raysonmeng/agent-bridge/issues"
1049
+ },
1050
+ keywords: [
1051
+ "claude-code",
1052
+ "codex",
1053
+ "mcp",
1054
+ "agent",
1055
+ "bridge",
1056
+ "multi-agent",
1057
+ "channels"
1058
+ ],
1059
+ author: "AgentBridge Contributors",
1060
+ license: "MIT",
1061
+ devDependencies: {
1062
+ "@modelcontextprotocol/sdk": "^1.27.1",
1063
+ "@types/bun": "^1.3.11",
1064
+ typescript: "^5.8.0"
1065
+ }
1066
+ };
1067
+ });
1068
+
1069
+ // src/cli.ts
1070
+ async function main() {
1071
+ switch (command) {
1072
+ case "init":
1073
+ const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), exports_init));
1074
+ await runInit2();
1075
+ break;
1076
+ case "dev":
1077
+ const { runDev: runDev2 } = await Promise.resolve().then(() => (init_dev(), exports_dev));
1078
+ await runDev2();
1079
+ break;
1080
+ case "claude":
1081
+ const { runClaude: runClaude2 } = await Promise.resolve().then(() => (init_claude(), exports_claude));
1082
+ await runClaude2(restArgs);
1083
+ break;
1084
+ case "codex":
1085
+ const { runCodex: runCodex2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
1086
+ await runCodex2(restArgs);
1087
+ break;
1088
+ case "kill":
1089
+ const { runKill: runKill2 } = await Promise.resolve().then(() => (init_kill(), exports_kill));
1090
+ await runKill2();
1091
+ break;
1092
+ case "--help":
1093
+ case "-h":
1094
+ case undefined:
1095
+ printHelp();
1096
+ break;
1097
+ case "--version":
1098
+ case "-v":
1099
+ printVersion();
1100
+ break;
1101
+ default:
1102
+ console.error(`Unknown command: ${command}`);
1103
+ console.error(`Run "agentbridge --help" (or "abg --help") for usage.`);
1104
+ process.exit(1);
1105
+ }
1106
+ }
1107
+ function printHelp() {
1108
+ console.log(`
1109
+ AgentBridge \u2014 Multi-agent collaboration bridge
1110
+
1111
+ Usage:
1112
+ agentbridge <command> [args...]
1113
+ abg <command> [args...]
1114
+
1115
+ Commands:
1116
+ init Install plugin, check dependencies, generate project config
1117
+ dev Register local marketplace + install plugin (for local dev)
1118
+ claude [args...] Start Claude Code with push channel enabled
1119
+ codex [args...] Start Codex TUI connected to AgentBridge daemon
1120
+ kill Force kill all AgentBridge processes
1121
+
1122
+ Options:
1123
+ --help, -h Show this help message
1124
+ --version, -v Show version
1125
+
1126
+ Examples:
1127
+ abg init # First-time setup
1128
+ abg claude # Start Claude Code
1129
+ abg claude --resume # Start Claude Code and resume session
1130
+ abg codex # Start Codex TUI
1131
+ abg codex --model o3 # Start Codex with specific model
1132
+ abg kill # Emergency: kill all processes
1133
+ `.trim());
1134
+ }
1135
+ function printVersion() {
1136
+ try {
1137
+ const pkg = require_package();
1138
+ console.log(`agentbridge v${pkg.version}`);
1139
+ } catch {
1140
+ console.log("agentbridge (version unknown)");
1141
+ }
1142
+ }
1143
+ var args, command, restArgs, MARKETPLACE_NAME = "agentbridge", PLUGIN_NAME = "agentbridge";
1144
+ var init_cli = __esm(() => {
1145
+ args = process.argv.slice(2);
1146
+ command = args[0];
1147
+ restArgs = args.slice(1);
1148
+ main().catch((err) => {
1149
+ console.error(`Error: ${err.message}`);
1150
+ process.exit(1);
1151
+ });
1152
+ });
1153
+ init_cli();
1154
+
1155
+ export {
1156
+ PLUGIN_NAME,
1157
+ MARKETPLACE_NAME
1158
+ };