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!
pip installed; the guide assumes a Unix‑like shell.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.txtNow 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.stdoutIn 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())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 8000With 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 '{}'| Check | Command / Why it matters |
|---|---|
| Virtual environment activated and dependencies installed | pip list shows fastapi, uvicorn, cli-anything |
| Server starts without errors | uvicorn main:app --reload prints "Uvicorn running on http://127.0.0.1:8000" |
| POST request returns git status output | curl -X POST http://127.0.0.1:8000/run -d '{}' returns JSON with an "output" field containing "On branch" |
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.