Simple CLI Wrapper with CLI-Anything

Turn any command‑line tool into an AI‑agent friendly interface in minutes

Welcome to another official UVF IT build where we turn a regular command‑line program into an agent‑native wrapper using the open‑source CLI‑Anything library. We'll create a minimal, working clone that lets an AI agent invoke git status through a clean Python API—no paid services required.

In the next ~45 minutes you'll scaffold a tiny Python project, define a model for the CLI, expose a tiny FastAPI endpoint, run it locally, and see the agent call it back. Ready? Let's dive in!

Prerequisites & Gotchas

Component Overview
LOCAL RUNTIME POST /run executes CLI Wrapper (FastAPI) Underlying CLI (git) Agent / HTTP Client
1

Phase 1: Scaffold the project

Create a fresh folder for the wrapper, set up an isolated Python virtual environment, and install the required libraries. This gives us a clean sandbox so our dependencies won’t clash with other projects on the machine.

We’ll also add a basic requirements.txt so the next developer can reproduce the environment with a single command. All of this is done from the terminal, no IDE needed.

mkdir cli_wrapper && cd cli_wrapper
python3 -m venv .venv
source .venv/bin/activate
cat > requirements.txt <<'EOF'
fastapi
uvicorn[standard]
cli-anything
EOF
pip install -r requirements.txt
2

Phase 2: Define the CLI model

Now we describe the external command we want to expose (git status) using CLI‑Anything's CommandSpec. The spec tells the wrapper how to invoke the tool, what arguments are allowed, and how to capture its output.

Keeping the model in its own file (model.py) makes the FastAPI layer thin and testable. Later we can add more commands by copying this pattern.

model.py

from cli_anything import CommandSpec, Arg

# Define a simple spec for `git status`
GitStatusSpec = CommandSpec(
    name="git-status",
    executable="git",
    args=[
        Arg(name="status", flag="status", type=str, required=False, help="Run git status")
    ],
    description="Runs `git status` in the current repository and returns the raw output.",
)

def run_git_status() -> str:
    """Execute the spec and return stdout.
    The CLI‑Anything library handles subprocess creation and error handling.
    """
    result = GitStatusSpec.run(["status"])
    return result.stdout
3

Phase 3: Build the FastAPI wrapper

In this step we turn the command definition into a tiny web service. A POST request to `/run` will trigger the CLI‑Anything runner and capture whatever the tool prints to standard output. FastAPI gives us automatic JSON handling and an interactive docs page, which is perfect for early prototyping.

We keep the API minimal: the request body is empty because the command we want to expose (for example `git status) does not need extra arguments. The response is a JSON object with an output` field that contains the raw stdout string. This makes it easy for an AI‑agent or any client to consume the result.

main.py

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import subprocess

app = FastAPI()

class RunResponse(BaseModel):
    output: str

@app.post("/run", response_model=RunResponse)
def run_command():
    """Execute the predefined CLI‑Anything command and return its stdout.
    If the subprocess fails we surface the error as a 500 response.
    """
    try:
        # Example command – replace with any CLI‑Anything invocation
        result = subprocess.run(
            ["git", "status"],
            capture_output=True,
            text=True,
            check=True,
        )
        return RunResponse(output=result.stdout.strip())
    except subprocess.CalledProcessError as e:
        raise HTTPException(status_code=500, detail=e.stderr.strip())
4

Phase 4: Run locally

Now that the API server is written we can start it with Uvicorn, the production‑grade ASGI server that FastAPI recommends. The command below launches the app in development mode, watches for file changes, and binds to the local loopback address on port 8000.

You can verify the server is alive by opening `http://127.0.0.1:8000/docs in a browser – FastAPI automatically generates a Swagger UI where you can manually invoke the /run` endpoint.

uvicorn main:app --reload --host 127.0.0.1 --port 8000
5

Phase 5: Verify & extend

With the server running, a quick `curl call confirms that the endpoint works and returns the expected Git status. The JSON payload includes an output` key that holds the command’s stdout, which you can parse or feed to an LLM.

Later you might want to expose multiple commands, accept arguments in the request body, or protect the API with API keys. Adding those features is just a matter of expanding the `run_command` function and updating the Pydantic models.

curl -X POST http://127.0.0.1:8000/run -d '{}'

Verifying the Stack

CheckCommand / Why it matters
Virtual environment activated and dependencies installedpip list shows fastapi, uvicorn, cli-anything
Server starts without errorsuvicorn main:app --reload prints "Uvicorn running on http://127.0.0.1:8000"
POST request returns git status outputcurl -X POST http://127.0.0.1:8000/run -d '{}' returns JSON with an "output" field containing "On branch"

Next Steps

You now have a minimal AI‑agent friendly CLI wrapper. Add more commands in `main.py or a separate model.py`, secure the API with API keys, or containerise it with Docker for broader deployment.