agent-comms 8.15.8 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bridges/user/web/server.d.ts.map +1 -1
- package/dist/bridges/user/web/server.js +44 -24
- package/dist/bridges/user/web/server.js.map +1 -1
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts +5 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js +90 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts +2 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js +75 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js.map +1 -0
- package/dist/bridges/user/web/web-assets.d.ts +28 -0
- package/dist/bridges/user/web/web-assets.d.ts.map +1 -0
- package/dist/bridges/user/web/web-assets.js +67 -0
- package/dist/bridges/user/web/web-assets.js.map +1 -0
- package/package.json +8 -2
- package/src/account-cli.ts +195 -0
- package/src/account-join-cli.ts +191 -0
- package/src/bridges/cc-peer/approval-commands.ts +82 -0
- package/src/bridges/cc-peer/bridge.ts +138 -0
- package/src/bridges/cc-peer/default-front.ts +58 -0
- package/src/bridges/cc-peer/front-controller.ts +160 -0
- package/src/bridges/cc-peer/front-relay.ts +191 -0
- package/src/bridges/cc-peer/front-runtime.ts +167 -0
- package/src/bridges/cc-peer/front.ts +59 -0
- package/src/bridges/cc-peer/reply-aliases.ts +90 -0
- package/src/bridges/cc-peer/run.ts +100 -0
- package/src/bridges/cc-peer/shared-peer.ts +28 -0
- package/src/bridges/claude-code/channel.ts +318 -0
- package/src/bridges/codex/tool.ts +132 -0
- package/src/bridges/mcp/server.ts +137 -0
- package/src/bridges/opencode/plugin.ts +130 -0
- package/src/bridges/pi/extension.ts +330 -0
- package/src/bridges/registry.ts +17 -0
- package/src/bridges/user/cli.ts +87 -0
- package/src/bridges/user/controller.ts +345 -0
- package/src/bridges/user/tui.ts +300 -0
- package/src/bridges/user/web/actions.ts +173 -0
- package/src/bridges/user/web/contract.ts +222 -0
- package/src/bridges/user/web/dist/bundle.js +49 -0
- package/src/bridges/user/web/dist/icons/icon-192x192.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-512x512.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-96x96.svg +9 -0
- package/src/bridges/user/web/dist/index.html +21 -0
- package/src/bridges/user/web/dist/manifest.json +26 -0
- package/src/bridges/user/web/dist/mesh-worker.js +64 -0
- package/src/bridges/user/web/dist/sw.js +1 -0
- package/src/bridges/user/web/event-publisher.ts +114 -0
- package/src/bridges/user/web/port-discovery.ts +49 -0
- package/src/bridges/user/web/reads.ts +35 -0
- package/src/bridges/user/web/router.ts +158 -0
- package/src/bridges/user/web/server.ts +782 -0
- package/src/bridges/user/web/vite.config.ts +71 -0
- package/src/bridges/user/web/web-access.ts +404 -0
- package/src/bridges/user/web/web-assets.ts +90 -0
- package/src/bridges/user/web/web-beacon.ts +69 -0
- package/src/bridges/user/web/web-console-static.ts +90 -0
- package/src/cli-main.ts +635 -0
- package/src/cli.ts +13 -0
- package/src/core/account-bundle.ts +264 -0
- package/src/core/account-join.ts +289 -0
- package/src/core/account-ledger-crypto.ts +154 -0
- package/src/core/account-ledger-store.ts +66 -0
- package/src/core/account-ledger.ts +385 -0
- package/src/core/agent-details.ts +216 -0
- package/src/core/agent-list-format.ts +97 -0
- package/src/core/agent-registry.ts +282 -0
- package/src/core/agent-table.ts +58 -0
- package/src/core/atomic-file.ts +46 -0
- package/src/core/bind-retry.ts +38 -0
- package/src/core/bridge-mesh.ts +176 -0
- package/src/core/bridge.ts +718 -0
- package/src/core/capability-ask.ts +162 -0
- package/src/core/capability-bubble-up.ts +237 -0
- package/src/core/comms-store.ts +130 -0
- package/src/core/connection-approval.ts +134 -0
- package/src/core/connection-code-tool.ts +92 -0
- package/src/core/connection-code.ts +296 -0
- package/src/core/coordinator-role.ts +224 -0
- package/src/core/data-directory.ts +32 -0
- package/src/core/data-frame-routing.ts +309 -0
- package/src/core/default-command.ts +12 -0
- package/src/core/delegation-policy.ts +41 -0
- package/src/core/delivery-engine.ts +649 -0
- package/src/core/device-membership-verification.ts +125 -0
- package/src/core/device-membership.ts +130 -0
- package/src/core/directory-admission.ts +155 -0
- package/src/core/discovery-mdns.ts +247 -0
- package/src/core/discovery-tailscale.ts +224 -0
- package/src/core/discovery.ts +303 -0
- package/src/core/display-name.ts +73 -0
- package/src/core/dm-grant-actions.ts +72 -0
- package/src/core/dm-send-delegation.ts +73 -0
- package/src/core/dm-token-verification.ts +76 -0
- package/src/core/election-sessions.ts +135 -0
- package/src/core/error-reporting.ts +85 -0
- package/src/core/first-contact.ts +242 -0
- package/src/core/gateway-trust-actions.ts +184 -0
- package/src/core/gateway-trust.ts +305 -0
- package/src/core/gossip-directory.ts +276 -0
- package/src/core/gossip-extensions.ts +123 -0
- package/src/core/group-proofs.ts +205 -0
- package/src/core/handshake.ts +354 -0
- package/src/core/hub-forwarding.ts +24 -0
- package/src/core/hub-link.ts +176 -0
- package/src/core/hub-session.ts +374 -0
- package/src/core/identity-store.ts +715 -0
- package/src/core/identity.ts +489 -0
- package/src/core/index.ts +65 -0
- package/src/core/issuer-grouping.ts +165 -0
- package/src/core/issuer-identity-file.ts +187 -0
- package/src/core/lan-web-actions.ts +34 -0
- package/src/core/lan-web-uis.ts +55 -0
- package/src/core/ledger-lock.ts +81 -0
- package/src/core/listener-registry.ts +133 -0
- package/src/core/loopback-address.ts +22 -0
- package/src/core/machine-identity.ts +77 -0
- package/src/core/machine-list-groups.ts +53 -0
- package/src/core/mcp-registry-retry.ts +69 -0
- package/src/core/mcp-registry-version.ts +18 -0
- package/src/core/membership-proof.ts +139 -0
- package/src/core/membership-proofs.ts +149 -0
- package/src/core/mesh-graph-trace-tool.ts +81 -0
- package/src/core/mesh-graph.ts +89 -0
- package/src/core/mesh-network-actions.ts +183 -0
- package/src/core/mesh-store-options.ts +23 -0
- package/src/core/mesh-store-shared.ts +134 -0
- package/src/core/mesh-store.ts +1114 -0
- package/src/core/name-claim.ts +145 -0
- package/src/core/naming-actions.ts +90 -0
- package/src/core/naming.ts +138 -0
- package/src/core/nanoid.ts +20 -0
- package/src/core/network-interfaces.ts +24 -0
- package/src/core/package-version.ts +34 -0
- package/src/core/peer-lifecycle.ts +377 -0
- package/src/core/peer-link-error.ts +41 -0
- package/src/core/peer-versions.ts +98 -0
- package/src/core/pending-connection.ts +53 -0
- package/src/core/petnames.ts +121 -0
- package/src/core/pgp-keyserver.ts +32 -0
- package/src/core/pid-lock-file.ts +122 -0
- package/src/core/push-manager.ts +76 -0
- package/src/core/random-id.ts +9 -0
- package/src/core/receiver-set.ts +42 -0
- package/src/core/relay-role.ts +182 -0
- package/src/core/relay-selection.ts +120 -0
- package/src/core/relay-server.ts +198 -0
- package/src/core/relay-uplink.ts +78 -0
- package/src/core/request-timeouts.ts +37 -0
- package/src/core/room-discovery.ts +48 -0
- package/src/core/room-lifecycle.ts +838 -0
- package/src/core/room-lookup.ts +41 -0
- package/src/core/room-messaging.ts +247 -0
- package/src/core/room-notice-log.ts +61 -0
- package/src/core/room-path.ts +84 -0
- package/src/core/room-protocol.ts +893 -0
- package/src/core/room-router.ts +180 -0
- package/src/core/room-token-verification.ts +85 -0
- package/src/core/room-wire-extensions.ts +60 -0
- package/src/core/self-name-claim.ts +67 -0
- package/src/core/send-outcome.ts +69 -0
- package/src/core/shutdown-signals.ts +57 -0
- package/src/core/stale-agent-checker.ts +108 -0
- package/src/core/store.ts +704 -0
- package/src/core/token-text.ts +40 -0
- package/src/core/tool.ts +853 -0
- package/src/core/transport-events.ts +73 -0
- package/src/core/transport-queries.ts +54 -0
- package/src/core/transport.ts +367 -0
- package/src/core/types.ts +550 -0
- package/src/core/user-identity.ts +290 -0
- package/src/core/vapid.ts +81 -0
- package/src/core/version-check.ts +147 -0
- package/src/core/version-report-actions.ts +79 -0
- package/src/core/web-beacon.ts +31 -0
- package/src/core/web-push.ts +322 -0
- package/src/core/wire-mesh-core-version.ts +25 -0
- package/src/core/wire-mesh-identity.ts +36 -0
- package/src/core/wire-mesh-transport-options.ts +82 -0
- package/src/core/wire-mesh-transport.ts +1007 -0
- package/src/core/wire-protocol.ts +153 -0
- package/src/core/ws-dial.ts +191 -0
package/src/cli-main.ts
ADDED
|
@@ -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
|
+
});
|