agent-comm-hub 0.3.0 → 0.5.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/lib/index.js CHANGED
@@ -515,6 +515,12 @@ var AgentHub = class {
515
515
  const filtered = this.historyRing.filter((message) => message.from === peerId || message.to === peerId || message.to === BROADCAST);
516
516
  return filtered.slice(-Math.max(0, limit)).reverse();
517
517
  }
518
+ /** Most recent messages across every peer, unfiltered (newest first).
519
+ * Backs `bridge_history { peer: "all" }` — lets an archiver (the desktop
520
+ * app) capture peer-to-peer traffic it is not a party of. */
521
+ historyAll(limit) {
522
+ return this.historyRing.slice(-Math.max(0, limit)).reverse();
523
+ }
518
524
  /**
519
525
  * Live summary for the status tool. `livePeers` (sessions with a live SSE
520
526
  * stream) count as connected even without recent tool activity.
@@ -843,9 +849,13 @@ function hubTools(hub, registry, options) {
843
849
  },
844
850
  {
845
851
  name: "bridge_history",
846
- description: "Recent messages involving you (newest first); pass `peer` to inspect another peer's conversation. Use to refresh context after a reconnect.",
847
- inputSchema: schema({ peer: optStr("PeerId whose conversation to inspect; default: yourself."), limit: int("How many messages to return (default 20).") }),
848
- handler: wrap(true, async (args, peer) => ({ messages: hub.history(args.peer === void 0 ? peer : String(args.peer), Math.min(args.limit === void 0 ? 20 : Number(args.limit), 100)).map(present) }))
852
+ description: 'Recent messages involving you (newest first); pass `peer` to inspect another peer\'s conversation, or `peer: "all"` for the unfiltered tail across every peer. Use to refresh context after a reconnect.',
853
+ inputSchema: schema({ peer: optStr('PeerId whose conversation to inspect; "all" = every peer; default: yourself.'), limit: int("How many messages to return (default 20).") }),
854
+ handler: wrap(true, async (args, peer) => {
855
+ const limit = Math.min(args.limit === void 0 ? 20 : Number(args.limit), 1e3);
856
+ const messages = args.peer === "all" ? hub.historyAll(limit) : hub.history(args.peer === void 0 ? peer : String(args.peer), limit);
857
+ return { messages: messages.map(present) };
858
+ })
849
859
  },
850
860
  // ---- herdr control tools ------------------------------------------
851
861
  // These type into real agent terminals via the herdr runtime. They are
@@ -1337,7 +1347,7 @@ function readBody(req) {
1337
1347
 
1338
1348
  // src/index.ts
1339
1349
  var SERVER_NAME = "agent-comm-hub";
1340
- var SERVER_VERSION = "0.3.0";
1350
+ var SERVER_VERSION = "0.5.0";
1341
1351
  var DEFAULT_HOST = "127.0.0.1";
1342
1352
  var DEFAULT_PORT = 18764;
1343
1353
  var DEFAULT_PATH = "/mcp";
@@ -1346,7 +1356,10 @@ var DEFAULT_CONFIG = {
1346
1356
  port: DEFAULT_PORT,
1347
1357
  path: DEFAULT_PATH,
1348
1358
  maxQueue: 200,
1349
- historyLimit: 100,
1359
+ // 100 was too small to survive a long multi-agent session: the ring is the
1360
+ // only archive source until the desktop app persists it to SQLite, and a
1361
+ // night of agent-to-agent chatter evicts everything within minutes.
1362
+ historyLimit: 1e3,
1350
1363
  waitTimeoutMs: 6e4,
1351
1364
  defaultWaitMs: 3e4,
1352
1365
  connectedWindowMs: 3e4,
package/lib/setup.js CHANGED
@@ -1,13 +1,177 @@
1
1
  // src/setup.ts
2
- import { copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
- import { existsSync } from "node:fs";
2
+ import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { existsSync as existsSync2 } from "node:fs";
4
+ import { homedir as homedir2 } from "node:os";
5
+ import { dirname as dirname2, join as join2 } from "node:path";
6
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
7
+
8
+ // src/discover.ts
9
+ import { execFileSync } from "node:child_process";
10
+ import { existsSync, readdirSync, accessSync, readFileSync, constants as fsConstants } from "node:fs";
4
11
  import { homedir } from "node:os";
5
- import { dirname, join } from "node:path";
12
+ import { dirname, join, sep } from "node:path";
6
13
  import { fileURLToPath } from "node:url";
14
+ function registryFile() {
15
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "agents", "registry.json");
16
+ }
17
+ function expandHome(file, home) {
18
+ return file.startsWith("~/") ? join(home, file.slice(2)) : file;
19
+ }
20
+ function expandConfigFile(file, home) {
21
+ const expanded = expandHome(file, home);
22
+ const star = expanded.indexOf("*");
23
+ if (star < 0) return [expanded];
24
+ const prefix = expanded.slice(0, star);
25
+ const suffix = expanded.slice(star + 1);
26
+ const base = prefix.slice(0, prefix.lastIndexOf(sep));
27
+ if (!existsSync(base)) return [];
28
+ return readdirSync(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join(base, entry.name, suffix));
29
+ }
30
+ function validateRegistry(registry) {
31
+ if (!Array.isArray(registry.agents)) throw new Error('registry: missing "agents" array');
32
+ const ids = /* @__PURE__ */ new Set();
33
+ for (const agent of registry.agents) {
34
+ if (typeof agent.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(agent.id)) {
35
+ throw new Error(`registry: bad agent id ${JSON.stringify(agent.id)}`);
36
+ }
37
+ if (ids.has(agent.id)) throw new Error(`registry: duplicate agent id '${agent.id}'`);
38
+ ids.add(agent.id);
39
+ if (!Array.isArray(agent.probe)) throw new Error(`registry: ${agent.id}: probe must be an array`);
40
+ if (agent.npm !== void 0 && !Array.isArray(agent.npm)) throw new Error(`registry: ${agent.id}: npm must be an array`);
41
+ if (!Array.isArray(agent.configs)) throw new Error(`registry: ${agent.id}: configs must be an array`);
42
+ for (const config of agent.configs) {
43
+ if (typeof config.file !== "string" || !config.file.startsWith("~/")) {
44
+ throw new Error(`registry: ${agent.id}: config file must be '~'-relative`);
45
+ }
46
+ if (config.file.split("/").includes("..")) {
47
+ throw new Error(`registry: ${agent.id}: config file must not contain '..'`);
48
+ }
49
+ if (config.file.split("*").length > 2) {
50
+ throw new Error(`registry: ${agent.id}: at most one '*' segment allowed`);
51
+ }
52
+ if (!["json", "toml", "dsh"].includes(config.strategy)) {
53
+ throw new Error(`registry: ${agent.id}: unknown strategy '${config.strategy}'`);
54
+ }
55
+ if (config.strategy === "json" && (typeof config.section !== "string" || config.entry === null)) {
56
+ throw new Error(`registry: ${agent.id}: json strategy needs a section and an entry`);
57
+ }
58
+ if (config.strategy !== "json" && config.entry !== null) {
59
+ throw new Error(`registry: ${agent.id}: only json strategy may carry an entry`);
60
+ }
61
+ }
62
+ if (agent.skill !== null && (typeof agent.skill !== "string" || !agent.skill.startsWith("~/"))) {
63
+ throw new Error(`registry: ${agent.id}: skill must be '~'-relative or null`);
64
+ }
65
+ if (agent.os !== void 0 && (!Array.isArray(agent.os) || agent.os.some((os) => !["win32", "darwin", "linux"].includes(os)))) {
66
+ throw new Error(`registry: ${agent.id}: invalid os list`);
67
+ }
68
+ }
69
+ }
70
+ function loadRegistry(file = registryFile()) {
71
+ const registry = JSON.parse(readFileSync(file, "utf8"));
72
+ validateRegistry(registry);
73
+ return registry;
74
+ }
75
+ function commandOnPath(command, pathEnv, pathext, platform) {
76
+ const dirs = pathEnv.split(platform === "win32" ? ";" : ":");
77
+ const extensions = platform === "win32" ? ["", ...(pathext || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean)] : [""];
78
+ for (const dir of dirs) {
79
+ const base = dir === "" ? "." : dir;
80
+ for (const ext of extensions) {
81
+ const candidate = join(base, command + ext);
82
+ try {
83
+ accessSync(candidate, fsConstants.X_OK);
84
+ return true;
85
+ } catch {
86
+ }
87
+ }
88
+ }
89
+ return false;
90
+ }
91
+ function readNpmNames(root) {
92
+ const names = [];
93
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
94
+ if (!entry.isDirectory()) continue;
95
+ if (entry.name.startsWith("@")) {
96
+ const scopeDir = join(root, entry.name);
97
+ for (const sub of readdirSync(scopeDir, { withFileTypes: true })) {
98
+ if (sub.isDirectory()) names.push(sub.name);
99
+ }
100
+ } else {
101
+ names.push(entry.name);
102
+ }
103
+ }
104
+ return names;
105
+ }
106
+ function npmGlobalRoot(platform) {
107
+ try {
108
+ const out = execFileSync(platform === "win32" ? "npm.cmd" : "npm", ["root", "-g"], { encoding: "utf8", windowsHide: true }).trim();
109
+ return out || null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ function npmFallbackRoots(home, platform) {
115
+ if (platform === "win32") {
116
+ const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
117
+ return [join(appData, "npm", "node_modules")];
118
+ }
119
+ return [
120
+ "/usr/local/lib/node_modules",
121
+ "/usr/lib/node_modules",
122
+ ...existsSync(join(home, ".nvm", "versions", "node")) ? readdirSync(join(home, ".nvm", "versions", "node")).map((dir) => join(home, ".nvm", "versions", "node", dir, "lib", "node_modules")) : []
123
+ ];
124
+ }
125
+ function discover(registry, options = {}) {
126
+ const home = options.homeDir ?? homedir();
127
+ const platform = options.platform ?? process.platform;
128
+ const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
129
+ const pathext = options.pathext ?? process.env.PATHEXT ?? "";
130
+ let npmNames = null;
131
+ if (!options.noNpm) {
132
+ if (options.npmRoot !== void 0 && options.npmRoot !== null) {
133
+ npmNames = existsSync(options.npmRoot) ? readNpmNames(options.npmRoot) : [];
134
+ } else if (options.npmRoot !== null) {
135
+ const root = npmGlobalRoot(platform);
136
+ if (root && existsSync(root)) {
137
+ npmNames = readNpmNames(root);
138
+ } else {
139
+ npmNames = [];
140
+ for (const fallback of npmFallbackRoots(home, platform)) {
141
+ if (existsSync(fallback)) {
142
+ npmNames = readNpmNames(fallback);
143
+ break;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ return registry.agents.filter((agent) => agent.os === void 0 || agent.os.includes(platform)).map((agent) => {
150
+ let source = "none";
151
+ const configFiles = [];
152
+ for (const config of agent.configs) {
153
+ for (const file of expandConfigFile(config.file, home)) {
154
+ if (existsSync(file)) {
155
+ configFiles.push(file);
156
+ if (source === "none") source = "config";
157
+ }
158
+ }
159
+ }
160
+ if (source === "none" && agent.probe.some((command) => commandOnPath(command, pathEnv, pathext, platform))) {
161
+ source = "path";
162
+ }
163
+ if (source === "none" && npmNames !== null && (agent.probe.some((command) => npmNames.includes(command)) || (agent.npm ?? []).some((name) => npmNames.includes(name)))) {
164
+ source = "npm";
165
+ }
166
+ return { id: agent.id, source, configFiles, present: source !== "none" };
167
+ });
168
+ }
169
+
170
+ // src/setup.ts
7
171
  var DEFAULT_URL = "http://127.0.0.1:18764/mcp";
8
172
  var DEFAULT_SERVER = "agent-hub";
9
173
  function defaultSkillSrc() {
10
- return join(dirname(fileURLToPath(import.meta.url)), "..", "agents", "SKILL.md");
174
+ return join2(dirname2(fileURLToPath2(import.meta.url)), "..", "agents", "SKILL.md");
11
175
  }
12
176
  function stamp() {
13
177
  const now = /* @__PURE__ */ new Date();
@@ -15,7 +179,7 @@ function stamp() {
15
179
  return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
16
180
  }
17
181
  async function readJson(file) {
18
- if (!existsSync(file)) return null;
182
+ if (!existsSync2(file)) return null;
19
183
  try {
20
184
  return JSON.parse(await readFile(file, "utf8"));
21
185
  } catch (error) {
@@ -23,7 +187,7 @@ async function readJson(file) {
23
187
  }
24
188
  }
25
189
  async function writeJsonNoBom(file, doc) {
26
- await mkdir(dirname(file), { recursive: true });
190
+ await mkdir(dirname2(file), { recursive: true });
27
191
  await writeFile(file, JSON.stringify(doc, null, 2) + "\n", "utf8");
28
192
  }
29
193
  async function backup(file) {
@@ -44,7 +208,7 @@ function resolveSection(doc, section) {
44
208
  return node;
45
209
  }
46
210
  async function mergeJsonServer(file, section, entry, opts) {
47
- if (!existsSync(file)) return "skipped";
211
+ if (!existsSync2(file)) return "skipped";
48
212
  const doc = await readJson(file);
49
213
  if (doc === null) return "skipped";
50
214
  const servers = resolveSection(doc, section);
@@ -66,7 +230,7 @@ async function mergeJsonServer(file, section, entry, opts) {
66
230
  return "changed";
67
231
  }
68
232
  async function mergeTomlSection(file, opts) {
69
- if (!existsSync(file)) return "skipped";
233
+ if (!existsSync2(file)) return "skipped";
70
234
  const text = await readFile(file, "utf8");
71
235
  const marker = `[mcp_servers.${opts.serverName}]`;
72
236
  const markerRe = new RegExp(`^\\[mcp_servers\\.${escapeRegExp(opts.serverName)}\\]`, "m");
@@ -104,7 +268,7 @@ ${DSH_PATCH_MARKER} (installed by \`agent-comm-hub setup\`; undo with \`setup --
104
268
  `;
105
269
  }
106
270
  async function mergeDshPatch(file, opts) {
107
- if (!existsSync(file)) return "skipped";
271
+ if (!existsSync2(file)) return "skipped";
108
272
  const text = await readFile(file, "utf8");
109
273
  const lines = text.split("\n");
110
274
  const markerLine = lines.findIndex((line) => line.includes(DSH_PATCH_MARKER));
@@ -154,29 +318,40 @@ async function mergeDshPatch(file, opts) {
154
318
  }
155
319
  async function syncSkill(skillDir, skillSrc, remove, log) {
156
320
  if (remove) {
157
- if (existsSync(skillDir)) {
158
- await mkdir(dirname(skillDir), { recursive: true });
321
+ if (existsSync2(skillDir)) {
322
+ await mkdir(dirname2(skillDir), { recursive: true });
159
323
  await rmRecursive(skillDir);
160
324
  log(` skill removed: ${skillDir}`);
161
325
  }
162
326
  return;
163
327
  }
164
- if (!existsSync(skillSrc)) {
328
+ if (!existsSync2(skillSrc)) {
165
329
  log(` SKILL.md source missing: ${skillSrc} (skipped)`);
166
330
  return;
167
331
  }
168
332
  await mkdir(skillDir, { recursive: true });
169
- await copyFile(skillSrc, join(skillDir, "SKILL.md"));
170
- log(` skill -> ${join(skillDir, "SKILL.md")}`);
333
+ await copyFile(skillSrc, join2(skillDir, "SKILL.md"));
334
+ log(` skill -> ${join2(skillDir, "SKILL.md")}`);
171
335
  }
172
336
  async function rmRecursive(dir) {
173
337
  const { rm } = await import("node:fs/promises");
174
338
  await rm(dir, { recursive: true, force: true });
175
339
  }
340
+ function substitute(entry, values) {
341
+ const out = {};
342
+ for (const [key, value] of Object.entries(entry)) {
343
+ if (typeof value === "string") {
344
+ out[key] = value.replaceAll("{url}", values.url).replaceAll("{serverName}", values.serverName);
345
+ } else {
346
+ out[key] = value;
347
+ }
348
+ }
349
+ return out;
350
+ }
176
351
  async function runSetup(options = {}) {
177
352
  const url = options.url ?? DEFAULT_URL;
178
353
  const serverName = options.serverName ?? DEFAULT_SERVER;
179
- const home = options.homeDir ?? homedir();
354
+ const home = options.homeDir ?? homedir2();
180
355
  const skillSrc = options.skillSrc ?? defaultSkillSrc();
181
356
  const remove = options.remove === true;
182
357
  const log = options.log ?? ((message) => console.log(message));
@@ -186,65 +361,40 @@ async function runSetup(options = {}) {
186
361
  else if (status === "unchanged" || status === "absent") summary.unchanged.push(`${label}: ${file}`);
187
362
  else if (status === "skipped") summary.skipped.push(`${label}: ${file}`);
188
363
  };
189
- const jsonTargets = [
190
- { label: "mcode", file: join(home, ".minimax", "mcp.json"), section: "mcpServers", entry: { url, type: "streamable-http", enabled: true, configured: true, timeout: 12e4, description: "agent-comm-hub: talk to every other agent connected to the hub." } },
191
- { label: "mcode", file: join(home, ".minimax", "mcp", "mcp.json"), section: "mcpServers", entry: { url, type: "streamable-http", enabled: true, configured: true, timeout: 12e4, description: "agent-comm-hub: talk to every other agent connected to the hub." } },
192
- { label: "opencode", file: join(home, ".config", "opencode", "opencode.json"), section: "mcp", entry: { type: "remote", url, enabled: true } },
193
- { label: "kimi-code", file: join(home, ".kimi-code", "mcp.json"), section: "mcpServers", entry: { transport: "http", url, startupTimeoutMs: 3e4, toolTimeoutMs: 12e4 } },
194
- { label: "gemini-cli", file: join(home, ".gemini", "settings.json"), section: "mcpServers", entry: { type: "http", url } },
195
- { label: "zcode", file: join(home, ".zcode", "cli", "config.json"), section: "mcp.servers", entry: { type: "remote", url, enabled: true } }
196
- ];
197
- for (const target of jsonTargets) {
198
- try {
199
- const status = await mergeJsonServer(target.file, target.section, target.entry, { serverName, url, remove });
200
- record(status, target.label, target.file);
201
- } catch (error) {
202
- summary.errors.push(`${target.label}: ${target.file} \u2014 ${error.message}`);
203
- log(` ${target.label}: SKIPPED \u2014 ${error.message}`);
364
+ const registry = loadRegistry();
365
+ const found = discover(registry, { homeDir: home, pathEnv: options.pathEnv, noNpm: options.noNpm === true });
366
+ const only = options.agent;
367
+ let targetAgents = [];
368
+ if (only !== void 0) {
369
+ const match = registry.agents.find((agent) => agent.id === only);
370
+ if (match === void 0) {
371
+ log(`agent '${only}' is not in the registry (see agents/registry.json)`);
372
+ } else {
373
+ targetAgents = [match];
374
+ log(`configure only: ${only}`);
204
375
  }
376
+ } else {
377
+ targetAgents = found.filter((agent) => agent.present).map((agent) => registry.agents.find((entry) => entry.id === agent.id));
378
+ const present = targetAgents.map((agent) => agent.id);
379
+ log(`discovered: ${present.length > 0 ? present.join(", ") : "none"}`);
205
380
  }
206
- const codexFile = join(home, ".codex", "config.toml");
207
- try {
208
- const status = await mergeTomlSection(codexFile, { serverName, url, remove });
209
- record(status, "codex", codexFile);
210
- } catch (error) {
211
- summary.errors.push(`codex: ${codexFile} \u2014 ${error.message}`);
212
- log(` codex: SKIPPED \u2014 ${error.message}`);
213
- }
214
- const dshProfilesDir = join(home, ".dsh", "profiles");
215
- if (existsSync(dshProfilesDir)) {
216
- let entries = [];
217
- try {
218
- entries = await readdir(dshProfilesDir, { withFileTypes: true });
219
- } catch (error) {
220
- summary.errors.push(`dsh profiles scan \u2014 ${error.message}`);
221
- }
222
- for (const entry of entries) {
223
- if (!entry.isDirectory()) continue;
224
- const patch = join(dshProfilesDir, entry.name, "cordis.patch.yml");
225
- try {
226
- const status = await mergeDshPatch(patch, { serverName, url, remove });
227
- record(status, `dsh (${entry.name})`, patch);
228
- } catch (error) {
229
- summary.errors.push(`dsh (${entry.name}): ${patch} \u2014 ${error.message}`);
230
- log(` dsh (${entry.name}): SKIPPED \u2014 ${error.message}`);
381
+ for (const agent of targetAgents) {
382
+ for (const config of agent.configs) {
383
+ for (const file of expandConfigFile(config.file, home)) {
384
+ try {
385
+ const status = config.strategy === "json" ? await mergeJsonServer(file, config.section, substitute(config.entry, { url, serverName }), { serverName, url, remove }) : config.strategy === "toml" ? await mergeTomlSection(file, { serverName, url, remove }) : await mergeDshPatch(file, { serverName, url, remove });
386
+ record(status, agent.id, file);
387
+ } catch (error) {
388
+ summary.errors.push(`${agent.id}: ${file} \u2014 ${error.message}`);
389
+ log(` ${agent.id}: SKIPPED \u2014 ${error.message}`);
390
+ }
231
391
  }
232
392
  }
233
393
  }
234
- const skillDirs = [
235
- join(home, ".agents", "skills", serverName),
236
- // cross-agent standard
237
- join(home, ".minimax", "skills", serverName),
238
- join(home, ".config", "opencode", "skills", serverName),
239
- join(home, ".kimi-code", "skills", serverName),
240
- join(home, ".gemini", "skills", serverName),
241
- join(home, ".codex", "skills", serverName),
242
- join(home, ".zcode", "skills", serverName),
243
- join(home, ".claude", "skills", serverName),
244
- // config is manual; skill still useful
245
- join(home, ".dsh", "skills", serverName)
246
- // DSH skill (config auto-installed)
247
- ];
394
+ const skillDirs = [join2(home, ".agents", "skills", serverName)];
395
+ for (const agent of targetAgents) {
396
+ if (agent.skill !== null) skillDirs.push(join2(expandHome(agent.skill, home), serverName));
397
+ }
248
398
  for (const dir of skillDirs) {
249
399
  try {
250
400
  await syncSkill(dir, skillSrc, remove, log);
package/package.json CHANGED
@@ -1,46 +1,44 @@
1
- {
2
- "name": "agent-comm-hub",
3
- "version": "0.3.0",
4
- "description": "Generic multi-peer MCP hub: any MCP-capable agent (MiniMax Code, Claude Code, opencode, Codex, Gemini CLI, DSH, ...) connects to one local streamable-http endpoint and they chat, delegate tasks, and acknowledge in real time",
5
- "keywords": [
6
- "mcp",
7
- "agent",
8
- "hub",
9
- "multi-agent",
10
- "communication",
11
- "streamable-http",
12
- "cli",
13
- "ai"
14
- ],
15
- "author": "agent-comm-hub contributors",
16
- "type": "module",
17
- "main": "lib/index.js",
18
- "bin": {
19
- "agent-comm-hub": "lib/cli.js"
20
- },
21
- "files": [
22
- "lib",
23
- "agents",
24
- "assets",
25
- "README.md"
26
- ],
27
- "engines": {
28
- "node": ">=22"
29
- },
30
- "license": "MIT",
31
- "dependencies": {},
32
- "devDependencies": {
33
- "@types/node": "^22.0.0",
34
- "esbuild": "^0.25.0",
35
- "typescript": "^5.8.0"
36
- },
37
- "scripts": {
38
- "build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=lib/cli.js && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=lib/index.js && esbuild src/setup.ts --bundle --platform=node --format=esm --outfile=lib/setup.js",
39
- "build:test": "esbuild test/entry.ts --bundle --platform=node --format=esm --outfile=test/entry.mjs && esbuild test/setup-entry.ts --bundle --platform=node --format=esm --outfile=test/setup-entry.mjs && esbuild test/ops-entry.ts --bundle --platform=node --format=esm --outfile=test/ops-entry.mjs && esbuild test/herdr-entry.ts --bundle --platform=node --format=esm --outfile=test/herdr-entry.mjs",
40
- "typecheck": "tsc --noEmit",
41
- "test": "pnpm run build:test && node test/smoke.mjs && node test/setup.mjs && node test/ops.mjs && node test/herdr.mjs",
42
- "pack": "pnpm run build && pnpm pack",
43
- "prepublishOnly": "pnpm run test && pnpm run build",
44
- "prepare": "pnpm run build"
45
- }
46
- }
1
+ {
2
+ "name": "agent-comm-hub",
3
+ "version": "0.5.0",
4
+ "description": "Generic multi-peer MCP hub: any MCP-capable agent (MiniMax Code, Claude Code, opencode, Codex, Gemini CLI, DSH, ...) connects to one local streamable-http endpoint and they chat, delegate tasks, and acknowledge in real time",
5
+ "keywords": [
6
+ "mcp",
7
+ "agent",
8
+ "hub",
9
+ "multi-agent",
10
+ "communication",
11
+ "streamable-http",
12
+ "cli",
13
+ "ai"
14
+ ],
15
+ "author": "agent-comm-hub contributors",
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "bin": {
19
+ "agent-comm-hub": "lib/cli.js"
20
+ },
21
+ "files": [
22
+ "lib",
23
+ "agents",
24
+ "assets",
25
+ "README.md"
26
+ ],
27
+ "engines": {
28
+ "node": ">=22"
29
+ },
30
+ "license": "MIT",
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "@types/node": "^22.0.0",
34
+ "esbuild": "^0.25.0",
35
+ "typescript": "^5.8.0"
36
+ },
37
+ "scripts": {
38
+ "build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=lib/cli.js && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=lib/index.js && esbuild src/setup.ts --bundle --platform=node --format=esm --outfile=lib/setup.js",
39
+ "build:test": "esbuild test/entry.ts --bundle --platform=node --format=esm --outfile=test/entry.mjs && esbuild test/setup-entry.ts --bundle --platform=node --format=esm --outfile=test/setup-entry.mjs && esbuild test/ops-entry.ts --bundle --platform=node --format=esm --outfile=test/ops-entry.mjs && esbuild test/herdr-entry.ts --bundle --platform=node --format=esm --outfile=test/herdr-entry.mjs && esbuild test/discover-entry.ts --bundle --platform=node --format=esm --outfile=test/discover-entry.mjs",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "pnpm run build:test && node test/smoke.mjs && node test/setup.mjs && node test/ops.mjs && node test/herdr.mjs && node test/discover.mjs",
42
+ "pack": "pnpm run build && pnpm pack"
43
+ }
44
+ }