DEV Community

丁久
丁久

Posted on • Originally published at dingjiu1989-hue.github.io

Building AI-Powered CLI Tools: A Complete Guide for Developers

This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.

Building AI-Powered CLI Tools: A Complete Guide for Developers

The terminal is having a renaissance. Developers spend hours in it every day — and LLMs have turned it from a read-only window into something that can understand, generate, and transform code and text. An AI-powered CLI tool isn't just an API wrapper with a flag parser. It's a new kind of interface: one where the computer can interpret intent, reason about context, and take action.

This guide covers how to build these tools end-to-end: architecture patterns, Python and Node.js implementations, streaming, interactive flows, file-aware agents, packaging, and two real-world examples you can adapt right now.

Why AI CLI Tools Are Different

A traditional CLI tool maps flags to function calls. An AI CLI tool does something fundamentally different:

  • It interprets natural language. tldr docker-compose searches a cheat sheet. gpt "explain docker-compose networking" understands intent.
  • It has context. File-aware tools read your codebase before answering.
  • It can take multi-step actions. Not just "output text" but "read files, plan, execute, verify."
  • It streams reasoning. Users see the model think, which builds trust and lets them cancel early.

The architecture looks like this:

User Input (args, stdin, interactive) → CLI Framework (Click/Commander)
  → Orchestrator (prompt construction, tool management)
    → LLM SDK (OpenAI/Anthropic/Claude)
      → Streaming stdout / file writes / git commits / API calls
Enter fullscreen mode Exit fullscreen mode

The CLI framework handles input parsing and help text. The orchestrator constructs prompts, manages conversation history, and decides when to call tools. The LLM SDK is a thin wrapper — the real work is in prompt engineering and tool orchestration.

Architecture of AI CLI Tools

Every AI CLI tool shares these layers:

Input Layer. Accepts flags, arguments, stdin, and interactive input. This is where you decide between tool ask "question", cat file | tool, and tool --interactive.

Context Layer. Gathers information the model needs: file contents, git diff output, directory listings, environment variables, previous conversation turns.

Orchestration Layer. Manages the conversation loop. For simple tools this is one request/response. For agents, it's a loop: model responds, you execute tool calls, you feed results back, model responds again.

Output Layer. Streams tokens to stdout, formats structured output (JSON, markdown), and handles errors gracefully.

The key design decision is stateless vs. stateful. Stateless tools (one question, one answer) are simpler. Stateful tools (multi-turn conversations, file edits, undo) require persistence — typically a session file or a temp directory.

Python: Click + LLM SDK

Python is the most popular language for CLI tools, and Click is the standard framework. Pair it with the openai or anthropic SDK.

import click
from openai import OpenAI

client = OpenAI()

@click.command()
@click.argument("prompt", required=False)
@click.option("--model", default="gpt-4o", help="Model to use")
@click.option("--system", default="You are a helpful assistant.")
def ask(prompt, model, system):
    """Ask an LLM a question from the command line."""
    if not prompt and not click.get_text_stream("stdin").isatty():
        prompt = click.get_text_stream("stdin").read().strip()

    if not prompt:
        click.echo("Usage: ask PROMPT or pipe input")
        return

    stream = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": prompt},
        ],
        stream=True,
    )

    for chunk in stream:
        content = chunk.choices[0].delta.content or ""
        click.echo(content, nl=False)
    click.echo()

if __name__ == "__main__":
    ask()
Enter fullscreen mode Exit fullscreen mode

This tool accepts input as an argument or via stdin pipe, streams the response character by character, and uses Click's built-in help formatting. The streaming loop is the critical difference from a non-AI CLI — users expect to see output appear incrementally, not wait for a full response.

For a richer experience with Typer (Click with type hints):


python
import typer
from rich.console import Console
from rich.live import Live
from rich.markdown import Markdown
from anthropic import Anthropic

app = typer.Typer()
console = Console()
client = Anthropic()

@app.command()
def chat(
    prompt: str = typer.Argument(None, help="Your question"),
    model: str = "claude-sonnet-4-20250514",
):
    """Chat with Claude from the terminal."""
    if not prompt:
        import sys
        prompt = sys.stdin.read().strip()

    with client.messages.stream(
        model=model,
        max_tokens=4096,
        messages=[{"role": "user", "content": prompt}],
    ) as stream:
        with Live(refresh_per_second=15) as live:


---

**Read the full article on [AI Study Room](https://dingjiu1989-hue.github.io/en/tools/ai-cli-tools-guide.html)** for complete code examples, comparison tables, and related resources.

*Found this useful? Check out more [developer guides and tool comparisons](https://dingjiu1989-hue.github.io/en/) on AI Study Room.*
Enter fullscreen mode Exit fullscreen mode

Top comments (0)