Skip to content

Make CLI output machine-readable and pipeable - #588

Draft
razor-x wants to merge 2 commits into
mainfrom
claude/cli-stdout-abstraction-a6znxb
Draft

Make CLI output machine-readable and pipeable#588
razor-x wants to merge 2 commits into
mainfrom
claude/cli-stdout-abstraction-a6znxb

Conversation

@razor-x

@razor-x razor-x commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

This PR refactors the CLI to produce machine-readable, pipeable output. Only API responses are written to stdout (as JSON or pretty-printed text), while prompts, progress, and other information are written to stderr. This allows CLI commands to be safely piped into other programs.

Key Changes

  • Output abstraction: Created Output interface with separate methods for data (stdout), text (stdout), info/warn/error (stderr). Implemented createOutput() factory supporting both JSON and text formats.

  • Response trimming: Added selectResponsePayload() to reduce API responses to only the response key and pagination, removing transport metadata like ok and request_id.

  • JSON mode: Implemented --json flag to read request params from stdin as JSON and write responses as JSON. Automatically enabled when stdout is not a terminal.

  • Stdin param reading: Added readStdinJson() and parseJsonParams() utilities to parse request parameters from stdin or --json flag, with proper error handling.

  • Prompt refactoring: Extracted prompt() and canPrompt() utilities that render prompts to stderr and prevent prompting when stdin is not a TTY (piped input).

  • CLI flag handling: Introduced cliFlags set to prevent CLI-specific flags (--json, -y, --help, etc.) from being sent as request parameters.

  • Response key extraction: Added getResponseKey() to determine the response key for a command from the API blueprint, used to trim responses appropriately.

  • Error handling: Failed requests now exit with code 1 and report only the error object on stdout for inspection from pipes.

  • Updated all interactions: Migrated all prompts imports to use the new prompt() wrapper, and replaced console.log calls with output.info(), output.error(), etc.

Notable Implementation Details

  • The output system is globally managed via getOutput()/setOutput() to allow tests to inject a memory-based output implementation.
  • Params from stdin/--json are merged with flag params, with flags taking precedence.
  • Reading from stdin automatically implies -y (non-interactive) since there's nobody to prompt.
  • Progress spinners are suppressed in JSON mode and when stdout is not a TTY.
  • Comprehensive test suite added (test/cli.test.ts) covering version, help, JSON I/O, param merging, error handling, and output formatting.

https://claude.ai/code/session_01NKsb8oTieozDEGJtiwWqBo

claude added 2 commits July 28, 2026 21:33
Replace ad hoc console.log calls with an injectable Output so the CLI has
one place that decides what goes where, and so output can be asserted in
tests with an in-memory implementation.

- Only command results go to stdout: prompts, spinners, progress, and
  other information are written to stderr.
- Add --json to read request params from stdin and write the response as
  JSON. The JSON format is used automatically when stdout is not a
  terminal, and --no-json opts out.
- Support unix pipes end to end: params may be piped or redirected in,
  and the response piped or redirected out.
- Trim responses to the response key and pagination, using the response
  key from the API blueprint.
- Exit non-zero on a failed request or an error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKsb8oTieozDEGJtiwWqBo
…traction-a6znxb

# Conflicts:
#	src/lib/interact-for-custom-metadata.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants