From c50bfec01b29ef338f0d2c7b2c75e0cfc31a74a3 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 16 Jan 2026 03:18:56 +0100 Subject: [PATCH] Fix piped stdin support, auto-enable print mode When stdin is piped (not a TTY), read the content and treat it as the first message. Since interactive mode requires a TTY for keyboard input, print mode is automatically enabled. - echo foo | pi -> equivalent to pi -p foo - echo foo | pi -p -> equivalent to pi -p foo - RPC mode unaffected (uses stdin for JSON-RPC) fixes #708 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/main.ts | 35 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 59a0f050..f847a342 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixed +- Piped stdin now works correctly: `echo foo | pi` is equivalent to `pi -p foo`. When stdin is piped, print mode is automatically enabled since interactive mode requires a TTY ([#708](https://github.com/badlogic/pi-mono/issues/708)) - Session tree now preserves branch connectors and indentation when filters hide intermediate entries so descendants attach to the nearest visible ancestor and sibling branches align. Fixed in both TUI and HTML export ([#739](https://github.com/badlogic/pi-mono/pull/739) by [@w-winter](https://github.com/w-winter)) - Added `upstream connect`, `connection refused`, and `reset before headers` patterns to auto-retry error detection ([#733](https://github.com/badlogic/pi-mono/issues/733)) diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 996b0922..abcc838c 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -30,6 +30,29 @@ import { runMigrations, showDeprecationWarnings } from "./migrations.js"; import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js"; import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js"; +/** + * Read all content from piped stdin. + * Returns undefined if stdin is a TTY (interactive terminal). + */ +async function readPipedStdin(): Promise { + // If stdin is a TTY, we're running interactively - don't read stdin + if (process.stdin.isTTY) { + return undefined; + } + + return new Promise((resolve) => { + let data = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => { + data += chunk; + }); + process.stdin.on("end", () => { + resolve(data.trim() || undefined); + }); + process.stdin.resume(); + }); +} + async function prepareInitialMessage( parsed: Args, autoResizeImages: boolean, @@ -310,6 +333,18 @@ export async function main(args: string[]) { return; } + // Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC + if (parsed.mode !== "rpc") { + const stdinContent = await readPipedStdin(); + if (stdinContent !== undefined) { + // Force print mode since interactive mode requires a TTY for keyboard input + parsed.print = true; + // Prepend stdin content to messages + parsed.messages.unshift(stdinContent); + } + time("readPipedStdin"); + } + if (parsed.export) { try { const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;