test-stdin-stream.ts 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import path from "path"
  2. import { fileURLToPath } from "url"
  3. import readline from "readline"
  4. import { execa } from "execa"
  5. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  6. const cliRoot = path.resolve(__dirname, "..")
  7. async function main() {
  8. const child = execa(
  9. "pnpm",
  10. ["dev", "--print", "--stdin-prompt-stream", "--provider", "roo", "--output-format", "stream-json"],
  11. {
  12. cwd: cliRoot,
  13. stdin: "pipe",
  14. stdout: "pipe",
  15. stderr: "pipe",
  16. reject: false,
  17. forceKillAfterDelay: 2_000,
  18. },
  19. )
  20. child.stdout?.on("data", (chunk) => process.stdout.write(chunk))
  21. child.stderr?.on("data", (chunk) => process.stderr.write(chunk))
  22. console.log("[wrapper] Type a message and press Enter to send it.")
  23. console.log("[wrapper] Type /exit to close stdin and let the CLI finish.")
  24. let requestCounter = 0
  25. let hasStartedTask = false
  26. const sendCommand = (payload: Record<string, unknown>) => {
  27. if (child.stdin?.destroyed) {
  28. return
  29. }
  30. child.stdin?.write(JSON.stringify(payload) + "\n")
  31. }
  32. const rl = readline.createInterface({
  33. input: process.stdin,
  34. output: process.stdout,
  35. terminal: true,
  36. })
  37. rl.on("line", (line) => {
  38. if (line.trim() === "/exit") {
  39. console.log("[wrapper] Closing stdin...")
  40. sendCommand({
  41. command: "shutdown",
  42. requestId: `shutdown-${Date.now()}-${++requestCounter}`,
  43. })
  44. child.stdin?.end()
  45. rl.close()
  46. return
  47. }
  48. const command = hasStartedTask ? "message" : "start"
  49. sendCommand({
  50. command,
  51. requestId: `${command}-${Date.now()}-${++requestCounter}`,
  52. prompt: line,
  53. })
  54. hasStartedTask = true
  55. })
  56. const onSignal = (signal: NodeJS.Signals) => {
  57. console.log(`[wrapper] Received ${signal}, forwarding to CLI...`)
  58. rl.close()
  59. child.kill(signal)
  60. }
  61. process.on("SIGINT", () => onSignal("SIGINT"))
  62. process.on("SIGTERM", () => onSignal("SIGTERM"))
  63. const result = await child
  64. rl.close()
  65. console.log(`[wrapper] CLI exited with code ${result.exitCode}`)
  66. process.exit(result.exitCode ?? 1)
  67. }
  68. main().catch((error) => {
  69. console.error("[wrapper] Fatal error:", error)
  70. process.exit(1)
  71. })