Skip to content
Learn Agentic AI12 min read0 views

Building a CLI Assistant Agent: Natural Language Command Line Interactions

Build an AI agent that translates natural language into shell commands, explains what each command does, asks for confirmation before executing dangerous operations, and learns from command history.

Why a CLI Assistant Agent

The command line is powerful but has a steep learning curve. Developers frequently search the internet for the right flags to pass to git, docker, kubectl, or ffmpeg. A CLI assistant agent lets you describe what you want in plain English, translates it into the correct command, explains what it will do, and optionally executes it after confirmation.

Unlike static cheatsheets, the agent understands your current context — your OS, installed tools, and working directory — to produce commands that actually work.

The CLI Agent Core

The agent maps natural language to shell commands, classifying each as safe or dangerous before execution.

import os
import subprocess
import shutil
from dataclasses import dataclass
from openai import OpenAI

client = OpenAI()

@dataclass
class CommandResult:
    command: str
    explanation: str
    is_dangerous: bool
    output: str | None = None
    error: str | None = None
    executed: bool = False

class CLIAssistantAgent:
    def __init__(self, model: str = "gpt-4o"):
        self.model = model
        self.history: list[dict] = []
        self.dangerous_patterns = [
            "rm -rf", "mkfs", "dd if=", "> /dev/",
            "chmod 777", ":(){ :|:& };:",
            "DROP TABLE", "DELETE FROM",
            "--force", "--hard",
        ]

    def get_system_context(self) -> str:
        import platform
        shell = os.environ.get("SHELL", "unknown")
        cwd = os.getcwd()
        tools = {}
        for tool in ["git", "docker", "kubectl", "python", "node"]:
            tools[tool] = shutil.which(tool) is not None
        return (
            f"OS: {platform.system()} {platform.release()}\n"
            f"Shell: {shell}\n"
            f"CWD: {cwd}\n"
            f"Available tools: {tools}"
        )

Translating Natural Language to Commands

The core translation step uses the system context to produce environment-specific commands.

import json

def translate(self, user_request: str) -> CommandResult:
    context = self.get_system_context()
    history_context = ""
    if self.history:
        recent = self.history[-5:]
        history_context = "Recent commands:\n" + "\n".join(
            f"- {h['request']} -> {h['command']}" for h in recent
        )

    response = client.chat.completions.create(
        model=self.model,
        messages=[
            {"role": "system", "content": f"""You are a CLI assistant.
Translate user requests into shell commands.

System info:
{context}

{history_context}

Return JSON with:
- "command": the shell command to execute
- "explanation": plain English explanation of what it does
- "is_dangerous": boolean, true if the command modifies or deletes data

IMPORTANT: Use tools available on this system. Adapt commands
for the detected OS (e.g., use gsed on macOS if needed).
Return ONLY valid JSON."""},
            {"role": "user", "content": user_request},
        ],
        temperature=0,
        response_format={"type": "json_object"},
    )

    data = json.loads(response.choices[0].message.content)
    result = CommandResult(
        command=data["command"],
        explanation=data["explanation"],
        is_dangerous=data.get("is_dangerous", False),
    )

    for pattern in self.dangerous_patterns:
        if pattern in result.command:
            result.is_dangerous = True
            break

    return result

Notice the double check: the LLM classifies danger, and then the agent applies its own pattern-based check. This defense-in-depth approach prevents the LLM from accidentally marking a destructive command as safe.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

Safe Execution with Confirmation

Dangerous commands require explicit user confirmation before running.

def execute(self, result: CommandResult, force: bool = False) -> CommandResult:
    if result.is_dangerous and not force:
        result.error = "BLOCKED: Dangerous command requires confirmation"
        return result

    try:
        proc = subprocess.run(
            result.command,
            shell=True,
            capture_output=True,
            text=True,
            timeout=30,
            cwd=os.getcwd(),
        )
        result.output = proc.stdout
        result.error = proc.stderr if proc.returncode != 0 else None
        result.executed = True
    except subprocess.TimeoutExpired:
        result.error = "Command timed out after 30 seconds"
    except Exception as e:
        result.error = str(e)

    self.history.append({
        "request": "executed",
        "command": result.command,
        "success": result.error is None,
    })
    return result

Building an Interactive Loop

The agent runs as a REPL that continuously accepts requests.

def run_interactive(self):
    print("CLI Assistant (type 'exit' to quit)")
    print("-" * 40)

    while True:
        user_input = input("\n> ").strip()
        if user_input.lower() in ("exit", "quit"):
            break
        if not user_input:
            continue

        result = self.translate(user_input)
        print(f"\nCommand:     {result.command}")
        print(f"Explanation: {result.explanation}")

        if result.is_dangerous:
            print("\n[WARNING] This command modifies or deletes data.")
            confirm = input("Execute? (y/N): ").strip().lower()
            if confirm != "y":
                print("Cancelled.")
                continue
            result = self.execute(result, force=True)
        else:
            result = self.execute(result)

        if result.output:
            print(f"\nOutput:\n{result.output}")
        if result.error:
            print(f"\nError:\n{result.error}")

agent = CLIAssistantAgent()
agent.run_interactive()

FAQ

How do I prevent command injection if the user input is malicious?

The agent should never interpolate user input directly into shell commands. The LLM generates the full command as a string, and the dangerous-pattern checker blocks known attack vectors like ; rm -rf / or backtick injection. For additional safety, run commands in a restricted subprocess with limited permissions and environment variables.

Can the agent compose multi-step commands like pipes and redirects?

Yes. The LLM naturally understands piping, redirection, and command chaining. A request like "find all Python files larger than 1MB and sort by size" produces find . -name '*.py' -size +1M -exec ls -lh {} + | sort -k5 -h. The explanation breaks down each step so the user understands what each part does.

How does command history improve the agent's responses?

The last five commands are included in the prompt context. This allows the agent to understand follow-up requests like "now do the same but only for .js files" or "run that again with verbose output." The agent resolves these references against recent history.


#CLITools #AIAgents #Python #Shell #DeveloperProductivity #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.