@puzzmo/cli 1.0.33 → 1.0.34
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/lib/agents/claude.d.ts +5 -0
- package/lib/agents/claude.js +69 -0
- package/lib/agents/claude.js.map +1 -0
- package/lib/agents/codex.d.ts +4 -0
- package/lib/agents/codex.js +90 -0
- package/lib/agents/codex.js.map +1 -0
- package/lib/agents/copilot.d.ts +4 -0
- package/lib/agents/copilot.js +123 -0
- package/lib/agents/copilot.js.map +1 -0
- package/lib/agents/gemini.d.ts +5 -0
- package/lib/agents/gemini.js +69 -0
- package/lib/agents/gemini.js.map +1 -0
- package/lib/agents/index.d.ts +5 -0
- package/lib/agents/index.js +15 -0
- package/lib/agents/index.js.map +1 -0
- package/lib/agents/opencode.d.ts +5 -0
- package/lib/agents/opencode.js +159 -0
- package/lib/agents/opencode.js.map +1 -0
- package/lib/agents/stream-json-cli.d.ts +12 -0
- package/lib/agents/stream-json-cli.js +75 -0
- package/lib/agents/stream-json-cli.js.map +1 -0
- package/lib/agents/types.d.ts +37 -0
- package/lib/agents/types.js +2 -0
- package/lib/agents/types.js.map +1 -0
- package/lib/commands/agent-test.d.ts +3 -0
- package/lib/commands/agent-test.js +79 -0
- package/lib/commands/agent-test.js.map +1 -0
- package/lib/commands/game/create.js +6 -4
- package/lib/commands/game/create.js.map +1 -1
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -1
- package/lib/tui/pipeline.d.ts +1 -2
- package/lib/tui/pipeline.js +160 -267
- package/lib/tui/pipeline.js.map +1 -1
- package/package.json +4 -1
- package/src/agents/claude.ts +72 -0
- package/src/agents/codex.ts +82 -0
- package/src/agents/copilot.ts +118 -0
- package/src/agents/gemini.ts +74 -0
- package/src/agents/index.ts +20 -0
- package/src/agents/opencode.ts +154 -0
- package/src/agents/stream-json-cli.ts +86 -0
- package/src/agents/types.ts +22 -0
- package/src/commands/agent-test.ts +81 -0
- package/src/commands/game/create.ts +6 -4
- package/src/index.ts +5 -0
- package/src/tui/pipeline.ts +151 -268
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from "node:child_process"
|
|
2
|
+
|
|
3
|
+
import type { AgentEvent, RunInput } from "./types.js"
|
|
4
|
+
|
|
5
|
+
/** Shared driver for any CLI that emits NDJSON over stdout when given a prompt
|
|
6
|
+
* in print/headless mode. Used by both the claude and gemini adapters. */
|
|
7
|
+
export type StreamJsonCliConfig = {
|
|
8
|
+
cmd: string
|
|
9
|
+
buildArgs: (prompt: string) => string[]
|
|
10
|
+
/** Map one parsed JSON line to an AgentEvent (or null to drop). */
|
|
11
|
+
parseLine: (line: string) => AgentEvent | null
|
|
12
|
+
/** Optional env mutator. Receives a copy of process.env. */
|
|
13
|
+
env?: (base: NodeJS.ProcessEnv) => NodeJS.ProcessEnv
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const runStreamJsonCli = async function* (config: StreamJsonCliConfig, input: RunInput): AsyncIterable<AgentEvent> {
|
|
17
|
+
const args = config.buildArgs(input.prompt)
|
|
18
|
+
const env = config.env ? config.env({ ...process.env }) : { ...process.env }
|
|
19
|
+
|
|
20
|
+
let proc: ChildProcess
|
|
21
|
+
try {
|
|
22
|
+
proc = spawn(config.cmd, args, {
|
|
23
|
+
cwd: input.cwd ?? process.cwd(),
|
|
24
|
+
env,
|
|
25
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
26
|
+
})
|
|
27
|
+
} catch (e: any) {
|
|
28
|
+
yield { type: "error", message: `Failed to spawn ${config.cmd}: ${e.message}` }
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (input.signal) input.signal.addEventListener("abort", () => proc.kill())
|
|
33
|
+
|
|
34
|
+
// Bridge node streams + child events into an async generator.
|
|
35
|
+
const queue: AgentEvent[] = []
|
|
36
|
+
let done = false
|
|
37
|
+
let resolveWaiting: (() => void) | null = null
|
|
38
|
+
let buf = ""
|
|
39
|
+
let sawResult = false
|
|
40
|
+
|
|
41
|
+
const push = (e: AgentEvent) => {
|
|
42
|
+
if (e.type === "result") sawResult = true
|
|
43
|
+
queue.push(e)
|
|
44
|
+
resolveWaiting?.()
|
|
45
|
+
resolveWaiting = null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const flushLine = (line: string) => {
|
|
49
|
+
if (!line.trim()) return
|
|
50
|
+
const event = config.parseLine(line)
|
|
51
|
+
if (event) push(event)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
proc.stdout?.on("data", (chunk: Buffer) => {
|
|
55
|
+
buf += chunk.toString("utf8")
|
|
56
|
+
const lines = buf.split("\n")
|
|
57
|
+
buf = lines.pop() ?? ""
|
|
58
|
+
for (const line of lines) flushLine(line)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
proc.stderr?.on("data", (chunk: Buffer) => {
|
|
62
|
+
const text = chunk.toString("utf8").trim()
|
|
63
|
+
if (text) push({ type: "error", message: text })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
proc.on("close", (code) => {
|
|
67
|
+
if (buf.trim()) flushLine(buf)
|
|
68
|
+
if (code !== 0 && !sawResult) push({ type: "result", ok: false })
|
|
69
|
+
done = true
|
|
70
|
+
resolveWaiting?.()
|
|
71
|
+
resolveWaiting = null
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
proc.on("error", (err) => {
|
|
75
|
+
push({ type: "error", message: err.message })
|
|
76
|
+
done = true
|
|
77
|
+
resolveWaiting?.()
|
|
78
|
+
resolveWaiting = null
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
while (true) {
|
|
82
|
+
if (queue.length > 0) yield queue.shift()!
|
|
83
|
+
else if (done) return
|
|
84
|
+
else await new Promise<void>((r) => (resolveWaiting = r))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Normalized event union emitted by every agent adapter. Keep this small —
|
|
2
|
+
* adapters fold their richer native event shapes into one of these. */
|
|
3
|
+
export type AgentEvent =
|
|
4
|
+
| { type: "text"; text: string } // assistant text (delta or full chunk)
|
|
5
|
+
| { type: "tool_use"; name: string; summary: string } // tool starting
|
|
6
|
+
| { type: "tool_result"; ok: boolean; summary: string } // tool finished
|
|
7
|
+
| { type: "thinking"; text: string }
|
|
8
|
+
| { type: "system"; text: string }
|
|
9
|
+
| { type: "result"; ok: boolean; costUSD?: number }
|
|
10
|
+
| { type: "error"; message: string }
|
|
11
|
+
|
|
12
|
+
export type RunInput = {
|
|
13
|
+
prompt: string
|
|
14
|
+
cwd?: string
|
|
15
|
+
signal?: AbortSignal
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Agent {
|
|
19
|
+
readonly name: string
|
|
20
|
+
/** Yield events as the agent runs. Should complete (or throw) when the run finishes. */
|
|
21
|
+
run(input: RunInput): AsyncIterable<AgentEvent>
|
|
22
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
/** Undocumented: send "say hello world" to a chosen agent through the meta-library
|
|
13
|
+
* and stream events to stdout. Use this to verify each adapter end-to-end. */
|
|
14
|
+
export const agentTest = async () => {
|
|
15
|
+
p.intro("Agent SDK Test")
|
|
16
|
+
|
|
17
|
+
const choice = await p.select({
|
|
18
|
+
message: "Which agent?",
|
|
19
|
+
options: agentNames().map((name) => ({ value: name, label: name })),
|
|
20
|
+
initialValue: "claude",
|
|
21
|
+
})
|
|
22
|
+
if (p.isCancel(choice)) process.exit(0)
|
|
23
|
+
const agent = getAgent(String(choice))
|
|
24
|
+
if (!agent) {
|
|
25
|
+
p.log.error(`Unknown agent: ${choice}`)
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const prompt = "say hello world"
|
|
30
|
+
const cwd = process.cwd()
|
|
31
|
+
const debugPath = "/tmp/puzzmo-agent-test.log"
|
|
32
|
+
fs.writeFileSync(debugPath, "")
|
|
33
|
+
const log = (msg: string) => fs.appendFileSync(debugPath, `[${new Date().toISOString()}] ${msg}\n`)
|
|
34
|
+
|
|
35
|
+
log(`agent=${agent.name} cwd=${cwd}`)
|
|
36
|
+
log(`prompt=${JSON.stringify(prompt)}`)
|
|
37
|
+
|
|
38
|
+
process.stderr.write(`\n${dim("[agent]")} ${agent.name}\n`)
|
|
39
|
+
process.stderr.write(`${dim("[cwd]")} ${cwd}\n`)
|
|
40
|
+
process.stderr.write(`${dim("[log]")} ${debugPath}\n`)
|
|
41
|
+
process.stderr.write(`${dim("---- events below (Ctrl+C to abort) ----")}\n\n`)
|
|
42
|
+
|
|
43
|
+
const controller = new AbortController()
|
|
44
|
+
const onSig = () => {
|
|
45
|
+
process.stderr.write(`\n${dim("[interrupt]")}\n`)
|
|
46
|
+
controller.abort()
|
|
47
|
+
}
|
|
48
|
+
process.on("SIGINT", onSig)
|
|
49
|
+
|
|
50
|
+
const start = Date.now()
|
|
51
|
+
let count = 0
|
|
52
|
+
try {
|
|
53
|
+
for await (const event of agent.run({ prompt, cwd, signal: controller.signal })) {
|
|
54
|
+
count++
|
|
55
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(3)
|
|
56
|
+
log(`+${elapsed}s ${event.type} ${JSON.stringify(event).slice(0, 400)}`)
|
|
57
|
+
printEvent(event)
|
|
58
|
+
}
|
|
59
|
+
} catch (e: any) {
|
|
60
|
+
process.stderr.write(`\n${red("[run threw]")} ${e.message ?? e}\n`)
|
|
61
|
+
log(`THROW ${e.message ?? e}`)
|
|
62
|
+
process.exit(1)
|
|
63
|
+
} finally {
|
|
64
|
+
process.off("SIGINT", onSig)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(3)
|
|
68
|
+
process.stderr.write(`\n${dim(`[+${elapsed}s done]`)} events=${count}\n`)
|
|
69
|
+
log(`done events=${count} elapsed=${elapsed}s`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const printEvent = (e: { type: string } & Record<string, any>) => {
|
|
73
|
+
if (e.type === "text") process.stdout.write(e.text)
|
|
74
|
+
else if (e.type === "thinking") process.stdout.write(dim(e.text))
|
|
75
|
+
else if (e.type === "tool_use") process.stdout.write(`\n${yellow(`[${e.name}]`)} ${dim(e.summary ?? "")}\n`)
|
|
76
|
+
else if (e.type === "tool_result") process.stdout.write(`${e.ok ? green(" ✓") : red(" ✗")} ${dim(e.summary ?? "")}\n`)
|
|
77
|
+
else if (e.type === "system") process.stdout.write(dim(`\n[${e.text}]\n`))
|
|
78
|
+
else if (e.type === "result")
|
|
79
|
+
process.stdout.write(`\n${e.ok ? green("== Done") : red("== Failed")}${e.costUSD ? ` ($${e.costUSD.toFixed(4)})` : ""}\n`)
|
|
80
|
+
else if (e.type === "error") process.stdout.write(red(`\n[error] ${e.message}\n`))
|
|
81
|
+
}
|
|
@@ -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
|
|
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.
|
|
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(
|
|
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
|