@promptprojectmanager/mcp-server 4.4.3 → 4.5.1

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/index.js CHANGED
@@ -1,4 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ isProcessRunning,
4
+ isWatcherRunning,
5
+ readPid,
6
+ readStatus,
7
+ removePid,
8
+ writeStatus
9
+ } from "./chunk-POSCT6WJ.js";
2
10
 
3
11
  // src/index.ts
4
12
  import minimist from "minimist";
@@ -68,19 +76,19 @@ function parseUpdateArgs(args) {
68
76
  if (!ticketSlug || !content) return null;
69
77
  return { ticketSlug, content };
70
78
  }
71
- function parseRunPromptArgs(args) {
79
+ function parsePromptsRunArgs(args) {
72
80
  const parsed = args;
73
81
  const slug = typeof parsed?.slug === "string" ? parsed.slug : void 0;
74
82
  if (!slug) return null;
75
83
  return { slug };
76
84
  }
77
- function parseSystemPromptsArgs(args) {
85
+ function parsePromptsListArgs(args) {
78
86
  const parsed = args;
79
87
  return {
80
88
  search: typeof parsed?.search === "string" ? parsed.search : void 0
81
89
  };
82
90
  }
83
- function parseSystemPromptsUpdateArgs(args) {
91
+ function parsePromptsUpdateArgs(args) {
84
92
  const parsed = args;
85
93
  const slug = typeof parsed?.slug === "string" ? parsed.slug : void 0;
86
94
  if (!slug) return null;
@@ -90,6 +98,171 @@ function parseSystemPromptsUpdateArgs(args) {
90
98
  changeLogMessage: typeof parsed?.changeLogMessage === "string" ? parsed.changeLogMessage : void 0
91
99
  };
92
100
  }
101
+ function parseWatcherStartArgs(args) {
102
+ const parsed = args;
103
+ return {
104
+ pollIntervalMs: typeof parsed?.pollIntervalMs === "number" ? parsed.pollIntervalMs : void 0,
105
+ maxParallel: typeof parsed?.maxParallel === "number" ? parsed.maxParallel : void 0,
106
+ ticketTimeout: typeof parsed?.ticketTimeout === "number" ? parsed.ticketTimeout : void 0,
107
+ enableNotifications: typeof parsed?.enableNotifications === "boolean" ? parsed.enableNotifications : void 0,
108
+ workingDirectory: typeof parsed?.workingDirectory === "string" ? parsed.workingDirectory : void 0
109
+ };
110
+ }
111
+
112
+ // src/watcher/watcher_controller.ts
113
+ import { spawn } from "child_process";
114
+ import { dirname, join } from "path";
115
+ import { fileURLToPath } from "url";
116
+ async function startWatcher(baseConfig, args) {
117
+ const workingDirectory = args?.workingDirectory ?? baseConfig.workingDirectory;
118
+ if (isWatcherRunning(workingDirectory)) {
119
+ const existingPid = readPid(workingDirectory);
120
+ const existingStatus = readStatus(workingDirectory);
121
+ return {
122
+ success: true,
123
+ message: "Watcher is already running",
124
+ pid: existingPid,
125
+ alreadyRunning: true,
126
+ config: existingStatus?.config
127
+ };
128
+ }
129
+ const config = {
130
+ projectSlug: baseConfig.projectSlug,
131
+ projectToken: baseConfig.projectToken,
132
+ convexUrl: baseConfig.convexUrl,
133
+ workingDirectory,
134
+ pollIntervalMs: args?.pollIntervalMs ?? 3e4,
135
+ maxParallel: args?.maxParallel ?? 1,
136
+ ticketTimeout: args?.ticketTimeout ?? 18e5,
137
+ enableNotifications: args?.enableNotifications ?? true
138
+ };
139
+ const configJson = JSON.stringify(config);
140
+ const configBase64 = Buffer.from(configJson).toString("base64");
141
+ const daemonPath = getDaemonPath();
142
+ try {
143
+ const child = spawn(process.execPath, [daemonPath, "--config", configBase64], {
144
+ detached: true,
145
+ stdio: ["ignore", "ignore", "ignore"],
146
+ cwd: workingDirectory
147
+ });
148
+ child.unref();
149
+ await new Promise((resolve) => setTimeout(resolve, 500));
150
+ const pid = readPid(workingDirectory);
151
+ if (pid && isProcessRunning(pid)) {
152
+ return {
153
+ success: true,
154
+ message: "Watcher started successfully",
155
+ pid,
156
+ alreadyRunning: false,
157
+ config: {
158
+ projectSlug: config.projectSlug,
159
+ convexUrl: config.convexUrl,
160
+ pollIntervalMs: config.pollIntervalMs,
161
+ maxParallel: config.maxParallel,
162
+ ticketTimeout: config.ticketTimeout,
163
+ enableNotifications: config.enableNotifications,
164
+ workingDirectory: config.workingDirectory
165
+ }
166
+ };
167
+ } else {
168
+ return {
169
+ success: false,
170
+ message: "Daemon process started but failed to initialize. Check logs in .ppm/yolo/"
171
+ };
172
+ }
173
+ } catch (error) {
174
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
175
+ return {
176
+ success: false,
177
+ message: `Failed to start watcher: ${errorMessage}`
178
+ };
179
+ }
180
+ }
181
+ async function stopWatcher(workingDirectory) {
182
+ const pid = readPid(workingDirectory);
183
+ if (!pid) {
184
+ return {
185
+ success: true,
186
+ message: "No watcher is running",
187
+ wasRunning: false
188
+ };
189
+ }
190
+ if (!isProcessRunning(pid)) {
191
+ removePid(workingDirectory);
192
+ const status = readStatus(workingDirectory);
193
+ if (status) {
194
+ writeStatus(workingDirectory, { ...status, state: "stopped" });
195
+ }
196
+ return {
197
+ success: true,
198
+ message: "Watcher was not running (cleaned up stale PID file)",
199
+ wasRunning: false
200
+ };
201
+ }
202
+ try {
203
+ process.kill(pid, "SIGTERM");
204
+ const maxWait = 5e3;
205
+ const checkInterval = 100;
206
+ let waited = 0;
207
+ while (waited < maxWait && isProcessRunning(pid)) {
208
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
209
+ waited += checkInterval;
210
+ }
211
+ if (isProcessRunning(pid)) {
212
+ try {
213
+ process.kill(pid, "SIGKILL");
214
+ } catch {
215
+ }
216
+ }
217
+ removePid(workingDirectory);
218
+ const status = readStatus(workingDirectory);
219
+ if (status) {
220
+ writeStatus(workingDirectory, { ...status, state: "stopped" });
221
+ }
222
+ return {
223
+ success: true,
224
+ message: "Watcher stopped successfully",
225
+ wasRunning: true
226
+ };
227
+ } catch (error) {
228
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
229
+ return {
230
+ success: false,
231
+ message: `Failed to stop watcher: ${errorMessage}`,
232
+ wasRunning: true
233
+ };
234
+ }
235
+ }
236
+ function getWatcherStatus(workingDirectory) {
237
+ const status = readStatus(workingDirectory);
238
+ if (!status) {
239
+ return {
240
+ success: true,
241
+ message: "No watcher has been started for this project"
242
+ };
243
+ }
244
+ if (status.state === "running" && status.pid) {
245
+ if (!isProcessRunning(status.pid)) {
246
+ const updatedStatus = { ...status, state: "stopped" };
247
+ writeStatus(workingDirectory, updatedStatus);
248
+ removePid(workingDirectory);
249
+ return {
250
+ success: true,
251
+ status: updatedStatus,
252
+ message: "Watcher process has stopped unexpectedly"
253
+ };
254
+ }
255
+ }
256
+ return {
257
+ success: true,
258
+ status
259
+ };
260
+ }
261
+ function getDaemonPath() {
262
+ const currentFilePath = fileURLToPath(import.meta.url);
263
+ const currentDir = dirname(currentFilePath);
264
+ return join(currentDir, "watcher_daemon.js");
265
+ }
93
266
 
94
267
  // src/prompt-builder.ts
95
268
  var AmbiguousPromptError = class extends Error {
@@ -173,7 +346,6 @@ async function startServer(config, convexClientRaw) {
173
346
  config.projectToken
174
347
  );
175
348
  console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);
176
- const projectsForTickets = [{ slug: tokenProjectSlug, name: tokenProjectSlug }];
177
349
  console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);
178
350
  console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);
179
351
  if (accountScopedPrompts.length === 0) {
@@ -181,78 +353,100 @@ async function startServer(config, convexClientRaw) {
181
353
  "[MCP] WARNING: No prompts found. Create prompts in the 'prompts' section to expose them via MCP."
182
354
  );
183
355
  }
184
- const projectSlugs = new Set(projectsForTickets.map((p) => p.slug));
185
- const dynamicTicketTools = [];
186
- for (const projectSlug of projectSlugs) {
187
- dynamicTicketTools.push({
188
- name: `${projectSlug}_tickets_work`,
356
+ const projectSlug = tokenProjectSlug;
357
+ const dynamicTicketTools = [
358
+ // Unified work command (replaces both tickets_open and tickets_work)
359
+ {
360
+ name: `tickets_work`,
189
361
  description: `Get work from the "${projectSlug}" project. With no args: gets next ticket from open queue. With ticket slug/number: opens or resumes that specific ticket.`,
190
362
  slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,
191
363
  projectSlug,
192
364
  type: "work"
193
- });
194
- dynamicTicketTools.push({
195
- name: `${projectSlug}_tickets_close`,
365
+ },
366
+ {
367
+ name: `tickets_close`,
196
368
  description: `Mark a working ticket as completed in the "${projectSlug}" project`,
197
369
  slashDescription: `Mark a working ticket as completed`,
198
370
  projectSlug,
199
371
  type: "close"
200
- });
201
- dynamicTicketTools.push({
202
- name: `${projectSlug}_tickets_create`,
372
+ },
373
+ {
374
+ name: `tickets_create`,
203
375
  description: `Create a new ticket in the "${projectSlug}" project queue`,
204
376
  slashDescription: `Create a new ticket in the backlog queue`,
205
377
  projectSlug,
206
378
  type: "create"
207
- });
208
- dynamicTicketTools.push({
209
- name: `${projectSlug}_tickets_search`,
379
+ },
380
+ {
381
+ name: `tickets_search`,
210
382
  description: `Search for tickets by content in the "${projectSlug}" project`,
211
383
  slashDescription: `Search for tickets by content`,
212
384
  projectSlug,
213
385
  type: "search"
214
- });
215
- dynamicTicketTools.push({
216
- name: `${projectSlug}_tickets_get`,
386
+ },
387
+ {
388
+ name: `tickets_get`,
217
389
  description: `Get a specific ticket by number or slug from "${projectSlug}" (read-only)`,
218
390
  slashDescription: `Get a specific ticket by number or slug (read-only)`,
219
391
  projectSlug,
220
392
  type: "get"
221
- });
222
- dynamicTicketTools.push({
223
- name: `${projectSlug}_tickets_list`,
393
+ },
394
+ {
395
+ name: `tickets_list`,
224
396
  description: `List active tickets in the "${projectSlug}" project (backlog + open + working)`,
225
397
  slashDescription: `List active tickets (backlog + open + working)`,
226
398
  projectSlug,
227
399
  type: "list"
228
- });
229
- dynamicTicketTools.push({
230
- name: `${projectSlug}_tickets_update`,
400
+ },
401
+ {
402
+ name: `tickets_update`,
231
403
  description: `Update a ticket in the "${projectSlug}" project by appending content with timestamp`,
232
404
  slashDescription: `Update a ticket by appending content with timestamp`,
233
405
  projectSlug,
234
406
  type: "update"
235
- });
236
- }
237
- console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools for ${projectSlugs.size} project(s)...`);
238
- const globalSystemTools = [
407
+ },
408
+ // YOLO watcher tools - autonomous ticket execution
239
409
  {
240
- name: "system_run_prompt",
241
- description: "Execute a prompt by slug. Use system_prompts to list available prompts.",
242
- slashDescription: "Execute a prompt by slug. Use system_prompts to list available prompts."
410
+ name: `tickets_yolo_start`,
411
+ description: `Start the YOLO ticket watcher for "${projectSlug}". Polls for open tickets with yolo flag and spawns Terminal windows with Claude Code to execute them autonomously.`,
412
+ slashDescription: `Start YOLO watcher for autonomous ticket execution`,
413
+ projectSlug,
414
+ type: "yolo_start"
415
+ },
416
+ {
417
+ name: `tickets_yolo_stop`,
418
+ description: `Stop the YOLO ticket watcher for "${projectSlug}"`,
419
+ slashDescription: `Stop the YOLO watcher daemon`,
420
+ projectSlug,
421
+ type: "yolo_stop"
243
422
  },
244
423
  {
245
- name: "system_prompts",
424
+ name: `tickets_yolo_status`,
425
+ description: `Get YOLO watcher status for "${projectSlug}"`,
426
+ slashDescription: `Check YOLO watcher status and metrics`,
427
+ projectSlug,
428
+ type: "yolo_status"
429
+ }
430
+ ];
431
+ console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools...`);
432
+ const globalPromptTools = [
433
+ {
434
+ name: "prompts_run",
435
+ description: "Execute a prompt by slug. Use prompts_list to list available prompts.",
436
+ slashDescription: "Execute a prompt by slug. Use prompts_list to list available prompts."
437
+ },
438
+ {
439
+ name: "prompts_list",
246
440
  description: "List all available prompts",
247
441
  slashDescription: "List all available prompts"
248
442
  },
249
443
  {
250
- name: "system_prompts_update",
444
+ name: "prompts_update",
251
445
  description: "Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.",
252
446
  slashDescription: "Read or update a prompt by slug"
253
447
  }
254
448
  ];
255
- console.error(`[MCP] Registering ${globalSystemTools.length} global system tools...`);
449
+ console.error(`[MCP] Registering ${globalPromptTools.length} global prompt tools...`);
256
450
  const dynamicPromptTools = [];
257
451
  for (const prompt of accountScopedPrompts) {
258
452
  const folderPrefix = prompt.folderSlug ? `[${prompt.folderSlug}] ` : "";
@@ -295,16 +489,16 @@ async function startServer(config, convexClientRaw) {
295
489
  name: tt.name,
296
490
  description: tt.slashDescription
297
491
  }));
298
- const systemPromptsSchemas = globalSystemTools.map((st) => ({
492
+ const promptToolSchemas = globalPromptTools.map((st) => ({
299
493
  name: st.name,
300
494
  description: st.slashDescription
301
495
  }));
302
496
  return {
303
497
  prompts: [
304
- ...systemPromptsSchemas,
305
- // system_prompts, system_run_prompt (global)
498
+ ...promptToolSchemas,
499
+ // prompts_run, prompts_list, prompts_update (global)
306
500
  ...ticketPromptSchemas
307
- // project_tickets_* (project-scoped)
501
+ // tickets_* (token-scoped)
308
502
  ]
309
503
  };
310
504
  });
@@ -326,26 +520,25 @@ async function startServer(config, convexClientRaw) {
326
520
  };
327
521
  }
328
522
  const ticketTool = dynamicTicketTools.find((tt) => tt.name === promptName);
329
- const ticketWorkMatch = promptName.match(/^(.+)_tickets_work\s+(.+)$/);
523
+ const ticketWorkMatch = promptName.match(/^tickets_work\s+(.+)$/);
330
524
  if (ticketTool || ticketWorkMatch) {
331
525
  let promptContent;
332
526
  let description;
333
527
  if (ticketWorkMatch) {
334
- const projectSlug = ticketWorkMatch[1];
335
- const ticketArg = ticketWorkMatch[2].trim();
528
+ const ticketArg = ticketWorkMatch[1].trim();
336
529
  description = `Work on ticket "${ticketArg}" from "${projectSlug}"`;
337
530
  promptContent = `Get work on ticket "${ticketArg}" from the "${projectSlug}" project.
338
531
 
339
- Call the \`${projectSlug}_tickets_work\` tool with ticketSlug: "${ticketArg}".
532
+ Call the \`tickets_work\` tool with ticketSlug: "${ticketArg}".
340
533
 
341
534
  This will open the ticket if it's in the open queue, or resume it if already working.`;
342
535
  } else if (ticketTool.type === "work") {
343
536
  description = ticketTool.description;
344
537
  promptContent = `Get work from the "${ticketTool.projectSlug}" project.
345
538
 
346
- Call the \`${ticketTool.name}\` tool to get the next ticket from the open queue.
539
+ Call the \`tickets_work\` tool to get the next ticket from the open queue.
347
540
 
348
- You can also specify a ticket: /ppm:${ticketTool.projectSlug}_tickets_work <number-or-slug>
541
+ You can also specify a ticket: /ppm:tickets_work <number-or-slug>
349
542
 
350
543
  This unified command handles both opening new tickets and resuming in-progress work.`;
351
544
  } else if (ticketTool.type === "create") {
@@ -397,7 +590,7 @@ Call the \`${ticketTool.name}\` tool with the ticket number or slug.`;
397
590
  ]
398
591
  };
399
592
  }
400
- if (promptName === "system_prompts") {
593
+ if (promptName === "prompts_list") {
401
594
  return {
402
595
  description: "List all available prompts",
403
596
  messages: [
@@ -407,14 +600,14 @@ Call the \`${ticketTool.name}\` tool with the ticket number or slug.`;
407
600
  type: "text",
408
601
  text: `List all available prompts.
409
602
 
410
- Call the \`system_prompts\` tool with optional \`search\` parameter to filter results.`
603
+ Call the \`prompts_list\` tool with optional \`search\` parameter to filter results.`
411
604
  }
412
605
  }
413
606
  ]
414
607
  };
415
608
  }
416
- const runPromptMatch = promptName.match(/^system_run_prompt\s+(.+)$/);
417
- if (promptName === "system_run_prompt" || runPromptMatch) {
609
+ const runPromptMatch = promptName.match(/^prompts_run\s+(.+)$/);
610
+ if (promptName === "prompts_run" || runPromptMatch) {
418
611
  let promptContent;
419
612
  let description;
420
613
  if (runPromptMatch) {
@@ -422,19 +615,19 @@ Call the \`system_prompts\` tool with optional \`search\` parameter to filter re
422
615
  description = `Execute prompt "${promptSlug}"`;
423
616
  promptContent = `Execute the "${promptSlug}" prompt.
424
617
 
425
- Call the \`system_run_prompt\` tool with slug: "${promptSlug}".`;
618
+ Call the \`prompts_run\` tool with slug: "${promptSlug}".`;
426
619
  } else {
427
620
  description = "Execute a prompt by slug";
428
621
  promptContent = `Execute a prompt by slug.
429
622
 
430
623
  ## Usage
431
- Call the \`system_run_prompt\` tool with the prompt slug.
624
+ Call the \`prompts_run\` tool with the prompt slug.
432
625
 
433
626
  ## Available Prompts
434
- Use \`system_prompts\` to list all available prompts.
627
+ Use \`prompts_list\` to list all available prompts.
435
628
 
436
629
  ## Example
437
- /ppm:system_run_prompt code-review
630
+ /ppm:prompts_run code-review
438
631
 
439
632
  This will execute the "code-review" prompt.`;
440
633
  }
@@ -451,7 +644,7 @@ This will execute the "code-review" prompt.`;
451
644
  ]
452
645
  };
453
646
  }
454
- throw new Error(`Unknown prompt: ${promptName}. Use system_run_prompt to execute prompts.`);
647
+ throw new Error(`Unknown prompt: ${promptName}. Use prompts_run to execute prompts.`);
455
648
  });
456
649
  server.setRequestHandler(ListToolsRequestSchema, async () => {
457
650
  const tools = [
@@ -545,6 +738,45 @@ This will execute the "code-review" prompt.`;
545
738
  },
546
739
  required: ["ticketSlug", "content"]
547
740
  };
741
+ } else if (tt.type === "yolo_start") {
742
+ inputSchema = {
743
+ type: "object",
744
+ properties: {
745
+ pollIntervalMs: {
746
+ type: "number",
747
+ description: "Milliseconds between polls (default: 30000 = 30s)"
748
+ },
749
+ maxParallel: {
750
+ type: "number",
751
+ description: "Maximum concurrent ticket executions (default: 1)"
752
+ },
753
+ ticketTimeout: {
754
+ type: "number",
755
+ description: "Timeout for ticket execution in ms (default: 1800000 = 30 min)"
756
+ },
757
+ enableNotifications: {
758
+ type: "boolean",
759
+ description: "Show macOS notifications (default: true)"
760
+ },
761
+ workingDirectory: {
762
+ type: "string",
763
+ description: "Working directory for Claude Code sessions (default: current working directory)"
764
+ }
765
+ },
766
+ required: []
767
+ };
768
+ } else if (tt.type === "yolo_stop") {
769
+ inputSchema = {
770
+ type: "object",
771
+ properties: {},
772
+ required: []
773
+ };
774
+ } else if (tt.type === "yolo_status") {
775
+ inputSchema = {
776
+ type: "object",
777
+ properties: {},
778
+ required: []
779
+ };
548
780
  } else {
549
781
  inputSchema = {
550
782
  type: "object",
@@ -558,10 +790,10 @@ This will execute the "code-review" prompt.`;
558
790
  inputSchema
559
791
  };
560
792
  }),
561
- // Global system_run_prompt tool
793
+ // Global prompts_run tool
562
794
  {
563
- name: "system_run_prompt",
564
- description: "Execute a prompt by slug. Use system_prompts to list available prompts.",
795
+ name: "prompts_run",
796
+ description: "Execute a prompt by slug. Use prompts_list to list available prompts.",
565
797
  inputSchema: {
566
798
  type: "object",
567
799
  properties: {
@@ -573,9 +805,9 @@ This will execute the "code-review" prompt.`;
573
805
  required: ["slug"]
574
806
  }
575
807
  },
576
- // Global system_prompts tool
808
+ // Global prompts_list tool
577
809
  {
578
- name: "system_prompts",
810
+ name: "prompts_list",
579
811
  description: "List all available prompts",
580
812
  inputSchema: {
581
813
  type: "object",
@@ -587,9 +819,9 @@ This will execute the "code-review" prompt.`;
587
819
  }
588
820
  }
589
821
  },
590
- // Global system_prompts_update tool (CLI-based prompt editing)
822
+ // Global prompts_update tool (CLI-based prompt editing)
591
823
  {
592
- name: "system_prompts_update",
824
+ name: "prompts_update",
593
825
  description: "Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.",
594
826
  inputSchema: {
595
827
  type: "object",
@@ -625,8 +857,8 @@ This will execute the "code-review" prompt.`;
625
857
  });
626
858
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
627
859
  const toolName = request.params.name;
628
- if (toolName === "system_run_prompt") {
629
- const parsedArgs = parseRunPromptArgs(request.params.arguments);
860
+ if (toolName === "prompts_run") {
861
+ const parsedArgs = parsePromptsRunArgs(request.params.arguments);
630
862
  if (!parsedArgs) {
631
863
  return {
632
864
  content: [
@@ -634,7 +866,7 @@ This will execute the "code-review" prompt.`;
634
866
  type: "text",
635
867
  text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').
636
868
 
637
- Use \`system_prompts\` to list available prompts.`
869
+ Use \`prompts_list\` to list available prompts.`
638
870
  }
639
871
  ],
640
872
  isError: true
@@ -668,7 +900,7 @@ Use \`system_prompts\` to list available prompts.`
668
900
 
669
901
  ${suggestionsList}
670
902
 
671
- Example: \`system_run_prompt ${error.suggestions[0]}\``
903
+ Example: \`prompts_run ${error.suggestions[0]}\``
672
904
  }
673
905
  ],
674
906
  isError: true
@@ -687,8 +919,8 @@ Example: \`system_run_prompt ${error.suggestions[0]}\``
687
919
  };
688
920
  }
689
921
  }
690
- if (toolName === "system_prompts") {
691
- const { search: searchTerm } = parseSystemPromptsArgs(request.params.arguments);
922
+ if (toolName === "prompts_list") {
923
+ const { search: searchTerm } = parsePromptsListArgs(request.params.arguments);
692
924
  let filteredPrompts = [...accountScopedPrompts];
693
925
  if (searchTerm) {
694
926
  const lowerSearch = searchTerm.toLowerCase();
@@ -716,8 +948,8 @@ No prompts found.`
716
948
  ]
717
949
  };
718
950
  }
719
- if (toolName === "system_prompts_update") {
720
- const parsedArgs = parseSystemPromptsUpdateArgs(request.params.arguments);
951
+ if (toolName === "prompts_update") {
952
+ const parsedArgs = parsePromptsUpdateArgs(request.params.arguments);
721
953
  if (!parsedArgs) {
722
954
  return {
723
955
  content: [
@@ -726,8 +958,8 @@ No prompts found.`
726
958
  text: `Error: Missing 'slug' parameter. Provide prompt slug to read or update.
727
959
 
728
960
  Usage:
729
- - Read mode: system_prompts_update { slug: "my-prompt" }
730
- - Write mode: system_prompts_update { slug: "my-prompt", content: "new content..." }`
961
+ - Read mode: prompts_update { slug: "my-prompt" }
962
+ - Write mode: prompts_update { slug: "my-prompt", content: "new content..." }`
731
963
  }
732
964
  ],
733
965
  isError: true
@@ -797,7 +1029,7 @@ ${JSON.stringify(readResult.context, null, 2)}
797
1029
  ${readResult.editingGuidance}
798
1030
 
799
1031
  ---
800
- _To update this prompt, call system_prompts_update with content parameter._`
1032
+ _To update this prompt, call prompts_update with content parameter._`
801
1033
  }
802
1034
  ]
803
1035
  };
@@ -823,7 +1055,7 @@ Updated: ${writeResult.updatedAt}`
823
1055
  };
824
1056
  } catch (error) {
825
1057
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
826
- console.error(`[MCP] system_prompts_update error:`, error);
1058
+ console.error(`[MCP] prompts_update error:`, error);
827
1059
  return {
828
1060
  content: [
829
1061
  {
@@ -1274,6 +1506,132 @@ _Ticket content has been appended with your update._`
1274
1506
  isError: true
1275
1507
  };
1276
1508
  }
1509
+ } else if (ticketTool.type === "yolo_start") {
1510
+ const args = parseWatcherStartArgs(request.params.arguments);
1511
+ const workingDirectory = args.workingDirectory ?? process.cwd();
1512
+ try {
1513
+ const result = await startWatcher(
1514
+ {
1515
+ projectSlug: ticketTool.projectSlug,
1516
+ projectToken: config.projectToken,
1517
+ convexUrl: config.convexUrl,
1518
+ workingDirectory
1519
+ },
1520
+ args
1521
+ );
1522
+ if (result.success) {
1523
+ const statusLines = [
1524
+ result.alreadyRunning ? `\u2139\uFE0F Watcher is already running (PID: ${result.pid})` : `\u2705 Watcher started (PID: ${result.pid})`
1525
+ ];
1526
+ if (result.config) {
1527
+ statusLines.push("");
1528
+ statusLines.push("**Configuration:**");
1529
+ statusLines.push(` Poll Interval: ${result.config.pollIntervalMs / 1e3}s`);
1530
+ statusLines.push(` Max Parallel: ${result.config.maxParallel}`);
1531
+ statusLines.push(` Ticket Timeout: ${result.config.ticketTimeout / 1e3 / 60} min`);
1532
+ statusLines.push(` Notifications: ${result.config.enableNotifications ? "enabled" : "disabled"}`);
1533
+ statusLines.push(` Working Directory: ${result.config.workingDirectory}`);
1534
+ }
1535
+ statusLines.push("");
1536
+ statusLines.push("_The watcher will poll for open tickets with the YOLO flag and spawn Claude Code terminals to execute them._");
1537
+ statusLines.push("_Use `tickets_yolo_status` to check progress, `tickets_yolo_stop` to stop._");
1538
+ return {
1539
+ content: [{ type: "text", text: statusLines.join("\n") }]
1540
+ };
1541
+ } else {
1542
+ return {
1543
+ content: [{ type: "text", text: `\u274C ${result.message}` }],
1544
+ isError: true
1545
+ };
1546
+ }
1547
+ } catch (error) {
1548
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1549
+ console.error(`[MCP] tickets_yolo_start error:`, error);
1550
+ return {
1551
+ content: [{ type: "text", text: `Error starting watcher: ${errorMessage}` }],
1552
+ isError: true
1553
+ };
1554
+ }
1555
+ } else if (ticketTool.type === "yolo_stop") {
1556
+ const workingDirectory = process.cwd();
1557
+ try {
1558
+ const result = await stopWatcher(workingDirectory);
1559
+ if (result.success) {
1560
+ return {
1561
+ content: [{
1562
+ type: "text",
1563
+ text: result.wasRunning ? `\u2705 Watcher stopped successfully` : `\u2139\uFE0F ${result.message}`
1564
+ }]
1565
+ };
1566
+ } else {
1567
+ return {
1568
+ content: [{ type: "text", text: `\u274C ${result.message}` }],
1569
+ isError: true
1570
+ };
1571
+ }
1572
+ } catch (error) {
1573
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1574
+ console.error(`[MCP] tickets_yolo_stop error:`, error);
1575
+ return {
1576
+ content: [{ type: "text", text: `Error stopping watcher: ${errorMessage}` }],
1577
+ isError: true
1578
+ };
1579
+ }
1580
+ } else if (ticketTool.type === "yolo_status") {
1581
+ const workingDirectory = process.cwd();
1582
+ try {
1583
+ const result = getWatcherStatus(workingDirectory);
1584
+ if (!result.status) {
1585
+ return {
1586
+ content: [{ type: "text", text: `\u2139\uFE0F ${result.message ?? "No watcher status available"}` }]
1587
+ };
1588
+ }
1589
+ const status = result.status;
1590
+ const lines = [
1591
+ `# YOLO Watcher Status: ${status.projectSlug}`,
1592
+ "",
1593
+ `**State:** ${status.state.toUpperCase()}`
1594
+ ];
1595
+ if (status.pid) {
1596
+ lines.push(`**PID:** ${status.pid}`);
1597
+ }
1598
+ if (status.startedAt) {
1599
+ lines.push(`**Started:** ${status.startedAt}`);
1600
+ }
1601
+ if (status.lastPollAt) {
1602
+ lines.push(`**Last Poll:** ${status.lastPollAt}`);
1603
+ }
1604
+ lines.push(`**Tickets Processed:** ${status.ticketsProcessed}`);
1605
+ if (status.currentlyExecuting && status.currentlyExecuting.length > 0) {
1606
+ lines.push("");
1607
+ lines.push("**Currently Executing:**");
1608
+ for (const exec of status.currentlyExecuting) {
1609
+ const displayName = exec.ticketNumber ? `#${exec.ticketNumber} ${exec.ticketSlug}` : exec.ticketSlug;
1610
+ lines.push(` \u2022 ${displayName} (started: ${exec.startedAt})`);
1611
+ }
1612
+ }
1613
+ if (status.lastError) {
1614
+ lines.push("");
1615
+ lines.push(`**Last Error:** ${status.lastError}`);
1616
+ }
1617
+ if (status.config) {
1618
+ lines.push("");
1619
+ lines.push("**Configuration:**");
1620
+ lines.push(` Poll Interval: ${status.config.pollIntervalMs / 1e3}s`);
1621
+ lines.push(` Max Parallel: ${status.config.maxParallel}`);
1622
+ lines.push(` Working Directory: ${status.config.workingDirectory}`);
1623
+ }
1624
+ return {
1625
+ content: [{ type: "text", text: lines.join("\n") }]
1626
+ };
1627
+ } catch (error) {
1628
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1629
+ console.error(`[MCP] tickets_yolo_status error:`, error);
1630
+ return {
1631
+ content: [{ type: "text", text: `Error getting watcher status: ${errorMessage}` }],
1632
+ isError: true
1633
+ };
1634
+ }
1277
1635
  }
1278
1636
  }
1279
1637
  const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);
@@ -1297,7 +1655,7 @@ _Ticket content has been appended with your update._`
1297
1655
  };
1298
1656
  }
1299
1657
  }
1300
- throw new Error(`Unknown tool: ${toolName}. Use system_run_prompt to execute prompts by name, or check available tools.`);
1658
+ throw new Error(`Unknown tool: ${toolName}. Use prompts_run to execute prompts by name, or check available tools.`);
1301
1659
  });
1302
1660
  const transport = new StdioServerTransport();
1303
1661
  await server.connect(transport);