adhdev 0.9.61 → 0.9.63
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 +64 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +62 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/vendor/mcp-server/index.js +584 -11
- package/vendor/mcp-server/index.js.map +1 -1
|
@@ -129,6 +129,18 @@ var CloudTransport = class {
|
|
|
129
129
|
if (!res.ok) throw new Error(`Git status failed: ${res.status}`);
|
|
130
130
|
return res.json();
|
|
131
131
|
}
|
|
132
|
+
async stop(daemonId, opts) {
|
|
133
|
+
const res = await fetch(
|
|
134
|
+
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/stop`,
|
|
135
|
+
{
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: this.headers(),
|
|
138
|
+
body: JSON.stringify(opts)
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
if (!res.ok) throw new Error(`Stop failed: ${res.status}`);
|
|
142
|
+
return res.json();
|
|
143
|
+
}
|
|
132
144
|
async launch(daemonId, opts) {
|
|
133
145
|
const res = await fetch(
|
|
134
146
|
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/launch`,
|
|
@@ -141,6 +153,55 @@ var CloudTransport = class {
|
|
|
141
153
|
if (!res.ok) throw new Error(`Launch failed: ${res.status}`);
|
|
142
154
|
return res.json();
|
|
143
155
|
}
|
|
156
|
+
async gitLog(daemonId, workspace, opts = {}) {
|
|
157
|
+
const params = new URLSearchParams({ workspace });
|
|
158
|
+
if (opts.limit) params.set("limit", String(opts.limit));
|
|
159
|
+
if (opts.file) params.set("file", opts.file);
|
|
160
|
+
if (opts.since) params.set("since", opts.since);
|
|
161
|
+
if (opts.until) params.set("until", opts.until);
|
|
162
|
+
const res = await fetch(
|
|
163
|
+
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-log?${params}`,
|
|
164
|
+
{ headers: this.headers() }
|
|
165
|
+
);
|
|
166
|
+
if (!res.ok) throw new Error(`Git log failed: ${res.status}`);
|
|
167
|
+
return res.json();
|
|
168
|
+
}
|
|
169
|
+
async gitDiff(daemonId, workspace, opts = {}) {
|
|
170
|
+
const params = new URLSearchParams({ workspace });
|
|
171
|
+
if (opts.file) params.set("file", opts.file);
|
|
172
|
+
if (opts.maxLines) params.set("maxLines", String(opts.maxLines));
|
|
173
|
+
if (opts.staged) params.set("staged", "true");
|
|
174
|
+
const res = await fetch(
|
|
175
|
+
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-diff?${params}`,
|
|
176
|
+
{ headers: this.headers() }
|
|
177
|
+
);
|
|
178
|
+
if (!res.ok) throw new Error(`Git diff failed: ${res.status}`);
|
|
179
|
+
return res.json();
|
|
180
|
+
}
|
|
181
|
+
async gitPush(daemonId, opts) {
|
|
182
|
+
const res = await fetch(
|
|
183
|
+
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-push`,
|
|
184
|
+
{
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: this.headers(),
|
|
187
|
+
body: JSON.stringify(opts)
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
if (!res.ok) throw new Error(`Git push failed: ${res.status}`);
|
|
191
|
+
return res.json();
|
|
192
|
+
}
|
|
193
|
+
async gitCheckpoint(daemonId, opts) {
|
|
194
|
+
const res = await fetch(
|
|
195
|
+
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-checkpoint`,
|
|
196
|
+
{
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: this.headers(),
|
|
199
|
+
body: JSON.stringify(opts)
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
if (!res.ok) throw new Error(`Git checkpoint failed: ${res.status}`);
|
|
203
|
+
return res.json();
|
|
204
|
+
}
|
|
144
205
|
async ping() {
|
|
145
206
|
try {
|
|
146
207
|
await this.listDaemons();
|
|
@@ -151,6 +212,11 @@ var CloudTransport = class {
|
|
|
151
212
|
}
|
|
152
213
|
};
|
|
153
214
|
|
|
215
|
+
// src/transports/mode.ts
|
|
216
|
+
function isLocalTransport(transport) {
|
|
217
|
+
return typeof transport.command === "function";
|
|
218
|
+
}
|
|
219
|
+
|
|
154
220
|
// src/tools/list-sessions.ts
|
|
155
221
|
var FORMAT_PROP = {
|
|
156
222
|
format: {
|
|
@@ -176,7 +242,7 @@ var LIST_SESSIONS_TOOL = {
|
|
|
176
242
|
};
|
|
177
243
|
async function listSessions(transport, args = {}) {
|
|
178
244
|
const asJson = args.format === "json";
|
|
179
|
-
if (
|
|
245
|
+
if (isLocalTransport(transport)) {
|
|
180
246
|
const status = await transport.getStatus();
|
|
181
247
|
const sessions = status?.sessions ?? [];
|
|
182
248
|
if (asJson) {
|
|
@@ -253,6 +319,62 @@ async function listSessionsCloud(transport, daemonId, asJson) {
|
|
|
253
319
|
${lines.join("\n")}`;
|
|
254
320
|
}
|
|
255
321
|
|
|
322
|
+
// src/tools/list-daemons.ts
|
|
323
|
+
var LIST_DAEMONS_TOOL = {
|
|
324
|
+
name: "list_daemons",
|
|
325
|
+
description: "List all connected daemons (machines running the ADHDev agent). Use this to discover daemon IDs before calling launch_session, git_status, or other tools that require daemon_id. In local mode returns the single standalone daemon info.",
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
...FORMAT_PROP
|
|
330
|
+
},
|
|
331
|
+
required: []
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
async function listDaemons(transport, args = {}) {
|
|
335
|
+
const asJson = args.format === "json";
|
|
336
|
+
if (isLocalTransport(transport)) {
|
|
337
|
+
const status = await transport.getStatus();
|
|
338
|
+
const daemon = {
|
|
339
|
+
id: status?.id ?? status?.instanceId ?? "standalone",
|
|
340
|
+
hostname: status?.hostname ?? status?.machine?.hostname ?? "localhost",
|
|
341
|
+
platform: status?.platform ?? status?.machine?.platform ?? "unknown",
|
|
342
|
+
version: status?.version ?? null,
|
|
343
|
+
sessions: (status?.sessions ?? []).length
|
|
344
|
+
};
|
|
345
|
+
if (asJson) return JSON.stringify({ daemons: [daemon] }, null, 2);
|
|
346
|
+
return `Daemons (1):
|
|
347
|
+
id: ${daemon.id}, hostname: ${daemon.hostname}, platform: ${daemon.platform}${daemon.version ? `, version: ${daemon.version}` : ""}, sessions: ${daemon.sessions}`;
|
|
348
|
+
}
|
|
349
|
+
const data = await transport.listDaemons();
|
|
350
|
+
const daemons = data?.daemons ?? [];
|
|
351
|
+
if (asJson) {
|
|
352
|
+
return JSON.stringify({
|
|
353
|
+
daemons: daemons.map((d) => ({
|
|
354
|
+
id: d.id,
|
|
355
|
+
hostname: d.hostname ?? null,
|
|
356
|
+
platform: d.platform ?? null,
|
|
357
|
+
nickname: d.nickname ?? null,
|
|
358
|
+
version: d.version ?? null,
|
|
359
|
+
p2p_available: d.p2p?.available ?? null,
|
|
360
|
+
cdp_connected: d.cdpConnected ?? null
|
|
361
|
+
}))
|
|
362
|
+
}, null, 2);
|
|
363
|
+
}
|
|
364
|
+
if (daemons.length === 0) return "No connected daemons.";
|
|
365
|
+
const lines = daemons.map((d) => {
|
|
366
|
+
const parts = [`id: ${d.id}`];
|
|
367
|
+
if (d.nickname) parts.push(`nickname: ${d.nickname}`);
|
|
368
|
+
if (d.hostname) parts.push(`hostname: ${d.hostname}`);
|
|
369
|
+
if (d.platform) parts.push(`platform: ${d.platform}`);
|
|
370
|
+
if (d.version) parts.push(`version: ${d.version}`);
|
|
371
|
+
if (d.p2p?.available != null) parts.push(`p2p: ${d.p2p.available ? "yes" : "no"}`);
|
|
372
|
+
return parts.join(", ");
|
|
373
|
+
});
|
|
374
|
+
return `Daemons (${daemons.length}):
|
|
375
|
+
${lines.join("\n")}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
256
378
|
// src/tools/read-chat.ts
|
|
257
379
|
var READ_CHAT_TOOL = {
|
|
258
380
|
name: "read_chat",
|
|
@@ -279,7 +401,7 @@ var READ_CHAT_TOOL = {
|
|
|
279
401
|
};
|
|
280
402
|
async function readChat(transport, args) {
|
|
281
403
|
const limit = args.limit ?? 50;
|
|
282
|
-
if (
|
|
404
|
+
if (isLocalTransport(transport)) {
|
|
283
405
|
const result2 = await transport.command("read_chat", {
|
|
284
406
|
...args.session_id ? { targetSessionId: args.session_id } : {},
|
|
285
407
|
limit
|
|
@@ -343,7 +465,7 @@ var SEND_CHAT_TOOL = {
|
|
|
343
465
|
};
|
|
344
466
|
async function sendChat(transport, args) {
|
|
345
467
|
if (!args.message?.trim()) throw new Error("message is required");
|
|
346
|
-
if (
|
|
468
|
+
if (isLocalTransport(transport)) {
|
|
347
469
|
const result2 = await transport.command("send_chat", {
|
|
348
470
|
message: args.message,
|
|
349
471
|
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
@@ -386,7 +508,7 @@ var APPROVE_TOOL = {
|
|
|
386
508
|
};
|
|
387
509
|
async function approve(transport, args) {
|
|
388
510
|
const action = args.action === "reject" ? "reject" : "approve";
|
|
389
|
-
if (
|
|
511
|
+
if (isLocalTransport(transport)) {
|
|
390
512
|
const result2 = await transport.command("resolve_action", {
|
|
391
513
|
action,
|
|
392
514
|
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
@@ -418,7 +540,7 @@ var SCREENSHOT_TOOL = {
|
|
|
418
540
|
};
|
|
419
541
|
async function screenshot(transport, args) {
|
|
420
542
|
let result;
|
|
421
|
-
if (
|
|
543
|
+
if (isLocalTransport(transport)) {
|
|
422
544
|
result = await transport.command("screenshot", {
|
|
423
545
|
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
424
546
|
});
|
|
@@ -463,7 +585,7 @@ var GIT_STATUS_TOOL = {
|
|
|
463
585
|
async function gitStatus(transport, args) {
|
|
464
586
|
let status;
|
|
465
587
|
let diffSummary;
|
|
466
|
-
if (
|
|
588
|
+
if (isLocalTransport(transport)) {
|
|
467
589
|
const statusResult = await transport.command("git_status", {
|
|
468
590
|
workspace: args.workspace
|
|
469
591
|
});
|
|
@@ -551,6 +673,372 @@ async function gitStatus(transport, args) {
|
|
|
551
673
|
return lines.join("\n");
|
|
552
674
|
}
|
|
553
675
|
|
|
676
|
+
// src/tools/git-log.ts
|
|
677
|
+
var GIT_LOG_TOOL = {
|
|
678
|
+
name: "git_log",
|
|
679
|
+
description: "Get commit history for a workspace. Shows hash, message, author, and date for recent commits. Use this to track what changes an agent has made, verify checkpoint commits, or understand project history.",
|
|
680
|
+
inputSchema: {
|
|
681
|
+
type: "object",
|
|
682
|
+
properties: {
|
|
683
|
+
workspace: {
|
|
684
|
+
type: "string",
|
|
685
|
+
description: "Absolute path to the workspace/repository directory."
|
|
686
|
+
},
|
|
687
|
+
limit: {
|
|
688
|
+
type: "number",
|
|
689
|
+
description: "Max commits to return (default: 20, max: 100)."
|
|
690
|
+
},
|
|
691
|
+
file: {
|
|
692
|
+
type: "string",
|
|
693
|
+
description: "Filter history to commits that touched this repo-relative file path (optional)."
|
|
694
|
+
},
|
|
695
|
+
since: {
|
|
696
|
+
type: "string",
|
|
697
|
+
description: "Only commits after this date (ISO 8601 or git date string, optional)."
|
|
698
|
+
},
|
|
699
|
+
until: {
|
|
700
|
+
type: "string",
|
|
701
|
+
description: "Only commits before this date (ISO 8601 or git date string, optional)."
|
|
702
|
+
},
|
|
703
|
+
daemon_id: {
|
|
704
|
+
type: "string",
|
|
705
|
+
description: "Daemon ID (cloud mode only, required)."
|
|
706
|
+
},
|
|
707
|
+
...FORMAT_PROP
|
|
708
|
+
},
|
|
709
|
+
required: ["workspace"]
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
async function gitLog(transport, args) {
|
|
713
|
+
const limit = Math.max(1, Math.min(100, args.limit ?? 20));
|
|
714
|
+
let raw;
|
|
715
|
+
if (isLocalTransport(transport)) {
|
|
716
|
+
raw = await transport.command("git_log", {
|
|
717
|
+
workspace: args.workspace,
|
|
718
|
+
limit,
|
|
719
|
+
...args.file ? { path: args.file } : {},
|
|
720
|
+
...args.since ? { since: args.since } : {},
|
|
721
|
+
...args.until ? { until: args.until } : {}
|
|
722
|
+
});
|
|
723
|
+
raw = raw?.log ?? raw;
|
|
724
|
+
} else {
|
|
725
|
+
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
726
|
+
const result = await transport.gitLog(args.daemon_id, args.workspace, {
|
|
727
|
+
limit,
|
|
728
|
+
file: args.file,
|
|
729
|
+
since: args.since,
|
|
730
|
+
until: args.until
|
|
731
|
+
});
|
|
732
|
+
raw = result?.log ?? result;
|
|
733
|
+
}
|
|
734
|
+
if (raw?.success === false || raw?.reason) {
|
|
735
|
+
const msg = raw?.error ?? raw?.reason ?? "unknown";
|
|
736
|
+
if (args.format === "json") return JSON.stringify({ error: msg }, null, 2);
|
|
737
|
+
return `Git log error: ${msg}`;
|
|
738
|
+
}
|
|
739
|
+
if (!raw?.isGitRepo) {
|
|
740
|
+
const msg = `Not a git repository: ${args.workspace}`;
|
|
741
|
+
if (args.format === "json") return JSON.stringify({ error: msg }, null, 2);
|
|
742
|
+
return msg;
|
|
743
|
+
}
|
|
744
|
+
const entries = raw?.entries ?? [];
|
|
745
|
+
if (args.format === "json") {
|
|
746
|
+
return JSON.stringify({
|
|
747
|
+
workspace: raw.workspace,
|
|
748
|
+
branch: raw.branch ?? null,
|
|
749
|
+
entries: entries.map((e) => ({
|
|
750
|
+
commit: e.commit,
|
|
751
|
+
short: e.commit?.slice(0, 7),
|
|
752
|
+
message: e.message,
|
|
753
|
+
author: e.authorName ?? null,
|
|
754
|
+
author_email: e.authorEmail ?? null,
|
|
755
|
+
authored_at: e.authoredAt ? new Date(e.authoredAt).toISOString() : null
|
|
756
|
+
})),
|
|
757
|
+
total: entries.length,
|
|
758
|
+
truncated: raw.truncated ?? false
|
|
759
|
+
}, null, 2);
|
|
760
|
+
}
|
|
761
|
+
if (entries.length === 0) return "No commits found.";
|
|
762
|
+
const lines = entries.map((e) => {
|
|
763
|
+
const hash = e.commit?.slice(0, 7) ?? "???????";
|
|
764
|
+
const date = e.authoredAt ? new Date(e.authoredAt).toISOString().slice(0, 10) : "";
|
|
765
|
+
const author = e.authorName ? ` (${e.authorName})` : "";
|
|
766
|
+
return `${hash} ${date}${author} ${e.message}`;
|
|
767
|
+
});
|
|
768
|
+
const header = `Commits (${entries.length}${raw.truncated ? ", truncated" : ""}):`;
|
|
769
|
+
return `${header}
|
|
770
|
+
${lines.join("\n")}`;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/tools/git-diff.ts
|
|
774
|
+
var GIT_DIFF_TOOL = {
|
|
775
|
+
name: "git_diff",
|
|
776
|
+
description: "Get the actual diff content for changed files in a workspace. Without a specific file, returns diffs for up to 5 changed files. Use this to review what an agent actually changed \u2014 file names alone (from git_status) are not enough for code review.",
|
|
777
|
+
inputSchema: {
|
|
778
|
+
type: "object",
|
|
779
|
+
properties: {
|
|
780
|
+
workspace: {
|
|
781
|
+
type: "string",
|
|
782
|
+
description: "Absolute path to the workspace/repository directory."
|
|
783
|
+
},
|
|
784
|
+
file: {
|
|
785
|
+
type: "string",
|
|
786
|
+
description: "Specific repo-relative file path to diff (optional \u2014 if omitted, returns top 5 changed files)."
|
|
787
|
+
},
|
|
788
|
+
max_lines: {
|
|
789
|
+
type: "number",
|
|
790
|
+
description: "Max diff lines per file before truncating (default: 300)."
|
|
791
|
+
},
|
|
792
|
+
staged: {
|
|
793
|
+
type: "boolean",
|
|
794
|
+
description: "Show staged changes instead of unstaged (default: false)."
|
|
795
|
+
},
|
|
796
|
+
daemon_id: {
|
|
797
|
+
type: "string",
|
|
798
|
+
description: "Daemon ID (cloud mode only, required)."
|
|
799
|
+
},
|
|
800
|
+
...FORMAT_PROP
|
|
801
|
+
},
|
|
802
|
+
required: ["workspace"]
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
async function gitDiff(transport, args) {
|
|
806
|
+
const maxLines = Math.max(10, Math.min(2e3, args.max_lines ?? 300));
|
|
807
|
+
const staged = args.staged ?? false;
|
|
808
|
+
if (isLocalTransport(transport)) {
|
|
809
|
+
return localGitDiff(transport, args.workspace, args.file, maxLines, staged, args.format);
|
|
810
|
+
}
|
|
811
|
+
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
812
|
+
const result = await transport.gitDiff(args.daemon_id, args.workspace, {
|
|
813
|
+
file: args.file,
|
|
814
|
+
maxLines,
|
|
815
|
+
staged
|
|
816
|
+
});
|
|
817
|
+
if (result?.error) {
|
|
818
|
+
if (args.format === "json") return JSON.stringify({ error: result.error }, null, 2);
|
|
819
|
+
return `Git diff error: ${result.error}`;
|
|
820
|
+
}
|
|
821
|
+
return formatDiffResult(result, args.format);
|
|
822
|
+
}
|
|
823
|
+
async function localGitDiff(transport, workspace, file, maxLines, staged, format) {
|
|
824
|
+
if (file) {
|
|
825
|
+
const raw = await transport.command("git_diff_file", { workspace, path: file, staged });
|
|
826
|
+
const d = raw?.diff ?? raw;
|
|
827
|
+
if (d?.success === false || d?.reason) {
|
|
828
|
+
const msg = d?.error ?? d?.reason ?? "unknown";
|
|
829
|
+
if (format === "json") return JSON.stringify({ error: msg }, null, 2);
|
|
830
|
+
return `Git diff error: ${msg}`;
|
|
831
|
+
}
|
|
832
|
+
const lines = (d?.diff ?? "").split("\n");
|
|
833
|
+
const truncated = lines.length > maxLines;
|
|
834
|
+
const result = {
|
|
835
|
+
files: [{
|
|
836
|
+
path: file,
|
|
837
|
+
diff: truncated ? lines.slice(0, maxLines).join("\n") + "\n... (truncated)" : d?.diff ?? "",
|
|
838
|
+
truncated,
|
|
839
|
+
binary: d?.binary ?? false
|
|
840
|
+
}],
|
|
841
|
+
total_files: 1,
|
|
842
|
+
shown_files: 1,
|
|
843
|
+
truncated
|
|
844
|
+
};
|
|
845
|
+
return formatDiffResult(result, format);
|
|
846
|
+
}
|
|
847
|
+
const summaryRaw = await transport.command("git_diff_summary", { workspace, staged });
|
|
848
|
+
const summary = summaryRaw?.diffSummary ?? summaryRaw;
|
|
849
|
+
if (summary?.success === false || summary?.reason) {
|
|
850
|
+
const msg = summary?.error ?? summary?.reason ?? "unknown";
|
|
851
|
+
if (format === "json") return JSON.stringify({ error: msg }, null, 2);
|
|
852
|
+
return `Git diff error: ${msg}`;
|
|
853
|
+
}
|
|
854
|
+
if (!summary?.isGitRepo) {
|
|
855
|
+
const msg = `Not a git repository: ${workspace}`;
|
|
856
|
+
if (format === "json") return JSON.stringify({ error: msg }, null, 2);
|
|
857
|
+
return msg;
|
|
858
|
+
}
|
|
859
|
+
const files = summary?.files ?? [];
|
|
860
|
+
if (files.length === 0) {
|
|
861
|
+
if (format === "json") return JSON.stringify({ files: [], total_files: 0, shown_files: 0, truncated: false }, null, 2);
|
|
862
|
+
return "No changed files.";
|
|
863
|
+
}
|
|
864
|
+
const topFiles = files.slice(0, 5);
|
|
865
|
+
const fileDiffs = await Promise.all(
|
|
866
|
+
topFiles.map(async (f) => {
|
|
867
|
+
try {
|
|
868
|
+
const raw = await transport.command("git_diff_file", { workspace, path: f.path, staged });
|
|
869
|
+
const d = raw?.diff ?? raw;
|
|
870
|
+
const lines = (d?.diff ?? "").split("\n");
|
|
871
|
+
const trunc = lines.length > maxLines;
|
|
872
|
+
return {
|
|
873
|
+
path: f.path,
|
|
874
|
+
old_path: f.oldPath ?? null,
|
|
875
|
+
status: f.status ?? "M",
|
|
876
|
+
diff: trunc ? lines.slice(0, maxLines).join("\n") + "\n... (truncated)" : d?.diff ?? "",
|
|
877
|
+
truncated: trunc,
|
|
878
|
+
binary: d?.binary ?? false
|
|
879
|
+
};
|
|
880
|
+
} catch {
|
|
881
|
+
return { path: f.path, diff: "", truncated: false, binary: false, error: "fetch failed" };
|
|
882
|
+
}
|
|
883
|
+
})
|
|
884
|
+
);
|
|
885
|
+
return formatDiffResult({
|
|
886
|
+
files: fileDiffs,
|
|
887
|
+
total_files: files.length,
|
|
888
|
+
shown_files: topFiles.length,
|
|
889
|
+
truncated: files.length > 5
|
|
890
|
+
}, format);
|
|
891
|
+
}
|
|
892
|
+
function formatDiffResult(result, format) {
|
|
893
|
+
if (format === "json") return JSON.stringify(result, null, 2);
|
|
894
|
+
const files = result?.files ?? [];
|
|
895
|
+
if (files.length === 0) return "No changed files.";
|
|
896
|
+
const parts = [];
|
|
897
|
+
const totalShown = result?.shown_files ?? files.length;
|
|
898
|
+
const totalAll = result?.total_files ?? files.length;
|
|
899
|
+
if (totalAll > totalShown) {
|
|
900
|
+
parts.push(`Showing ${totalShown} of ${totalAll} changed files:
|
|
901
|
+
`);
|
|
902
|
+
}
|
|
903
|
+
for (const f of files) {
|
|
904
|
+
const header = `--- ${f.path}${f.old_path ? ` (was ${f.old_path})` : ""} ---`;
|
|
905
|
+
if (f.error) {
|
|
906
|
+
parts.push(`${header}
|
|
907
|
+
(error: ${f.error})
|
|
908
|
+
`);
|
|
909
|
+
} else if (f.binary) {
|
|
910
|
+
parts.push(`${header}
|
|
911
|
+
(binary file)
|
|
912
|
+
`);
|
|
913
|
+
} else if (!f.diff) {
|
|
914
|
+
parts.push(`${header}
|
|
915
|
+
(no diff)
|
|
916
|
+
`);
|
|
917
|
+
} else {
|
|
918
|
+
parts.push(`${header}
|
|
919
|
+
${f.diff}${f.truncated ? "" : "\n"}`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return parts.join("\n");
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/tools/git-checkpoint.ts
|
|
926
|
+
var GIT_CHECKPOINT_TOOL = {
|
|
927
|
+
name: "git_checkpoint",
|
|
928
|
+
description: "Create a checkpoint commit in a workspace. Stages all tracked changes (or all files including untracked) and commits with a prefixed message. Use this to save progress before a risky operation, or to create a restore point the orchestrator can reference.",
|
|
929
|
+
inputSchema: {
|
|
930
|
+
type: "object",
|
|
931
|
+
properties: {
|
|
932
|
+
workspace: {
|
|
933
|
+
type: "string",
|
|
934
|
+
description: "Absolute path to the workspace/repository directory."
|
|
935
|
+
},
|
|
936
|
+
message: {
|
|
937
|
+
type: "string",
|
|
938
|
+
description: 'Checkpoint message (max 200 chars). Will be prefixed with "adhdev: checkpoint ".'
|
|
939
|
+
},
|
|
940
|
+
include_untracked: {
|
|
941
|
+
type: "boolean",
|
|
942
|
+
description: "Also stage and commit untracked files (default: false)."
|
|
943
|
+
},
|
|
944
|
+
daemon_id: {
|
|
945
|
+
type: "string",
|
|
946
|
+
description: "Daemon ID (cloud mode only, required)."
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
required: ["workspace", "message"]
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
async function gitCheckpoint(transport, args) {
|
|
953
|
+
const message = args.message?.trim();
|
|
954
|
+
if (!message) return "Error: message is required";
|
|
955
|
+
if (message.length > 200) return "Error: message must be 200 characters or fewer";
|
|
956
|
+
let raw;
|
|
957
|
+
if (isLocalTransport(transport)) {
|
|
958
|
+
raw = await transport.command("git_checkpoint", {
|
|
959
|
+
workspace: args.workspace,
|
|
960
|
+
message,
|
|
961
|
+
includeUntracked: args.include_untracked ?? false
|
|
962
|
+
});
|
|
963
|
+
raw = raw?.checkpoint ?? raw;
|
|
964
|
+
} else {
|
|
965
|
+
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
966
|
+
const result = await transport.gitCheckpoint(args.daemon_id, {
|
|
967
|
+
workspace: args.workspace,
|
|
968
|
+
message,
|
|
969
|
+
includeUntracked: args.include_untracked ?? false
|
|
970
|
+
});
|
|
971
|
+
raw = result?.checkpoint ?? result;
|
|
972
|
+
}
|
|
973
|
+
if (raw?.success === false || raw?.reason) {
|
|
974
|
+
const msg = raw?.error ?? raw?.reason ?? "unknown";
|
|
975
|
+
if (msg.includes("Nothing to commit") || msg.includes("nothing to commit")) {
|
|
976
|
+
return "Nothing to commit \u2014 working tree is clean.";
|
|
977
|
+
}
|
|
978
|
+
return `Git checkpoint error: ${msg}`;
|
|
979
|
+
}
|
|
980
|
+
const commit = raw?.commit?.slice(0, 7) ?? "???????";
|
|
981
|
+
const fullMsg = raw?.message ?? `adhdev: checkpoint ${message}`;
|
|
982
|
+
return `Checkpoint created: ${commit} \u2014 ${fullMsg}`;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// src/tools/git-push.ts
|
|
986
|
+
var GIT_PUSH_TOOL = {
|
|
987
|
+
name: "git_push",
|
|
988
|
+
description: "Push a branch to a remote repository on the daemon machine. If the branch has no upstream configured, sets it automatically. Key for parallel multi-machine workflows: after git_checkpoint, push each machine's branch to origin so changes are available for PR/review.",
|
|
989
|
+
inputSchema: {
|
|
990
|
+
type: "object",
|
|
991
|
+
properties: {
|
|
992
|
+
workspace: {
|
|
993
|
+
type: "string",
|
|
994
|
+
description: "Absolute path to the workspace/repository directory."
|
|
995
|
+
},
|
|
996
|
+
remote: {
|
|
997
|
+
type: "string",
|
|
998
|
+
description: 'Remote name (default: "origin").'
|
|
999
|
+
},
|
|
1000
|
+
branch: {
|
|
1001
|
+
type: "string",
|
|
1002
|
+
description: "Branch to push (default: current branch)."
|
|
1003
|
+
},
|
|
1004
|
+
daemon_id: {
|
|
1005
|
+
type: "string",
|
|
1006
|
+
description: "Daemon ID (cloud mode only, required)."
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
required: ["workspace"]
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
async function gitPush(transport, args) {
|
|
1013
|
+
let raw;
|
|
1014
|
+
if (isLocalTransport(transport)) {
|
|
1015
|
+
raw = await transport.command("git_push", {
|
|
1016
|
+
workspace: args.workspace,
|
|
1017
|
+
remote: args.remote ?? "origin",
|
|
1018
|
+
...args.branch ? { branch: args.branch } : {}
|
|
1019
|
+
});
|
|
1020
|
+
raw = raw?.push ?? raw;
|
|
1021
|
+
} else {
|
|
1022
|
+
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
1023
|
+
const result = await transport.gitPush(args.daemon_id, {
|
|
1024
|
+
workspace: args.workspace,
|
|
1025
|
+
remote: args.remote,
|
|
1026
|
+
branch: args.branch
|
|
1027
|
+
});
|
|
1028
|
+
raw = result?.push ?? result;
|
|
1029
|
+
}
|
|
1030
|
+
if (raw?.success === false || raw?.reason) {
|
|
1031
|
+
const msg = raw?.error ?? raw?.reason ?? "unknown";
|
|
1032
|
+
return `Git push error: ${msg}`;
|
|
1033
|
+
}
|
|
1034
|
+
const branch = raw?.branch ?? args.branch ?? "(current)";
|
|
1035
|
+
const remote = raw?.remote ?? args.remote ?? "origin";
|
|
1036
|
+
const newBranch = raw?.newBranch ? " [new branch]" : "";
|
|
1037
|
+
const output = raw?.output ? `
|
|
1038
|
+
${raw.output}` : "";
|
|
1039
|
+
return `Pushed ${branch} \u2192 ${remote}${newBranch}${output}`;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
554
1042
|
// src/tools/launch-session.ts
|
|
555
1043
|
var LAUNCH_SESSION_TOOL = {
|
|
556
1044
|
name: "launch_session",
|
|
@@ -579,7 +1067,7 @@ var LAUNCH_SESSION_TOOL = {
|
|
|
579
1067
|
}
|
|
580
1068
|
};
|
|
581
1069
|
async function launchSession(transport, args) {
|
|
582
|
-
if (
|
|
1070
|
+
if (isLocalTransport(transport)) {
|
|
583
1071
|
const isCliOrAcp = args.type.includes("-cli") || args.type.includes("-acp") || args.type === "codex";
|
|
584
1072
|
const commandType = isCliOrAcp ? "launch_cli" : "launch_ide";
|
|
585
1073
|
const payload = isCliOrAcp ? { cliType: args.type, dir: args.workspace ?? "~", ...args.model ? { model: args.model } : {} } : { ideType: args.type, enableCdp: true };
|
|
@@ -599,6 +1087,57 @@ async function launchSession(transport, args) {
|
|
|
599
1087
|
return id ? `Session launched. id: ${id}, type: ${args.type}` : `Launched: ${JSON.stringify(result)}`;
|
|
600
1088
|
}
|
|
601
1089
|
|
|
1090
|
+
// src/tools/stop-session.ts
|
|
1091
|
+
var STOP_SESSION_TOOL = {
|
|
1092
|
+
name: "stop_session",
|
|
1093
|
+
description: "Stop a running agent session. For CLI agents (hermes-cli, claude-cli, etc.) this sends a graceful stop signal. Use list_sessions to find the session_id.",
|
|
1094
|
+
inputSchema: {
|
|
1095
|
+
type: "object",
|
|
1096
|
+
properties: {
|
|
1097
|
+
session_id: {
|
|
1098
|
+
type: "string",
|
|
1099
|
+
description: "Session ID to stop (from list_sessions)."
|
|
1100
|
+
},
|
|
1101
|
+
daemon_id: {
|
|
1102
|
+
type: "string",
|
|
1103
|
+
description: "Daemon ID (cloud mode only, required)."
|
|
1104
|
+
},
|
|
1105
|
+
type: {
|
|
1106
|
+
type: "string",
|
|
1107
|
+
description: "Provider type (e.g. hermes-cli, claude-cli). Local mode auto-resolves from session_id if omitted; cloud mode forwards the session_id and omits type unless explicitly provided."
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
required: ["session_id"]
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
async function stopSession(transport, args) {
|
|
1114
|
+
if (isLocalTransport(transport)) {
|
|
1115
|
+
const local = transport;
|
|
1116
|
+
let resolvedType = args.type;
|
|
1117
|
+
if (!resolvedType) {
|
|
1118
|
+
const status = await local.getStatus();
|
|
1119
|
+
const session = (status?.sessions ?? []).find((s) => s.id === args.session_id);
|
|
1120
|
+
resolvedType = session?.providerType ?? session?.type;
|
|
1121
|
+
}
|
|
1122
|
+
if (!resolvedType) {
|
|
1123
|
+
return `Error: could not resolve session type for ${args.session_id}. Pass type= explicitly.`;
|
|
1124
|
+
}
|
|
1125
|
+
const result2 = await local.command("stop_cli", {
|
|
1126
|
+
targetSessionId: args.session_id,
|
|
1127
|
+
cliType: resolvedType
|
|
1128
|
+
});
|
|
1129
|
+
if (result2?.success === false) return `Error: ${result2.error ?? "stop failed"}`;
|
|
1130
|
+
return `Session ${args.session_id} stopped.`;
|
|
1131
|
+
}
|
|
1132
|
+
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
1133
|
+
const result = await transport.stop(args.daemon_id, {
|
|
1134
|
+
id: args.session_id,
|
|
1135
|
+
...args.type ? { type: args.type } : {}
|
|
1136
|
+
});
|
|
1137
|
+
if (result?.success === false || result?.error) return `Error: ${result.error ?? "stop failed"}`;
|
|
1138
|
+
return `Session ${args.session_id} stopped.`;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
602
1141
|
// src/tools/check-pending.ts
|
|
603
1142
|
var CHECK_PENDING_TOOL = {
|
|
604
1143
|
name: "check_pending",
|
|
@@ -616,7 +1155,7 @@ var CHECK_PENDING_TOOL = {
|
|
|
616
1155
|
}
|
|
617
1156
|
};
|
|
618
1157
|
async function checkPending(transport, args) {
|
|
619
|
-
if (
|
|
1158
|
+
if (isLocalTransport(transport)) {
|
|
620
1159
|
return checkPendingLocal(transport, args.format);
|
|
621
1160
|
}
|
|
622
1161
|
return checkPendingCloud(transport, args.daemon_id, args.format);
|
|
@@ -715,17 +1254,23 @@ async function startMcpServer(opts) {
|
|
|
715
1254
|
}
|
|
716
1255
|
const isLocal = opts.mode === "local";
|
|
717
1256
|
const allTools = [
|
|
1257
|
+
LIST_DAEMONS_TOOL,
|
|
718
1258
|
LIST_SESSIONS_TOOL,
|
|
719
1259
|
LAUNCH_SESSION_TOOL,
|
|
1260
|
+
STOP_SESSION_TOOL,
|
|
720
1261
|
CHECK_PENDING_TOOL,
|
|
721
1262
|
READ_CHAT_TOOL,
|
|
722
1263
|
SEND_CHAT_TOOL,
|
|
723
1264
|
APPROVE_TOOL,
|
|
724
1265
|
GIT_STATUS_TOOL,
|
|
1266
|
+
GIT_LOG_TOOL,
|
|
1267
|
+
GIT_DIFF_TOOL,
|
|
1268
|
+
GIT_CHECKPOINT_TOOL,
|
|
1269
|
+
GIT_PUSH_TOOL,
|
|
725
1270
|
...isLocal ? [SCREENSHOT_TOOL] : []
|
|
726
1271
|
];
|
|
727
1272
|
const server = new import_server.Server(
|
|
728
|
-
{ name: "adhdev-mcp-server", version: "0.9.
|
|
1273
|
+
{ name: "adhdev-mcp-server", version: "0.9.63" },
|
|
729
1274
|
{ capabilities: { tools: {} } }
|
|
730
1275
|
);
|
|
731
1276
|
server.setRequestHandler(import_types.ListToolsRequestSchema, async () => ({ tools: allTools }));
|
|
@@ -734,6 +1279,10 @@ async function startMcpServer(opts) {
|
|
|
734
1279
|
const a = args ?? {};
|
|
735
1280
|
try {
|
|
736
1281
|
switch (name) {
|
|
1282
|
+
case "list_daemons": {
|
|
1283
|
+
const text = await listDaemons(transport, { format: a.format });
|
|
1284
|
+
return { content: [{ type: "text", text }] };
|
|
1285
|
+
}
|
|
737
1286
|
case "list_sessions": {
|
|
738
1287
|
const text = await listSessions(transport, { format: a.format, daemon_id: a.daemon_id });
|
|
739
1288
|
return { content: [{ type: "text", text }] };
|
|
@@ -764,6 +1313,22 @@ async function startMcpServer(opts) {
|
|
|
764
1313
|
const text = await gitStatus(transport, { workspace: a.workspace, include_diff: a.include_diff, daemon_id: a.daemon_id, format: a.format });
|
|
765
1314
|
return { content: [{ type: "text", text }] };
|
|
766
1315
|
}
|
|
1316
|
+
case "git_log": {
|
|
1317
|
+
const text = await gitLog(transport, { workspace: a.workspace, limit: a.limit, file: a.file, since: a.since, until: a.until, daemon_id: a.daemon_id, format: a.format });
|
|
1318
|
+
return { content: [{ type: "text", text }] };
|
|
1319
|
+
}
|
|
1320
|
+
case "git_diff": {
|
|
1321
|
+
const text = await gitDiff(transport, { workspace: a.workspace, file: a.file, max_lines: a.max_lines, staged: a.staged, daemon_id: a.daemon_id, format: a.format });
|
|
1322
|
+
return { content: [{ type: "text", text }] };
|
|
1323
|
+
}
|
|
1324
|
+
case "git_checkpoint": {
|
|
1325
|
+
const text = await gitCheckpoint(transport, { workspace: a.workspace, message: a.message, include_untracked: a.include_untracked, daemon_id: a.daemon_id });
|
|
1326
|
+
return { content: [{ type: "text", text }] };
|
|
1327
|
+
}
|
|
1328
|
+
case "git_push": {
|
|
1329
|
+
const text = await gitPush(transport, { workspace: a.workspace, remote: a.remote, branch: a.branch, daemon_id: a.daemon_id });
|
|
1330
|
+
return { content: [{ type: "text", text }] };
|
|
1331
|
+
}
|
|
767
1332
|
case "launch_session": {
|
|
768
1333
|
const text = await launchSession(transport, {
|
|
769
1334
|
type: a.type,
|
|
@@ -773,6 +1338,14 @@ async function startMcpServer(opts) {
|
|
|
773
1338
|
});
|
|
774
1339
|
return { content: [{ type: "text", text }] };
|
|
775
1340
|
}
|
|
1341
|
+
case "stop_session": {
|
|
1342
|
+
const text = await stopSession(transport, {
|
|
1343
|
+
session_id: a.session_id,
|
|
1344
|
+
daemon_id: a.daemon_id,
|
|
1345
|
+
type: a.type
|
|
1346
|
+
});
|
|
1347
|
+
return { content: [{ type: "text", text }] };
|
|
1348
|
+
}
|
|
776
1349
|
case "check_pending": {
|
|
777
1350
|
const text = await checkPending(transport, { daemon_id: a.daemon_id, format: a.format });
|
|
778
1351
|
return { content: [{ type: "text", text }] };
|
|
@@ -843,8 +1416,8 @@ Environment variables:
|
|
|
843
1416
|
ADHDEV_API_KEY API key (cloud mode)
|
|
844
1417
|
ADHDEV_PASSWORD Daemon password (local mode)
|
|
845
1418
|
|
|
846
|
-
Local mode tools: list_sessions, launch_session, check_pending, read_chat, send_chat, approve, git_status, screenshot
|
|
847
|
-
Cloud mode tools: list_sessions, launch_session, check_pending, read_chat, send_chat, approve, git_status
|
|
1419
|
+
Local mode 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
|
|
1420
|
+
Cloud mode 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
|
|
848
1421
|
`.trim());
|
|
849
1422
|
}
|
|
850
1423
|
startMcpServer(parseArgs(process.argv)).catch((err) => {
|