Learn and use Linux through natural language — powered by local LLMs via Ollama.
Describe what you want in plain English and get 3 executable command options, each with detailed explanations that break down every pipe, flag, and chained command so you understand what you're running.
- Interactive shell mode — Run
oshwith no arguments to enter a REPL; type queries continuously, use!helpfor commands, orexit/!exitto leave - Natural language to shell — Describe what you want; get valid commands back
- 3 alternatives per query — Each generated by its own focused LLM call: a couple of shell variants, then one-liners in awk, perl, python3, etc. depending on what's installed
- Detailed explanations — Each chained command (pipes,
&&,;) explained on its own line - QA safety review — Second-pass LLM review flags dangerous, incorrect, or imprecise commands
- Command availability check — Detects whether each suggested tool is installed before you run it
- Language detection — Auto-detects available scripting languages (bash, awk, perl, python3, ruby, node, etc.) and only suggests commands using what's installed
- Cloud model support — Use any Ollama cloud model by appending
:cloudor-cloudto the model name (e.g.llama3.2:cloud); authenticates viaOLLAMA_API_KEY - Per-invocation model override — Use
-m <model>to pick a different model without editing config; use-m -to select interactively from available Ollama models - Thinking model support — Handles models that use a separate
thinkingfield (e.g., gpt-oss, deepseek-r1) with automatic response extraction and reformatting - Clipboard support — Copy any command to clipboard instead of executing
- Retry on failure — If a command fails or misses the question, refine and retry interactively
- Daily log files — All queries, responses, verdicts, and user actions logged to
~/.local/state/osh/YYYYMMDD.log - XDG-compliant paths — Config in
~/.config/osh/, state in~/.local/state/osh/, app in~/.local/osh/ askcompanion — Pipe any text throughaskfor general-purpose LLM Q&A
- Python 3.11+
- Ollama installed and running locally
# Install
./install.sh
# Use
osh what is my public IP address
osh find all python files modified in the last 24 hours
osh show failed ssh login attempts grouped by IP./install.shThe installer will:
- Copy app files to
~/.local/osh/ - Create symlinks in
~/.local/bin/(osh,ask,computer) - Prompt for Python environment (pyenv, venv, or system)
- Install pip dependencies
- Let you select an Ollama model
- Save configuration to
~/.config/osh/config.json
After installation, ensure ~/.local/bin is in your PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc # or ~/.bashrc
source ~/.zshrcpip3 install -r requirements.txt
chmod +x osh.py ask.py
mkdir -p ~/.local/bin
ln -s $(pwd)/osh.py ~/.local/bin/osh
ln -s $(pwd)/ask.py ~/.local/bin/ask
osh --initosh # Enter interactive shell mode (REPL)
osh <natural language query>
osh -a <query> # Always prompt before executing
osh -m <model> <query> # Use a specific model (overrides config)
osh -m - <query> # Pick model interactively from Ollama
osh --config PATH <query> # Use alternate config file
osh --init # Interactive configuration
osh --version # Show versionRunning osh without arguments enters an interactive session where you can submit queries one after another without re-invoking the command. The prompt mimics a zsh-style two-line prompt, showing your user, current directory, and git branch (when inside a repo).
$ osh
Host: http://localhost:11434 Model: gpt-oss:latest
Oh Shell! - Interactive Shell Mode
Type your request in plain English, or '!help' for commands, '!' to exit.
┌(raul)-[~/src/osh][main]
└─: show disk usage of home directory
...
┌(raul)-[~/src/osh][main]
└─: !help
Available commands:
!exit Exit shell mode
!quit Exit shell mode
!help Show this help message
!version Show osh version
!history Show recent queries from today's session log
! Exit shell mode (shorthand)
Or type any request in plain English.
┌(raul)-[~/src/osh][main]
└─: exit
Exiting shell mode.
| Input | Action |
|---|---|
| Any natural language | Translated to shell commands as normal |
?<question> |
Ask the LLM directly (like the ask companion), bypassing shell-command generation |
exit, quit, bye, done, … |
Exit shell mode |
!exit / !quit / ! |
Exit shell mode |
!help |
List available ! commands |
!version |
Show osh version |
!history |
Show recent queries from today's session log |
| Ctrl-C / Ctrl-D | Exit shell mode |
$ osh find all log files modified in the last 7 days and show their sizes
Host: http://localhost:11434 Model: gpt-oss:latest
Generating command options...
Checking command availability...
Running safety review...
Available commands:
1. find /var/log -name '*.log' -mtime -7 -exec du -sh {} + [PASS]
Find all .log files modified within 7 days and display their sizes
find /var/log -name '*.log' -mtime -7: search for .log files modified in the last 7 days
-exec du -sh {} +: calculate and display human-readable size for each match
2. find /var/log -name '*.log' -mtime -7 | xargs du -sh [PASS]
Same goal using pipe to xargs instead of -exec
find /var/log -name '*.log' -mtime -7: locate recently modified .log files
| xargs du -sh: pass found files to du for size display
3. perl -e 'use File::Find; find(sub { print `du -sh $_` if /\.log$/ && -M $_ < 7 }, "/var/log")' [PASS]
Perl one-liner using File::Find to traverse /var/log
find(sub { ... }, "/var/log"): recursively walk the directory
/\.log$/ && -M $_ < 7: match .log files modified within 7 days
print `du -sh $_`: shell out to du for each match
Select command [1/2/3] [c]opy or [n]o ==>| Key | Action |
|---|---|
| 1–3 | Execute that command |
| c | Copy a command to clipboard |
| n / Enter | Cancel |
Each command is tagged by the safety review:
| Verdict | Meaning |
|---|---|
| PASS (green) | Correct, safe, matches intent |
| WARN (yellow) | Works but has a concern — confirmation required |
| MISS (magenta) | Safe but doesn't precisely answer the question |
| FAIL (red) | Dangerous, incorrect, or insecure — execution blocked |
Pipe text to ask for general-purpose Q&A:
ask "What is the capital of France?"
ask what is the capital of France?
echo "What is the capital of France?" | ask
cat error.log | ask "What went wrong?"osh --init~/.config/osh/config.json:
{
"api": "ollama",
"model": "gpt-oss:latest",
"temperature": 0.3,
"max_tokens": 2400,
"safety": true,
"qa_review": true,
"suggested_command_color": "blue",
"python_venv": null,
"ollama_endpoint": "http://localhost:11434",
"ollama_cloud_endpoint": "https://ollama.com",
"logging_enabled": true,
"log_retention_days": 30
}| Option | Description | Default |
|---|---|---|
model |
Ollama model name | gpt-oss:latest |
temperature |
Randomness (0.0–2.0) | 0.3 |
max_tokens |
Max response tokens (increase for thinking models) | 2400 |
safety |
Stored/shown by --init and the usage screen; not currently wired to any prompt-before-execute logic (that's controlled by -a/--ask and WARN verdicts) |
true |
qa_review |
Enable second-pass safety review | true |
suggested_command_color |
Terminal color for displayed commands | blue |
python_venv |
null, "pyenv:name", or "venv:/path" |
null |
ollama_endpoint |
Local Ollama API URL | http://localhost:11434 |
ollama_cloud_endpoint |
Ollama cloud API URL | https://ollama.com |
logging_enabled |
Enable daily log files | true |
log_retention_days |
Auto-delete logs older than N days | 30 |
osh --config /path/to/config.json what is my username{"python_venv": "pyenv:312"}
{"python_venv": "venv:/home/user/.venvs/myenv"}
{"python_venv": null}Ollama cloud models can be used by appending :cloud or -cloud to any model name:
osh -m llama3.2:cloud list files modified today
osh -m llama3.2-cloud what is my public IPOr set a cloud model as your default in ~/.config/osh/config.json:
{"model": "llama3.2:cloud"}Setup (one-time):
- Create a free account at https://ollama.com
- Generate an API key in your account settings
- Export the key in your shell profile (
~/.bashrcor~/.zshrc):
export OLLAMA_API_KEY=<your-key>If OLLAMA_API_KEY is not set when a cloud model is invoked, osh will exit with a clear error and setup instructions.
# System
osh what is my current working directory
osh show me system uptime
osh check if port 8080 is open
# Files
osh find all files larger than 100MB
osh compress the logs directory
osh count lines in all python files recursively
# Network
osh what is my public IP address
osh list all IP addresses of NICs that start with wlp
osh show all open network connections
# Processes
osh is nginx running
osh show all python processes
osh kill process on port 3000
# Data processing
osh extract columns 1 and 3 from data.csv
osh count unique values in column 2 of users.csv sorted by frequency
# Log analysis
osh show failed ssh login attempts from auth.log grouped by IP
osh extract unique IPs from nginx access.log that returned 404
osh find all sudo commands in auth.log grouped by userUser query
→ For each approach (2 shell variants, then one per detected scripting
language) issue a focused LLM call requesting exactly one command
(XML-tagged: <c1>...<e1>...)
→ Collect unique commands, skipping duplicates/parse failures, until
3 have been gathered
→ Check command availability (shutil.which + shell builtins)
→ QA safety review (single LLM call evaluating all collected commands)
→ Display options with verdicts
→ User selects → Execute
| File | Purpose |
|---|---|
osh.py |
Main application — prompts, LLM interaction, parsing, execution |
ask.py |
Pipe-based general Q&A companion |
install.sh |
Interactive installer with venv and model selection |
tests/ |
pytest suite covering config, parsing, safety guards, and interactive flows |
evals/ |
Opt-in script comparing QA safety-verdict accuracy across local Ollama models |
Models like gpt-oss place output in a thinking field instead of content. When content comes back empty, osh:
- Searches the
thinkingtext for<cN>/<eN>tagged commands and explanations - If tags are found, rebuilds them into a normal tagged response
- If no tags are found, falls back to returning the raw thinking text as-is
- Filters placeholder/garbage commands (literal "command", "echo 'Could not extract commands'")
This is a single best-effort extraction pass — there's no reformat retry or token-budget scaling. If a thinking model's output doesn't contain recognizable tags, that approach is simply skipped, and collect_unique_options moves on (it targets 3 options but doesn't strictly require them).
On startup, osh probes for available scripting languages:
bash, awk, sed, perl, python3, ruby, node, php, lua, Rscript
The detected list is injected into the system prompt so the model only suggests commands using tools actually installed on the system.
Daily log files in ~/.local/state/osh/YYYYMMDD.log:
2026-02-21 10:30:45 | INFO | AVAILABLE_LANGUAGES: bash, awk, sed, perl, python3, node
2026-02-21 10:30:46 | INFO | SINGLE_OPTION_REQUEST: approach=shell_1
2026-02-21 10:30:47 | INFO | SINGLE_OPTION_RESPONSE: approach=shell_1 | <c1>find / -size +100M -exec ls -lh {} \; </c1><e1>...</e1>
2026-02-21 10:30:47 | INFO | OPTION_ACCEPTED: approach=shell_1 | cmd=find / -size +100M -exec ls -lh {} \;
2026-02-21 10:30:47 | INFO | ... (repeated per approach until 3 unique options are collected)
2026-02-21 10:30:48 | INFO | COMMAND_CHECK: find | EXISTS: True
2026-02-21 10:30:49 | INFO | QA_REVIEW: Sending 3 commands for safety review
2026-02-21 10:30:50 | INFO | QA_RESPONSE: 1|PASS| | 2|PASS| | 3|WARN|Requires sudo
2026-02-21 10:30:52 | INFO | USER_SELECTED: Option 1 | COMMAND: find / -size +100M -exec ls -lh {} \;
2026-02-21 10:30:52 | INFO | ACTION: EXECUTE
Old logs auto-deleted after log_retention_days (default: 30).
rm -rf ~/.local/osh ~/.config/osh ~/.local/state/osh
rm -f ~/.local/bin/osh ~/.local/bin/ask ~/.local/bin/computer
pip3 uninstall ollama termcolor pyperclipcurl -fsSL https://ollama.ai/install.sh | sh
ollama pull llama3