@promptprojectmanager/mcp-server 4.6.8 → 4.7.0

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,12 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- getWatcherDir,
4
- isWatcherRunning,
5
- readPid,
6
- readStatus,
7
- removePid,
8
- writeStatus
9
- } from "./chunk-PY22KZ7Z.js";
10
2
 
11
3
  // src/index.ts
12
4
  import minimist from "minimist";
@@ -98,314 +90,6 @@ function parsePromptsUpdateArgs(args) {
98
90
  changeLogMessage: typeof parsed?.changeLogMessage === "string" ? parsed.changeLogMessage : void 0
99
91
  };
100
92
  }
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 { fileURLToPath } from "url";
115
- import { dirname, join } from "path";
116
- function startWatcher(baseConfig, args) {
117
- const workingDirectory = args?.workingDirectory ?? baseConfig.workingDirectory;
118
- if (isWatcherRunning(workingDirectory)) {
119
- const existingPid = readPid(workingDirectory);
120
- const status = readStatus(workingDirectory);
121
- return {
122
- success: true,
123
- message: `Watcher already running (PID: ${existingPid})`,
124
- pid: existingPid,
125
- alreadyRunning: true,
126
- config: status?.config
127
- };
128
- }
129
- const config = {
130
- projectSlug: baseConfig.projectSlug,
131
- projectToken: baseConfig.projectToken,
132
- convexUrl: baseConfig.convexUrl,
133
- pollIntervalMs: args?.pollIntervalMs ?? 3e4,
134
- maxParallel: args?.maxParallel ?? 1,
135
- ticketTimeout: args?.ticketTimeout ?? 18e5,
136
- enableNotifications: args?.enableNotifications ?? true,
137
- workingDirectory
138
- };
139
- getWatcherDir(workingDirectory);
140
- const daemonPath = getDaemonPath();
141
- const configJson = JSON.stringify(config);
142
- const configB64 = Buffer.from(configJson).toString("base64");
143
- try {
144
- const child = spawn("node", [daemonPath, "--config", configB64], {
145
- detached: true,
146
- stdio: ["ignore", "ignore", "ignore"],
147
- cwd: workingDirectory
148
- });
149
- child.unref();
150
- const pid = child.pid;
151
- if (!pid) {
152
- return {
153
- success: false,
154
- message: "Failed to spawn daemon - no PID returned"
155
- };
156
- }
157
- const configWithoutToken = {
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
- const statusFile = join(getWatcherDir(workingDirectory), "status.json");
167
- const monitoringPrompt = generateMonitoringPrompt(statusFile, workingDirectory);
168
- return {
169
- success: true,
170
- message: `Watcher started (PID: ${pid})`,
171
- pid,
172
- alreadyRunning: false,
173
- config: configWithoutToken,
174
- monitoringPrompt,
175
- statusFile
176
- };
177
- } catch (error) {
178
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
179
- return {
180
- success: false,
181
- message: `Failed to start watcher: ${errorMessage}`
182
- };
183
- }
184
- }
185
- function getDaemonPath() {
186
- const currentFile = fileURLToPath(import.meta.url);
187
- const currentDir = dirname(currentFile);
188
- return join(currentDir, "watcher", "watcher_daemon.js");
189
- }
190
- function stopWatcher(workingDirectory) {
191
- const pid = readPid(workingDirectory);
192
- if (!pid) {
193
- return {
194
- success: true,
195
- message: "No watcher running (no PID file)",
196
- wasRunning: false
197
- };
198
- }
199
- if (!isWatcherRunning(workingDirectory)) {
200
- removePid(workingDirectory);
201
- return {
202
- success: true,
203
- message: "Watcher was not running (stale PID file cleaned up)",
204
- wasRunning: false
205
- };
206
- }
207
- try {
208
- process.kill(pid, "SIGTERM");
209
- const status = readStatus(workingDirectory);
210
- if (status) {
211
- writeStatus(workingDirectory, {
212
- ...status,
213
- state: "stopped"
214
- });
215
- }
216
- removePid(workingDirectory);
217
- return {
218
- success: true,
219
- message: `Watcher stopped (PID: ${pid})`,
220
- wasRunning: true
221
- };
222
- } catch (error) {
223
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
224
- removePid(workingDirectory);
225
- return {
226
- success: false,
227
- message: `Failed to stop watcher: ${errorMessage}`,
228
- wasRunning: true
229
- };
230
- }
231
- }
232
- function getWatcherStatus(workingDirectory) {
233
- const status = readStatus(workingDirectory);
234
- const isRunning = isWatcherRunning(workingDirectory);
235
- if (status?.state === "running" && !isRunning) {
236
- const updatedStatus = {
237
- ...status,
238
- state: "stopped"
239
- };
240
- writeStatus(workingDirectory, updatedStatus);
241
- return {
242
- success: true,
243
- status: updatedStatus,
244
- formattedStatus: formatStatus(updatedStatus),
245
- message: "Watcher died unexpectedly - status updated"
246
- };
247
- }
248
- if (!status) {
249
- return {
250
- success: true,
251
- message: "No watcher session found for this project",
252
- formattedStatus: formatStatus(null)
253
- };
254
- }
255
- return {
256
- success: true,
257
- status,
258
- formattedStatus: formatStatus(status)
259
- };
260
- }
261
- function formatStatus(status) {
262
- if (!status) {
263
- return `# YOLO Watcher Status
264
-
265
- **State**: Not started
266
-
267
- _Use \`tickets_yolo_start\` to begin watching for YOLO tickets._`;
268
- }
269
- const lines = [
270
- "# YOLO Watcher Status",
271
- "",
272
- `**State**: ${status.state}`,
273
- `**Project**: ${status.projectSlug}`
274
- ];
275
- if (status.pid) {
276
- lines.push(`**Daemon PID**: ${status.pid}`);
277
- }
278
- if (status.startedAt) {
279
- lines.push(`**Started**: ${status.startedAt}`);
280
- }
281
- if (status.lastPollAt) {
282
- lines.push(`**Last Poll**: ${status.lastPollAt}`);
283
- }
284
- lines.push(`**Tickets Processed**: ${status.ticketsProcessed}`);
285
- if (status.config) {
286
- lines.push("");
287
- lines.push("## Configuration");
288
- lines.push(`- Poll Interval: ${status.config.pollIntervalMs / 1e3}s`);
289
- lines.push(`- Max Parallel: ${status.config.maxParallel}`);
290
- lines.push(`- Ticket Timeout: ${status.config.ticketTimeout / 1e3 / 60} min`);
291
- lines.push(`- Notifications: ${status.config.enableNotifications ? "enabled" : "disabled"}`);
292
- }
293
- if (status.currentlyExecuting && status.currentlyExecuting.length > 0) {
294
- lines.push("");
295
- lines.push("## Currently Executing");
296
- for (const exec of status.currentlyExecuting) {
297
- const num = exec.ticketNumber ? `#${exec.ticketNumber}` : "";
298
- const elapsed = getElapsedTime(exec.startedAt);
299
- lines.push(`- **${num} ${exec.ticketSlug}** (${elapsed})`);
300
- }
301
- }
302
- if (status.completedTickets && status.completedTickets.length > 0) {
303
- lines.push("");
304
- lines.push("## Recently Completed");
305
- for (const completed of status.completedTickets.slice(0, 10)) {
306
- const num = completed.ticketNumber ? `#${completed.ticketNumber}` : "";
307
- const emoji = completed.success ? "\u2705" : completed.ticketClosed ? "\u26A0\uFE0F" : "\u274C";
308
- const duration = formatDuration(completed.durationMs);
309
- lines.push(`### ${emoji} ${num} ${completed.ticketSlug} (${duration})`);
310
- if (completed.toolCalls && completed.toolCalls.length > 0) {
311
- const toolNames = [...new Set(completed.toolCalls.map((t) => t.tool))];
312
- lines.push(`- **Tools**: ${toolNames.join(", ")}`);
313
- }
314
- if (completed.ticketClosed) {
315
- lines.push(`- **Status**: Ticket closed successfully`);
316
- } else if (completed.exitCode === 0) {
317
- lines.push(`- **Status**: Completed but ticket not closed`);
318
- }
319
- if (completed.errors && completed.errors.length > 0) {
320
- lines.push(`- **Errors**: ${completed.errors.slice(0, 2).join("; ")}`);
321
- }
322
- if (completed.summary) {
323
- lines.push(`- **Summary**: ${completed.summary.slice(0, 150)}`);
324
- }
325
- lines.push("");
326
- }
327
- }
328
- if (status.lastError) {
329
- lines.push("");
330
- lines.push("## Last Error");
331
- lines.push(`\`${status.lastError}\``);
332
- }
333
- return lines.join("\n");
334
- }
335
- function getElapsedTime(startedAt) {
336
- const start = new Date(startedAt).getTime();
337
- const elapsed = Date.now() - start;
338
- const minutes = Math.floor(elapsed / 1e3 / 60);
339
- const seconds = Math.floor(elapsed / 1e3 % 60);
340
- if (minutes > 0) {
341
- return `${minutes}m ${seconds}s`;
342
- }
343
- return `${seconds}s`;
344
- }
345
- function formatDuration(ms) {
346
- if (ms < 1e3) {
347
- return `${ms}ms`;
348
- }
349
- const seconds = Math.floor(ms / 1e3);
350
- const minutes = Math.floor(seconds / 60);
351
- if (minutes > 0) {
352
- const remainingSeconds = seconds % 60;
353
- return `${minutes}m ${remainingSeconds}s`;
354
- }
355
- return `${seconds}s`;
356
- }
357
- function generateMonitoringPrompt(statusFile, workingDirectory) {
358
- const pidFile = join(getWatcherDir(workingDirectory), "watcher.pid");
359
- return `You are a YOLO Watcher Monitor. Watch for ticket completions and report them.
360
-
361
- ## Status File
362
- ${statusFile}
363
-
364
- ## PID File (check if daemon is running)
365
- ${pidFile}
366
-
367
- ## Your Workflow
368
-
369
- 1. Read status.json and record the current count of completedTickets
370
- 2. Run this bash loop to wait for changes (bash does the waiting, not you):
371
- \`\`\`bash
372
- last_count=$(cat "${statusFile}" 2>/dev/null | grep -o '"ticketSlug"' | wc -l | tr -d ' ')
373
- while [ -f "${pidFile}" ]; do
374
- current=$(cat "${statusFile}" 2>/dev/null | grep -o '"ticketSlug"' | wc -l | tr -d ' ')
375
- if [ "$current" != "$last_count" ]; then
376
- echo "CHANGED: $last_count -> $current"
377
- exit 0
378
- fi
379
- sleep 5
380
- done
381
- echo "DAEMON_STOPPED"
382
- \`\`\`
383
- 3. When the loop exits:
384
- - If "CHANGED": read status.json and report the NEW completed ticket(s)
385
- - If "DAEMON_STOPPED": report that the watcher has stopped and exit
386
- 4. Loop back to step 1 (unless daemon stopped)
387
-
388
- ## Reporting Format
389
-
390
- When a ticket completes:
391
- \`\`\`
392
- \u{1F3AB} YOLO TICKET COMPLETED
393
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
394
- Ticket: {slug} (#{number})
395
- Duration: {X}s
396
- Status: {\u2705 Success | \u26A0\uFE0F Completed (not closed) | \u274C Failed}
397
- Tools: {comma-separated list}
398
- Summary: {brief from status}
399
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
400
- \`\`\`
401
-
402
- ## Important
403
- - Let bash do the waiting (cheap) - only "think" when reporting
404
- - Exit gracefully when daemon stops
405
- - Keep reports concise
406
-
407
- Start monitoring now!`;
408
- }
409
93
 
410
94
  // src/prompt-builder.ts
411
95
  var AmbiguousPromptError = class extends Error {
@@ -547,28 +231,6 @@ async function startServer(config, convexClientRaw) {
547
231
  slashDescription: `Update a ticket by appending content with timestamp`,
548
232
  projectSlug,
549
233
  type: "update"
550
- },
551
- // YOLO orchestrator tools - sub-agent based autonomous ticket execution
552
- {
553
- name: `tickets_yolo_start`,
554
- 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.`,
555
- slashDescription: `Start YOLO orchestrator for autonomous ticket execution`,
556
- projectSlug,
557
- type: "yolo_start"
558
- },
559
- {
560
- name: `tickets_yolo_stop`,
561
- description: `Stop the YOLO ticket watcher for "${projectSlug}"`,
562
- slashDescription: `Stop the YOLO orchestrator`,
563
- projectSlug,
564
- type: "yolo_stop"
565
- },
566
- {
567
- name: `tickets_yolo_status`,
568
- description: `Get YOLO watcher status for "${projectSlug}"`,
569
- slashDescription: `Check YOLO orchestrator status and pending tickets`,
570
- projectSlug,
571
- type: "yolo_status"
572
234
  }
573
235
  ];
574
236
  console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools...`);
@@ -881,45 +543,6 @@ This will execute the "code-review" prompt.`;
881
543
  },
882
544
  required: ["ticketSlug", "content"]
883
545
  };
884
- } else if (tt.type === "yolo_start") {
885
- inputSchema = {
886
- type: "object",
887
- properties: {
888
- pollIntervalMs: {
889
- type: "number",
890
- description: "Milliseconds between polls (default: 30000 = 30s)"
891
- },
892
- maxParallel: {
893
- type: "number",
894
- description: "Maximum concurrent ticket executions (default: 1)"
895
- },
896
- ticketTimeout: {
897
- type: "number",
898
- description: "Timeout for ticket execution in ms (default: 1800000 = 30 min)"
899
- },
900
- enableNotifications: {
901
- type: "boolean",
902
- description: "Show macOS notifications (default: true)"
903
- },
904
- workingDirectory: {
905
- type: "string",
906
- description: "Working directory for Claude Code sessions (default: current working directory)"
907
- }
908
- },
909
- required: []
910
- };
911
- } else if (tt.type === "yolo_stop") {
912
- inputSchema = {
913
- type: "object",
914
- properties: {},
915
- required: []
916
- };
917
- } else if (tt.type === "yolo_status") {
918
- inputSchema = {
919
- type: "object",
920
- properties: {},
921
- required: []
922
- };
923
546
  } else {
924
547
  inputSchema = {
925
548
  type: "object",
@@ -1649,109 +1272,6 @@ _Ticket content has been appended with your update._`
1649
1272
  isError: true
1650
1273
  };
1651
1274
  }
1652
- } else if (ticketTool.type === "yolo_start") {
1653
- const args = parseWatcherStartArgs(request.params.arguments);
1654
- const workingDirectory = args.workingDirectory ?? process.cwd();
1655
- try {
1656
- const result = startWatcher(
1657
- {
1658
- projectSlug: ticketTool.projectSlug,
1659
- projectToken: config.projectToken,
1660
- convexUrl: config.convexUrl,
1661
- workingDirectory
1662
- },
1663
- args
1664
- );
1665
- if (result.success) {
1666
- const configInfo = result.config ? `
1667
-
1668
- **Configuration:**
1669
- - Poll Interval: ${result.config.pollIntervalMs / 1e3}s
1670
- - Max Parallel: ${result.config.maxParallel}
1671
- - Ticket Timeout: ${result.config.ticketTimeout / 1e3 / 60} min
1672
- - Working Directory: ${result.config.workingDirectory}` : "";
1673
- const monitoringSection = result.monitoringPrompt && !result.alreadyRunning ? `
1674
-
1675
- ---
1676
-
1677
- ## \u{1F514} Real-time Monitoring
1678
-
1679
- To receive notifications when tickets complete, spawn a background monitoring agent:
1680
-
1681
- <monitoring_prompt>
1682
- ${result.monitoringPrompt}
1683
- </monitoring_prompt>
1684
-
1685
- **Usage:** Use the Task tool with subagent_type="general-purpose", run_in_background=true, and the prompt above.` : "";
1686
- const text = result.alreadyRunning ? `\u2139\uFE0F ${result.message}${configInfo}` : `\u2705 ${result.message}
1687
-
1688
- The daemon is now polling for YOLO-flagged tickets and will spawn Claude CLI processes to execute them.
1689
-
1690
- Logs: \`.ppm/yolo/logs/\`${configInfo}${monitoringSection}`;
1691
- return {
1692
- content: [{ type: "text", text }]
1693
- };
1694
- } else {
1695
- return {
1696
- content: [{ type: "text", text: `\u274C ${result.message}` }],
1697
- isError: true
1698
- };
1699
- }
1700
- } catch (error) {
1701
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1702
- console.error(`[MCP] tickets_yolo_start error:`, error);
1703
- return {
1704
- content: [{ type: "text", text: `Error starting watcher daemon: ${errorMessage}` }],
1705
- isError: true
1706
- };
1707
- }
1708
- } else if (ticketTool.type === "yolo_stop") {
1709
- const workingDirectory = process.cwd();
1710
- try {
1711
- const result = stopWatcher(workingDirectory);
1712
- if (result.success) {
1713
- return {
1714
- content: [{
1715
- type: "text",
1716
- text: result.wasRunning ? `\u2705 ${result.message}
1717
-
1718
- The daemon has been stopped. Any running ticket executions will complete.` : `\u2139\uFE0F ${result.message}`
1719
- }]
1720
- };
1721
- } else {
1722
- return {
1723
- content: [{ type: "text", text: `\u274C ${result.message}` }],
1724
- isError: true
1725
- };
1726
- }
1727
- } catch (error) {
1728
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1729
- console.error(`[MCP] tickets_yolo_stop error:`, error);
1730
- return {
1731
- content: [{ type: "text", text: `Error stopping watcher daemon: ${errorMessage}` }],
1732
- isError: true
1733
- };
1734
- }
1735
- } else if (ticketTool.type === "yolo_status") {
1736
- const workingDirectory = process.cwd();
1737
- try {
1738
- const result = getWatcherStatus(workingDirectory);
1739
- if (result.formattedStatus) {
1740
- return {
1741
- content: [{ type: "text", text: result.formattedStatus }]
1742
- };
1743
- }
1744
- return {
1745
- content: [{ type: "text", text: `\u2139\uFE0F ${result.message ?? "No watcher status available"}` }]
1746
- };
1747
- } catch (error) {
1748
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1749
- console.error(`[MCP] tickets_yolo_status error:`, error);
1750
- return {
1751
- content: [{ type: "text", text: `Error getting watcher status: ${errorMessage}` }],
1752
- isError: true
1753
- };
1754
- }
1755
1275
  }
1756
1276
  }
1757
1277
  const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);