How to Build CLI Tools Using Claude Code

Building command-line interface (CLI) tools can greatly boost productivity for backend developers and DevOps engineers. With Claude Code, Anthropic’s AI coding assistant, you can accelerate this process by generating and iterating on code using natural language. This comprehensive guide will show experienced developers (comfortable with terminal, Git, Python/Node runtimes) how to leverage Claude Code – both via its web interface and CLI tool – to build real-world CLI utilities.

We will cover initial setup, using Claude Code in practice (for code generation, refactoring, and debugging), and provide concrete examples in Python and JavaScript (Node.js) using popular frameworks (argparse, click, typer for Python, and commander.js, yargs, oclif for Node). Each example will include a full CLI program illustrating argument parsing, file I/O, and integrating Claude’s API. By the end, you’ll know how to harness Claude’s AI capabilities to create and test your own CLI tools effectively.

Overview: Claude Code for AI-Powered Coding

What is Claude Code? Claude Code is an AI coding agent (developed by Anthropic) that lives in your terminal and assists with programming tasks. It uses Anthropic’s latest large language models (e.g. the Claude 4.5 series like Sonnet 4.5 and Opus 4.5) to understand your codebase and perform coding actions. In essence, Claude Code functions like an advanced pair programmer: you tell it what you need in natural language, and it writes or edits code accordingly. It can even run commands, manage Git operations, and coordinate multi-step coding plans autonomously. Many compare it to OpenAI’s Codex or GitHub Copilot, but Claude Code stands out for its strong context management and “agentic” abilities to follow plans and adjust code in multiple files.

Web Interface vs CLI Tool: Originally, Claude Code was accessible as a CLI-only tool (invoked in your terminal), but Anthropic has since introduced a web-based interface for it. Both interfaces let you accomplish similar tasks with Claude’s help, but they offer different workflows:

  • Claude Code (CLI tool): Running claude in your terminal opens a REPL-like chat environment scoped to your current project directory. Claude can only see and modify files in that folder (which is great for focusing on a specific project). The CLI mode allows Claude to execute shell commands on your machine, run tests, install packages, and even commit code to git – all under your supervision. It’s a powerful way to build or refactor a project locally, especially if you prefer working in the terminal. The CLI supports interactive modes (e.g. a default step-by-step mode, or an “auto” mode where it continues executing tasks without waiting for manual approval) to suit different coding styles.
  • Claude Code (Web interface): The new web UI lets you delegate coding tasks from your browser without opening a terminal. You connect a repository (e.g. from GitHub) or create a project in Claude’s interface, describe high-level tasks (“Add a new CLI command for exporting data”, “Fix the bug in CLI argument parsing”), and Claude will work on them in an isolated cloud environment. The web version excels at running multiple tasks in parallel across different projects, with real-time progress updates and the ability to steer Claude mid-task. It can even automatically create pull requests with the changes it makes. The web UI is convenient if you want to use Claude Code on any device (even mobile) and let Claude handle the heavy lifting on Anthropic’s servers. However, it requires a suitable subscription (Pro/Max plan or enterprise access) and may be subject to sandbox constraints (Claude’s cloud sandbox only has access to connected repos and limited external network for security).

When to use which? In practice, you might use both. For quickly scaffolding or troubleshooting a CLI tool, the Claude CLI is great – you can work in a local dev environment where Claude has direct access to run and test your code. For larger projects or CI workflows, the Claude web interface is useful to offload tasks or let Claude propose changes via pull requests. This guide will show how to use either interface to assist in building CLI applications.

Initial Setup and Requirements

Before diving into code generation, ensure you have the following set up:

Claude Access: You’ll need an Anthropic Claude account with appropriate access:

If you plan to use the Claude Code web interface, a Claude Pro or Max subscription (or enterprise/team account) is required, since Claude Code on the web is enabled for those tiersclaude.com. Sign in at claude.ai and ensure you have Claude Code access (check settings for Claude Code toggle if you’re an admin).

If you plan to use the Claude CLI tool, you need to install Claude Code locally and authenticate it. This does not require a separate Pro plan if you use the usage-based API billing instead, but you must have an Anthropic API key or an Anthropic account to link. We’ll cover installation next.

Programming Environment: Since we’ll build CLI examples in both Python and Node.js, make sure you have:

Python 3.9+ (for compatibility with Anthropic’s Python SDK). Python 3.10 or 3.11 is recommended.

Node.js 18+ (the Claude Code CLI itself requires Node 18+ if installed via npm, and our Node examples will use modern syntax).

Anthropic API Key: If you plan to integrate Claude via API calls in your code (which we will in our examples), obtain an API key:

Log in to the Anthropic Claude console with your account. Under your organization/project settings, find the API Keys section and create a new key (if using the Claude Code-specific workspace, note that direct API keys might not be available. In that case, use a personal or default workspace for API access).

Copy the API key (a long string starting with sk-ant-... typically). Keep it secret.

Set your API key as an environment variable on your development machine, for example:

export ANTHROPIC_API_KEY="sk-ant-YourKeyHere"

This ensures the key is available to the Claude SDKs. (We will use environment variables in code rather than hardcoding keys.)

Claude CLI Installation (Optional for CLI mode): If you want to use the Claude Code terminal assistant:

Install the CLI – The official tool can be installed via multiple methods (npm, Homebrew, or a one-line script). The simplest method on macOS/Linux is the curl script:

curl -fsSL https://claude.ai/install.sh | bash

This will download and set up the claude CLI globally. (On Windows, you can use the PowerShell command from Anthropic’s docs, or use WSL as the CLI runs in a Unix-like environment.) Alternatively, you can use Homebrew (brew install --cask claude-code) or npm (npm install -g @anthropic-ai/claude-code) according to the [official guide].

Authenticate – After installation, open a terminal, navigate to a project folder of your choice, and run claude. The first time, it will prompt you to authenticate. You can either login via the browser to your Anthropic account (if you have a Claude subscription) or provide an API key for usage-based billing. Follow the interactive prompts to complete setup. Claude will then launch a local session in that folder.

Once authenticated, you can start a Claude session in any project directory by simply running the claude command there. Claude will only have access to files in that directory tree, ensuring project isolation.

Required Libraries for Examples: To follow along the code examples, install the following:

Python:

pip install anthropic argparse click typer

(The argparse module is actually part of Python’s standard library, but we list it here for completeness. anthropic is the official SDK for Claude’s API, and click/typer are third-party CLI frameworks.)

Node.js:

npm install @anthropic-ai/sdk commander yargs
npm install --save-dev @oclif/core

We install Anthropic’s Node SDK (@anthropic-ai/sdk) and popular CLI libraries. For oclif, which is a comprehensive CLI framework, it’s typically used via its generator, but here we include its core library for an example.

With setup done, you’re ready to use Claude Code to actually build something!

Using Claude Code via the Web Interface

Claude’s web interface provides an asynchronous, cloud-based coding assistant. Here’s how you can use it to create and refine a CLI tool:

  1. Access Claude Code Web: Log in to the Claude web app (at claude.ai) and navigate to Claude Code (usually under a “Code” or “Projects” section). If it’s your first time, you may need to connect a GitHub repository to start a session. Claude Code works best when it has a codebase to work on. You can create a new, empty repo for your CLI tool (e.g., on GitHub, GitLab) and connect it, or use an existing repository if you plan to integrate the CLI into it.
  2. Describe Your Task: Once the repository is connected and a Claude Code session is active, you’ll see an interface where you can enter instructions (like a chat). Tell Claude what CLI tool you want to build. For example, you might say: “Create a new Python script that acts as a CLI tool to summarize text files. It should accept an input file path and an optional output file path. Use the argparse library for argument parsing. After reading the input file, it should call the Claude API (using my API key) to generate a summary of the text, then either print the summary or write it to the output file if provided.” Claude will analyze this request and likely break the task into steps automatically. It may create a plan or todo list (Claude Code often starts by outlining a plan in a plan.md for complex tasks). Then it will begin generating code.
  3. Review Generated Code: The web interface will show you Claude’s progress. It might create a new file, e.g. summarize_cli.py, and write code into it. You will see the code it writes in real-time, and Claude might also run tests or linting if you requested that. Always review the code suggestions – since you are an advanced user, you can spot if something looks off or if adjustments are needed. Claude is quite capable, but it’s best used interactively: you can intervene if needed.
  4. Steer or Refine: If Claude’s first attempt isn’t perfect, you can give further instructions in the chat:
    • Refactoring: Maybe you notice the code could be better structured. You can say: “Refactor the argument parsing to use subcommands” or “Improve error handling for file I/O.” Claude will then modify the code accordingly.
    • Debugging: If you run the code (outside Claude) and hit an error, you can copy the error trace into the Claude chat and ask, “The tool crashed with this error when I tried X. Please fix it.” Claude will diagnose and patch the code.
    • Additional features: You might ask Claude to add features, e.g., “Add an option for the user to specify the model name for the Claude API, defaulting to Claude 2.” The key is you can iteratively build up the CLI tool by conversing with Claude.
  5. Test in Browser or Locally: The Claude Code web interface can run tests in a sandbox environment. For instance, you could ask Claude to “Run the CLI with a sample text file to ensure it works”. Because Claude Code on the web runs in isolation, it can execute commands and even create output artifacts for you to download. It might simulate running python summarize_cli.py sample.txt and show the output or any errors. Once you’re satisfied, you can pull the changes to your local machine (if using GitHub, you might get a pull request from Claude with all the commits; otherwise you can download the file directly from the interface).

Tips for using the web interface:

  • Keep your prompts clear about desired libraries or approaches (e.g. “use click instead of argparse”).
  • Use Claude’s ability to answer questions: you can ask “How does this function work?” or “Why did you choose this approach?” to get insight into the generated code (this helps you verify and learn).
  • Leverage parallel sessions if you have multiple tasks (Claude Code web allows running tasks on different repos at once, though for one CLI tool you’ll typically just use one session).
  • Remember that any changes made by Claude in the web session need to be pulled to your local environment to run locally. Claude can automatically commit changes and create a PR; you can then review and merge.

Using Claude Code via the web can significantly speed up development, as you can delegate routine coding and focus on higher-level decisions. Next, we’ll look at using the Claude CLI tool for a more hands-on approach.

Using the Claude CLI Tool (Terminal Assistant)

The Claude Code CLI brings the AI agent into your terminal for an interactive development experience. Here’s how to use it in practice for building a CLI tool:

Launch Claude in Your Project: Open your terminal, create a new directory for your CLI project (e.g., mkdir mycli-tool && cd mycli-tool), and run claude. This starts a Claude session scoped to the current folder. You’ll see a prompt (usually something like Claude: waiting for your instructions).

Describe the Goal: Similar to the web, tell Claude what you want. For example:

You: Create a Python CLI tool called "summarize" that uses argparse. 
     Requirements:
     - It should accept --input (path to a text file) and --output (optional path for summary).
     - It should read the input file, summarize its content using the Claude API (assume the API key is set in environment), and either print the summary or save to output file.
     - Use best practices for CLI structure and error handling.

Press Enter and Claude will start analyzing. In CLI mode, Claude may respond with a plan or directly propose to create a file.

Accepting and Executing Changes: In the default mode, Claude will show you a diff or code block of what it intends to do and ask for confirmation (e.g., “Claude: I will create summarize.py with the following content … [diff]. Proceed? (y/n)”). You can type y to apply the change. Claude will then actually create the file on disk with that content.

Iterate Development in REPL: Now you can continue the conversation:

If you want to refine the code, just tell Claude. For instance: “Refactor the file reading logic to handle large files efficiently.” Claude will open summarize.py in the session and apply edits.

You can also ask Claude to run the tool: “Run python summarize.py --input sample.txt. Because Claude CLI has the ability to execute shell commands, it will run that command and capture the output for you. If an error occurs, it will report it. This is extremely useful for fast feedback and debugging.

The Claude CLI supports auto mode (where it continues working through tasks without asking each time). You can switch modes by pressing Shift+Tab in the CLI. Auto mode can be handy once you trust Claude’s plan – it will create/edit files and run tests on its own until the task is done, checking off items on its to-do list as it goes.

Using CLI for Complex Tasks: The CLI tool can manage multi-file projects. If your CLI tool grows (say you split functionality into multiple modules), Claude can handle that. You might instruct, “Add a new module for handling API interactions (e.g., a claude_api.py file to encapsulate calls to Claude’s API)”. Claude will create the new file, write code, and update your main CLI code to use that module if needed. It keeps track of context across files – for example, it will remember to import the new module in your main program after creating it. This agentic behavior means you can build non-trivial CLI applications with several components, and Claude will handle the coordination.

Save and Exit: At any time, you can end the session (usually by typing a special command like /exit or pressing Ctrl+D). All files created/modified remain in your directory. Ensure you save any changes (Claude typically writes directly to files, so they are already saved).

Tips for CLI mode:

  • Authentication/Costs: If using the API key method, be mindful of token usage – Claude Code will be running on your machine but still calling Anthropic’s API under the hood, incurring usage costs. You might prefer linking a Claude.ai account subscription for unlimited usage (Pro/Max) in development.
  • Scope: The CLI’s sandbox is your folder – it won’t accidentally wander into other directories. Use this to your advantage by keeping test files (like sample.txt) in the folder so Claude can access them when running commands.
  • Auto vs Manual: Default (manual confirm) mode lets you review each change. This is safer for critical code. Auto mode is great when doing a lot of boilerplate so you don’t have to hit yes every time. You can mix modes (start manual, then switch to auto for a while).
  • Tool Use: Claude CLI has some built-in “tools” beyond just editing files – for instance, it can use a web browser tool or run tests. If you prompt it with something like “Open a browser and search for …”, it has the capability (though depending on your config, external net access might be disabled for safety). Generally for coding tasks, it primarily uses the file system and shell tools.

At this point, we’ve seen how to utilize Claude Code itself (web or CLI) to generate the bulk of our CLI application code. Next, we’ll dive into the code examples in Python and Node.js, demonstrating complete CLI tool implementations and highlighting how to parse arguments, do file I/O, and call the Claude API within your tool’s code.

Example 1: Python CLI Tool with argparse

Our first example is a Python CLI built with the standard library’s argparse. This tool will read a text file and produce a summary of its contents using Claude’s API.

Scenario: Imagine we want a CLI tool named pysummarize. The user will provide an input text file path and can optionally specify an output file. The tool will call Claude (via the API) to summarize the text.

Below is a full Python script using argparse for argument parsing:

#!/usr/bin/env python3
import argparse
import os
from anthropic import Anthropic

def summarize_file(input_path: str, output_path: str = None, model: str = "claude-2") -> None:
    """Read the input file, get a summary from Claude, and write or print the result."""
    # Read content from the input file
    with open(input_path, "r", encoding="utf-8") as f:
        text = f.read()
    # Initialize Claude API client (api key is read from ANTHROPIC_API_KEY env variable)
    client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
    # Prepare the prompt for Claude: we ask it to summarize the text
    prompt = f"Summarize the following text:\n\n{text}"
    # Call Claude API to get completion
    response = client.messages.create(
        model=model,
        max_tokens=1000,
        messages=[{"role": "user", "content": prompt}]
    )
    summary = response.content  # The summary text returned by Claude
    # Output the summary
    if output_path:
        with open(output_path, "w", encoding="utf-8") as f:
            f.write(summary)
        print(f"Summary written to {output_path}")
    else:
        print(summary)

def main():
    parser = argparse.ArgumentParser(prog="pysummarize", 
                    description="Summarize a text file using Claude AI.")
    parser.add_argument("input_file", help="Path to the input text file")
    parser.add_argument("-o", "--output", dest="output_file", 
                        help="Path to save the summary (if not provided, will print to stdout)")
    parser.add_argument("--model", default="claude-2", 
                        help="Claude model ID to use (default: claude-2)")
    args = parser.parse_args()
    # Call the summarization function with provided arguments
    summarize_file(args.input_file, args.output_file, args.model)

if __name__ == "__main__":
    main()

Let’s break down how this Python CLI works:

  • Argument Parsing: We use argparse.ArgumentParser to define required and optional arguments. The tool expects an input_file (positional argument) and allows an optional -o/--output path for the summary. We also included a --model option to specify which Claude model to use (defaulting to "claude-2"). This way advanced users can override the model if needed. The parsing logic is in main() where args = parser.parse_args() pulls the values from the command line.
  • Reading/Writing Files: In summarize_file, the code opens the input file and reads all text. It later writes the summary to the output file if one is specified. We use UTF-8 encoding explicitly to handle unicode in text. If no output file is given, it prints the summary to the console (stdout).
  • Calling Claude’s API: We use the official Anthropic Python SDK (anthropic library). We instantiate an Anthropic client. By default, if the environment variable ANTHROPIC_API_KEY is set, Anthropic() will use it, but here we show explicitly passing it from os.environ for clarity. We then construct a user message in the required format (Anthropic’s API expects a list of messages with roles; we use a single user message containing our prompt). We call client.messages.create(...) with parameters:
    • model: the model ID (e.g. "claude-2" in this example; you could use a newer model if available).
    • max_tokens: a limit for the response length (1000 tokens is arbitrary here, adjust as needed).
    • messages: the conversation list; we send one user prompt asking for a summary of the text.
      The API returns a response object, and response.content contains Claude’s answer (the summary text). We then handle that content as needed (printing or writing to file).

Usage Example: After saving this script (say as pysummarize.py), a developer could run it from the command line like:

$ python pysummarize.py documents/report.txt -o summary.txt
Summary written to summary.txt

Or simply:

$ python pysummarize.py documents/report.txt
[prints the summary to screen]

This CLI tool is rudimentary but demonstrates the essential integration: parse CLI args, perform file I/O, and use Claude’s AI to process data.

Example 2: Python CLI with click

Next, let’s implement a similar CLI using the Click library. Click simplifies CLI creation with decorators and is very popular in the Python community for building user-friendly command-line tools.

Scenario: We’ll create a CLI called pysum (short for Python summarize) that functions like the previous example. It will demonstrate how Click handles arguments and options.

#!/usr/bin/env python3
import os
import click
from anthropic import Anthropic

@click.command()
@click.argument("input_file", type=click.Path(exists=True))
@click.option("-o", "--output", "output_file", type=click.Path(writable=True), 
              help="File path to write the summary (prints to stdout if not provided)")
@click.option("--model", default="claude-2", show_default=True, 
              help="Claude model ID to use for summarization")
def summarize(input_file, output_file, model):
    """Summarize the content of INPUT_FILE using Claude AI."""
    # Read input file content
    with open(input_file, "r", encoding="utf-8") as f:
        text = f.read()
    # Call Claude API to get summary
    client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
    prompt = f"Summarize the following text:\n\n{text}"
    response = client.messages.create(
        model=model,
        max_tokens=1000,
        messages=[{"role": "user", "content": prompt}]
    )
    summary = response.content
    # Output handling
    if output_file:
        with open(output_file, "w", encoding="utf-8") as f:
            f.write(summary)
        click.echo(f"Summary written to {output_file}")
    else:
        click.echo(summary)

if __name__ == "__main__":
    summarize()  # click automatically parses args from sys.argv

Key points for this Click-based CLI:

Click Decorators: We use @click.command() to mark the main function as a CLI entry point. @click.argument("input_file", ...) defines a mandatory positional argument. We specified the type as a click.Path that must exist, which means Click will validate that the file exists before calling our function. The options -o/--output and --model are added via @click.option. We gave output_file a custom internal name (the second parameter "output_file") to use that variable name in the function signature. We also set writable=True for output file to ensure we can write to the path (Click will validate this). The --model option has a default value and show_default=True so that --help text shows the default model.

Function Signature: The function summarize(input_file, output_file, model) receives arguments already parsed and cast to appropriate types. Click automatically converts the input_file to a string path (since we used click.Path) and ensures it exists. If the user provides an output file path, Click stores it in output_file (or None if not provided). The model comes as a string with default "claude-2".

Business Logic: Inside the function, we perform the same file read and Claude API call as before. The only difference in output is that we use click.echo for printing, which is Click’s recommended way to print to the console (it handles some encoding issues gracefully and can be tested more easily). We still open the file in Python for writing the summary if needed, then use click.echo to output a confirmation message.

Running the CLI: Once this script (say pysum.py) is in place, the CLI is invoked in the same way but you could also make it executable and on PATH. For example:

$ python pysum.py input.txt --model claude-2.1

Click automatically provides a --help:

$ python pysum.py --help
Usage: pysum.py [OPTIONS] INPUT_FILE
(...)
Options:
  -o, --output FILE    File path to write the summary (prints to stdout if not provided)
  --model TEXT         Claude model ID to use for summarization  [default: claude-2]
  --help               Show this message and exit.

Integration with Claude API: It’s done in the same way as the argparse example – using the anthropic SDK to create a client and get a completion. The integration logic is identical, demonstrating that switching the CLI framework doesn’t impact how we call Claude.

Using Click can make the CLI output and help messages more polished with less manual effort. It also supports features like colored output, prompts, and complex subcommands if needed, making it suitable for larger CLI projects.

Example 3: Python CLI with typer

Typer is a modern library for CLI apps, built on top of Click, but it uses Python 3’s type hints and function declarations to create CLIs in a very intuitive way. Let’s see the same tool implemented with Typer.

#!/usr/bin/env python3
import os
import typer
from anthropic import Anthropic

app = typer.Typer(help="Summarize text files using Claude AI.")

def summarize_text(text: str, model: str) -> str:
    """Helper function to call Claude API and get summary of the given text."""
    client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
    prompt = f"Summarize the following text:\n\n{text}"
    response = client.messages.create(
        model=model,
        max_tokens=1000,
        messages=[{"role": "user", "content": prompt}]
    )
    return response.content

@app.command()
def summarize(input_file: str = typer.Argument(..., help="Path to the input text file"),
              output_file: str = typer.Option(None, "--output", "-o", help="File to save the summary"),
              model: str = typer.Option("claude-2", "--model", help="Claude model ID to use")):
    """
    Read INPUT_FILE, summarize its content using Claude AI, and save to OUTPUT_FILE or print to console.
    """
    # Read the input file
    text = open(input_file, "r", encoding="utf-8").read()
    # Get summary via Claude
    summary = summarize_text(text, model)
    # Output handling
    if output_file:
        with open(output_file, "w", encoding="utf-8") as out:
            out.write(summary)
        typer.echo(f"Summary written to {output_file}")
    else:
        typer.echo(summary)

if __name__ == "__main__":
    app()

Important aspects of the Typer version:

Typer App and Decorators: We create a Typer app with app = typer.Typer(...). We then use @app.command() decorator on a function to indicate it’s a CLI command. In this simple case, our app has one command summarize (Typer will use the function name as the command name by default).

Function Arguments as CLI Params: The function signature of summarize defines input_file, output_file, and model with type hints and default values. Typer uses these to generate the CLI arguments:

input_file: str = typer.Argument(..., help="...") means a required positional argument of type string. The ... as default signals it’s required. We provide a help text.

output_file: str = typer.Option(None, "--output", "-o", ...) means an optional option --output/-o that takes a string (file path). We default it to None to indicate it’s optional.

model: str = typer.Option("claude-2", "--model", ...) defines a --model option with default “claude-2”.
Typer (via Click under the hood) will ensure input_file exists if we want (we could add exists=True as with Click, but here for brevity we didn’t; Typer has ways to enforce that using typer.FileText types, etc.).

Logic Separation: We wrote a helper function summarize_text to call Claude’s API. This is just to illustrate good practice (keeping API calls separate from CLI I/O logic), but it’s not strictly necessary. It returns the summary string which the main command then handles (writing to file or echoing).

Running the CLI: If saved as app.py, you can run:

$ python app.py summarize input.txt -o summary.txt --model claude-2

Typer automatically creates a top-level help (for the app itself) and subcommand help:

$ python app.py --help
Usage: app.py [OPTIONS] COMMAND [ARGS]...

Summarize text files using Claude AI.

Commands:
  summarize  Read INPUT_FILE, summarize its content using Claude AI, and...

And python app.py summarize --help will show the options for that command.

Output: Typer’s typer.echo is analogous to Click’s echo for printing to console.

Typer is very convenient for larger Python CLIs, especially if you plan to have multiple commands or want to leverage Python’s type hints for automatic validation and documentation. Since it builds on Click, the integration with the rest of the code (including the Anthropc API call) is seamless.

At this stage, we have demonstrated building CLI tools in Python using three approaches. Now, let’s switch to Node.js and achieve similar functionality with popular Node CLI frameworks.

Example 4: Node.js CLI with Commander.js

For Node.js, one of the classic libraries for CLI development is Commander. We’ll create a Node script that does the same summarization task. This will highlight differences in syntax and how to call the Claude API from Node.

Scenario: The tool (let’s call it nodesum) will take -i <inputfile> and -o <outputfile> options similar to the Python version.

#!/usr/bin/env node
const { program } = require("commander");
const fs = require("fs");
const { Anthropic } = require("@anthropic-ai/sdk");

// Configure CLI options using Commander
program
  .name("nodesum")
  .description("Summarize a text file using Claude AI.")
  .requiredOption("-i, --input <file>", "Path to input text file")
  .option("-o, --output <file>", "Path to output file for the summary")
  .option("--model <id>", "Claude model ID to use", "claude-2")
  .showHelpAfterError();  // Show help if any error in args

program.parse(process.argv);  // parse command-line args
const options = program.opts();

// Read input file content
let text;
try {
  text = fs.readFileSync(options.input, "utf-8");
} catch (err) {
  console.error("Error reading input file:", err.message);
  process.exit(1);
}

// Call Claude API via Node SDK
const anthropicClient = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
(async () => {
  try {
    const prompt = `Summarize the following text:\n\n${text}`;
    const response = await anthropicClient.messages.create({
      model: options.model,
      max_tokens: 1000,
      messages: [{ role: "user", content: prompt }]
    });
    const summary = response.content;
    if (options.output) {
      fs.writeFileSync(options.output, summary, "utf-8");
      console.log(`Summary written to ${options.output}`);
    } else {
      console.log(summary);
    }
  } catch (error) {
    console.error("Error from Claude API:", error);
    process.exit(1);
  }
})();

Let’s explain the Node Commander example:

  • Commander Setup: We import program from commander and define the CLI’s name, description, and options. We use requiredOption for -i, --input to make input file mandatory. The --output is optional. We also add a --model with a default "claude-2". After defining options, program.parse(process.argv) does the actual parsing, and program.opts() returns an object with parsed options.
  • File I/O: We use Node’s fs.readFileSync to read the input file synchronously (for simplicity). In a real app, using async I/O (fs.promises.readFile or an async function with await) would be better, but sync is fine here since we likely need the content before proceeding anyway. We wrap it in a try-catch to handle errors (like if file not found), printing an error and exiting with a non-zero code.
  • Calling Claude API (Node SDK): We create an Anthropic client from the @anthropic-ai/sdk. The usage is very similar to Python’s: anthropicClient.messages.create({...}) with model, messages, etc. We use an async IIFE ((async () => { ... })();) to be able to use await at the top level (since we cannot top-level await without ESM modules in Node < v14, but using an IIFE is a common pattern in scripts). We await the response from Claude, then extract response.content. If an error occurs in the API call, we catch it and log an error.
  • Writing Output: If options.output is set, we write the summary to that file using fs.writeFileSync. Otherwise, we console.log the summary to print it out. We include a confirmation message when writing to file.
  • Execution: Make sure to put #!/usr/bin/env node at the top (Unix shebang) and give execute permission (chmod +x nodesum.js) if you want to run it directly as ./nodesum.js. Or run via Node: node nodesum.js -i input.txt -o summary.txt.

Commander handles the --help output automatically, listing all defined options.

This example shows that integrating with the Claude API in Node is also straightforward. The Node SDK call is asynchronous, but otherwise akin to the Python usage. Now onto another Node library.

Example 5: Node.js CLI with Yargs

Yargs is another popular library for building CLI tools in Node, known for its robust parsing and helpful output. We’ll implement the same functionality with Yargs.

#!/usr/bin/env node
const fs = require("fs");
const { Anthropic } = require("@anthropic-ai/sdk");
const yargs = require("yargs/yargs");
const { hideBin } = require("yargs/helpers");

// Configure Yargs
const argv = yargs(hideBin(process.argv))
  .usage("Usage: $0 -i <inputFile> [-o <outputFile>] [--model <id>]")
  .option("input", {
    alias: "i",
    type: "string",
    describe: "Path to the input text file",
    demandOption: true
  })
  .option("output", {
    alias: "o",
    type: "string",
    describe: "Path to output file for summary (optional)"
  })
  .option("model", {
    type: "string",
    default: "claude-2",
    describe: "Claude model ID to use for summarization"
  })
  .example("$0 -i notes.txt -o notes_summary.txt", "Summarize notes.txt and save the summary")
  .wrap(null)  // wrap text to terminal width
  .argv;

// At this point, argv contains parsed options
const inputFile = argv.input;
const outputFile = argv.output;
const modelId = argv.model;

// Read the input file
let text;
try {
  text = fs.readFileSync(inputFile, "utf-8");
} catch (err) {
  console.error("Failed to read input file:", err.message);
  process.exit(1);
}

// Call Claude API to summarize text
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
client.messages.create({
  model: modelId,
  max_tokens: 1000,
  messages: [{ role: "user", content: `Summarize the following text:\n\n${text}` }]
})
  .then(response => {
    const summary = response.content;
    if (outputFile) {
      fs.writeFileSync(outputFile, summary, "utf-8");
      console.log(`Summary written to ${outputFile}`);
    } else {
      console.log(summary);
    }
  })
  .catch(error => {
    console.error("Error during summarization:", error);
    process.exit(1);
  });

Breaking down the Yargs example:

  • Yargs Configuration: We start by calling yargs(hideBin(process.argv)). The hideBin helper strips out the first two default arguments (node executable and script path) so that Yargs sees only the actual options. We define options for input, output, and model with their aliases, types, descriptions, and whether they are required. demandOption: true for input means it will error if -i is not provided. We also use .usage() to show a usage template and .example() to add an example in the help output. wrap(null) just disables line wrapping for help text (so it uses full terminal width).
  • Parsing: Calling .argv triggers the parsing and returns an argv object with the values. We immediately destructure argv.input, etc., for easier use.
  • File reading: Same as before, with error handling.
  • Claude API call: Here, instead of using async/await, we demonstrate using the promise directly (client.messages.create(...).then(...).catch(...)). The logic inside is the same: on success, output to file or console; on error, print a message and exit.
  • Running & Help: Yargs automatically generates a help message accessible via --help. It will include our usage string, option descriptions, and example usage. Running the tool is similar to the Commander example.

Yargs is flexible and has many features (like command submodules, interactive prompts, etc.), but for this basic use-case it behaves similarly to Commander. It often comes down to preference which library to use.

Example 6: Node.js CLI with Oclif

Oclif (Open CLI Framework by Heroku/Salesforce) is a powerful framework for building CLI applications in Node.js, especially if you need multi-command CLIs (like Git or Heroku CLI style). It typically involves generating a project structure, but we can show a simplified single-command example to illustrate how it works.

Typically, you’d create an Oclif CLI by running a generator (npx oclif single <cli-name> for a single-command CLI, or oclif multi for multi-command). Oclif uses a class-based approach for commands. Below is an example command class that would be part of an Oclif project for our summarize tool:

const { Command, Flags } = require('@oclif/core');
const fs = require('fs');
const { Anthropic } = require('@anthropic-ai/sdk');

class SummarizeCommand extends Command {
  async run() {
    const { flags } = await this.parse(SummarizeCommand);
    const inputFile = flags.input;
    const outputFile = flags.output;
    const modelId = flags.model;

    // Read file content
    let text;
    try {
      text = fs.readFileSync(inputFile, 'utf-8');
    } catch (err) {
      this.error(`Failed to read input file: ${err.message}`, { exit: 1 });
    }

    // Call Claude API
    const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
    try {
      const response = await client.messages.create({
        model: modelId,
        max_tokens: 1000,
        messages: [{ role: 'user', content: `Summarize this text:\n\n${text}` }]
      });
      const summary = response.content;
      if (outputFile) {
        fs.writeFileSync(outputFile, summary, 'utf-8');
        this.log(`Summary written to ${outputFile}`);
      } else {
        this.log(summary);
      }
    } catch (error) {
      this.error(`API call failed: ${error}`, { exit: 1 });
    }
  }
}

// Define flags for the command
SummarizeCommand.description = `Summarize a text file using Claude AI.
Specify an input file and optionally an output file to save the summary.`;
SummarizeCommand.flags = {
  input: Flags.string({ char: 'i', description: 'input file path', required: true }),
  output: Flags.string({ char: 'o', description: 'output file path' }),
  model: Flags.string({ description: 'Claude model ID to use', default: 'claude-2' })
};

module.exports = SummarizeCommand;

Explanation of the Oclif example:

  • Command Class: We define a class SummarizeCommand that extends Command from @oclif/core. The run() method is where the command’s logic resides. Oclif will call this method when the command is invoked.
  • Flags (Options): We define SummarizeCommand.flags as a static property. Using Flags.string we declare three options: --input (-i) required, --output (-o) optional, and --model with a default value. Oclif automatically parses these and provides them to us in this.parse(SummarizeCommand) which we call at the start of run() to get the flags object.
  • Reading and writing: We use this.error() to handle errors, which is an Oclif utility to print an error and optionally exit. this.log() is used for normal output (similar to console.log but with some Oclif sugar).
  • Claude API call: Done with await inside the async run(), similar to previous examples. On success, we either log or write to file.
  • Description: We provide a description for help text. Oclif will use that plus the flags definition to auto-generate help messages.

To use this in an actual CLI, you’d normally have it in a file under a commands/ directory and an entry in the CLI’s index. Given the complexity of setting up a full Oclif project, advanced users would follow Oclif’s documentation to bootstrap one. The above snippet demonstrates how integration inside an Oclif command looks.

Why use Oclif? For a simple single-command tool, Oclif might be overkill. Its strengths shine when you have a suite of commands (like mycli add ..., mycli remove ..., mycli list ... subcommands, etc.), need auto-completion, plugins, or a robust structure. Many large CLIs (Heroku CLI, Salesforce CLI, etc.) are built on Oclif. It’s good to know it exists for when your CLI project grows in scope.

Setting Up, Testing, and Deploying Your CLI Tool

Now that we’ve built example CLI tools, let’s cover how to run and test them, and what it means to “deploy” a CLI locally:

Install Dependencies: Make sure you’ve installed the necessary packages for whichever example you’re running. (We listed pip/npm commands in the setup section. For Node examples, ensure @anthropic-ai/sdk is installed along with Commander/Yargs/Oclif if running those scripts.)

Set Environment Variables: As emphasized earlier, your ANTHROPIC_API_KEY must be available in the environment for the API calls to succeed. Double-check by running echo $ANTHROPIC_API_KEY (on Linux/macOS) or echo %ANTHROPIC_API_KEY% (on Windows) to ensure it’s set in the session where you’ll run the CLI. If not, set it accordingly.

Run the CLI Locally: Invoke the script with proper arguments. For example:Python/argparse version: python pysummarize.py input.txt -o output.txtPython/typer version: python app.py summarize input.txt --output output.txtNode/commander version: node nodesum.js -i input.txt -o output.txtNode/yargs version: node nodesum-yargs.js --input input.txtIf you converted a script into an executable (with shebang and chmod as shown), you can run ./pysummarize.py ... directly, or even add it to your PATH for convenience.For an Oclif CLI, if properly set up as a package, you would run it as mycli (depending on how you named it) once installed.Observe the output. If you provided an output file, open that file to confirm the content. If printed to console, review the summary text.

Testing and Debugging: Try edge cases:

Provide a non-existent file path to see if error handling works.

Try a very large text file to summarize (Claude might return a very long summary or might consume more tokens; you might adjust max_tokens or chunk input in a real scenario).

If something fails (e.g., an API error or bug in code), use Claude Code to debug: copy any error trace and ask Claude for help, or step through logic in an interactive manner (if using the CLI assistant, you could let it run the failing scenario).

Iterate with Claude: The development doesn’t have to stop at the first draft. Perhaps after initial testing, you decide to add a feature (say, an option to choose summary length, or support reading from STDIN). You can go back to Claude (either web or CLI) and request those changes. Because Claude can work with the existing code, it might just modify or add the necessary parts rather than starting from scratch. This iterative loop – Generate ➡️ Test ➡️ Refine – is where Claude really boosts productivity, handling the boilerplate while you guide the high-level design.

Deploying Locally: For personal use, “deploying” the CLI might simply mean moving it to a directory in your PATH or packaging it:

Python: You could turn the script into a pip-installable package if you want (using setuptools or pyproject.toml). But if it’s just for you, placing the file in /usr/local/bin (or Windows PATH) works. For example:

chmod +x pysummarize.py
mv pysummarize.py /usr/local/bin/pysummarize

Now you can use $ pysummarize input.txt.

Node: If you created a package.json for it, you can add a "bin" field and do npm link to symlink it for local use, or npm publish if you want to share it. With the shebang in place and npm link, running nodesum -i file.txt would work globally on your system.

Keeping Configs: If your tool needs configuration (like the API key), ensure those env vars are set on any system where you use the tool. You might integrate Dotenv for convenience in the code, but be cautious not to hardcode secrets.

Using the Tool in Automation: As an advanced user, you might integrate the CLI tool in scripts or CI pipelines. For example, perhaps you schedule pysummarize to summarize logs or reports daily. Because it’s a normal CLI command now, it can be invoked in cron jobs, CI jobs, etc. Just ensure Claude API usage is accounted for (e.g., rate limits or cost).

Version Control: Don’t forget to version control your CLI project. Even though Claude wrote a lot of the code, treat it like any codebase – commit to Git, write README/documentation for usage, and consider adding tests. Claude can also assist with writing test cases or documentation if asked!

Throughout development, always remain the human in charge: use Claude’s suggestions as accelerators, but review the code for security and correctness. For instance, when dealing with file paths, ensure you guard against path traversal if it’s user input. When calling external APIs (even Claude’s), handle exceptions and failures gracefully. Claude is quite good at including such considerations when prompted, but it’s ultimately your responsibility to validate them.

Conclusion

Building CLI tools is a breeze when you pair your expertise with the power of Claude Code. We demonstrated how to create practical CLI applications in Python and Node.js, using Claude to generate and improve code. Advanced developers can benefit immensely from this workflow: you maintain architectural and domain control, while Claude handles repetitive coding, offers creative solutions, and even debugs alongside you.

To recap, we covered:

Setting up Claude Code via both web and CLI, and when to use each.

Using Claude’s web interface to rapidly prototype or fix CLI code in a repository, with the ability to parallelize tasks and get cloud-based execution.

Using the Claude CLI agent to interactively build and test a tool in your local environment, leveraging its ability to run commands and manage context within your project.

Full code examples of CLI tools in two languages and multiple frameworks, illustrating common tasks (arg parsing, file I/O, API integration).

Tips for testing and deploying your new CLI tools in real-world scenarios.

By following a structured approach – Plan → Generate (with Claude) → Review → Refine → Test – you can create robust CLI utilities in a fraction of the time it would normally take, without sacrificing quality or control. Claude Code essentially becomes a junior developer in your terminal, one who works at superhuman speed but still relies on your guidance.

As you build more complex tools, you can explore additional Claude Code features: multi-command projects, integrating Claude Skills or custom tools, and even using Claude’s memory to maintain state across sessions. The combination of modern AI and traditional software development opens up exciting possibilities; CLI tools are just one area to apply this.

Finally, remember that while Claude can write code, you are the ultimate code owner. Always audit and test the outputs, especially for critical applications. With responsible use, Claude Code can be a game-changer in your development workflow – helping you focus on creative problem-solving while it takes care of the boilerplate and heavy lifting.

Leave a Reply

Your email address will not be published. Required fields are marked *