# Wrap a call in a small CLI

Web page: https://academy.memberpress.ai/courses/api-fundamentals/l7/

Lesson 7 of 9 in [Anthropic API Fundamentals](https://academy.memberpress.ai/courses/api-fundamentals/), 15 min.

Video: https://www.youtube.com/watch?v=saPIesfZzT0

A script that runs when you execute a file is a draft. A tool reads its input from the caller, reads secrets from the environment, prints only what is needed, and exits with a code the caller can act on.

**The full CLI**

```python
# scripts/ask.py
import anthropic
import argparse
import os
import sys

def main():
    parser = argparse.ArgumentParser(description="Ask Claude a question.")
    parser.add_argument("--prompt", required=True, help="The user message.")
    parser.add_argument("--system", default="", help="Optional system prompt.")
    parser.add_argument("--max-tokens", type=int, default=512)
    parser.add_argument("--stream", action="store_true", help="Stream output.")
    args = parser.parse_args()

    key = os.getenv("ANTHROPIC_API_KEY")
    if not key:
        print("Error: ANTHROPIC_API_KEY is not set.", file=sys.stderr)
        sys.exit(1)

    client = anthropic.Anthropic()
    messages = [{"role": "user", "content": args.prompt}]

    if args.stream:
        with client.messages.stream(
            model="claude-opus-4-5",
            max_tokens=args.max_tokens,
            system=args.system,
            messages=messages,
        ) as stream:
            for text in stream.text_stream:
                print(text, end="", flush=True)
        print()
    else:
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=args.max_tokens,
            system=args.system,
            messages=messages,
        )
        print(response.content[0].text)

if __name__ == "__main__":
    main()
```

**What each choice does**

`required=True` on `--prompt` means the parser exits with a readable usage message if the argument is missing — before any network call is made.

The key check runs before the API client is initialized. If the variable is missing, the error is clear and immediate: `Error: ANTHROPIC_API_KEY is not set.` The `sys.exit(1)` instead of `sys.exit(0)` lets a shell script or CI job detect the failure.

`print(text, end="", flush=True)` in the streaming branch suppresses the newline between chunks so tokens accumulate on one line in the terminal, and `flush=True` pushes each chunk to the terminal immediately rather than buffering.

**Test the exit codes**

```bash
# Normal operation
python scripts/ask.py --prompt "What is entropy?" --system "Reply in one sentence."
echo $?  # → 0

# Missing key
ANTHROPIC_API_KEY="" python scripts/ask.py --prompt "Hello"
echo $?  # → 1

# Streaming
python scripts/ask.py --prompt "Count to five." --stream
```

Exit codes matter when this tool is called from a Makefile, a CI job, or another script. A silent `0` on failure silently corrupts the pipeline.

**Composing with pipes**

Because the tool writes to stdout and exits cleanly, it composes with standard Unix tools:

```bash
# Pipe a file into Claude
cat README.md | python scripts/ask.py --prompt "$(cat -)" --system "Summarize this in two sentences."

# Feed the output into another command
python scripts/ask.py --prompt "List five HTTP status codes." | grep "^[0-9]"

# Use it in a Makefile target
# Makefile
.PHONY: changelog
changelog:
    git log --oneline -20 | python scripts/ask.py \
      --prompt "$(shell git log --oneline -20)" \
      --system "Turn these git commits into a changelog entry. Plain text, no markdown." \
      > CHANGELOG_ENTRY.txt
```

The pipe pattern works because the tool does not print anything to stdout except the Claude response. No "Calling Claude..." prefix, no status lines, no token counts on stdout. Status and debug output go to stderr.

**Make it executable**

Add a shebang line at the top of the file:

```python
#!/usr/bin/env python3
```

Then run `chmod +x scripts/ask.py`. Now `./scripts/ask.py --prompt "Hello"` works without the `python` prefix. Add the `scripts/` directory to your `PATH` and you have a system-wide tool.

**If you get stuck**

- `argparse` exits with code `2` on bad input: that is correct behavior. Code `2` conventionally means usage error in Unix tools.
- Streaming output is buffered in a terminal: confirm `flush=True` is set. You can also run with `python -u` to disable Python's output buffering globally.
- The shebang line is ignored: the file may not be executable. Run `chmod +x scripts/ask.py` and confirm with `ls -l scripts/ask.py`.
- `anthropic.Anthropic()` ignores the key: the SDK reads `ANTHROPIC_API_KEY` from the environment automatically. You do not need to pass it to the constructor — just confirm the variable is set before instantiating.
- Pipes truncate output: confirm nothing before the streaming loop is writing to stdout. A stray `print()` before the loop can break the pipe contract.

**Cross-reference**

l3 (Streaming vs. blocking) explains the streaming event model the `--stream` flag builds on. l8 (Add it to a real app) takes the same call and embeds it in an HTTP route with structured output and error handling.

**Deliverable**

`python scripts/ask.py --prompt "What is entropy?" --system "Reply in one sentence."` returns a single sentence and exits `0`. Running with `ANTHROPIC_API_KEY=""` exits `1` with a message on stderr and makes no network call. Piping the output into `wc -w` prints a word count without error.

## Try these prompts

- Write the argparse setup for a CLI that accepts --prompt, --system, --max-tokens, and --stream flags, with --prompt required.
- Write the shell one-liner that pipes a file into the CLI and feeds Claude's output to wc -w.
- The CLI should exit with code 1 if ANTHROPIC_API_KEY is not set, before making any network call. Write that check.

Next: [Add it to a real app](https://academy.memberpress.ai/courses/api-fundamentals/l8/)
