Unix philosophy is composition, and composition requires stdin. When mytool validate only accepts --file path.json and refuses piped input, users cannot run curl api.example.com/data | mytool validate or chain your tool into generators. Tools that cannot participate in pipelines get replaced by tools that can. Worse, stdin reading without TTY detection causes interactive invocations to hang forever — the user sees a frozen terminal and assumes the tool crashed.
Medium because missing stdin support blocks pipeline composition without causing data loss or crashes.
Accept - as a filename meaning stdin, or auto-detect !process.stdin.isTTY and read from the pipe. Implement once in src/cli/lib/input.ts:
async function getInput(filePath?: string) {
if (filePath && filePath !== '-') return readFileSync(filePath, 'utf8')
const chunks: Buffer[] = []
for await (const chunk of process.stdin) chunks.push(chunk)
return Buffer.concat(chunks).toString('utf8')
}
ID: cli-quality.io-behavior.stdin-pipe
Severity: medium
What to look for: Count all input-accepting commands. For each, check if commands that accept file input can also read from stdin (pipe). Look for - as a filename convention (e.g., mytool process - reads from stdin). Check for process.stdin, sys.stdin, os.Stdin, or std::io::stdin() usage. Verify the tool can participate in Unix pipelines (e.g., cat data.json | mytool validate).
Pass criteria: At least one command that accepts input can read from stdin/pipe, either via - filename convention or automatic stdin detection when no file argument is given. The CLI correctly handles both TTY (interactive) and non-TTY (piped) stdin — at least 50% of input commands should accept stdin for pipeline composition. Report: "X input commands found, Y accept stdin/pipe input."
Fail criteria: Commands that accept file input only accept file paths — no way to pipe data in. Or stdin reading is attempted but blocks indefinitely when no pipe is connected.
Skip (N/A) when: The CLI does not accept data input — it only takes configuration options and performs actions. All checks skip when no CLI entry point is detected.
Detail on fail: "'validate' command requires a --file argument — cannot read from stdin/pipe" or "stdin reading implemented but hangs when run interactively without pipe input (no TTY detection)"
Remediation: Pipe support makes your CLI composable with other tools:
// Node.js — read from file or stdin
async function getInput(filePath?: string): Promise<string> {
if (filePath && filePath !== '-') {
return readFileSync(filePath, 'utf8')
}
// Read from stdin
const chunks: Buffer[] = []
for await (const chunk of process.stdin) {
chunks.push(chunk)
}
return Buffer.concat(chunks).toString('utf8')
}
# click — use click.File to handle both
@cli.command()
@click.argument('input', type=click.File('r'), default='-')
def validate(input):
"""Validate JSON input. Reads from stdin if no file given."""
data = input.read()