agent-comm-hub 0.2.0 → 0.3.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 +408 -361
- package/README.zh.md +282 -250
- package/lib/cli.js +741 -16
- package/lib/index.js +642 -4
- package/lib/setup.js +88 -4
- package/package.json +3 -3
package/lib/setup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/setup.ts
|
|
2
|
-
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
@@ -90,6 +90,68 @@ url = "${opts.url}"
|
|
|
90
90
|
function escapeRegExp(value) {
|
|
91
91
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
92
92
|
}
|
|
93
|
+
var DSH_PATCH_MARKER = "# \u2500\u2500 agent-comm-hub MCP client";
|
|
94
|
+
function dshPatchBlock(url, serverName) {
|
|
95
|
+
return `
|
|
96
|
+
${DSH_PATCH_MARKER} (installed by \`agent-comm-hub setup\`; undo with \`setup --remove\`) \u2500
|
|
97
|
+
- insert:
|
|
98
|
+
- id: ${serverName}
|
|
99
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
100
|
+
config:
|
|
101
|
+
serverName: ${serverName}
|
|
102
|
+
transport: streamable-http
|
|
103
|
+
url: ${url}
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
async function mergeDshPatch(file, opts) {
|
|
107
|
+
if (!existsSync(file)) return "skipped";
|
|
108
|
+
const text = await readFile(file, "utf8");
|
|
109
|
+
const lines = text.split("\n");
|
|
110
|
+
const markerLine = lines.findIndex((line) => line.includes(DSH_PATCH_MARKER));
|
|
111
|
+
const hasBlock = markerLine >= 0;
|
|
112
|
+
const blockRange = () => {
|
|
113
|
+
let entryStart = -1;
|
|
114
|
+
for (let i = markerLine + 1; i < lines.length; i++) {
|
|
115
|
+
if (/^- /.test(lines[i])) {
|
|
116
|
+
entryStart = i;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let end = lines.length;
|
|
121
|
+
if (entryStart >= 0) {
|
|
122
|
+
for (let i = entryStart + 1; i < lines.length; i++) {
|
|
123
|
+
if (/^- /.test(lines[i])) {
|
|
124
|
+
end = i;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let start = markerLine;
|
|
130
|
+
while (start > 0 && lines[start - 1].trim() === "") start--;
|
|
131
|
+
return { start, end };
|
|
132
|
+
};
|
|
133
|
+
const withoutBlock = () => {
|
|
134
|
+
const { start, end } = blockRange();
|
|
135
|
+
return lines.slice(0, start).concat(lines.slice(end)).join("\n");
|
|
136
|
+
};
|
|
137
|
+
if (opts.remove) {
|
|
138
|
+
if (!hasBlock) return "absent";
|
|
139
|
+
const cleaned = withoutBlock();
|
|
140
|
+
await backup(file);
|
|
141
|
+
await writeFile(file, cleaned, "utf8");
|
|
142
|
+
return "removed";
|
|
143
|
+
}
|
|
144
|
+
if (hasBlock) {
|
|
145
|
+
if (lines.some((line) => line.includes(`url: ${opts.url}`))) return "unchanged";
|
|
146
|
+
const replaced = withoutBlock();
|
|
147
|
+
await backup(file);
|
|
148
|
+
await writeFile(file, replaced.trimEnd() + dshPatchBlock(opts.url, opts.serverName), "utf8");
|
|
149
|
+
return "changed";
|
|
150
|
+
}
|
|
151
|
+
await backup(file);
|
|
152
|
+
await writeFile(file, text.trimEnd() + dshPatchBlock(opts.url, opts.serverName), "utf8");
|
|
153
|
+
return "changed";
|
|
154
|
+
}
|
|
93
155
|
async function syncSkill(skillDir, skillSrc, remove, log) {
|
|
94
156
|
if (remove) {
|
|
95
157
|
if (existsSync(skillDir)) {
|
|
@@ -149,6 +211,26 @@ async function runSetup(options = {}) {
|
|
|
149
211
|
summary.errors.push(`codex: ${codexFile} \u2014 ${error.message}`);
|
|
150
212
|
log(` codex: SKIPPED \u2014 ${error.message}`);
|
|
151
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}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
152
234
|
const skillDirs = [
|
|
153
235
|
join(home, ".agents", "skills", serverName),
|
|
154
236
|
// cross-agent standard
|
|
@@ -158,8 +240,10 @@ async function runSetup(options = {}) {
|
|
|
158
240
|
join(home, ".gemini", "skills", serverName),
|
|
159
241
|
join(home, ".codex", "skills", serverName),
|
|
160
242
|
join(home, ".zcode", "skills", serverName),
|
|
161
|
-
join(home, ".claude", "skills", serverName)
|
|
243
|
+
join(home, ".claude", "skills", serverName),
|
|
162
244
|
// config is manual; skill still useful
|
|
245
|
+
join(home, ".dsh", "skills", serverName)
|
|
246
|
+
// DSH skill (config auto-installed)
|
|
163
247
|
];
|
|
164
248
|
for (const dir of skillDirs) {
|
|
165
249
|
try {
|
|
@@ -169,8 +253,8 @@ async function runSetup(options = {}) {
|
|
|
169
253
|
log(` skill ${dir}: SKIPPED \u2014 ${error.message}`);
|
|
170
254
|
}
|
|
171
255
|
}
|
|
172
|
-
if (remove) log("done. Manual
|
|
173
|
-
else log("done. Manual
|
|
256
|
+
if (remove) log("done. Manual target (see agents/README.md): Claude Code (.mcp.json).");
|
|
257
|
+
else log("done. Manual target (see agents/README.md): Claude Code (.mcp.json). Restart agent sessions (and the dsh profile) to pick up the MCP server.");
|
|
174
258
|
return summary;
|
|
175
259
|
}
|
|
176
260
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-comm-hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
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",
|
|
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
40
|
"typecheck": "tsc --noEmit",
|
|
41
|
-
"test": "pnpm run build:test && node test/smoke.mjs && node test/setup.mjs && node test/ops.mjs",
|
|
41
|
+
"test": "pnpm run build:test && node test/smoke.mjs && node test/setup.mjs && node test/ops.mjs && node test/herdr.mjs",
|
|
42
42
|
"pack": "pnpm run build && pnpm pack",
|
|
43
43
|
"prepublishOnly": "pnpm run test && pnpm run build",
|
|
44
44
|
"prepare": "pnpm run build"
|