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/README.md +43 -6
- package/README.zh.md +42 -5
- package/agents/SKILL.md +3 -1
- package/agents/registry.json +201 -0
- package/assets/ach-cn.png +0 -0
- package/assets/ach-en.png +0 -0
- package/lib/cli.js +347 -98
- package/lib/index.js +18 -5
- package/lib/setup.js +219 -69
- package/package.json +44 -46
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:
|
|
847
|
-
inputSchema: schema({ peer: optStr(
|
|
848
|
-
handler: wrap(true, async (args, peer) =>
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
158
|
-
await mkdir(
|
|
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 (!
|
|
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,
|
|
170
|
-
log(` skill -> ${
|
|
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 ??
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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.
|
|
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
|
-
|
|
44
|
-
|
|
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
|
+
}
|