How to Use stdin with the DESIGN.md CLI for Build Pipeline Integration

Supply - as the file argument to read DESIGN.md content from standard input, enabling seamless integration with CI/CD pipelines and shell scripts.

The google-labs-code/design.md CLI provides first-class support for Unix-style piping by accepting - as a file path placeholder. This capability allows you to use stdin with the DESIGN.md CLI in automated build pipelines, avoiding the need to write temporary files to disk.

How Stdin Works in the DESIGN.md CLI

The CLI treats the positional file argument as a path to a DESIGN.md document. When you supply the literal string -, the utility helper readInput() in packages/cli/src/utils.ts streams data directly from process.stdin instead of the filesystem.

The readInput() Implementation

According to the source code in packages/cli/src/utils.ts (lines 42-51), readInput() performs a strict equality check against the string -. When matched, it asynchronously consumes all chunks from the stdin stream, concatenates them, and returns the result as a UTF-8 string. If the terminal is interactive (TTY), the function prints a hint to stderr prompting the user to finish input with Ctrl-D.

Commands Supporting Stdin

All CLI commands that accept a DESIGN.md file path import and reuse this helper:

  • lint – validates the document and returns JSON
  • export – converts the document to other formats
  • diff – compares two versions, where the first argument can be -

Implementation Details

Error Handling with FileReadError

When file system operations fail, the CLI wraps native errors using FileReadError (defined in packages/cli/src/utils.ts lines 19-35). This provides user-friendly error messages while preserving the stack trace for debugging pipeline failures.

Argument Declaration

The argument definition in packages/cli/src/commands/lint.ts (lines 24-28) explicitly documents the feature: "Path to DESIGN.md (use "-" for stdin)". This pattern is consistent across export.ts and diff.ts, ensuring uniform behavior across the toolchain.

Practical Examples for Build Pipelines

Pipe a File into the CLI

Unix pipelines allow you to stream an existing file directly without temporary storage:

cat path/to/DESIGN.md | design lint -

The CLI reads the piped content, validates it, and prints a JSON report to stdout.

Inline Heredoc for CI Scripts

For ephemeral documents in shell scripts, use a heredoc:

design export - <<'EOF'
title: My Project
primary: true
tokens:
  - name: brand
    value: '#FF5733'
EOF

This supplies a minimal DESIGN.md document directly to the export command.

GitHub Actions Integration

In CI environments, you can pipe secrets or previous step outputs:

- name: Lint DESIGN.md from pipeline
  run: |
    echo "${{ secrets.DESIGN_MD_CONTENT }}" | design lint -
  env:
    DESIGN_MD_CONTENT: ${{ steps.fetch_design.outputs.content }}

The command exits with code 1 if validation fails, causing the job to fail immediately.

Combine with JSON Processing Tools

Parse the JSON output for further decision-making:

cat DESIGN.md | design lint - | jq '.summary.errors'

This extracts the error count for conditional pipeline logic.

Diff Command with Piped Input

Compare a previous version from stdin against a current file:

echo "$OLD_DESIGN" | design diff - ./DESIGN.md

The first argument (-) reads the "before" state from stdin while the second points to the current file on disk.

Summary

  • Supply - as the file path to read DESIGN.md content from stdin across all CLI commands.
  • The readInput() helper in packages/cli/src/utils.ts (lines 42-51) handles the streaming logic and TTY detection.
  • Supported commands include lint, export, and diff, enabling full pipeline integration.
  • In CI/CD environments, combine with heredocs, pipes, or secrets to avoid writing sensitive files to disk.
  • Exit codes propagate correctly, allowing standard CI failure mechanisms to function.

Frequently Asked Questions

Can I use stdin with all DESIGN.md CLI commands?

Yes. Any command that accepts a file path argument—including lint, export, and diff—supports the - convention. The readInput() utility is shared across these commands in packages/cli/src/commands/.

How does the CLI handle interactive stdin input?

When stdin.isTTY is true (interactive terminal), readInput() prints a hint to stderr instructing you to finish input with Ctrl-D. In non-interactive pipelines, it silently consumes all available data until EOF.

What happens if stdin is empty or malformed?

If the stream ends without data, readInput() returns an empty string, which typically causes validation commands like lint to report parse errors. The CLI exits with code 1 and surfaces the error via FileReadError or validation-specific error messages.

Can I use stdin for both files in the diff command?

No. The diff command only supports stdin for the first (left-hand) argument. The second argument must be a path to a file on disk. You can work around this by writing the second version to a temporary file or using process substitution if your shell supports it.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →