A minimal command-line agent that connects an arbitrary text prompt to a remote LLM (OpenAI or Anthropic), gathers host facts so generated commands match the local environment, and executes those commands only after explicit user approval. Single Python file. No frameworks.
Off-the-shelf agentic CLIs (aider, claude-code, etc.) optimize for code authoring. This one optimizes for a different loop: ad-hoc system administration, infrastructure debugging, and "what's the right command on this host?" questions where the answer depends on distro, init system, installed package manager, and which tools happen to be on PATH.
The agent ships those host facts to the model up front so command syntax
matches reality — no more apt suggestions on a Mac.
- Multi-provider: OpenAI or Anthropic, selected at startup. Both keys present → interactive prompt. One key → auto-pick.
- Host-aware: distro, shell, machine arch, and a probe of installed
tools (
apt/brew/systemctl/docker/etc.) are injected into the system prompt. - Approval-gated execution: every proposed command is shown with its reason and CWD before it runs. Edit-before-run supported.
- Local hard-deny list: a short list of catastrophic patterns
(
rm -rf /,mkfs, fork bomb, rawddto block devices) is blocked client-side regardless of model output or user approval. - Token usage display: optional per-turn input/output token counts plus running session totals and percentage of context window consumed.
- Color output: semantic ANSI coloring with auto-detection
(TTY/
NO_COLOR) and runtime toggle. - Persistent history: line editing and history navigation via readline
(gnureadline on macOS). Conversational prompts persist to
~/.config/sys_agent/historyacross sessions; meta-commands and short-answer prompts are excluded so Up-arrow recall stays useful. See Tips & shortcuts for keystrokes. - Zero install footprint with uv: PEP 723 inline-script dependencies;
uvhandles the environment transparently.
- Python ≥ 3.10
- One of:
uv(recommended) or pip + venv - An OpenAI and/or Anthropic API key
- macOS/Linux for the full experience. On Windows, the stdlib lacks
readline— the script still runs, but loses history persistence, Up/Down recall, and line-editing keystrokes.
git clone https://github.com/mikeoc61/sys_agent.git
cd sys_agent
chmod +x sys_agent.py
mkdir -p ~/.local/bin
ln -sf "$PWD/sys_agent.py" ~/.local/bin/sys_agent
sys_agentFirst invocation builds a cached environment from the script's inline dependency block; subsequent runs are instant.
macOS: the inline block pulls in
gnureadlineautomatically (sys_platform == 'darwin') to replace the system libedit-backed readline with proper GNU readline — colored prompts render correctly and Up/Down history navigation redraws cleanly. Linux installs skip this dependency.
git clone https://github.com/mikeoc61/sys_agent.git
cd sys_agent
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
./sys_agent.pyrequirements.txt carries the same gnureadline macOS-only marker as
the inline block.
API keys can come from the shell environment or from a key-value file. If
SYS_ENV_FILE is set, that path is used exclusively. Otherwise sys_agent
searches the following locations in priority order and uses the first that
exists:
./.env— current working directory (dotenv convention)$XDG_CONFIG_HOME/sys_agent/.env(default~/.config/sys_agent/.env)~/.sys_agent.env— home dotfile fallback
Shell-exported variables always override file values.
mkdir -p ~/.config/sys_agent
cp .env.example ~/.config/sys_agent/.env
chmod 600 ~/.config/sys_agent/.env
$EDITOR ~/.config/sys_agent/.envThe template (.env.example) is committed; the real .env is gitignored.
Shell-style KEY=value, one per line. export prefix and # comments
are accepted; matching surrounding quotes are stripped.
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# Optional overrides
SYS_PROVIDER=anthropic
SYS_OPENAI_MODEL=gpt-5.4-mini
SYS_ANTHROPIC_MODEL=claude-sonnet-4-6| Var | Purpose | Default |
|---|---|---|
OPENAI_API_KEY |
OpenAI auth (one required) | — |
ANTHROPIC_API_KEY |
Anthropic auth (one required) | — |
SYS_PROVIDER |
Skip provider prompt: openai or anthropic |
(prompt) |
SYS_OPENAI_MODEL |
OpenAI model string | gpt-4o-mini |
SYS_ANTHROPIC_MODEL |
Anthropic model string | claude-haiku-4-5-20251001 |
SYS_ENV_FILE |
Explicit env-file path; skips the search above | (search) |
SYS_COLOR |
on / off / auto |
auto |
NO_COLOR |
If set, disables color regardless of SYS_COLOR=auto |
— |
| Path | Purpose |
|---|---|
~/.config/sys_agent/.env (or one of the alternatives above) |
API keys and SYS_* overrides |
~/.config/sys_agent/history |
Readline history (1000-line cap, persistent across sessions) |
The history file is created on first exit. Only conversational prompts are
retained — meta-commands (/info, /exit, etc.) and short-answer prompts
(y/n, 1/2) are excluded so Up-arrow recall stays useful. Clear with
> ~/.config/sys_agent/history if you ever want a fresh slate.
Once running, type natural-language requests:
you> show me which services are using the most RAM
you> check why bitcoind is restarting
you> what's the current load average and what process is dominating?
you> upgrade nginx to the latest stable version
For mutating actions, the approval prompt is your safety net. Type e to edit
the command before execution.
Line editing is provided by readline (or gnureadline on macOS, installed automatically).
| Key | Action |
|---|---|
| Up / Down | Cycle through prior conversational prompts |
| Ctrl-R | Reverse-incremental search through history |
| Ctrl-A / Ctrl-E | Jump to start / end of line |
| Ctrl-W | Delete previous word |
| Ctrl-U / Ctrl-K | Delete to start / end of line |
| Tab | (No completion — sys_agent doesn't bind any) |
History persists across sessions; recall surfaces only real prompts, so
Up-arrow won't waste your time on y/n answers or meta-commands. To
start with a clean slate: > ~/.config/sys_agent/history.
| Command | Effect |
|---|---|
/exit, /quit |
End session |
/reset |
Clear conversation history and token counters |
/info |
Print provider/model, session token usage, host facts |
/auto on|off |
Skip approval prompt (hard-deny list still applies) |
/tokens on|off |
Toggle per-turn token-usage line |
/tokens |
Print current snapshot without changing toggle |
/color on|off |
Toggle ANSI color output |
Three layers, weakest to strongest:
- Approval prompt (default-on, per-command). Every
run_commandshows the exact shell string, the model's stated reason, and the CWD before any subprocess is spawned. Default answer isyso casualEnterruns it — read the line. Read the line. - Local hard-deny list (always-on). A short set of irrecoverable command
patterns is blocked before the approval prompt is even shown. The model
cannot disable this and
/auto oncannot bypass it. SeeDENY_SUBSTRINGSinsys_agent.py. - Command timeout (60s wall-clock per command). Prevents runaway model loops from hanging the REPL on a single command.
The deny list is intentionally short and pattern-matched. It is not a
substitute for paying attention to the approval prompt. Sandbox the agent
(VM, container, firejail) if you want to test it on untrusted prompts.
┌────────────────────────────────────┐
│ User REPL │
│ (sys_agent.py: run_repl) │
└──────────────┬─────────────────────┘
│
host_facts + │ command output
conversation │ (stdout, stderr, exit)
▼
┌────────────────────────────────────┐
│ Provider abstraction │
│ OpenAIProvider | AnthropicProvider│
└──────────────┬─────────────────────┘
│ HTTPS + tool calling
▼
┌────────────────────────────────────┐
│ Remote LLM API │
└────────────────────────────────────┘
▲
│ run_command(cmd, reason)
│ ──┐
│ │ approval gate
│ │ deny-list check
│ │ subprocess.run(...)
│ │
▼ ▼
┌────────────────────────────────────┐
│ Local host │
└────────────────────────────────────┘
The provider abstraction normalizes tool-call/tool-result message structure
between the two APIs (OpenAI sends one role: tool message per call;
Anthropic batches all tool_result blocks into a single user message). The
REPL is provider-agnostic.
- Not a coding agent. Use aider, claude-code, or Cursor for that.
- Not a long-horizon autonomous agent. There is no planner, no memory beyond the active conversation, no parallel workers.
- Not sandboxed. Commands run as the invoking user, with that user's full privileges.
MIT — see LICENSE.