adhdev 0.9.59 → 0.9.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.9.59",
3
+ "version": "0.9.60",
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": {
@@ -75,6 +75,15 @@ var CloudTransport = class {
75
75
  if (!res.ok) throw new Error(`Status failed: ${res.status}`);
76
76
  return res.json();
77
77
  }
78
+ /** Get all sessions for a daemon (returns CompactSessionEntry[]). */
79
+ async getDaemonStatus(daemonId) {
80
+ const res = await fetch(
81
+ `${this.baseUrl}/api/v1/daemons/${encodeURIComponent(daemonId)}/status`,
82
+ { headers: this.headers() }
83
+ );
84
+ if (!res.ok) throw new Error(`Daemon status failed: ${res.status}`);
85
+ return res.json();
86
+ }
78
87
  async readChat(targetId, opts = {}) {
79
88
  const params = new URLSearchParams();
80
89
  if (opts.limit) params.set("limit", String(opts.limit));
@@ -111,6 +120,27 @@ var CloudTransport = class {
111
120
  if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
112
121
  return res.json();
113
122
  }
123
+ async gitStatus(daemonId, workspace, includeDiff = true) {
124
+ const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
125
+ const res = await fetch(
126
+ `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
127
+ { headers: this.headers() }
128
+ );
129
+ if (!res.ok) throw new Error(`Git status failed: ${res.status}`);
130
+ return res.json();
131
+ }
132
+ async launch(daemonId, opts) {
133
+ const res = await fetch(
134
+ `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/launch`,
135
+ {
136
+ method: "POST",
137
+ headers: this.headers(),
138
+ body: JSON.stringify(opts)
139
+ }
140
+ );
141
+ if (!res.ok) throw new Error(`Launch failed: ${res.status}`);
142
+ return res.json();
143
+ }
114
144
  async ping() {
115
145
  try {
116
146
  await this.listDaemons();
@@ -122,25 +152,43 @@ var CloudTransport = class {
122
152
  };
123
153
 
124
154
  // src/tools/list-sessions.ts
155
+ var FORMAT_PROP = {
156
+ format: {
157
+ type: "string",
158
+ enum: ["text", "json"],
159
+ description: "Output format: 'text' (default, human-readable) or 'json' (structured, for programmatic use)."
160
+ }
161
+ };
125
162
  var LIST_SESSIONS_TOOL = {
126
163
  name: "list_sessions",
127
164
  description: "List all currently connected IDE and CLI agent sessions on the local machine.",
128
165
  inputSchema: {
129
166
  type: "object",
130
- properties: {},
167
+ properties: {
168
+ ...FORMAT_PROP
169
+ },
131
170
  required: []
132
171
  }
133
172
  };
134
- async function listSessions(transport) {
173
+ async function listSessions(transport, args = {}) {
174
+ const asJson = args.format === "json";
135
175
  if ("getStatus" in transport) {
136
176
  const status = await transport.getStatus();
137
177
  const sessions = status?.sessions ?? [];
178
+ if (asJson) {
179
+ return JSON.stringify({
180
+ sessions: sessions.map((s) => ({
181
+ id: s.id,
182
+ type: s.providerType ?? s.type ?? "unknown",
183
+ label: s.label ?? null,
184
+ status: s.status ?? s.agentStatus ?? null,
185
+ workspace: s.workspace ?? null
186
+ }))
187
+ }, null, 2);
188
+ }
138
189
  if (sessions.length === 0) return "No active sessions.";
139
190
  const lines2 = sessions.map((s) => {
140
- const parts = [
141
- `id: ${s.id}`,
142
- `type: ${s.providerType ?? s.type ?? "unknown"}`
143
- ];
191
+ const parts = [`id: ${s.id}`, `type: ${s.providerType ?? s.type ?? "unknown"}`];
144
192
  if (s.label) parts.push(`label: ${s.label}`);
145
193
  if (s.agentStatus) parts.push(`status: ${s.agentStatus}`);
146
194
  if (s.workspace) parts.push(`workspace: ${s.workspace}`);
@@ -151,6 +199,21 @@ ${lines2.join("\n")}`;
151
199
  }
152
200
  const data = await transport.listDaemons();
153
201
  const daemons = data?.daemons ?? data ?? [];
202
+ if (asJson) {
203
+ const sessions = [];
204
+ for (const d of daemons) {
205
+ for (const s of d.sessions ?? []) {
206
+ sessions.push({
207
+ daemon_id: d.id,
208
+ id: s.id,
209
+ type: s.providerType ?? "unknown",
210
+ status: s.status ?? s.agentStatus ?? null,
211
+ workspace: s.workspace ?? null
212
+ });
213
+ }
214
+ }
215
+ return JSON.stringify({ sessions }, null, 2);
216
+ }
154
217
  if (daemons.length === 0) return "No connected daemons.";
155
218
  const lines = [];
156
219
  for (const d of daemons) {
@@ -160,9 +223,7 @@ ${lines2.join("\n")}`;
160
223
  `daemon: ${d.id}, session: ${s.id}, type: ${s.providerType ?? "unknown"}${s.agentStatus ? `, status: ${s.agentStatus}` : ""}`
161
224
  );
162
225
  }
163
- if (sessions.length === 0) {
164
- lines.push(`daemon: ${d.id} (no sessions)`);
165
- }
226
+ if (sessions.length === 0) lines.push(`daemon: ${d.id} (no sessions)`);
166
227
  }
167
228
  return lines.length > 0 ? `Sessions:
168
229
  ${lines.join("\n")}` : "No active sessions.";
@@ -186,7 +247,8 @@ var READ_CHAT_TOOL = {
186
247
  daemon_id: {
187
248
  type: "string",
188
249
  description: "Daemon ID (cloud mode only). Omit for local mode."
189
- }
250
+ },
251
+ ...FORMAT_PROP
190
252
  },
191
253
  required: []
192
254
  }
@@ -198,16 +260,30 @@ async function readChat(transport, args) {
198
260
  ...args.session_id ? { targetSessionId: args.session_id } : {},
199
261
  limit
200
262
  });
201
- return formatChatResult(result2);
263
+ return formatChatResult(result2, args.session_id, args.format);
202
264
  }
203
265
  if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
204
266
  const targetId = args.session_id ? `${args.daemon_id}:session:${args.session_id}` : args.daemon_id;
205
267
  const result = await transport.readChat(targetId, { limit, sessionId: args.session_id });
206
- return formatChatResult(result);
268
+ return formatChatResult(result, args.session_id, args.format);
207
269
  }
208
- function formatChatResult(result) {
209
- if (!result?.success && result?.error) return `Error: ${result.error}`;
270
+ function formatChatResult(result, sessionId, format) {
271
+ if (!result?.success && result?.error) {
272
+ if (format === "json") return JSON.stringify({ error: result.error, messages: [] }, null, 2);
273
+ return `Error: ${result.error}`;
274
+ }
210
275
  const messages = result?.messages ?? result?.data?.messages ?? [];
276
+ if (format === "json") {
277
+ return JSON.stringify({
278
+ session_id: sessionId ?? null,
279
+ messages: messages.slice(-50).map((m) => ({
280
+ role: m.role,
281
+ kind: m.kind ?? null,
282
+ content: typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => typeof p === "string" ? p : p?.text ?? "").join("") : "",
283
+ timestamp: m.timestamp ?? null
284
+ }))
285
+ }, null, 2);
286
+ }
211
287
  if (messages.length === 0) return "No messages in chat.";
212
288
  const lines = messages.slice(-50).map((m) => {
213
289
  const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
@@ -343,7 +419,7 @@ async function screenshot(transport, args) {
343
419
  // src/tools/git-status.ts
344
420
  var GIT_STATUS_TOOL = {
345
421
  name: "git_status",
346
- description: "Get git repository status for a workspace on the local machine (local mode only).",
422
+ description: "Get git repository status for a workspace on the daemon machine.",
347
423
  inputSchema: {
348
424
  type: "object",
349
425
  properties: {
@@ -354,23 +430,79 @@ var GIT_STATUS_TOOL = {
354
430
  include_diff: {
355
431
  type: "boolean",
356
432
  description: "Include changed file list (default: true)."
357
- }
433
+ },
434
+ daemon_id: {
435
+ type: "string",
436
+ description: "Daemon ID (cloud mode only)."
437
+ },
438
+ ...FORMAT_PROP
358
439
  },
359
440
  required: ["workspace"]
360
441
  }
361
442
  };
362
443
  async function gitStatus(transport, args) {
363
- if (!("command" in transport)) {
364
- return "git_status is only available in local mode.";
365
- }
366
- const statusResult = await transport.command("git_status", {
367
- workspace: args.workspace
368
- });
369
- const status = statusResult?.status ?? statusResult;
370
- if (statusResult?.success === false || status?.reason) {
371
- return `Git error: ${statusResult?.error ?? status?.reason ?? "unknown"}`;
444
+ let status;
445
+ let diffSummary;
446
+ if ("command" in transport) {
447
+ const statusResult = await transport.command("git_status", {
448
+ workspace: args.workspace
449
+ });
450
+ status = statusResult?.status ?? statusResult;
451
+ if (args.include_diff !== false) {
452
+ const diffResult = await transport.command("git_diff_summary", {
453
+ workspace: args.workspace
454
+ });
455
+ diffSummary = diffResult?.diffSummary ?? diffResult;
456
+ }
457
+ } else {
458
+ if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
459
+ const result = await transport.gitStatus(
460
+ args.daemon_id,
461
+ args.workspace,
462
+ args.include_diff !== false
463
+ );
464
+ if (result?.error) {
465
+ if (args.format === "json") return JSON.stringify({ error: result.error }, null, 2);
466
+ return `Error: ${result.error}`;
467
+ }
468
+ status = result?.status;
469
+ diffSummary = result?.diff;
470
+ }
471
+ if (status?.success === false || status?.reason) {
472
+ const msg = status?.error ?? status?.reason ?? "unknown";
473
+ if (args.format === "json") return JSON.stringify({ error: msg }, null, 2);
474
+ return `Git error: ${msg}`;
475
+ }
476
+ if (!status?.isGitRepo) {
477
+ if (args.format === "json") return JSON.stringify({ error: `Not a git repository: ${args.workspace}` }, null, 2);
478
+ return `Not a git repository: ${args.workspace}`;
479
+ }
480
+ if (args.format === "json") {
481
+ const files = diffSummary?.files?.map((f) => ({
482
+ path: f.path,
483
+ old_path: f.oldPath ?? null,
484
+ status: f.status ?? "M",
485
+ insertions: f.insertions ?? 0,
486
+ deletions: f.deletions ?? 0
487
+ })) ?? [];
488
+ return JSON.stringify({
489
+ branch: status.branch ?? null,
490
+ head_commit: status.headCommit ?? null,
491
+ head_message: status.headMessage ?? null,
492
+ ahead: status.ahead ?? 0,
493
+ behind: status.behind ?? 0,
494
+ staged: status.staged ?? 0,
495
+ modified: status.modified ?? 0,
496
+ untracked: status.untracked ?? 0,
497
+ deleted: status.deleted ?? 0,
498
+ stash_count: status.stashCount ?? 0,
499
+ has_conflicts: status.hasConflicts ?? false,
500
+ dirty: status.dirty ?? false,
501
+ changed_files: files,
502
+ total_insertions: diffSummary?.totalInsertions ?? 0,
503
+ total_deletions: diffSummary?.totalDeletions ?? 0
504
+ }, null, 2);
372
505
  }
373
- if (!status?.isGitRepo) return `Not a git repository: ${args.workspace}`;
374
506
  const lines = [];
375
507
  if (status.branch) lines.push(`Branch: ${status.branch}`);
376
508
  if (status.headCommit) {
@@ -385,26 +517,172 @@ async function gitStatus(transport, args) {
385
517
  if (status.stashCount > 0) lines.push(`Stashes: ${status.stashCount}`);
386
518
  if (status.hasConflicts) lines.push("Conflicts: YES");
387
519
  if (!status.dirty) lines.push("Working tree: clean");
388
- if (args.include_diff !== false) {
389
- const diffResult = await transport.command("git_diff_summary", {
390
- workspace: args.workspace
391
- });
392
- const diffSummary = diffResult?.diffSummary ?? diffResult;
393
- if (diffSummary?.files?.length > 0) {
394
- lines.push("");
395
- lines.push(`Changed files (${diffSummary.files.length}):`);
396
- for (const f of diffSummary.files.slice(0, 20)) {
397
- lines.push(` ${f.status ?? "M"} ${f.path}${f.oldPath ? ` (was ${f.oldPath})` : ""}${f.insertions || f.deletions ? ` +${f.insertions ?? 0}/-${f.deletions ?? 0}` : ""}`);
398
- }
399
- if (diffSummary.files.length > 20) lines.push(` \u2026 and ${diffSummary.files.length - 20} more`);
400
- if (diffSummary.totalInsertions || diffSummary.totalDeletions) {
401
- lines.push(`Total: +${diffSummary.totalInsertions ?? 0}/-${diffSummary.totalDeletions ?? 0}`);
402
- }
520
+ if (diffSummary?.files?.length > 0) {
521
+ lines.push("");
522
+ lines.push(`Changed files (${diffSummary.files.length}):`);
523
+ for (const f of diffSummary.files.slice(0, 20)) {
524
+ lines.push(` ${f.status ?? "M"} ${f.path}${f.oldPath ? ` (was ${f.oldPath})` : ""}${f.insertions || f.deletions ? ` +${f.insertions ?? 0}/-${f.deletions ?? 0}` : ""}`);
525
+ }
526
+ if (diffSummary.files.length > 20) lines.push(` \u2026 and ${diffSummary.files.length - 20} more`);
527
+ if (diffSummary.totalInsertions || diffSummary.totalDeletions) {
528
+ lines.push(`Total: +${diffSummary.totalInsertions ?? 0}/-${diffSummary.totalDeletions ?? 0}`);
403
529
  }
404
530
  }
405
531
  return lines.join("\n");
406
532
  }
407
533
 
534
+ // src/tools/launch-session.ts
535
+ var LAUNCH_SESSION_TOOL = {
536
+ name: "launch_session",
537
+ description: "Launch a new agent session on the daemon. Supports CLI agents (e.g. hermes-cli, claude-cli, gemini-cli), ACP agents (e.g. claude-acp), and IDEs (e.g. cursor, vscode).",
538
+ inputSchema: {
539
+ type: "object",
540
+ properties: {
541
+ type: {
542
+ type: "string",
543
+ description: "Provider type to launch. CLI examples: hermes-cli, claude-cli, gemini-cli. ACP examples: claude-acp. IDE examples: cursor, vscode."
544
+ },
545
+ workspace: {
546
+ type: "string",
547
+ description: "Working directory for the session. Defaults to the daemon default workspace."
548
+ },
549
+ model: {
550
+ type: "string",
551
+ description: "Model override for ACP agents (e.g. claude-opus-4-7)."
552
+ },
553
+ daemon_id: {
554
+ type: "string",
555
+ description: "Daemon ID (cloud mode only). Required in cloud mode."
556
+ }
557
+ },
558
+ required: ["type"]
559
+ }
560
+ };
561
+ async function launchSession(transport, args) {
562
+ if ("command" in transport) {
563
+ const isCliOrAcp = args.type.includes("-cli") || args.type.includes("-acp") || args.type === "codex";
564
+ const commandType = isCliOrAcp ? "launch_cli" : "launch_ide";
565
+ const payload = isCliOrAcp ? { cliType: args.type, dir: args.workspace ?? "~", ...args.model ? { model: args.model } : {} } : { ideType: args.type, enableCdp: true };
566
+ const result2 = await transport.command(commandType, payload);
567
+ if (result2?.success === false) return `Error: ${result2.error ?? "launch failed"}`;
568
+ const id2 = result2?.id ?? result2?.sessionId;
569
+ return id2 ? `Session launched. id: ${id2}, type: ${args.type}` : `Launched: ${JSON.stringify(result2)}`;
570
+ }
571
+ if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
572
+ const result = await transport.launch(args.daemon_id, {
573
+ type: args.type,
574
+ dir: args.workspace,
575
+ model: args.model
576
+ });
577
+ if (result?.success === false || result?.error) return `Error: ${result.error ?? "launch failed"}`;
578
+ const id = result?.id ?? result?.sessionId;
579
+ return id ? `Session launched. id: ${id}, type: ${args.type}` : `Launched: ${JSON.stringify(result)}`;
580
+ }
581
+
582
+ // src/tools/check-pending.ts
583
+ var CHECK_PENDING_TOOL = {
584
+ name: "check_pending",
585
+ description: "List all agent sessions currently waiting for user approval (tool-use confirmation). Returns session ID, daemon ID, workspace, and the approval prompt message when available. Use approve() with the session_id to approve or reject.",
586
+ inputSchema: {
587
+ type: "object",
588
+ properties: {
589
+ daemon_id: {
590
+ type: "string",
591
+ description: "Daemon ID to check (cloud mode). Omit to check all daemons."
592
+ },
593
+ ...FORMAT_PROP
594
+ },
595
+ required: []
596
+ }
597
+ };
598
+ async function checkPending(transport, args) {
599
+ if ("getStatus" in transport) {
600
+ return checkPendingLocal(transport, args.format);
601
+ }
602
+ return checkPendingCloud(transport, args.daemon_id, args.format);
603
+ }
604
+ async function checkPendingLocal(transport, format) {
605
+ const status = await transport.getStatus();
606
+ const sessions = status?.sessions ?? [];
607
+ const pending = sessions.filter(
608
+ (s) => s.status === "waiting_approval" || s.agentStatus === "waiting_approval"
609
+ );
610
+ if (format === "json") {
611
+ return JSON.stringify({
612
+ pending: pending.map((s) => ({
613
+ session_id: s.id,
614
+ workspace: s.workspace ?? null,
615
+ type: s.providerType ?? null,
616
+ modal_message: s.activeChat?.activeModal?.message ?? null,
617
+ buttons: s.activeChat?.activeModal?.buttons ?? []
618
+ }))
619
+ }, null, 2);
620
+ }
621
+ if (pending.length === 0) return "No sessions waiting for approval.";
622
+ const lines = pending.map((s) => {
623
+ const modal = s.activeChat?.activeModal;
624
+ const parts = [`session_id: ${s.id}`];
625
+ if (s.workspace) parts.push(`workspace: ${s.workspace}`);
626
+ if (s.providerType) parts.push(`type: ${s.providerType}`);
627
+ if (modal?.message) parts.push(`prompt: ${modal.message}`);
628
+ if (modal?.buttons?.length) parts.push(`buttons: ${modal.buttons.join(", ")}`);
629
+ return parts.join("\n ");
630
+ });
631
+ return `Pending approvals (${pending.length}):
632
+
633
+ ${lines.join("\n\n")}`;
634
+ }
635
+ async function checkPendingCloud(transport, daemonId, format) {
636
+ const pending = [];
637
+ if (daemonId) {
638
+ const daemonStatus = await transport.getDaemonStatus(daemonId);
639
+ const sessions = daemonStatus?.sessions ?? [];
640
+ for (const s of sessions) {
641
+ if (s.status === "waiting_approval") pending.push({ daemonId, session: s });
642
+ }
643
+ } else {
644
+ const data = await transport.listDaemons();
645
+ const daemons = data?.daemons ?? [];
646
+ for (let i = 0; i < daemons.length; i += 5) {
647
+ await Promise.allSettled(
648
+ daemons.slice(i, i + 5).map(async (d) => {
649
+ try {
650
+ const daemonStatus = await transport.getDaemonStatus(d.id);
651
+ const sessions = daemonStatus?.sessions ?? [];
652
+ for (const s of sessions) {
653
+ if (s.status === "waiting_approval") pending.push({ daemonId: d.id, session: s });
654
+ }
655
+ } catch {
656
+ }
657
+ })
658
+ );
659
+ }
660
+ }
661
+ if (format === "json") {
662
+ return JSON.stringify({
663
+ pending: pending.map(({ daemonId: dId, session: s }) => ({
664
+ daemon_id: dId,
665
+ session_id: s.id,
666
+ workspace: s.workspace ?? null,
667
+ type: s.providerType ?? null,
668
+ modal_message: null,
669
+ buttons: []
670
+ }))
671
+ }, null, 2);
672
+ }
673
+ if (pending.length === 0) return "No sessions waiting for approval.";
674
+ const lines = pending.map(({ daemonId: dId, session: s }) => {
675
+ const parts = [`daemon_id: ${dId}`, `session_id: ${s.id}`];
676
+ if (s.workspace) parts.push(`workspace: ${s.workspace}`);
677
+ if (s.providerType) parts.push(`type: ${s.providerType}`);
678
+ parts.push("(use read_chat to see the approval prompt)");
679
+ return parts.join("\n ");
680
+ });
681
+ return `Pending approvals (${pending.length}):
682
+
683
+ ${lines.join("\n\n")}`;
684
+ }
685
+
408
686
  // src/server.ts
409
687
  async function startMcpServer(opts) {
410
688
  const transport = opts.mode === "cloud" ? new CloudTransport({ apiKey: opts.apiKey, baseUrl: opts.baseUrl }) : new LocalTransport({ port: opts.port, password: opts.password });
@@ -418,14 +696,16 @@ async function startMcpServer(opts) {
418
696
  const isLocal = opts.mode === "local";
419
697
  const allTools = [
420
698
  LIST_SESSIONS_TOOL,
699
+ LAUNCH_SESSION_TOOL,
700
+ CHECK_PENDING_TOOL,
421
701
  READ_CHAT_TOOL,
422
702
  SEND_CHAT_TOOL,
423
703
  APPROVE_TOOL,
424
- SCREENSHOT_TOOL,
425
- ...isLocal ? [GIT_STATUS_TOOL] : []
704
+ GIT_STATUS_TOOL,
705
+ ...isLocal ? [SCREENSHOT_TOOL] : []
426
706
  ];
427
707
  const server = new import_server.Server(
428
- { name: "adhdev-mcp-server", version: "0.9.59" },
708
+ { name: "adhdev-mcp-server", version: "0.9.60" },
429
709
  { capabilities: { tools: {} } }
430
710
  );
431
711
  server.setRequestHandler(import_types.ListToolsRequestSchema, async () => ({ tools: allTools }));
@@ -435,7 +715,7 @@ async function startMcpServer(opts) {
435
715
  try {
436
716
  switch (name) {
437
717
  case "list_sessions": {
438
- const text = await listSessions(transport);
718
+ const text = await listSessions(transport, { format: a.format });
439
719
  return { content: [{ type: "text", text }] };
440
720
  }
441
721
  case "read_chat": {
@@ -461,7 +741,20 @@ async function startMcpServer(opts) {
461
741
  return { content: [{ type: "text", text: result.text }] };
462
742
  }
463
743
  case "git_status": {
464
- const text = await gitStatus(transport, { workspace: a.workspace, include_diff: a.include_diff });
744
+ const text = await gitStatus(transport, { workspace: a.workspace, include_diff: a.include_diff, daemon_id: a.daemon_id, format: a.format });
745
+ return { content: [{ type: "text", text }] };
746
+ }
747
+ case "launch_session": {
748
+ const text = await launchSession(transport, {
749
+ type: a.type,
750
+ workspace: a.workspace,
751
+ model: a.model,
752
+ daemon_id: a.daemon_id
753
+ });
754
+ return { content: [{ type: "text", text }] };
755
+ }
756
+ case "check_pending": {
757
+ const text = await checkPending(transport, { daemon_id: a.daemon_id, format: a.format });
465
758
  return { content: [{ type: "text", text }] };
466
759
  }
467
760
  default:
@@ -530,8 +823,8 @@ Environment variables:
530
823
  ADHDEV_API_KEY API key (cloud mode)
531
824
  ADHDEV_PASSWORD Daemon password (local mode)
532
825
 
533
- Local mode tools: list_sessions, read_chat, send_chat, approve, screenshot, git_status
534
- Cloud mode tools: list_sessions, read_chat, send_chat, approve
826
+ Local mode tools: list_sessions, launch_session, check_pending, read_chat, send_chat, approve, git_status, screenshot
827
+ Cloud mode tools: list_sessions, launch_session, check_pending, read_chat, send_chat, approve, git_status
535
828
  `.trim());
536
829
  }
537
830
  startMcpServer(parseArgs(process.argv)).catch((err) => {