@puzzmo/cli 1.0.17 → 1.0.19

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.
@@ -5,6 +5,7 @@ import path from "node:path"
5
5
  import { execSync } from "node:child_process"
6
6
 
7
7
  import { skillsPipeline } from "../skills/registry.js"
8
+ import { getMcpUrl } from "../skills/mcp-client.js"
8
9
 
9
10
  type StepState = "pending" | "running" | "success" | "failed" | "skipped"
10
11
  type Phase = "agent" | "build" | "commit" | "idle"
@@ -57,24 +58,42 @@ const buildAgentCmd = (agent: string, prompt: string): { cmd: string; args: stri
57
58
  return { cmd: agent, args: [prompt] }
58
59
  }
59
60
 
60
- const buildPrompt = (skillName: string): string => {
61
- return `Use the MCP prompt "${skillName}" from the dev.puzzmo.com server and follow its instructions. The game source is in the current directory.`
61
+ const buildPrompt = (skillName: string, mcpUrl: string | null): string => {
62
+ if (!mcpUrl) return `Run the skill "${skillName}". The game source is in the current directory.`
63
+
64
+ return `First, fetch the skill instructions by using WebFetch to make a POST request to:
65
+ ${mcpUrl}
66
+
67
+ With headers:
68
+ - Content-Type: application/json
69
+ - Accept: application/json, text/event-stream
70
+
71
+ And body:
72
+ {"jsonrpc":"2.0","id":1,"method":"prompts/get","params":{"name":"${skillName}"}}
73
+
74
+ The response will be in SSE format. Parse the "data:" line to get the JSON result, which contains the skill instructions in result.messages[0].content.text.
75
+
76
+ Then follow those instructions. The game source is in the current directory.`
62
77
  }
63
78
 
64
79
  class PipelineTUI {
65
80
  private states: StepState[]
66
81
  private currentStep = 0
67
82
  private outputLines: string[] = []
83
+ private chatLines: string[] = []
68
84
  private phase: Phase = "idle"
69
85
  private done = false
70
86
  private activeProc: ChildProcess | null = null
71
87
  private sidebarWidth = 30
88
+ private logsDir: string
72
89
 
73
90
  constructor(
74
91
  private agent: string,
75
92
  private gameDir: string,
76
93
  ) {
77
94
  this.states = skillsPipeline.map(() => "pending")
95
+ this.logsDir = path.join(gameDir, ".puzzmo", "logs")
96
+ fs.mkdirSync(this.logsDir, { recursive: true })
78
97
  }
79
98
 
80
99
  private get rows(): number {
@@ -132,7 +151,7 @@ class PipelineTUI {
132
151
  const isActive = skillIndex === this.currentStep && !this.done
133
152
  const icon = stateIcon[state]
134
153
  const colorFn = stateColor[state]
135
- const label = `${icon} ${skill.name}${skill.optional ? " *" : ""}`
154
+ const label = `${icon} ${skill.name}`
136
155
  const styled = isActive ? bold(colorFn(label)) : colorFn(label)
137
156
  const rawLen = this.stripAnsi(styled).length
138
157
  buf += "│ " + styled + " ".repeat(Math.max(0, sidebarWidth - rawLen - 3)) + "│"
@@ -147,8 +166,6 @@ class PipelineTUI {
147
166
  : dim(`${completedCount}/${skillsPipeline.length} done`)
148
167
  const rawLen = this.stripAnsi(status).length
149
168
  buf += "│ " + status + " ".repeat(Math.max(0, sidebarWidth - rawLen - 3)) + "│"
150
- } else if (skillIndex === skillsPipeline.length + 2) {
151
- buf += "│ " + dim("* = optional") + " ".repeat(Math.max(0, sidebarWidth - 15)) + "│"
152
169
  } else {
153
170
  buf += "│" + " ".repeat(sidebarWidth - 2) + "│"
154
171
  }
@@ -205,13 +222,13 @@ class PipelineTUI {
205
222
  for (const line of rawLines) {
206
223
  const parsed = this.parseStreamLine(line)
207
224
  if (parsed) {
208
- // Split multi-line parsed output into separate display lines
209
225
  for (const displayLine of parsed.split("\n")) {
210
226
  this.outputLines.push(displayLine)
227
+ this.chatLines.push(this.stripAnsi(displayLine))
211
228
  }
212
229
  }
213
230
  }
214
- // Keep buffer bounded
231
+ // Keep display buffer bounded, but chatLines grows unbounded per skill
215
232
  if (this.outputLines.length > 500) {
216
233
  this.outputLines = this.outputLines.slice(-this.maxOutputLines)
217
234
  }
@@ -389,21 +406,19 @@ class PipelineTUI {
389
406
  this.states[stepIndex] = "running"
390
407
  this.currentStep = stepIndex
391
408
  this.outputLines = []
409
+ this.chatLines = []
392
410
  this.phase = "agent"
393
411
  this.render()
394
412
 
395
- const prompt = buildPrompt(skill.name)
413
+ const mcpUrl = getMcpUrl(this.gameDir)
414
+ const prompt = buildPrompt(skill.name, mcpUrl)
396
415
  let success = await this.runAgent(prompt)
397
416
 
398
417
  if (!success) {
399
- if (skill.optional) {
400
- this.states[stepIndex] = "skipped"
401
- this.render()
402
- return true
403
- }
404
418
  this.appendOutput("\n--- Retrying ---\n")
405
419
  success = await this.runAgent(prompt)
406
420
  if (!success) {
421
+ this.writeSkillLog(skill.name)
407
422
  this.states[stepIndex] = "failed"
408
423
  this.render()
409
424
  return false
@@ -422,11 +437,7 @@ class PipelineTUI {
422
437
 
423
438
  const retry = this.runCmd("npx vite build")
424
439
  if (!retry.success) {
425
- if (skill.optional) {
426
- this.states[stepIndex] = "skipped"
427
- this.render()
428
- return true
429
- }
440
+ this.writeSkillLog(skill.name)
430
441
  this.states[stepIndex] = "failed"
431
442
  this.render()
432
443
  return false
@@ -441,11 +452,20 @@ class PipelineTUI {
441
452
  if (commitResult.success) this.appendOutput("Committed.")
442
453
  else this.appendOutput("No changes to commit.")
443
454
 
455
+ // Write chat log for this skill
456
+ this.writeSkillLog(skill.name)
457
+
444
458
  this.states[stepIndex] = "success"
445
459
  this.render()
446
460
  return true
447
461
  }
448
462
 
463
+ /** Writes the collected chat lines for a skill to a log file */
464
+ private writeSkillLog(skillName: string) {
465
+ const logPath = path.join(this.logsDir, `${skillName}.txt`)
466
+ fs.writeFileSync(logPath, this.chatLines.join("\n") + "\n")
467
+ }
468
+
449
469
  /** Set up keyboard handling (Ctrl+C to quit) */
450
470
  private setupInput() {
451
471
  if (process.stdin.isTTY) {