adhdev 0.9.76-rc.2 → 0.9.76-rc.21
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/dist/cli/index.js +5840 -3156
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +5726 -3044
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/vendor/mcp-server/index.js +331 -51
- package/vendor/mcp-server/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adhdev",
|
|
3
|
-
"version": "0.9.76-rc.
|
|
3
|
+
"version": "0.9.76-rc.21",
|
|
4
4
|
"description": "ADHDev — Agent Dashboard Hub for Dev. Remote-control AI coding agents from anywhere.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"node": ">=18"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/daemon-core": "0.9.76-rc.
|
|
50
|
+
"@adhdev/daemon-core": "0.9.76-rc.21",
|
|
51
51
|
"@adhdev/ghostty-vt-node": "*",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
53
53
|
"@xterm/addon-serialize": "^0.14.0",
|
|
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
9
13
|
var __copyProps = (to, from, except, desc) => {
|
|
10
14
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
15
|
for (let key of __getOwnPropNames(from))
|
|
@@ -22,6 +26,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
26
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
27
|
mod
|
|
24
28
|
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
parseArgs: () => parseArgs
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
25
37
|
|
|
26
38
|
// src/server.ts
|
|
27
39
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
@@ -234,6 +246,107 @@ var CloudTransport = class {
|
|
|
234
246
|
}
|
|
235
247
|
};
|
|
236
248
|
|
|
249
|
+
// src/transports/ipc.ts
|
|
250
|
+
var DEFAULT_IPC_PORT = 19222;
|
|
251
|
+
var DEFAULT_IPC_PATH = "/ipc";
|
|
252
|
+
var IpcTransport = class {
|
|
253
|
+
port;
|
|
254
|
+
path;
|
|
255
|
+
constructor(opts = {}) {
|
|
256
|
+
this.port = opts.port ?? DEFAULT_IPC_PORT;
|
|
257
|
+
this.path = opts.path || DEFAULT_IPC_PATH;
|
|
258
|
+
}
|
|
259
|
+
async ping() {
|
|
260
|
+
try {
|
|
261
|
+
const res = await fetch(`http://127.0.0.1:${this.port}/health`);
|
|
262
|
+
return res.ok;
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async getStatus() {
|
|
268
|
+
return this.command("get_status_metadata");
|
|
269
|
+
}
|
|
270
|
+
async command(type, args = {}) {
|
|
271
|
+
return this.sendIpcCommand(type, args);
|
|
272
|
+
}
|
|
273
|
+
async meshCommand(targetDaemonId, command, args = {}) {
|
|
274
|
+
return this.sendIpcCommand("mesh_relay_command", {
|
|
275
|
+
targetDaemonId,
|
|
276
|
+
command,
|
|
277
|
+
args
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async sendIpcCommand(type, args) {
|
|
281
|
+
const WebSocketCtor = globalThis.WebSocket;
|
|
282
|
+
if (!WebSocketCtor) {
|
|
283
|
+
throw new Error("WebSocket is not available in this Node runtime; Node 20+ is required for daemon IPC mode");
|
|
284
|
+
}
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
const requestId = `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
287
|
+
const ws = new WebSocketCtor(`ws://127.0.0.1:${this.port}${this.path}`);
|
|
288
|
+
let settled = false;
|
|
289
|
+
const finish = (fn) => {
|
|
290
|
+
if (settled) return;
|
|
291
|
+
settled = true;
|
|
292
|
+
clearTimeout(timeout);
|
|
293
|
+
try {
|
|
294
|
+
ws.close();
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
fn();
|
|
298
|
+
};
|
|
299
|
+
const timeout = setTimeout(() => {
|
|
300
|
+
finish(() => reject(new Error(`Daemon IPC command '${type}' timed out after 15s`)));
|
|
301
|
+
}, 15e3);
|
|
302
|
+
let commandSent = false;
|
|
303
|
+
const send = () => {
|
|
304
|
+
if (commandSent) return;
|
|
305
|
+
commandSent = true;
|
|
306
|
+
ws.send(JSON.stringify({
|
|
307
|
+
type: "ext:command",
|
|
308
|
+
payload: { command: type, args, requestId }
|
|
309
|
+
}));
|
|
310
|
+
};
|
|
311
|
+
ws.addEventListener("open", () => {
|
|
312
|
+
ws.send(JSON.stringify({
|
|
313
|
+
type: "ext:register",
|
|
314
|
+
payload: {
|
|
315
|
+
ideType: "mcp-server",
|
|
316
|
+
ideVersion: "1.0.0",
|
|
317
|
+
extensionVersion: "1.0.0",
|
|
318
|
+
instanceId: `mcp-server-${process.pid}`,
|
|
319
|
+
machineId: "mcp-server",
|
|
320
|
+
workspaceFolders: []
|
|
321
|
+
}
|
|
322
|
+
}));
|
|
323
|
+
});
|
|
324
|
+
ws.addEventListener("message", (event) => {
|
|
325
|
+
try {
|
|
326
|
+
const raw = typeof event.data === "string" ? event.data : String(event.data);
|
|
327
|
+
const msg = JSON.parse(raw);
|
|
328
|
+
if (msg?.type === "daemon:welcome") {
|
|
329
|
+
send();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (msg?.type !== "ext:command_result") return;
|
|
333
|
+
if (msg?.payload?.requestId !== requestId) return;
|
|
334
|
+
const payload = msg.payload;
|
|
335
|
+
if (payload?.success === false) {
|
|
336
|
+
finish(() => reject(new Error(payload.error || `Daemon IPC command '${type}' failed`)));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
finish(() => resolve(payload?.result ?? payload));
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
ws.addEventListener("error", () => {
|
|
344
|
+
finish(() => reject(new Error(`Cannot connect to daemon IPC at ws://127.0.0.1:${this.port}${this.path}`)));
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
237
350
|
// src/transports/mode.ts
|
|
238
351
|
function isLocalTransport(transport) {
|
|
239
352
|
return typeof transport.command === "function";
|
|
@@ -1265,10 +1378,74 @@ ${lines.join("\n\n")}`;
|
|
|
1265
1378
|
}
|
|
1266
1379
|
|
|
1267
1380
|
// src/tools/mesh-tools.ts
|
|
1268
|
-
function
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1381
|
+
async function refreshMeshFromDaemon(ctx) {
|
|
1382
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
1383
|
+
try {
|
|
1384
|
+
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
1385
|
+
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
1386
|
+
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
1387
|
+
if (!refreshedNodes.length) return;
|
|
1388
|
+
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
1389
|
+
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
1390
|
+
} catch {
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
async function findNodeWithRefresh(ctx, nodeId) {
|
|
1394
|
+
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
1395
|
+
if (hit) return hit;
|
|
1396
|
+
await refreshMeshFromDaemon(ctx);
|
|
1397
|
+
const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
1398
|
+
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
1399
|
+
return refreshed;
|
|
1400
|
+
}
|
|
1401
|
+
function unwrapCommandPayload(value) {
|
|
1402
|
+
return value?.result?.result ?? value?.result ?? value;
|
|
1403
|
+
}
|
|
1404
|
+
function findNestedPayload(value, predicate) {
|
|
1405
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1406
|
+
const stack = [{ payload: value, depth: 0 }];
|
|
1407
|
+
while (stack.length) {
|
|
1408
|
+
const { payload, depth } = stack.pop();
|
|
1409
|
+
if (predicate(payload)) return payload;
|
|
1410
|
+
if (!payload || typeof payload !== "object" || seen.has(payload) || depth >= 8) continue;
|
|
1411
|
+
seen.add(payload);
|
|
1412
|
+
for (const key of ["payload", "result"]) {
|
|
1413
|
+
if (key in payload) stack.push({ payload: payload[key], depth: depth + 1 });
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return value;
|
|
1417
|
+
}
|
|
1418
|
+
function extractCloneNodePayload(value) {
|
|
1419
|
+
return findNestedPayload(value, (payload) => Boolean(payload?.node?.id));
|
|
1420
|
+
}
|
|
1421
|
+
function extractGitStatus(value) {
|
|
1422
|
+
const payload = unwrapCommandPayload(value);
|
|
1423
|
+
return payload?.status ?? value?.status ?? payload;
|
|
1424
|
+
}
|
|
1425
|
+
function extractGitDiff(value) {
|
|
1426
|
+
const payload = unwrapCommandPayload(value);
|
|
1427
|
+
return payload?.diffSummary ?? payload?.diff ?? value?.diffSummary ?? value?.diff ?? payload;
|
|
1428
|
+
}
|
|
1429
|
+
function countUncommittedChanges(status) {
|
|
1430
|
+
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
1431
|
+
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
1432
|
+
const counted = keys.reduce((sum, key) => sum + (Number.isFinite(Number(status?.[key])) ? Number(status[key]) : 0), 0);
|
|
1433
|
+
const conflicts = Array.isArray(status?.conflictFiles) ? status.conflictFiles.length : status?.hasConflicts ? 1 : 0;
|
|
1434
|
+
return counted + conflicts;
|
|
1435
|
+
}
|
|
1436
|
+
function isGitStatusDirty(status) {
|
|
1437
|
+
if (typeof status?.isDirty === "boolean") return status.isDirty;
|
|
1438
|
+
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
1439
|
+
return countUncommittedChanges(status) > 0;
|
|
1440
|
+
}
|
|
1441
|
+
async function commandForNode(ctx, node, command, args = {}) {
|
|
1442
|
+
if (ctx.transport instanceof IpcTransport && node.daemonId) {
|
|
1443
|
+
return ctx.transport.meshCommand(node.daemonId, command, args);
|
|
1444
|
+
}
|
|
1445
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1446
|
+
return ctx.transport.command(command, args);
|
|
1447
|
+
}
|
|
1448
|
+
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
1272
1449
|
}
|
|
1273
1450
|
var MESH_STATUS_TOOL = {
|
|
1274
1451
|
name: "mesh_status",
|
|
@@ -1314,12 +1491,12 @@ var MESH_READ_CHAT_TOOL = {
|
|
|
1314
1491
|
};
|
|
1315
1492
|
var MESH_LAUNCH_SESSION_TOOL = {
|
|
1316
1493
|
name: "mesh_launch_session",
|
|
1317
|
-
description: "Launch a new agent session on a mesh node. Returns the session ID for subsequent send_task/read_chat calls.",
|
|
1494
|
+
description: "Launch a new agent session on a mesh node. Returns the session ID for subsequent send_task/read_chat calls. If the user names a provider, preserve it exactly: Hermes = hermes-cli, Claude Code/Claude = claude-cli, Codex = codex-cli, Gemini = gemini-cli. Do not default to claude-cli unless the user requested Claude Code or no provider was specified.",
|
|
1318
1495
|
inputSchema: {
|
|
1319
1496
|
type: "object",
|
|
1320
1497
|
properties: {
|
|
1321
1498
|
node_id: { type: "string", description: "Target node ID." },
|
|
1322
|
-
type: { type: "string", description:
|
|
1499
|
+
type: { type: "string", description: "Provider type to launch. Use hermes-cli for Hermes, claude-cli for Claude Code, codex-cli for Codex, gemini-cli for Gemini." }
|
|
1323
1500
|
},
|
|
1324
1501
|
required: ["node_id", "type"]
|
|
1325
1502
|
}
|
|
@@ -1360,6 +1537,30 @@ var MESH_APPROVE_TOOL = {
|
|
|
1360
1537
|
required: ["node_id", "session_id", "action"]
|
|
1361
1538
|
}
|
|
1362
1539
|
};
|
|
1540
|
+
var MESH_CLONE_NODE_TOOL = {
|
|
1541
|
+
name: "mesh_clone_node",
|
|
1542
|
+
description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously.",
|
|
1543
|
+
inputSchema: {
|
|
1544
|
+
type: "object",
|
|
1545
|
+
properties: {
|
|
1546
|
+
source_node_id: { type: "string", description: "Node ID to clone from (from mesh_list_nodes)." },
|
|
1547
|
+
branch: { type: "string", description: 'Branch name for the new worktree (e.g. "feat/auth-refactor").' },
|
|
1548
|
+
base_branch: { type: "string", description: "Starting point for the branch (default: current HEAD)." }
|
|
1549
|
+
},
|
|
1550
|
+
required: ["source_node_id", "branch"]
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
var MESH_REMOVE_NODE_TOOL = {
|
|
1554
|
+
name: "mesh_remove_node",
|
|
1555
|
+
description: "Remove a node from the mesh. If the node is a worktree, also cleans up the git worktree and directory.",
|
|
1556
|
+
inputSchema: {
|
|
1557
|
+
type: "object",
|
|
1558
|
+
properties: {
|
|
1559
|
+
node_id: { type: "string", description: "Node ID to remove." }
|
|
1560
|
+
},
|
|
1561
|
+
required: ["node_id"]
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1363
1564
|
var ALL_MESH_TOOLS = [
|
|
1364
1565
|
MESH_STATUS_TOOL,
|
|
1365
1566
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1368,9 +1569,12 @@ var ALL_MESH_TOOLS = [
|
|
|
1368
1569
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1369
1570
|
MESH_GIT_STATUS_TOOL,
|
|
1370
1571
|
MESH_CHECKPOINT_TOOL,
|
|
1371
|
-
MESH_APPROVE_TOOL
|
|
1572
|
+
MESH_APPROVE_TOOL,
|
|
1573
|
+
MESH_CLONE_NODE_TOOL,
|
|
1574
|
+
MESH_REMOVE_NODE_TOOL
|
|
1372
1575
|
];
|
|
1373
1576
|
async function meshStatus(ctx) {
|
|
1577
|
+
await refreshMeshFromDaemon(ctx);
|
|
1374
1578
|
const { mesh, transport } = ctx;
|
|
1375
1579
|
const results = [];
|
|
1376
1580
|
for (const node of mesh.nodes) {
|
|
@@ -1381,18 +1585,22 @@ async function meshStatus(ctx) {
|
|
|
1381
1585
|
try {
|
|
1382
1586
|
if (!isLocalTransport(transport) && node.daemonId) {
|
|
1383
1587
|
const result = await transport.gitStatus(node.daemonId, node.workspace, false);
|
|
1384
|
-
const status = result
|
|
1385
|
-
|
|
1588
|
+
const status = extractGitStatus(result);
|
|
1589
|
+
const uncommittedChanges = countUncommittedChanges(status);
|
|
1590
|
+
const dirty = isGitStatusDirty(status);
|
|
1591
|
+
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1386
1592
|
entry.branch = status?.branch;
|
|
1387
|
-
entry.isDirty =
|
|
1388
|
-
entry.uncommittedChanges =
|
|
1593
|
+
entry.isDirty = dirty;
|
|
1594
|
+
entry.uncommittedChanges = uncommittedChanges;
|
|
1389
1595
|
} else if (isLocalTransport(transport)) {
|
|
1390
|
-
const statusResult = await
|
|
1391
|
-
const status = statusResult
|
|
1392
|
-
|
|
1596
|
+
const statusResult = await commandForNode(ctx, node, "git_status", { workspace: node.workspace });
|
|
1597
|
+
const status = extractGitStatus(statusResult);
|
|
1598
|
+
const uncommittedChanges = countUncommittedChanges(status);
|
|
1599
|
+
const dirty = isGitStatusDirty(status);
|
|
1600
|
+
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1393
1601
|
entry.branch = status?.branch;
|
|
1394
|
-
entry.isDirty =
|
|
1395
|
-
entry.uncommittedChanges =
|
|
1602
|
+
entry.isDirty = dirty;
|
|
1603
|
+
entry.uncommittedChanges = uncommittedChanges;
|
|
1396
1604
|
} else {
|
|
1397
1605
|
entry.health = "unknown";
|
|
1398
1606
|
entry.note = "No daemonId available for cloud status probe";
|
|
@@ -1413,6 +1621,7 @@ async function meshStatus(ctx) {
|
|
|
1413
1621
|
}, null, 2);
|
|
1414
1622
|
}
|
|
1415
1623
|
async function meshListNodes(ctx) {
|
|
1624
|
+
await refreshMeshFromDaemon(ctx);
|
|
1416
1625
|
const { mesh } = ctx;
|
|
1417
1626
|
return JSON.stringify({
|
|
1418
1627
|
meshId: mesh.id,
|
|
@@ -1428,14 +1637,15 @@ async function meshListNodes(ctx) {
|
|
|
1428
1637
|
}, null, 2);
|
|
1429
1638
|
}
|
|
1430
1639
|
async function meshSendTask(ctx, args) {
|
|
1431
|
-
const node =
|
|
1640
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1432
1641
|
if (node.policy?.readOnly) {
|
|
1433
1642
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
1434
1643
|
}
|
|
1435
1644
|
if (isLocalTransport(ctx.transport)) {
|
|
1436
|
-
await ctx
|
|
1645
|
+
await commandForNode(ctx, node, "send_chat", {
|
|
1437
1646
|
message: args.message,
|
|
1438
|
-
sessionId: args.session_id
|
|
1647
|
+
sessionId: args.session_id,
|
|
1648
|
+
targetSessionId: args.session_id
|
|
1439
1649
|
});
|
|
1440
1650
|
return JSON.stringify({ success: true, nodeId: args.node_id, sessionId: args.session_id });
|
|
1441
1651
|
} else {
|
|
@@ -1443,10 +1653,11 @@ async function meshSendTask(ctx, args) {
|
|
|
1443
1653
|
}
|
|
1444
1654
|
}
|
|
1445
1655
|
async function meshReadChat(ctx, args) {
|
|
1446
|
-
|
|
1656
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1447
1657
|
if (isLocalTransport(ctx.transport)) {
|
|
1448
|
-
const result = await ctx
|
|
1658
|
+
const result = await commandForNode(ctx, node, "read_chat", {
|
|
1449
1659
|
sessionId: args.session_id,
|
|
1660
|
+
targetSessionId: args.session_id,
|
|
1450
1661
|
tailLimit: args.tail ?? 10
|
|
1451
1662
|
});
|
|
1452
1663
|
return JSON.stringify(result, null, 2);
|
|
@@ -1455,9 +1666,9 @@ async function meshReadChat(ctx, args) {
|
|
|
1455
1666
|
}
|
|
1456
1667
|
}
|
|
1457
1668
|
async function meshLaunchSession(ctx, args) {
|
|
1458
|
-
const node =
|
|
1669
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1459
1670
|
if (isLocalTransport(ctx.transport)) {
|
|
1460
|
-
const result = await ctx
|
|
1671
|
+
const result = await commandForNode(ctx, node, "launch_cli", {
|
|
1461
1672
|
cliType: args.type,
|
|
1462
1673
|
dir: node.workspace,
|
|
1463
1674
|
settings: {
|
|
@@ -1471,39 +1682,39 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1471
1682
|
}
|
|
1472
1683
|
}
|
|
1473
1684
|
async function meshGitStatus(ctx, args) {
|
|
1474
|
-
const node =
|
|
1685
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1475
1686
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
1476
1687
|
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
1477
1688
|
return JSON.stringify({
|
|
1478
1689
|
nodeId: args.node_id,
|
|
1479
1690
|
workspace: node.workspace,
|
|
1480
|
-
status: result
|
|
1481
|
-
diff: result
|
|
1691
|
+
status: extractGitStatus(result),
|
|
1692
|
+
diff: extractGitDiff(result)
|
|
1482
1693
|
}, null, 2);
|
|
1483
1694
|
} else if (isLocalTransport(ctx.transport)) {
|
|
1484
|
-
const statusResult = await ctx
|
|
1695
|
+
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
1485
1696
|
workspace: node.workspace
|
|
1486
1697
|
});
|
|
1487
|
-
const diffResult = await ctx
|
|
1698
|
+
const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
|
|
1488
1699
|
workspace: node.workspace
|
|
1489
1700
|
});
|
|
1490
1701
|
return JSON.stringify({
|
|
1491
1702
|
nodeId: args.node_id,
|
|
1492
1703
|
workspace: node.workspace,
|
|
1493
|
-
status: statusResult
|
|
1494
|
-
diff: diffResult
|
|
1704
|
+
status: extractGitStatus(statusResult),
|
|
1705
|
+
diff: extractGitDiff(diffResult)
|
|
1495
1706
|
}, null, 2);
|
|
1496
1707
|
} else {
|
|
1497
1708
|
return JSON.stringify({ error: "No daemonId available for cloud git_status probe" });
|
|
1498
1709
|
}
|
|
1499
1710
|
}
|
|
1500
1711
|
async function meshCheckpoint(ctx, args) {
|
|
1501
|
-
const node =
|
|
1712
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1502
1713
|
if (node.policy?.readOnly) {
|
|
1503
1714
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only \u2014 cannot checkpoint` });
|
|
1504
1715
|
}
|
|
1505
1716
|
if (isLocalTransport(ctx.transport)) {
|
|
1506
|
-
const result = await ctx
|
|
1717
|
+
const result = await commandForNode(ctx, node, "git_checkpoint", {
|
|
1507
1718
|
workspace: node.workspace,
|
|
1508
1719
|
message: args.message
|
|
1509
1720
|
});
|
|
@@ -1513,10 +1724,11 @@ async function meshCheckpoint(ctx, args) {
|
|
|
1513
1724
|
}
|
|
1514
1725
|
}
|
|
1515
1726
|
async function meshApprove(ctx, args) {
|
|
1516
|
-
|
|
1727
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1517
1728
|
if (isLocalTransport(ctx.transport)) {
|
|
1518
|
-
const result = await ctx
|
|
1729
|
+
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
1519
1730
|
sessionId: args.session_id,
|
|
1731
|
+
targetSessionId: args.session_id,
|
|
1520
1732
|
action: args.action === "reject" ? "reject" : "approve"
|
|
1521
1733
|
});
|
|
1522
1734
|
return JSON.stringify(result, null, 2);
|
|
@@ -1524,13 +1736,63 @@ async function meshApprove(ctx, args) {
|
|
|
1524
1736
|
return JSON.stringify({ error: "Cloud mesh approve not yet implemented" });
|
|
1525
1737
|
}
|
|
1526
1738
|
}
|
|
1739
|
+
async function meshCloneNode(ctx, args) {
|
|
1740
|
+
const sourceNode = await findNodeWithRefresh(ctx, args.source_node_id);
|
|
1741
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1742
|
+
const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
|
|
1743
|
+
meshId: ctx.mesh.id,
|
|
1744
|
+
sourceNodeId: args.source_node_id,
|
|
1745
|
+
branch: args.branch,
|
|
1746
|
+
baseBranch: args.base_branch,
|
|
1747
|
+
inlineMesh: ctx.mesh
|
|
1748
|
+
});
|
|
1749
|
+
const clonePayload = extractCloneNodePayload(result);
|
|
1750
|
+
if (clonePayload?.success && clonePayload.node?.id) {
|
|
1751
|
+
const existingIndex = ctx.mesh.nodes.findIndex((n) => n.id === clonePayload.node.id);
|
|
1752
|
+
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
1753
|
+
else ctx.mesh.nodes.push(clonePayload.node);
|
|
1754
|
+
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1755
|
+
}
|
|
1756
|
+
return JSON.stringify(result, null, 2);
|
|
1757
|
+
} else {
|
|
1758
|
+
return JSON.stringify({ error: "Cloud mesh clone_node not yet implemented" });
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
async function meshRemoveNode(ctx, args) {
|
|
1762
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1763
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1764
|
+
const result = await commandForNode(ctx, node, "remove_mesh_node", {
|
|
1765
|
+
meshId: ctx.mesh.id,
|
|
1766
|
+
nodeId: args.node_id,
|
|
1767
|
+
inlineMesh: ctx.mesh
|
|
1768
|
+
});
|
|
1769
|
+
if (result?.success && result.removed !== false) {
|
|
1770
|
+
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
1771
|
+
if (idx >= 0) {
|
|
1772
|
+
ctx.mesh.nodes.splice(idx, 1);
|
|
1773
|
+
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return JSON.stringify(result, null, 2);
|
|
1777
|
+
} else {
|
|
1778
|
+
return JSON.stringify({ error: "Cloud mesh remove_node not yet implemented" });
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1527
1781
|
|
|
1528
1782
|
// src/server.ts
|
|
1783
|
+
async function buildMeshModeCoordinatorPrompt(mesh) {
|
|
1784
|
+
try {
|
|
1785
|
+
const { buildCoordinatorSystemPrompt } = await import("@adhdev/daemon-core");
|
|
1786
|
+
return buildCoordinatorSystemPrompt({ mesh });
|
|
1787
|
+
} catch (e) {
|
|
1788
|
+
throw new Error(`Failed to build Repo Mesh coordinator prompt: ${e?.message ?? String(e)}`);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1529
1791
|
async function startMcpServer(opts) {
|
|
1530
|
-
const transport = opts.mode === "cloud" ? new CloudTransport({ apiKey: opts.apiKey, baseUrl: opts.baseUrl }) : new LocalTransport({ port: opts.port, password: opts.password });
|
|
1792
|
+
const transport = opts.mode === "cloud" ? new CloudTransport({ apiKey: opts.apiKey, baseUrl: opts.baseUrl }) : opts.mode === "ipc" ? new IpcTransport({ port: opts.port }) : new LocalTransport({ port: opts.port, password: opts.password });
|
|
1531
1793
|
const alive = await transport.ping();
|
|
1532
1794
|
if (!alive) {
|
|
1533
|
-
const hint = opts.mode === "local" ? `Make sure the standalone daemon is running (adhdev standalone or npx @adhdev/daemon-standalone).` : `Check your API key and network connectivity.`;
|
|
1795
|
+
const hint = opts.mode === "local" ? `Make sure the standalone daemon is running (adhdev standalone or npx @adhdev/daemon-standalone).` : opts.mode === "ipc" ? `Make sure the cloud daemon is running with local IPC enabled (adhdev daemon).` : `Check your API key and network connectivity.`;
|
|
1534
1796
|
process.stderr.write(`[adhdev-mcp] Cannot reach ${opts.mode} daemon. ${hint}
|
|
1535
1797
|
`);
|
|
1536
1798
|
process.exit(1);
|
|
@@ -1613,7 +1875,7 @@ async function startMcpServer(opts) {
|
|
|
1613
1875
|
`);
|
|
1614
1876
|
}
|
|
1615
1877
|
}
|
|
1616
|
-
if (!mesh && transport instanceof LocalTransport) {
|
|
1878
|
+
if (!mesh && (transport instanceof LocalTransport || transport instanceof IpcTransport)) {
|
|
1617
1879
|
try {
|
|
1618
1880
|
const result = await transport.command("get_mesh", { meshId: opts.meshId });
|
|
1619
1881
|
if (result?.success && result.mesh) {
|
|
@@ -1632,13 +1894,7 @@ async function startMcpServer(opts) {
|
|
|
1632
1894
|
process.exit(1);
|
|
1633
1895
|
}
|
|
1634
1896
|
const meshCtx = { mesh, transport };
|
|
1635
|
-
|
|
1636
|
-
try {
|
|
1637
|
-
const { buildCoordinatorSystemPrompt } = await import("@adhdev/daemon-core");
|
|
1638
|
-
coordinatorPrompt = buildCoordinatorSystemPrompt({ mesh });
|
|
1639
|
-
} catch {
|
|
1640
|
-
coordinatorPrompt = `You are a Repo Mesh Coordinator for "${mesh.name}" (${mesh.repoIdentity}). Use mesh_* tools to orchestrate work.`;
|
|
1641
|
-
}
|
|
1897
|
+
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
1642
1898
|
const server2 = new import_server.Server(
|
|
1643
1899
|
{ name: "adhdev-mcp-server", version: "0.9.75" },
|
|
1644
1900
|
{ capabilities: { tools: {}, resources: {} } }
|
|
@@ -1689,6 +1945,12 @@ async function startMcpServer(opts) {
|
|
|
1689
1945
|
case "mesh_approve":
|
|
1690
1946
|
text = await meshApprove(meshCtx, a);
|
|
1691
1947
|
break;
|
|
1948
|
+
case "mesh_clone_node":
|
|
1949
|
+
text = await meshCloneNode(meshCtx, a);
|
|
1950
|
+
break;
|
|
1951
|
+
case "mesh_remove_node":
|
|
1952
|
+
text = await meshRemoveNode(meshCtx, a);
|
|
1953
|
+
break;
|
|
1692
1954
|
default:
|
|
1693
1955
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
1694
1956
|
}
|
|
@@ -1817,13 +2079,14 @@ async function startMcpServer(opts) {
|
|
|
1817
2079
|
}
|
|
1818
2080
|
|
|
1819
2081
|
// src/index.ts
|
|
1820
|
-
function parseArgs(argv) {
|
|
2082
|
+
function parseArgs(argv, env = process.env) {
|
|
1821
2083
|
const args = argv.slice(2);
|
|
1822
2084
|
let apiKey;
|
|
1823
2085
|
let baseUrl;
|
|
1824
2086
|
let port;
|
|
1825
2087
|
let password;
|
|
1826
2088
|
let meshId;
|
|
2089
|
+
let explicitMode;
|
|
1827
2090
|
for (let i = 0; i < args.length; i++) {
|
|
1828
2091
|
const arg = args[i];
|
|
1829
2092
|
if ((arg === "--api-key" || arg === "-k") && args[i + 1]) {
|
|
@@ -1832,6 +2095,12 @@ function parseArgs(argv) {
|
|
|
1832
2095
|
apiKey = arg.slice("--api-key=".length);
|
|
1833
2096
|
} else if (arg === "--base-url" && args[i + 1]) {
|
|
1834
2097
|
baseUrl = args[++i];
|
|
2098
|
+
} else if (arg === "--mode" && args[i + 1]) {
|
|
2099
|
+
const value = String(args[++i]).trim();
|
|
2100
|
+
if (value === "local" || value === "cloud" || value === "ipc") explicitMode = value;
|
|
2101
|
+
} else if (arg?.startsWith("--mode=")) {
|
|
2102
|
+
const value = arg.slice("--mode=".length).trim();
|
|
2103
|
+
if (value === "local" || value === "cloud" || value === "ipc") explicitMode = value;
|
|
1835
2104
|
} else if (arg === "--port" && args[i + 1]) {
|
|
1836
2105
|
port = Number(args[++i]);
|
|
1837
2106
|
} else if (arg?.startsWith("--port=")) {
|
|
@@ -1847,10 +2116,14 @@ function parseArgs(argv) {
|
|
|
1847
2116
|
process.exit(0);
|
|
1848
2117
|
}
|
|
1849
2118
|
}
|
|
1850
|
-
if (!apiKey &&
|
|
1851
|
-
if (!password &&
|
|
1852
|
-
if (!meshId &&
|
|
1853
|
-
|
|
2119
|
+
if (!apiKey && env.ADHDEV_API_KEY) apiKey = env.ADHDEV_API_KEY;
|
|
2120
|
+
if (!password && env.ADHDEV_PASSWORD) password = env.ADHDEV_PASSWORD;
|
|
2121
|
+
if (!meshId && env.ADHDEV_MESH_ID) meshId = env.ADHDEV_MESH_ID;
|
|
2122
|
+
if (!explicitMode && env.ADHDEV_MCP_TRANSPORT) {
|
|
2123
|
+
const value = env.ADHDEV_MCP_TRANSPORT.trim();
|
|
2124
|
+
if (value === "local" || value === "cloud" || value === "ipc") explicitMode = value;
|
|
2125
|
+
}
|
|
2126
|
+
const mode = explicitMode || (apiKey ? "cloud" : meshId && env.ADHDEV_INLINE_MESH ? "ipc" : "local");
|
|
1854
2127
|
return { mode, port, password, apiKey, baseUrl, meshId };
|
|
1855
2128
|
}
|
|
1856
2129
|
function printHelp() {
|
|
@@ -1860,10 +2133,12 @@ adhdev-mcp \u2014 ADHDev MCP Server
|
|
|
1860
2133
|
Usage:
|
|
1861
2134
|
adhdev-mcp Local mode (requires standalone daemon)
|
|
1862
2135
|
adhdev-mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
2136
|
+
adhdev-mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
1863
2137
|
adhdev-mcp --repo-mesh <mesh_id> Mesh mode (coordinator-scoped tools)
|
|
1864
2138
|
|
|
1865
2139
|
Options:
|
|
1866
|
-
--
|
|
2140
|
+
--mode <mode> Transport: local, cloud, or ipc
|
|
2141
|
+
--port <n> Standalone or IPC daemon port (defaults: local 3847, ipc 19222)
|
|
1867
2142
|
--password <pass> Standalone daemon password (if set)
|
|
1868
2143
|
--api-key <key> ADHDev cloud API key (switches to cloud mode)
|
|
1869
2144
|
--base-url <url> Override cloud API base URL
|
|
@@ -1874,9 +2149,10 @@ Environment variables:
|
|
|
1874
2149
|
ADHDEV_API_KEY API key (cloud mode)
|
|
1875
2150
|
ADHDEV_PASSWORD Daemon password (local mode)
|
|
1876
2151
|
ADHDEV_MESH_ID Mesh ID (mesh mode)
|
|
2152
|
+
ADHDEV_MCP_TRANSPORT Transport: local, cloud, or ipc
|
|
1877
2153
|
|
|
1878
2154
|
Standard tools: list_daemons, list_sessions, launch_session, stop_session, check_pending, read_chat, send_chat, approve, git_status, git_log, git_diff, git_checkpoint, git_push, screenshot
|
|
1879
|
-
Mesh tools: mesh_status, mesh_list_nodes, mesh_send_task, mesh_read_chat, mesh_launch_session, mesh_git_status, mesh_checkpoint, mesh_approve
|
|
2155
|
+
Mesh tools: mesh_status, mesh_list_nodes, mesh_send_task, mesh_read_chat, mesh_launch_session, mesh_git_status, mesh_checkpoint, mesh_approve, mesh_clone_node, mesh_remove_node
|
|
1880
2156
|
`.trim());
|
|
1881
2157
|
}
|
|
1882
2158
|
startMcpServer(parseArgs(process.argv)).catch((err) => {
|
|
@@ -1884,4 +2160,8 @@ startMcpServer(parseArgs(process.argv)).catch((err) => {
|
|
|
1884
2160
|
`);
|
|
1885
2161
|
process.exit(1);
|
|
1886
2162
|
});
|
|
2163
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2164
|
+
0 && (module.exports = {
|
|
2165
|
+
parseArgs
|
|
2166
|
+
});
|
|
1887
2167
|
//# sourceMappingURL=index.js.map
|