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.
@@ -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 ("getStatus" in transport) {
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 ("command" in transport) {
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 ("command" in transport) {
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 ("command" in transport) {
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 ("command" in transport) {
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 ("command" in transport) {
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 ("command" in transport) {
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 ("getStatus" in transport) {
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.61" },
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) => {