@puzzmo/cli 1.0.33 → 1.0.35

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.
Files changed (47) hide show
  1. package/lib/agents/claude.d.ts +7 -0
  2. package/lib/agents/claude.js +71 -0
  3. package/lib/agents/claude.js.map +1 -0
  4. package/lib/agents/codex.d.ts +6 -0
  5. package/lib/agents/codex.js +92 -0
  6. package/lib/agents/codex.js.map +1 -0
  7. package/lib/agents/copilot.d.ts +6 -0
  8. package/lib/agents/copilot.js +125 -0
  9. package/lib/agents/copilot.js.map +1 -0
  10. package/lib/agents/gemini.d.ts +7 -0
  11. package/lib/agents/gemini.js +73 -0
  12. package/lib/agents/gemini.js.map +1 -0
  13. package/lib/agents/index.d.ts +5 -0
  14. package/lib/agents/index.js +15 -0
  15. package/lib/agents/index.js.map +1 -0
  16. package/lib/agents/opencode.d.ts +7 -0
  17. package/lib/agents/opencode.js +161 -0
  18. package/lib/agents/opencode.js.map +1 -0
  19. package/lib/agents/stream-json-cli.d.ts +14 -0
  20. package/lib/agents/stream-json-cli.js +75 -0
  21. package/lib/agents/stream-json-cli.js.map +1 -0
  22. package/lib/agents/types.d.ts +39 -0
  23. package/lib/agents/types.js +2 -0
  24. package/lib/agents/types.js.map +1 -0
  25. package/lib/commands/agent-test.d.ts +5 -0
  26. package/lib/commands/agent-test.js +81 -0
  27. package/lib/commands/agent-test.js.map +1 -0
  28. package/lib/commands/game/create.js +6 -4
  29. package/lib/commands/game/create.js.map +1 -1
  30. package/lib/index.js +5 -0
  31. package/lib/index.js.map +1 -1
  32. package/lib/tui/pipeline.d.ts +1 -2
  33. package/lib/tui/pipeline.js +167 -267
  34. package/lib/tui/pipeline.js.map +1 -1
  35. package/package.json +4 -1
  36. package/src/agents/claude.ts +74 -0
  37. package/src/agents/codex.ts +84 -0
  38. package/src/agents/copilot.ts +120 -0
  39. package/src/agents/gemini.ts +78 -0
  40. package/src/agents/index.ts +20 -0
  41. package/src/agents/opencode.ts +156 -0
  42. package/src/agents/stream-json-cli.ts +88 -0
  43. package/src/agents/types.ts +24 -0
  44. package/src/commands/agent-test.ts +83 -0
  45. package/src/commands/game/create.ts +6 -4
  46. package/src/index.ts +5 -0
  47. package/src/tui/pipeline.ts +158 -268
@@ -0,0 +1,88 @@
1
+ import { spawn, type ChildProcess } from "node:child_process"
2
+
3
+ import type { AgentEvent, RunInput } from "./types.js"
4
+
5
+ /**
6
+ * Shared driver for any CLI that emits NDJSON over stdout when given a prompt
7
+ * in print/headless mode. Used by both the claude and gemini adapters.
8
+ */
9
+ export type StreamJsonCliConfig = {
10
+ cmd: string
11
+ buildArgs: (prompt: string) => string[]
12
+ /** Map one parsed JSON line to an AgentEvent (or null to drop). */
13
+ parseLine: (line: string) => AgentEvent | null
14
+ /** Optional env mutator. Receives a copy of process.env. */
15
+ env?: (base: NodeJS.ProcessEnv) => NodeJS.ProcessEnv
16
+ }
17
+
18
+ export const runStreamJsonCli = async function* (config: StreamJsonCliConfig, input: RunInput): AsyncIterable<AgentEvent> {
19
+ const args = config.buildArgs(input.prompt)
20
+ const env = config.env ? config.env({ ...process.env }) : { ...process.env }
21
+
22
+ let proc: ChildProcess
23
+ try {
24
+ proc = spawn(config.cmd, args, {
25
+ cwd: input.cwd ?? process.cwd(),
26
+ env,
27
+ stdio: ["ignore", "pipe", "pipe"],
28
+ })
29
+ } catch (e: any) {
30
+ yield { type: "error", message: `Failed to spawn ${config.cmd}: ${e.message}` }
31
+ return
32
+ }
33
+
34
+ if (input.signal) input.signal.addEventListener("abort", () => proc.kill())
35
+
36
+ // Bridge node streams + child events into an async generator.
37
+ const queue: AgentEvent[] = []
38
+ let done = false
39
+ let resolveWaiting: (() => void) | null = null
40
+ let buf = ""
41
+ let sawResult = false
42
+
43
+ const push = (e: AgentEvent) => {
44
+ if (e.type === "result") sawResult = true
45
+ queue.push(e)
46
+ resolveWaiting?.()
47
+ resolveWaiting = null
48
+ }
49
+
50
+ const flushLine = (line: string) => {
51
+ if (!line.trim()) return
52
+ const event = config.parseLine(line)
53
+ if (event) push(event)
54
+ }
55
+
56
+ proc.stdout?.on("data", (chunk: Buffer) => {
57
+ buf += chunk.toString("utf8")
58
+ const lines = buf.split("\n")
59
+ buf = lines.pop() ?? ""
60
+ for (const line of lines) flushLine(line)
61
+ })
62
+
63
+ proc.stderr?.on("data", (chunk: Buffer) => {
64
+ const text = chunk.toString("utf8").trim()
65
+ if (text) push({ type: "error", message: text })
66
+ })
67
+
68
+ proc.on("close", (code) => {
69
+ if (buf.trim()) flushLine(buf)
70
+ if (code !== 0 && !sawResult) push({ type: "result", ok: false })
71
+ done = true
72
+ resolveWaiting?.()
73
+ resolveWaiting = null
74
+ })
75
+
76
+ proc.on("error", (err) => {
77
+ push({ type: "error", message: err.message })
78
+ done = true
79
+ resolveWaiting?.()
80
+ resolveWaiting = null
81
+ })
82
+
83
+ while (true) {
84
+ if (queue.length > 0) yield queue.shift()!
85
+ else if (done) return
86
+ else await new Promise<void>((r) => (resolveWaiting = r))
87
+ }
88
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Normalized event union emitted by every agent adapter. Keep this small —
3
+ * adapters fold their richer native event shapes into one of these.
4
+ */
5
+ export type AgentEvent =
6
+ | { type: "text"; text: string } // assistant text (delta or full chunk)
7
+ | { type: "tool_use"; name: string; summary: string } // tool starting
8
+ | { type: "tool_result"; ok: boolean; summary: string } // tool finished
9
+ | { type: "thinking"; text: string }
10
+ | { type: "system"; text: string }
11
+ | { type: "result"; ok: boolean; costUSD?: number }
12
+ | { type: "error"; message: string }
13
+
14
+ export type RunInput = {
15
+ prompt: string
16
+ cwd?: string
17
+ signal?: AbortSignal
18
+ }
19
+
20
+ export interface Agent {
21
+ readonly name: string
22
+ /** Yield events as the agent runs. Should complete (or throw) when the run finishes. */
23
+ run(input: RunInput): AsyncIterable<AgentEvent>
24
+ }
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs"
2
+
3
+ import * as p from "@clack/prompts"
4
+
5
+ import { agentNames, getAgent } from "../agents/index.js"
6
+
7
+ const dim = (s: string) => `\x1b[2m${s}\x1b[0m`
8
+ const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`
9
+ const red = (s: string) => `\x1b[31m${s}\x1b[0m`
10
+ const green = (s: string) => `\x1b[32m${s}\x1b[0m`
11
+
12
+ /**
13
+ * Undocumented: send "say hello world" to a chosen agent through the meta-library
14
+ * and stream events to stdout. Use this to verify each adapter end-to-end.
15
+ */
16
+ export const agentTest = async () => {
17
+ p.intro("Agent SDK Test")
18
+
19
+ const choice = await p.select({
20
+ message: "Which agent?",
21
+ options: agentNames().map((name) => ({ value: name, label: name })),
22
+ initialValue: "claude",
23
+ })
24
+ if (p.isCancel(choice)) process.exit(0)
25
+ const agent = getAgent(String(choice))
26
+ if (!agent) {
27
+ p.log.error(`Unknown agent: ${choice}`)
28
+ process.exit(1)
29
+ }
30
+
31
+ const prompt = "say hello world"
32
+ const cwd = process.cwd()
33
+ const debugPath = "/tmp/puzzmo-agent-test.log"
34
+ fs.writeFileSync(debugPath, "")
35
+ const log = (msg: string) => fs.appendFileSync(debugPath, `[${new Date().toISOString()}] ${msg}\n`)
36
+
37
+ log(`agent=${agent.name} cwd=${cwd}`)
38
+ log(`prompt=${JSON.stringify(prompt)}`)
39
+
40
+ process.stderr.write(`\n${dim("[agent]")} ${agent.name}\n`)
41
+ process.stderr.write(`${dim("[cwd]")} ${cwd}\n`)
42
+ process.stderr.write(`${dim("[log]")} ${debugPath}\n`)
43
+ process.stderr.write(`${dim("---- events below (Ctrl+C to abort) ----")}\n\n`)
44
+
45
+ const controller = new AbortController()
46
+ const onSig = () => {
47
+ process.stderr.write(`\n${dim("[interrupt]")}\n`)
48
+ controller.abort()
49
+ }
50
+ process.on("SIGINT", onSig)
51
+
52
+ const start = Date.now()
53
+ let count = 0
54
+ try {
55
+ for await (const event of agent.run({ prompt, cwd, signal: controller.signal })) {
56
+ count++
57
+ const elapsed = ((Date.now() - start) / 1000).toFixed(3)
58
+ log(`+${elapsed}s ${event.type} ${JSON.stringify(event).slice(0, 400)}`)
59
+ printEvent(event)
60
+ }
61
+ } catch (e: any) {
62
+ process.stderr.write(`\n${red("[run threw]")} ${e.message ?? e}\n`)
63
+ log(`THROW ${e.message ?? e}`)
64
+ process.exit(1)
65
+ } finally {
66
+ process.off("SIGINT", onSig)
67
+ }
68
+
69
+ const elapsed = ((Date.now() - start) / 1000).toFixed(3)
70
+ process.stderr.write(`\n${dim(`[+${elapsed}s done]`)} events=${count}\n`)
71
+ log(`done events=${count} elapsed=${elapsed}s`)
72
+ }
73
+
74
+ const printEvent = (e: { type: string } & Record<string, any>) => {
75
+ if (e.type === "text") process.stdout.write(e.text)
76
+ else if (e.type === "thinking") process.stdout.write(dim(e.text))
77
+ else if (e.type === "tool_use") process.stdout.write(`\n${yellow(`[${e.name}]`)} ${dim(e.summary ?? "")}\n`)
78
+ else if (e.type === "tool_result") process.stdout.write(`${e.ok ? green(" ✓") : red(" ✗")} ${dim(e.summary ?? "")}\n`)
79
+ else if (e.type === "system") process.stdout.write(dim(`\n[${e.text}]\n`))
80
+ else if (e.type === "result")
81
+ process.stdout.write(`\n${e.ok ? green("== Done") : red("== Failed")}${e.costUSD ? ` ($${e.costUSD.toFixed(4)})` : ""}\n`)
82
+ else if (e.type === "error") process.stdout.write(red(`\n[error] ${e.message}\n`))
83
+ }
@@ -3,6 +3,7 @@ import path from "node:path"
3
3
  import { createRequire } from "node:module"
4
4
  import * as p from "@clack/prompts"
5
5
 
6
+ import { agentNames } from "../../agents/index.js"
6
7
  import { detectAgent } from "../../wizard/agent-detect.js"
7
8
  import { downloadPage } from "../../download/page-downloader.js"
8
9
  import { runCommand, gitCommit } from "../../util/exec.js"
@@ -260,10 +261,11 @@ export const gameCreate = async (args: string[]) => {
260
261
  gameDir = setupRepoGame(tmpDir, slug, repo.repoRoot, parentFolder)
261
262
  }
262
263
 
263
- // Step 7: Detect agent and run skills pipeline
264
- const agents = detectAgent()
264
+ // Step 7: Detect agent and run skills pipeline. Only offer CLIs we have an adapter for.
265
+ const supported = new Set(agentNames())
266
+ const agents = detectAgent().filter((a) => supported.has(a.id))
265
267
  const agentChoices = [
266
- ...agents.map((a) => ({ value: a.binary, label: `${a.displayName} (${a.path})` })),
268
+ ...agents.map((a) => ({ value: a.id, label: `${a.displayName} (${a.path})` })),
267
269
  { value: "none", label: "None - I'll run the steps manually" },
268
270
  ]
269
271
 
@@ -274,7 +276,7 @@ export const gameCreate = async (args: string[]) => {
274
276
  selectedAgent = (await p.select({ message: "Which LLM agent do you use?", options: agentChoices })) as string
275
277
  if (p.isCancel(selectedAgent)) process.exit(0)
276
278
  } else {
277
- p.log.info("No LLM agents detected (checked: claude, codex)")
279
+ p.log.info(`No supported LLM agents detected (checked: ${agentNames().join(", ")})`)
278
280
  selectedAgent = "none"
279
281
  }
280
282
 
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { agentTest } from "./commands/agent-test.js"
3
4
  import { login } from "./commands/login.js"
4
5
  import { upload } from "./commands/upload.js"
5
6
  import { validate } from "./commands/validate.js"
@@ -57,6 +58,10 @@ const run = async () => {
57
58
  await migrate()
58
59
  break
59
60
  }
61
+ case "agent-test": {
62
+ await agentTest()
63
+ break
64
+ }
60
65
  default:
61
66
  printUsage()
62
67
  break