agent-comms 8.15.9 → 8.15.10

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 (184) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/bridges/user/web/server.d.ts.map +1 -1
  4. package/dist/bridges/user/web/server.js +44 -24
  5. package/dist/bridges/user/web/server.js.map +1 -1
  6. package/dist/bridges/user/web/test/web-assets.unit.test.d.ts +5 -0
  7. package/dist/bridges/user/web/test/web-assets.unit.test.d.ts.map +1 -0
  8. package/dist/bridges/user/web/test/web-assets.unit.test.js +90 -0
  9. package/dist/bridges/user/web/test/web-assets.unit.test.js.map +1 -0
  10. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts +2 -0
  11. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts.map +1 -0
  12. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js +75 -0
  13. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js.map +1 -0
  14. package/dist/bridges/user/web/web-assets.d.ts +28 -0
  15. package/dist/bridges/user/web/web-assets.d.ts.map +1 -0
  16. package/dist/bridges/user/web/web-assets.js +67 -0
  17. package/dist/bridges/user/web/web-assets.js.map +1 -0
  18. package/package.json +8 -2
  19. package/src/account-cli.ts +195 -0
  20. package/src/account-join-cli.ts +191 -0
  21. package/src/bridges/cc-peer/approval-commands.ts +82 -0
  22. package/src/bridges/cc-peer/bridge.ts +138 -0
  23. package/src/bridges/cc-peer/default-front.ts +58 -0
  24. package/src/bridges/cc-peer/front-controller.ts +160 -0
  25. package/src/bridges/cc-peer/front-relay.ts +191 -0
  26. package/src/bridges/cc-peer/front-runtime.ts +167 -0
  27. package/src/bridges/cc-peer/front.ts +59 -0
  28. package/src/bridges/cc-peer/reply-aliases.ts +90 -0
  29. package/src/bridges/cc-peer/run.ts +100 -0
  30. package/src/bridges/cc-peer/shared-peer.ts +28 -0
  31. package/src/bridges/claude-code/channel.ts +318 -0
  32. package/src/bridges/codex/tool.ts +132 -0
  33. package/src/bridges/mcp/server.ts +137 -0
  34. package/src/bridges/opencode/plugin.ts +130 -0
  35. package/src/bridges/pi/extension.ts +330 -0
  36. package/src/bridges/registry.ts +17 -0
  37. package/src/bridges/user/cli.ts +87 -0
  38. package/src/bridges/user/controller.ts +345 -0
  39. package/src/bridges/user/tui.ts +300 -0
  40. package/src/bridges/user/web/actions.ts +173 -0
  41. package/src/bridges/user/web/contract.ts +222 -0
  42. package/src/bridges/user/web/dist/bundle.js +49 -0
  43. package/src/bridges/user/web/dist/icons/icon-192x192.svg +9 -0
  44. package/src/bridges/user/web/dist/icons/icon-512x512.svg +9 -0
  45. package/src/bridges/user/web/dist/icons/icon-96x96.svg +9 -0
  46. package/src/bridges/user/web/dist/index.html +21 -0
  47. package/src/bridges/user/web/dist/manifest.json +26 -0
  48. package/src/bridges/user/web/dist/mesh-worker.js +64 -0
  49. package/src/bridges/user/web/dist/sw.js +1 -0
  50. package/src/bridges/user/web/event-publisher.ts +114 -0
  51. package/src/bridges/user/web/port-discovery.ts +49 -0
  52. package/src/bridges/user/web/reads.ts +35 -0
  53. package/src/bridges/user/web/router.ts +158 -0
  54. package/src/bridges/user/web/server.ts +782 -0
  55. package/src/bridges/user/web/vite.config.ts +71 -0
  56. package/src/bridges/user/web/web-access.ts +404 -0
  57. package/src/bridges/user/web/web-assets.ts +90 -0
  58. package/src/bridges/user/web/web-beacon.ts +69 -0
  59. package/src/bridges/user/web/web-console-static.ts +90 -0
  60. package/src/cli-main.ts +635 -0
  61. package/src/cli.ts +13 -0
  62. package/src/core/account-bundle.ts +264 -0
  63. package/src/core/account-join.ts +289 -0
  64. package/src/core/account-ledger-crypto.ts +154 -0
  65. package/src/core/account-ledger-store.ts +66 -0
  66. package/src/core/account-ledger.ts +385 -0
  67. package/src/core/agent-details.ts +216 -0
  68. package/src/core/agent-list-format.ts +97 -0
  69. package/src/core/agent-registry.ts +282 -0
  70. package/src/core/agent-table.ts +58 -0
  71. package/src/core/atomic-file.ts +46 -0
  72. package/src/core/bind-retry.ts +38 -0
  73. package/src/core/bridge-mesh.ts +176 -0
  74. package/src/core/bridge.ts +718 -0
  75. package/src/core/capability-ask.ts +162 -0
  76. package/src/core/capability-bubble-up.ts +237 -0
  77. package/src/core/comms-store.ts +130 -0
  78. package/src/core/connection-approval.ts +134 -0
  79. package/src/core/connection-code-tool.ts +92 -0
  80. package/src/core/connection-code.ts +296 -0
  81. package/src/core/coordinator-role.ts +224 -0
  82. package/src/core/data-directory.ts +32 -0
  83. package/src/core/data-frame-routing.ts +309 -0
  84. package/src/core/default-command.ts +12 -0
  85. package/src/core/delegation-policy.ts +41 -0
  86. package/src/core/delivery-engine.ts +649 -0
  87. package/src/core/device-membership-verification.ts +125 -0
  88. package/src/core/device-membership.ts +130 -0
  89. package/src/core/directory-admission.ts +155 -0
  90. package/src/core/discovery-mdns.ts +247 -0
  91. package/src/core/discovery-tailscale.ts +224 -0
  92. package/src/core/discovery.ts +303 -0
  93. package/src/core/display-name.ts +73 -0
  94. package/src/core/dm-grant-actions.ts +72 -0
  95. package/src/core/dm-send-delegation.ts +73 -0
  96. package/src/core/dm-token-verification.ts +76 -0
  97. package/src/core/election-sessions.ts +135 -0
  98. package/src/core/error-reporting.ts +85 -0
  99. package/src/core/first-contact.ts +242 -0
  100. package/src/core/gateway-trust-actions.ts +184 -0
  101. package/src/core/gateway-trust.ts +305 -0
  102. package/src/core/gossip-directory.ts +276 -0
  103. package/src/core/gossip-extensions.ts +123 -0
  104. package/src/core/group-proofs.ts +205 -0
  105. package/src/core/handshake.ts +354 -0
  106. package/src/core/hub-forwarding.ts +24 -0
  107. package/src/core/hub-link.ts +176 -0
  108. package/src/core/hub-session.ts +374 -0
  109. package/src/core/identity-store.ts +715 -0
  110. package/src/core/identity.ts +489 -0
  111. package/src/core/index.ts +65 -0
  112. package/src/core/issuer-grouping.ts +165 -0
  113. package/src/core/issuer-identity-file.ts +187 -0
  114. package/src/core/lan-web-actions.ts +34 -0
  115. package/src/core/lan-web-uis.ts +55 -0
  116. package/src/core/ledger-lock.ts +81 -0
  117. package/src/core/listener-registry.ts +133 -0
  118. package/src/core/loopback-address.ts +22 -0
  119. package/src/core/machine-identity.ts +77 -0
  120. package/src/core/machine-list-groups.ts +53 -0
  121. package/src/core/mcp-registry-retry.ts +69 -0
  122. package/src/core/mcp-registry-version.ts +18 -0
  123. package/src/core/membership-proof.ts +139 -0
  124. package/src/core/membership-proofs.ts +149 -0
  125. package/src/core/mesh-graph-trace-tool.ts +81 -0
  126. package/src/core/mesh-graph.ts +89 -0
  127. package/src/core/mesh-network-actions.ts +183 -0
  128. package/src/core/mesh-store-options.ts +23 -0
  129. package/src/core/mesh-store-shared.ts +134 -0
  130. package/src/core/mesh-store.ts +1114 -0
  131. package/src/core/name-claim.ts +145 -0
  132. package/src/core/naming-actions.ts +90 -0
  133. package/src/core/naming.ts +138 -0
  134. package/src/core/nanoid.ts +20 -0
  135. package/src/core/network-interfaces.ts +24 -0
  136. package/src/core/package-version.ts +34 -0
  137. package/src/core/peer-lifecycle.ts +377 -0
  138. package/src/core/peer-link-error.ts +41 -0
  139. package/src/core/peer-versions.ts +98 -0
  140. package/src/core/pending-connection.ts +53 -0
  141. package/src/core/petnames.ts +121 -0
  142. package/src/core/pgp-keyserver.ts +32 -0
  143. package/src/core/pid-lock-file.ts +122 -0
  144. package/src/core/push-manager.ts +76 -0
  145. package/src/core/random-id.ts +9 -0
  146. package/src/core/receiver-set.ts +42 -0
  147. package/src/core/relay-role.ts +182 -0
  148. package/src/core/relay-selection.ts +120 -0
  149. package/src/core/relay-server.ts +198 -0
  150. package/src/core/relay-uplink.ts +78 -0
  151. package/src/core/request-timeouts.ts +37 -0
  152. package/src/core/room-discovery.ts +48 -0
  153. package/src/core/room-lifecycle.ts +838 -0
  154. package/src/core/room-lookup.ts +41 -0
  155. package/src/core/room-messaging.ts +247 -0
  156. package/src/core/room-notice-log.ts +61 -0
  157. package/src/core/room-path.ts +84 -0
  158. package/src/core/room-protocol.ts +893 -0
  159. package/src/core/room-router.ts +180 -0
  160. package/src/core/room-token-verification.ts +85 -0
  161. package/src/core/room-wire-extensions.ts +60 -0
  162. package/src/core/self-name-claim.ts +67 -0
  163. package/src/core/send-outcome.ts +69 -0
  164. package/src/core/shutdown-signals.ts +57 -0
  165. package/src/core/stale-agent-checker.ts +108 -0
  166. package/src/core/store.ts +704 -0
  167. package/src/core/token-text.ts +40 -0
  168. package/src/core/tool.ts +853 -0
  169. package/src/core/transport-events.ts +73 -0
  170. package/src/core/transport-queries.ts +54 -0
  171. package/src/core/transport.ts +367 -0
  172. package/src/core/types.ts +550 -0
  173. package/src/core/user-identity.ts +290 -0
  174. package/src/core/vapid.ts +81 -0
  175. package/src/core/version-check.ts +147 -0
  176. package/src/core/version-report-actions.ts +79 -0
  177. package/src/core/web-beacon.ts +31 -0
  178. package/src/core/web-push.ts +322 -0
  179. package/src/core/wire-mesh-core-version.ts +25 -0
  180. package/src/core/wire-mesh-identity.ts +36 -0
  181. package/src/core/wire-mesh-transport-options.ts +82 -0
  182. package/src/core/wire-mesh-transport.ts +1007 -0
  183. package/src/core/wire-protocol.ts +153 -0
  184. package/src/core/ws-dial.ts +191 -0
@@ -0,0 +1,635 @@
1
+ /**
2
+ * agent-comms — cross-harness LLM agent communication mesh.
3
+ *
4
+ * Usage:
5
+ * npx agent-comms # in a terminal: setup (auto-detect and configure); with a piped stdin: serve MCP, as `bridge mcp`
6
+ * npx agent-comms setup # same as above
7
+ * npx agent-comms status # check current configuration
8
+ * npx agent-comms remove # undo configuration
9
+ * npx agent-comms bridge <id> # run a bridge (used by harness configs)
10
+ * npx agent-comms chat # interactive TUI (web UI auto-starts)
11
+ * npx agent-comms send <room> <message> # one-shot send
12
+ * npx agent-comms dm <agent> <message> # one-shot DM
13
+ * npx agent-comms rooms # list rooms
14
+ * npx agent-comms agents # list agents
15
+ * npx agent-comms read <room> # read room messages
16
+ * npx agent-comms account export <file> # write this machine's account key, passphrase-sealed
17
+ * npx agent-comms account import <file> # make this machine hold an exported account
18
+ * npx agent-comms account invite # print a one-use invite that joins another machine to this account
19
+ * npx agent-comms account join # join this machine to an account with an invite
20
+ *
21
+ * The bridge subcommand lets harnesses invoke the bridge via npx:
22
+ * .mcp.json: \{ "command": "npx", "args": ["agent-comms", "bridge", "claude-code"] \}
23
+ * config.toml: command = "npx", args = ["agent-comms", "bridge", "codex"]
24
+ */
25
+
26
+ import * as fs from "node:fs";
27
+ import * as path from "node:path";
28
+ import * as os from "node:os";
29
+ import { execSync } from "node:child_process";
30
+ import { z } from "zod";
31
+ import { bridges } from "./bridges/registry.js";
32
+ import { resolveDefaultCommand } from "./core/default-command.js";
33
+ import type { BridgeRunOptions } from "./core/index.js";
34
+ import { runTui } from "./bridges/user/tui.js";
35
+ import { runCli } from "./bridges/user/cli.js";
36
+ import { readHiddenLine, runAccountCommand } from "./account-cli.js";
37
+ import { openMachineAccountStore } from "./account-join-cli.js";
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Zod schemas for config files
41
+ // ---------------------------------------------------------------------------
42
+
43
+ const PiSettingsSchema = z
44
+ .object({
45
+ extensions: z.array(z.string()).optional(),
46
+ })
47
+ .loose();
48
+
49
+ const McpServersSchema = z
50
+ .object({
51
+ mcpServers: z
52
+ .record(
53
+ z.string(),
54
+ z
55
+ .object({
56
+ command: z.string(),
57
+ args: z.array(z.string()).optional(),
58
+ })
59
+ .loose(),
60
+ )
61
+ .default({}),
62
+ })
63
+ .loose();
64
+
65
+ const OpenCodeConfigSchema = z
66
+ .object({
67
+ plugin: z.array(z.string()).optional(),
68
+ })
69
+ .loose();
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Config I/O
73
+ // ---------------------------------------------------------------------------
74
+
75
+ function readJsonFile(filePath: string): unknown {
76
+ try {
77
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
78
+ } catch {
79
+ return undefined;
80
+ }
81
+ }
82
+
83
+ function writeJsonFile(filePath: string, data: unknown): void {
84
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
85
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
86
+ console.log(` → Wrote ${filePath}`);
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Constants
91
+ // ---------------------------------------------------------------------------
92
+
93
+ const HOME = os.homedir();
94
+ const PKG_DIR = path.resolve(
95
+ path.dirname(new URL(import.meta.url).pathname),
96
+ "..",
97
+ );
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Types
101
+ // ---------------------------------------------------------------------------
102
+
103
+ interface CheckResult {
104
+ configured: boolean;
105
+ details: string[];
106
+ }
107
+
108
+ interface HarnessDef {
109
+ id: string;
110
+ detect: () => boolean;
111
+ configure: () => void;
112
+ remove: () => void;
113
+ check: () => CheckResult;
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Harness definitions
118
+ // ---------------------------------------------------------------------------
119
+
120
+ const harnesses: HarnessDef[] = [
121
+ {
122
+ id: "pi",
123
+ detect: () => fs.existsSync(path.join(HOME, ".pi", "agent", "extensions")),
124
+ configure: () => {
125
+ configurePi();
126
+ },
127
+ remove: () => {
128
+ removePi();
129
+ },
130
+ check: () => checkPi(),
131
+ },
132
+ {
133
+ id: "claude-code",
134
+ detect: () => {
135
+ try {
136
+ execSync("which claude", { stdio: "pipe" });
137
+ return true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ },
142
+ configure: () => {
143
+ configureClaudeCode();
144
+ },
145
+ remove: () => {
146
+ removeClaudeCode();
147
+ },
148
+ check: () => checkClaudeCode(),
149
+ },
150
+ {
151
+ id: "codex",
152
+ detect: () => {
153
+ try {
154
+ execSync("which codex", { stdio: "pipe" });
155
+ return true;
156
+ } catch {
157
+ return false;
158
+ }
159
+ },
160
+ configure: () => {
161
+ configureCodex();
162
+ },
163
+ remove: () => {
164
+ removeCodex();
165
+ },
166
+ check: () => checkCodex(),
167
+ },
168
+ {
169
+ id: "opencode",
170
+ detect: () => {
171
+ try {
172
+ execSync("which opencode", { stdio: "pipe" });
173
+ return true;
174
+ } catch {
175
+ return false;
176
+ }
177
+ },
178
+ configure: () => {
179
+ configureOpenCode();
180
+ },
181
+ remove: () => {
182
+ removeOpenCode();
183
+ },
184
+ check: () => checkOpenCode(),
185
+ },
186
+ ];
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Main
190
+ // ---------------------------------------------------------------------------
191
+
192
+ // Within the resolved arguments, index 0 is the subcommand and everything after it is the subcommand's own args.
193
+ const SUBCOMMAND_ARGS_START_INDEX = 1;
194
+
195
+ /** What the command line was started with, apart from the process itself, so a test can run the real dispatch against an isolated mesh. */
196
+ export interface CliOptions {
197
+ /** The subcommand and its own arguments, without the node binary and the script path. */
198
+ args: readonly string[];
199
+ /** Whether stdin is a terminal: a bare command is the setup tool on one and the MCP server when stdin is piped. Undefined, which Node reports for a stream that is not a terminal, reads as piped. */
200
+ stdinIsTerminal: boolean | undefined;
201
+ /** Passed to a bridge the command starts; see BridgeRunOptions. Only a test sets it. */
202
+ bridge?: Readonly<BridgeRunOptions> | undefined;
203
+ }
204
+
205
+ /** Runs the subcommand the arguments name, or the default one for a bare command (see resolveDefaultCommand). */
206
+ export function main(options: Readonly<CliOptions>): void {
207
+ const resolvedArgs = resolveDefaultCommand(
208
+ options.args,
209
+ options.stdinIsTerminal ?? false,
210
+ );
211
+ const command = resolvedArgs[0] ?? "setup";
212
+
213
+ switch (command) {
214
+ case "setup":
215
+ setup();
216
+ break;
217
+ case "status":
218
+ status();
219
+ break;
220
+ case "remove":
221
+ remove();
222
+ break;
223
+ case "bridge": {
224
+ const bridgeId = resolvedArgs[1];
225
+ if (bridgeId === undefined) {
226
+ console.error("Usage: agent-comms bridge <id>");
227
+ process.exit(1);
228
+ }
229
+ runBridge(bridgeId, options.bridge);
230
+ break;
231
+ }
232
+ case "chat": {
233
+ const chatArgs = resolvedArgs.slice(SUBCOMMAND_ARGS_START_INDEX);
234
+ runChat(chatArgs);
235
+ break;
236
+ }
237
+ case "send":
238
+ case "dm":
239
+ case "rooms":
240
+ case "agents":
241
+ case "read": {
242
+ const cliArgs = resolvedArgs.slice(SUBCOMMAND_ARGS_START_INDEX);
243
+ runUserCli(command, cliArgs);
244
+ break;
245
+ }
246
+ case "account": {
247
+ runAccountCommand(resolvedArgs.slice(SUBCOMMAND_ARGS_START_INDEX), {
248
+ readSecret: readHiddenLine,
249
+ log: (line) => {
250
+ console.log(line);
251
+ },
252
+ env: process.env,
253
+ openStore: openMachineAccountStore,
254
+ }).then(
255
+ () => {
256
+ // The store an invite or join ran leaves timers and sockets behind it, so the command ends the process itself once it is done.
257
+ process.exit(0);
258
+ },
259
+ (err: unknown) => {
260
+ console.error(err instanceof Error ? err.message : String(err));
261
+ process.exit(1);
262
+ },
263
+ );
264
+ break;
265
+ }
266
+ default:
267
+ console.log(
268
+ "Usage: agent-comms [setup|status|remove|bridge <id>|chat|send|dm|rooms|agents|read|account <export|import|invite|join>]",
269
+ );
270
+ console.log(
271
+ "With no subcommand: setup when stdin is a terminal, the MCP server when it is not.",
272
+ );
273
+ process.exit(1);
274
+ }
275
+ }
276
+
277
+ // ---------------------------------------------------------------------------
278
+ // Bridge runner
279
+ // ---------------------------------------------------------------------------
280
+
281
+ function runBridge(
282
+ id: string,
283
+ options: Readonly<BridgeRunOptions> | undefined,
284
+ ): void {
285
+ const bridge = bridges[id];
286
+ if (!bridge) {
287
+ console.error(
288
+ `Unknown bridge: ${id}. Available: ${Object.keys(bridges).join(", ")}`,
289
+ );
290
+ process.exit(1);
291
+ }
292
+ Promise.resolve(bridge.run(options)).catch((err: unknown) => {
293
+ console.error(err);
294
+ process.exit(1);
295
+ });
296
+ }
297
+
298
+ function parseNameArg(args: readonly string[]): {
299
+ name: string;
300
+ rest: readonly string[];
301
+ } {
302
+ const nameIdx = args.indexOf("--name");
303
+ if (nameIdx !== -1) {
304
+ const val = args[nameIdx + 1];
305
+ if (val !== undefined) {
306
+ return {
307
+ name: val,
308
+ rest: [...args.slice(0, nameIdx), ...args.slice(nameIdx + 2)],
309
+ };
310
+ }
311
+ }
312
+ return { name: "Joe", rest: args };
313
+ }
314
+
315
+ function runChat(args: readonly string[]): void {
316
+ const { name } = parseNameArg(args);
317
+ runTui(name).catch((err: unknown) => {
318
+ console.error(err);
319
+ process.exit(1);
320
+ });
321
+ }
322
+
323
+ function runUserCli(cliCommand: string, args: readonly string[]): void {
324
+ const { name, rest } = parseNameArg(args);
325
+ runCli(cliCommand, rest, name).catch((err: unknown) => {
326
+ console.error(err);
327
+ process.exit(1);
328
+ });
329
+ }
330
+
331
+ // ---------------------------------------------------------------------------
332
+ // Commands
333
+ // ---------------------------------------------------------------------------
334
+
335
+ function setup(): void {
336
+ console.log("🔌 Agent Comms setup\n");
337
+
338
+ const detected = harnesses.filter((h) => h.detect());
339
+
340
+ console.log("Detected harnesses:");
341
+ for (const h of harnesses) {
342
+ console.log(` ${detected.includes(h) ? "✓" : "✗"} ${h.id}`);
343
+ }
344
+
345
+ if (detected.length === 0) {
346
+ console.log(
347
+ "\nNo supported harnesses found. Install one of: pi, Claude Code, Codex, OpenCode",
348
+ );
349
+ process.exit(1);
350
+ }
351
+
352
+ console.log();
353
+
354
+ for (const h of detected) {
355
+ console.log(`Configuring ${h.id}...`);
356
+ h.configure();
357
+ }
358
+
359
+ const plural = detected.length === 1 ? "" : "es";
360
+ console.log(
361
+ `\n✓ Done! ${String(detected.length)} harness${plural} configured.`,
362
+ );
363
+ console.log("\nBridges run via: npx agent-comms bridge <id>");
364
+ }
365
+
366
+ function status(): void {
367
+ console.log("🔌 Agent Comms status\n");
368
+
369
+ for (const h of harnesses) {
370
+ const installed = h.detect();
371
+ const result = installed
372
+ ? h.check()
373
+ : { configured: false, details: Array<string>() };
374
+
375
+ if (!installed) {
376
+ console.log(` ✗ ${h.id} — not found`);
377
+ } else if (result.configured) {
378
+ console.log(` ✓ ${h.id} — configured`);
379
+ for (const detail of result.details) console.log(` ${detail}`);
380
+ } else {
381
+ console.log(` ⚠ ${h.id} — detected but not configured`);
382
+ for (const detail of result.details) console.log(` ${detail}`);
383
+ }
384
+ }
385
+ }
386
+
387
+ function remove(): void {
388
+ console.log("🔌 Agent Comms removal\n");
389
+
390
+ for (const h of harnesses) {
391
+ if (h.detect()) {
392
+ console.log(`Removing ${h.id}...`);
393
+ h.remove();
394
+ }
395
+ }
396
+
397
+ console.log("\n✓ Done!");
398
+ }
399
+
400
+ // ---------------------------------------------------------------------------
401
+ // pi
402
+ // ---------------------------------------------------------------------------
403
+
404
+ function configurePi(): void {
405
+ const settingsPath = path.join(HOME, ".pi", "agent", "settings.json");
406
+ const parsed = readJsonFile(settingsPath);
407
+ const settings = PiSettingsSchema.parse(parsed ?? {});
408
+
409
+ const bridgeDir = path.join(PKG_DIR, "src", "bridges", "pi");
410
+ const extensions = settings.extensions ?? [];
411
+
412
+ if (!extensions.includes(bridgeDir)) {
413
+ extensions.push(bridgeDir);
414
+ settings.extensions = extensions;
415
+ writeJsonFile(settingsPath, settings);
416
+ } else {
417
+ console.log(` → Already in ${settingsPath}`);
418
+ }
419
+ }
420
+
421
+ function removePi(): void {
422
+ const settingsPath = path.join(HOME, ".pi", "agent", "settings.json");
423
+ const parsed = readJsonFile(settingsPath);
424
+ const settings = PiSettingsSchema.parse(parsed ?? {});
425
+
426
+ const extensions = settings.extensions;
427
+
428
+ if (extensions) {
429
+ settings.extensions = extensions.filter((e: string) => {
430
+ const segments = e.toLowerCase().split("/");
431
+ const busIdx = segments.indexOf("bridges");
432
+ return !(
433
+ busIdx !== -1 &&
434
+ segments[busIdx + 1] === "pi" &&
435
+ e.toLowerCase().includes("agent-comms")
436
+ );
437
+ });
438
+ writeJsonFile(settingsPath, settings);
439
+ }
440
+ }
441
+
442
+ function checkPi(): CheckResult {
443
+ const settingsPath = path.join(HOME, ".pi", "agent", "settings.json");
444
+ const parsed = readJsonFile(settingsPath);
445
+ const settings = PiSettingsSchema.parse(parsed ?? {});
446
+ const bridgeDir = path.join(PKG_DIR, "src", "bridges", "pi");
447
+ const configured = (settings.extensions ?? []).includes(bridgeDir);
448
+ return {
449
+ configured,
450
+ details: configured
451
+ ? [`Extension: ${bridgeDir}`]
452
+ : [`Not in ${settingsPath} extensions`],
453
+ };
454
+ }
455
+
456
+ // ---------------------------------------------------------------------------
457
+ // Claude Code
458
+ // ---------------------------------------------------------------------------
459
+
460
+ function configureClaudeCode(): void {
461
+ const mcpPath = path.join(process.cwd(), ".mcp.json");
462
+ const parsed = readJsonFile(mcpPath);
463
+ const config = McpServersSchema.parse(parsed ?? {});
464
+
465
+ config.mcpServers["agent-comms"] = {
466
+ command: "npx",
467
+ args: ["agent-comms", "bridge", "claude-code"],
468
+ };
469
+
470
+ writeJsonFile(mcpPath, config);
471
+ }
472
+
473
+ function removeClaudeCode(): void {
474
+ const mcpPath = path.join(process.cwd(), ".mcp.json");
475
+ const parsed = readJsonFile(mcpPath);
476
+ if (parsed === undefined) return;
477
+ const config = McpServersSchema.safeParse(parsed);
478
+ if (!config.success) return;
479
+ if ("agent-comms" in config.data.mcpServers) {
480
+ delete config.data.mcpServers["agent-comms"];
481
+ writeJsonFile(mcpPath, config.data);
482
+ console.log(` Removed agent-comms from ${mcpPath}`);
483
+ }
484
+ }
485
+
486
+ function checkClaudeCode(): CheckResult {
487
+ const mcpPath = path.join(process.cwd(), ".mcp.json");
488
+ const parsed = readJsonFile(mcpPath);
489
+ const config = McpServersSchema.safeParse(parsed ?? {});
490
+ const entry = config.success
491
+ ? config.data.mcpServers["agent-comms"]
492
+ : undefined;
493
+ const configured = entry !== undefined;
494
+ return {
495
+ configured,
496
+ details: configured
497
+ ? [`Config: ${mcpPath}`, `Command: ${entry.command}`]
498
+ : [`Not in ${mcpPath}`],
499
+ };
500
+ }
501
+
502
+ // ---------------------------------------------------------------------------
503
+ // Codex
504
+ // ---------------------------------------------------------------------------
505
+
506
+ function configureCodex(): void {
507
+ const configDir = path.join(HOME, ".codex");
508
+
509
+ // Add MCP server to config.toml
510
+ const tomlPath = path.join(configDir, "config.toml");
511
+ if (fs.existsSync(tomlPath)) {
512
+ const content = fs.readFileSync(tomlPath, "utf-8");
513
+ if (!content.includes("agent-comms")) {
514
+ const block = [
515
+ "",
516
+ "# Agent Comms MCP tool server",
517
+ "[mcp_servers.agent-comms]",
518
+ 'command = "npx"',
519
+ 'args = ["agent-comms", "bridge", "codex"]',
520
+ ].join("\n");
521
+ fs.writeFileSync(tomlPath, content.trimEnd() + block + "\n");
522
+ console.log(` → Appended MCP server to ${tomlPath}`);
523
+ } else {
524
+ console.log(` → Already in ${tomlPath}`);
525
+ }
526
+ } else {
527
+ const content = [
528
+ "# Agent Comms MCP tool server",
529
+ "[mcp_servers.agent-comms]",
530
+ 'command = "npx"',
531
+ 'args = ["agent-comms", "bridge", "codex"]',
532
+ ].join("\n");
533
+ fs.mkdirSync(configDir, { recursive: true });
534
+ fs.writeFileSync(tomlPath, content + "\n");
535
+ console.log(` → Created ${tomlPath}`);
536
+ }
537
+ }
538
+
539
+ function removeCodex(): void {
540
+ const configDir = path.join(HOME, ".codex");
541
+
542
+ // Remove from config.toml
543
+ const tomlPath = path.join(configDir, "config.toml");
544
+ if (fs.existsSync(tomlPath)) {
545
+ const content = fs.readFileSync(tomlPath, "utf-8");
546
+ const lines = content
547
+ .split("\n")
548
+ .filter(
549
+ (line) =>
550
+ !line.includes("agent-comms") && !line.includes("Agent Comms"),
551
+ );
552
+ fs.writeFileSync(tomlPath, lines.join("\n").trimEnd() + "\n");
553
+ console.log(` Removed agent-comms from ${tomlPath}`);
554
+ }
555
+ }
556
+
557
+ function checkCodex(): CheckResult {
558
+ const details: string[] = [];
559
+ const tomlPath = path.join(HOME, ".codex", "config.toml");
560
+ if (fs.existsSync(tomlPath)) {
561
+ const content = fs.readFileSync(tomlPath, "utf-8");
562
+ const tomlOk = content.includes("agent-comms");
563
+ details.push(tomlOk ? `MCP server in ${tomlPath}` : `Not in ${tomlPath}`);
564
+ return { configured: tomlOk, details };
565
+ }
566
+ details.push(`${tomlPath} not found`);
567
+ return { configured: false, details };
568
+ }
569
+
570
+ // ---------------------------------------------------------------------------
571
+ // OpenCode
572
+ // ---------------------------------------------------------------------------
573
+
574
+ function configureOpenCode(): void {
575
+ const configPath = path.join(process.cwd(), "opencode.json");
576
+ const parsed = readJsonFile(configPath);
577
+ const config = OpenCodeConfigSchema.parse(parsed ?? {});
578
+
579
+ const bridgeDir = path.join(
580
+ PKG_DIR,
581
+ "src",
582
+ "bridges",
583
+ "opencode",
584
+ "plugin.ts",
585
+ );
586
+ const plugins = config.plugin ?? [];
587
+
588
+ if (!plugins.includes(bridgeDir)) {
589
+ plugins.push(bridgeDir);
590
+ config.plugin = plugins;
591
+ writeJsonFile(configPath, config);
592
+ } else {
593
+ console.log(` → Already in ${configPath}`);
594
+ }
595
+ }
596
+
597
+ function removeOpenCode(): void {
598
+ const configPath = path.join(process.cwd(), "opencode.json");
599
+ const parsed = readJsonFile(configPath);
600
+ if (parsed === undefined) return;
601
+ const config = OpenCodeConfigSchema.safeParse(parsed);
602
+ if (!config.success) return;
603
+ const bridgeDir = path.join(
604
+ PKG_DIR,
605
+ "src",
606
+ "bridges",
607
+ "opencode",
608
+ "plugin.ts",
609
+ );
610
+ const plugins = config.data.plugin;
611
+ if (plugins) {
612
+ config.data.plugin = plugins.filter((p: string) => p !== bridgeDir);
613
+ writeJsonFile(configPath, config.data);
614
+ console.log(` Removed agent-comms from ${configPath}`);
615
+ }
616
+ }
617
+
618
+ function checkOpenCode(): CheckResult {
619
+ const configPath = path.join(process.cwd(), "opencode.json");
620
+ const parsed = readJsonFile(configPath);
621
+ const config = OpenCodeConfigSchema.safeParse(parsed ?? {});
622
+ const bridgeDir = path.join(
623
+ PKG_DIR,
624
+ "src",
625
+ "bridges",
626
+ "opencode",
627
+ "plugin.ts",
628
+ );
629
+ const configured =
630
+ config.success && (config.data.plugin ?? []).includes(bridgeDir);
631
+ return {
632
+ configured,
633
+ details: configured ? [`Plugin: ${bridgeDir}`] : [`Not in ${configPath}`],
634
+ };
635
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The `agent-comms` executable: runs the command line against this process's own arguments and stdin. The dispatch itself lives in cli-main.ts, where a test can reach it without binding this machine's default ports.
3
+ */
4
+
5
+ import { main } from "./cli-main.js";
6
+
7
+ // process.argv[0] is the node binary, [1] the script path, the rest the subcommand and its own args.
8
+ const SCRIPT_ARGS_START_INDEX = 2;
9
+
10
+ main({
11
+ args: process.argv.slice(SCRIPT_ARGS_START_INDEX),
12
+ stdinIsTerminal: process.stdin.isTTY,
13
+ });