This document explains why OpsTerm is designed the way it is โ the reasoning behind each technical decision, including the trade-offs that were made.
| # | Decision | Choice | Rejected Alternatives |
|---|---|---|---|
| 1 | Language | Python 3 | Go, Rust, Node.js, Bash |
| 2 | Architecture | Single-file CLI | Modular package, Client-server |
| 3 | Dependencies | Zero (stdlib only) | PyYAML, requests, click |
| 4 | Config format | YAML (custom parser) | JSON, TOML, INI |
| 5 | AI Protocol | OpenAI-compatible API | LangChain, custom gRPC |
| 6 | SSH Method | Subprocess + system SSH | paramiko, asyncssh |
| 7 | State | File-based (no daemon) | SQLite daemon, Redis |
| 8 | Vault | Optional cryptography | Hardcoded key, no encryption |
| 9 | Completion | Generated script | Manual completion |
| 10 | Shell Integration | Separate Zsh plugin | Hook shell, LD_PRELOAD |
OpsTerm needed a language that is portable, zero-dependency, and easy to maintain.
Reasons:
Ubiquitous โ Every Linux and macOS system has Python 3. Users just download the script and run it. With Go/Rust, you'd need to compile first or download a binary.
Rich stdlib โ argparse, json, sqlite3, urllib, hashlib, subprocess โ all built-in. In Go/Rust, these libraries require external imports or have less complete standard libraries.
Maintainability โ Python is easier to read and modify. Potential contributors can understand the code more easily.
File size โ ~50KB Python script vs ~10MB Go binary. Small and easy to copy to any server.
Rapid iteration โ New features can be used immediately without compilation. Edit โ save โ run.
Trade-offs:
- Lower performance (but for a CLI tool, this is imperceptible)
- Requires Python interpreter (but every modern system already has one)
OpsTerm could have been built as a Python package installable via pip install.
bin/ai)Reasons:
Portable โ Copy one file to any server and it runs. scp bin/ai server:~/.local/bin/
Zero setup โ No pip install, no python -m opsterm. Just ./ai.
Transparent โ The entire code can be read and understood. No __pycache__, egg-info, etc.
Easy debugging โ If there's an error, just edit a specific line. No need to hunt through directories.
Trade-offs:
- Large file (~1500 lines). Still small compared to other applications.
- All functions in one file โ harder to unit test.
- Mitigation: code is organized with section header comments.
Many Python CLI tools use PyYAML, requests, click, etc.
Reasons:
No install step โ Clone repo โ run directly. No pip install -r requirements.txt needed.
No version conflicts โ Will never clash with system libraries or other projects.
Works in any Python environment โ Virtual env, system Python, container โ all work.
Easy to audit โ All running code can be read. No hidden dependencies.
What was sacrificed:
urllib) โ more verbose than requests, but functional.argparse) โ less powerful than click, but sufficient.Config format for servers, workflows, and AI settings.
Reasons:
Readability โ YAML is more human-readable than JSON. Comments and clean indentation.
User-friendly โ OpsTerm's target users are DevOps engineers who already use YAML (Docker Compose, Kubernetes, Ansible, GitHub Actions).
No brackets โ JSON is full of {} and [] that hurt readability for long configs.
Trade-offs:
- Custom parser โ limited. But the YAML subset we use (mapping, list, scalar) is stable and sufficient.
- Inconsistency with "zero dep" โ technically YAML parsing is custom code, not an external dependency.
Could have used LangChain, LiteLLM, or another AI abstraction library.
Reasons:
Zero dependency โ Only urllib (stdlib). LangChain requires 20+ dependencies.
Provider agnostic โ The OpenAI request/response format has become the de-facto standard. DeepSeek, Ollama, vLLM, OpenRouter โ all support it.
Simple โ The Chat Completion API is just one endpoint with a simple format.
Debug friendly โ Requests/responses can be logged, inspected, and tested with curl.
Trade-offs:
- No built-in retry logic, streaming, or tool calling.
- But for a blocking CLI tool, this doesn't matter.
Could have used paramiko (a Python SSH client library).
subprocess + /usr/bin/ssh)Reasons:
Feature parity โ System SSH has every feature: key management, ProxyJump, agent forwarding, compression, etc.
Zero dependency โ paramiko requires bcrypt, cryptography, pynacl, etc.
Familiar โ All SSH configuration (known_hosts, config, agent) works automatically.
Interactive support โ os.execvp() can provide a full interactive SSH session. paramiko can't do this seamlessly.
Already configured โ Users already have SSH keys set up on their system.
Trade-offs:
- Cannot parse SSH output in real-time.
- Limited error handling (exit code only).
Some tools use a background daemon that stores state in memory.
Reasons:
Zero resource โ No background process. No RAM/CPU consumed when not in use.
Crash-proof โ If the terminal is killed, data isn't lost. Everything is in files.
Transparent โ Users can open the config directly and edit with a text editor.
Simple โ No need to manage process lifecycle, signal handling, or lock files.
Trade-offs:
- State is read from disk on every command โ slight overhead (<10ms).
- No "real-time" notification capability.
Vault requires cryptography which is not in stdlib.
cryptography recommended, fallback availableReasons:
Zero dep still applies โ OpsTerm's core features work without the vault.
Security remains a priority โ Vault uses AES via cryptography. Fallback uses XOR + HMAC when cryptography is unavailable.
Progressive enhancement โ Users can start without the vault and install cryptography later if needed.
Trade-offs:
- Two code paths to maintain (with and without cryptography).
- Fallback encryption is weaker than AES.
Completion could be written manually or generated.
Reasons:
Always up-to-date โ When new subcommands are added, completion automatically updates because it's generated from the code.
Zero additional files โ ai completion bash prints directly to stdout. No separate file needed.
Works out of the box โ source <(ai completion bash) โ one command, done.
Trade-offs:
- Requires the ai script to be installed before completion works.
- Generator logic adds ~50 lines to the script.
Shell integration could be part of the main script or a separate file.
zsh/opsterm.plugin.zsh)Reasons:
Different language โ Zsh plugins use Zsh script, not Python. Combining them in the Python file would be messy.
Zsh-only โ Shell hook features (preexec, precmd) only exist in Zsh.
Lazy loading โ The plugin is only loaded if the user sources it in .zshrc. Doesn't add weight otherwise.
Familiar pattern โ Zsh plugin format is already standard (oh-my-zsh, antigen, zplug).
Trade-offs:
- Only supports Zsh out of the box (no Bash/Fish support yet).
- Users must manually source the plugin in their shell config.
Based on the design decisions above, some future considerations:
Python โ Rust (future) โ If performance and distribution become issues, rewriting in Rust would provide a single binary.
YAML โ TOML โ If stricter configs with type safety are needed, TOML would be a better fit. However, this would require an external library.
Add plugin system โ Allow the community to add features without editing the core script.
Support Fish shell โ Popular among modern developers.