Skip to content

Instantly share code, notes, and snippets.

@simonseo
Last active February 1, 2026 01:30
Show Gist options
  • Select an option

  • Save simonseo/1193b43f25c7305d9b795d71305afa7b to your computer and use it in GitHub Desktop.

Select an option

Save simonseo/1193b43f25c7305d9b795d71305afa7b to your computer and use it in GitHub Desktop.
whatcmd
#!/bin/bash
# Installation script for whatcmd
set -e
INSTALL_DIR="${INSTALL_DIR:-$HOME/bin}"
SCRIPT_NAME="whatcmd"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "Installing whatcmd to $INSTALL_DIR..."
# Create install directory if it doesn't exist
mkdir -p "$INSTALL_DIR"
# Copy the script
cp "$SCRIPT_DIR/$SCRIPT_NAME" "$INSTALL_DIR/$SCRIPT_NAME"
chmod +x "$INSTALL_DIR/$SCRIPT_NAME"
echo "✓ Installed $SCRIPT_NAME to $INSTALL_DIR/$SCRIPT_NAME"
# Check if install directory is in PATH
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
echo ""
echo "⚠️ Warning: $INSTALL_DIR is not in your PATH"
echo ""
echo "Add the following line to your ~/.zshrc or ~/.bashrc:"
echo ""
echo " export PATH=\"$INSTALL_DIR:\$PATH\""
echo ""
fi
# Check if opencode is installed
if ! command -v opencode &> /dev/null; then
echo ""
echo "⚠️ Warning: 'opencode' command not found"
echo ""
echo "whatcmd requires opencode to be installed."
echo "Please install opencode first: https://github.com/anomalyco/opencode"
echo ""
exit 1
fi
echo ""
echo "Installation complete! 🎉"
echo ""
echo "Usage: whatcmd <description of what you want to do>"
echo ""
echo "Examples:"
echo " whatcmd find all PDF files larger than 10MB"
echo " whatcmd turn this directory of images into a 5fps gif"
echo " whatcmd list all processes using more than 1GB RAM"
echo ""
#!/usr/bin/env python3
"""
what-cmd - Natural language CLI command generator
A Python wrapper around opencode that generates shell commands from natural language prompts.
Actively utilizes user-defined aliases and functions from ~/.zshrc.
"""
import sys
import os
import subprocess
import re
import tempfile
import json
from typing import Optional, Tuple, Dict, List
def extract_shell_context() -> str:
"""
Extract aliases and functions from ~/.zshrc for context.
Returns:
Formatted string with aliases and functions, or error message
"""
zshrc_path = os.path.expanduser('~/.zshrc')
try:
with open(zshrc_path, 'r') as f:
content = f.read()
except FileNotFoundError:
return "# No ~/.zshrc found"
except PermissionError:
return "# Unable to read ~/.zshrc (permission denied)"
except Exception as e:
return f"# Error reading shell config: {e}"
# Extract aliases
aliases = []
alias_pattern = r'^\s*alias\s+([^=]+)=[\'"](.*?)[\'"]'
for match in re.finditer(alias_pattern, content, re.MULTILINE):
name = match.group(1).strip()
command = match.group(2).strip()
aliases.append(f"- {name} → {command}")
# Also catch aliases without quotes
alias_pattern_noquote = r'^\s*alias\s+([^=]+)=([^\s]+)'
for match in re.finditer(alias_pattern_noquote, content, re.MULTILINE):
name = match.group(1).strip()
command = match.group(2).strip()
# Avoid duplicates from quoted pattern
alias_str = f"- {name} → {command}"
if alias_str not in aliases:
aliases.append(alias_str)
# Extract functions (simple parsing)
functions = []
# Pattern: function_name() { or function function_name {
func_pattern = r'^\s*(?:function\s+)?(\w+)\s*\(\)\s*\{'
for match in re.finditer(func_pattern, content, re.MULTILINE):
name = match.group(1).strip()
functions.append(f"- {name}()")
# Build context string
context_parts = ["Available shell aliases and functions from ~/.zshrc:\n"]
if aliases:
context_parts.append("ALIASES:")
context_parts.extend(aliases)
context_parts.append("")
if functions:
context_parts.append("FUNCTIONS:")
context_parts.extend(functions)
context_parts.append("")
if not aliases and not functions:
context_parts.append("# No aliases or functions found")
context_parts.append("Please use these aliases/functions when appropriate in your command suggestions.")
return "\n".join(context_parts)
def call_opencode(prompt: str, timeout: int = 60) -> Dict:
"""
Call opencode run with the given prompt.
Args:
prompt: The full prompt to send to opencode
timeout: Timeout in seconds
Returns:
Parsed response from opencode
Raises:
RuntimeError: If opencode fails or times out
"""
try:
result = subprocess.run(
['opencode', 'run', '--format', 'json', prompt],
capture_output=True,
text=True,
timeout=timeout
)
if result.returncode != 0:
raise RuntimeError(f"OpenCode failed with exit code {result.returncode}: {result.stderr}")
lines = result.stdout.strip().split('\n')
response_text = ""
for line in lines:
if not line.strip():
continue
try:
event = json.loads(line)
if event.get('type') == 'text':
part = event.get('part', {})
text = part.get('text', '') if isinstance(part, dict) else event.get('text', '')
response_text += text
except json.JSONDecodeError:
continue
if not response_text:
raise RuntimeError("No response text received from opencode")
return {'text': response_text.strip()}
except subprocess.TimeoutExpired:
raise RuntimeError("OpenCode request timed out")
except FileNotFoundError:
raise RuntimeError("'opencode' command not found. Please install opencode first.")
except Exception as e:
raise RuntimeError(f"Error calling opencode: {e}")
def parse_response(response_text: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""
Parse opencode response to extract question, command, or explanation.
Returns:
Tuple of (question, command, explanation)
- question: If opencode is asking a clarifying question
- command: If opencode provided a command
- explanation: Explanation of the command
"""
question = None
command = None
explanation = None
# Look for QUESTION: pattern
question_match = re.search(r'QUESTION:\s*(.+?)(?:\n|$)', response_text, re.IGNORECASE)
if question_match:
question = question_match.group(1).strip()
# Look for COMMAND: pattern
command_match = re.search(r'COMMAND:\s*(.+?)(?:\n|$)', response_text, re.IGNORECASE)
if command_match:
command = command_match.group(1).strip()
# Look for EXPLANATION: pattern
explanation_match = re.search(r'EXPLANATION:\s*(.+?)(?:\n|$)', response_text, re.IGNORECASE | re.DOTALL)
if explanation_match:
explanation = explanation_match.group(1).strip()
# If no structured format found, try to extract from code blocks
if not command:
code_block_match = re.search(r'```(?:bash|sh)?\n(.+?)\n```', response_text, re.DOTALL)
if code_block_match:
command = code_block_match.group(1).strip()
# Use rest of text as explanation
explanation = response_text.replace(code_block_match.group(0), '').strip()
return question, command, explanation
def build_system_prompt(shell_context: str, user_request: str) -> str:
"""
Build the system prompt for opencode.
Args:
shell_context: Extracted shell aliases and functions
user_request: User's natural language request
Returns:
Complete prompt string
"""
return f"""{shell_context}
User request: {user_request}
You are a shell command generator. Your task is to generate a single shell command that accomplishes the user's request.
Rules:
1. If the request is unclear, ask ONE specific clarifying question using format: QUESTION: [your question]
2. If clear, provide the exact command using format:
COMMAND: [the command]
EXPLANATION: [brief explanation of what it does]
3. If it requires a complex script (more than 3 piped commands or multiple steps), respond with:
COMPLEX: This requires a multi-step script
SUGGESTION: [suggest the approach or steps]
4. Prefer using user's aliases and functions when appropriate
5. Be concise and practical
Respond now:"""
def display_command(command: str, explanation: Optional[str]):
"""
Display the suggested command with formatting.
Args:
command: The command to display
explanation: Optional explanation
"""
print("\n╭─────────────────────────────────────────╮")
print("│ Suggested Command │")
print("╰─────────────────────────────────────────╯\n")
print(f" {command}\n")
if explanation:
print(f"💡 Explanation: {explanation}\n")
def get_approval() -> str:
"""
Get user approval to run the command.
Returns:
User's choice: 'y', 'n', or 'e'
"""
while True:
response = input("Run this command? [y/N/e(dit)]: ").strip().lower()
if response in ['y', 'yes']:
return 'y'
elif response in ['n', 'no', '']:
return 'n'
elif response in ['e', 'edit']:
return 'e'
else:
print("Please enter 'y' (yes), 'n' (no), or 'e' (edit)")
def edit_command(command: str) -> Optional[str]:
"""
Open command in editor for modification.
Args:
command: Initial command
Returns:
Edited command, or None if user cancelled
"""
editor = os.environ.get('EDITOR', os.environ.get('VISUAL', 'nano'))
with tempfile.NamedTemporaryFile(mode='w+', suffix='.sh', delete=False) as tf:
tf.write(command)
tf.flush()
temp_path = tf.name
try:
subprocess.run([editor, temp_path])
with open(temp_path, 'r') as f:
edited_command = f.read().strip()
return edited_command if edited_command else None
finally:
os.unlink(temp_path)
def execute_command(cmd: str) -> int:
"""
Execute command in user's shell with proper environment.
Args:
cmd: Command to execute
Returns:
Exit code
"""
shell = os.environ.get('SHELL', '/bin/zsh')
result = subprocess.run(
cmd,
shell=True,
executable=shell
)
return result.returncode
def interactive_loop(shell_context: str, user_request: str, max_questions: int = 3) -> Tuple[Optional[str], Optional[str]]:
"""
Interactive question/answer loop with opencode.
Args:
shell_context: Extracted shell context
user_request: User's initial request
max_questions: Maximum number of clarifying questions
Returns:
Tuple of (command, explanation) or (None, None) if failed
"""
conversation_history = []
question_count = 0
prompt = build_system_prompt(shell_context, user_request)
while question_count < max_questions:
try:
response = call_opencode(prompt)
response_text = response.get('text', '')
if not response_text:
print("Error: Empty response from opencode")
return None, None
question, command, explanation = parse_response(response_text)
# Check for complex script indication
if 'COMPLEX:' in response_text or 'requires a script' in response_text.lower():
print("\nThis task requires a script with multiple steps:\n")
# Extract suggestions
suggestion_match = re.search(r'SUGGESTION:\s*(.+)', response_text, re.IGNORECASE | re.DOTALL)
if suggestion_match:
print(suggestion_match.group(1).strip())
else:
print(response_text)
print("\nConsider creating a shell script or running these commands manually.")
return None, None
# If we got a command, return it
if command:
return command, explanation
# If we got a question, ask user
if question:
question_count += 1
print(f"\nQ{question_count}: {question}")
user_answer = input("> ").strip()
if not user_answer:
print("No answer provided. Exiting.")
return None, None
# Update prompt with the answer
conversation_history.append(f"Q: {question}")
conversation_history.append(f"A: {user_answer}")
prompt = build_system_prompt(shell_context, user_request)
prompt += "\n\nPrevious conversation:\n" + "\n".join(conversation_history)
prompt += "\n\nNow provide the command or ask another question if still unclear:"
else:
# No question and no command - unclear response
print("Unable to generate clear command. Response:")
print(response_text)
return None, None
except RuntimeError as e:
print(f"Error: {e}")
return None, None
# Reached max questions
print(f"\nUnable to generate a clear command after {max_questions} questions.")
print("\nSuggestions:")
print("- Your request might need a custom script")
print("- Try breaking it into smaller commands")
print("- Be more specific about what you want to accomplish")
return None, None
def main():
"""Main entry point."""
# Parse arguments
if len(sys.argv) < 2:
print("Usage: whatcmd <description of what you want to do>")
print("\nExamples:")
print(" whatcmd find all PDF files larger than 10MB")
print(" whatcmd turn this directory of images into a 5fps gif")
print(" whatcmd list all processes using more than 1GB RAM")
sys.exit(1)
# Check for help flag
if sys.argv[1] in ['-h', '--help', 'help']:
print("Usage: whatcmd <description of what you want to do>")
print("\nExamples:")
print(" whatcmd find all PDF files larger than 10MB")
print(" whatcmd turn this directory of images into a 5fps gif")
print(" whatcmd list all processes using more than 1GB RAM")
sys.exit(0)
user_request = ' '.join(sys.argv[1:])
if not user_request.strip():
print("Error: Empty request")
sys.exit(1)
# Extract shell context
print("Analyzing your shell environment...")
shell_context = extract_shell_context()
# Interactive loop to get command
print("Generating command...\n")
command, explanation = interactive_loop(shell_context, user_request)
if not command:
sys.exit(1)
# Display and get approval
display_command(command, explanation)
approval = get_approval()
if approval == 'n':
print("Command not executed.")
sys.exit(0)
elif approval == 'e':
edited = edit_command(command)
if not edited:
print("Edit cancelled.")
sys.exit(0)
# Display edited command and get approval again
display_command(edited, "Edited command")
approval = get_approval()
if approval != 'y':
print("Command not executed.")
sys.exit(0)
command = edited
# Execute command
print("\nExecuting...\n")
exit_code = execute_command(command)
if exit_code == 0:
print("\n✓ Command completed successfully")
else:
print(f"\n⚠️ Command exited with code {exit_code}")
sys.exit(exit_code)
if __name__ == '__main__':
main()
@simonseo
Copy link
Author

simonseo commented Feb 1, 2026

whatcmd

Generate shell commands from natural language using AI. No more memorizing syntax or searching StackOverflow.

whatcmd find all PDF files larger than 10MB
whatcmd turn images into a 5fps gif  
whatcmd show disk usage sorted by size

Powered by OpenCode.

Quick Install

# Download and run installer
curl -sL https://gist.github.com/simonseo/1193b43f25c7305d9b795d71305afa7b/raw/install.sh | bash

# Or clone first
git clone https://gist.github.com/1193b43f25c7305d9b795d71305afa7b.git whatcmd
cd whatcmd
./install.sh

Requirements:

  • Python 3.11+
  • OpenCode installed and configured
  • macOS or Linux

Usage

whatcmd <what you want to do>

No quotes needed. Just type naturally.

Examples

# File operations
whatcmd find all PDF files larger than 10MB
whatcmd count lines in all python files
whatcmd compress this directory into a tar.gz

# System info
whatcmd list processes using more than 1GB RAM
whatcmd show disk usage sorted by size
whatcmd find which process is using port 8080

# Git operations  
whatcmd show commits from last week
whatcmd undo last commit but keep changes
whatcmd list branches merged into main

# Image/video
whatcmd turn images into a 5fps gif
whatcmd resize all jpg files to 800px width
whatcmd convert video to mp4

# Network
whatcmd download file from URL with progress bar
whatcmd check if port 443 is open on example.com
whatcmd find my external IP address

# Kubernetes (if you have kubectl alias)
whatcmd get all pods in production namespace
whatcmd show logs from frontend pod
whatcmd restart deployment api-server

How It Works

  1. Reads your shell config - Parses ~/.zshrc for aliases and functions
  2. Sends to OpenCode - Generates command based on your request + shell context
  3. Shows command - Displays suggested command with explanation
  4. Waits for approval - Never auto-executes
╭─────────────────────────────────────────╮
│ Suggested Command                       │
╰─────────────────────────────────────────╯

  find . -type f -name "*.pdf" -size +10M

💡 Explanation: Finds all PDF files larger than 10MB in current directory

Run this command? [y/N/e(dit)]:

Your options:

  • y / yes → Execute immediately
  • n / no / Enter → Cancel (default)
  • e / edit → Open in $EDITOR to modify first

Features

Shell-aware - Uses your aliases automatically (e.g., k instead of kubectl)
Interactive clarification - Asks up to 3 questions if request is unclear
Safe by default - Requires explicit approval before running
Editable - Modify commands before execution
No API keys - Uses your OpenCode credentials
No quotes needed - whatcmd do the thing just works

Installation Details

The installer:

  1. Copies whatcmd to ~/bin/ (or $INSTALL_DIR)
  2. Makes it executable
  3. Checks if ~/bin is in your PATH
  4. Verifies OpenCode is installed

Custom install location:

INSTALL_DIR=/usr/local/bin ./install.sh

Add to PATH (if needed):

echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

Troubleshooting

Command not found

Check if ~/bin is in PATH:

echo $PATH | grep ~/bin

Add to ~/.zshrc:

export PATH="$HOME/bin:$PATH"

OpenCode not found

Install OpenCode first:

# See: https://github.com/anomalyco/opencode

Gets stuck or times out

  • Default timeout is 60 seconds
  • Check your OpenCode setup is working: opencode run "test"
  • Try a simpler request first

Command doesn't use my aliases

Make sure aliases are defined in ~/.zshrc (not .bash_profile or other files).

Test if alias parsing works:

grep "alias k=" ~/.zshrc

Advanced

Interactive Questions

If your request is unclear, whatcmd asks clarifying questions:

$ whatcmd compress files

Q1: Which files do you want to compress?
> all .log files in this directory

Q2: What compression format? (zip, tar.gz, etc.)
> tar.gz

╭─────────────────────────────────────────╮
│ Suggested Command                       │
╰─────────────────────────────────────────╯

  tar czf logs.tar.gz *.log

Max 3 questions. If still unclear, exits with suggestions.

Complex Tasks

For multi-step operations, whatcmd suggests an approach instead:

$ whatcmd set up a web server with nginx and ssl

This task requires a script with multiple steps:

1. Install nginx: sudo apt install nginx
2. Configure nginx: edit /etc/nginx/sites-available/default
3. Install certbot: sudo apt install certbot python3-certbot-nginx
4. Run certbot: sudo certbot --nginx

Consider creating a shell script or running these commands manually.

Limitations

  • Only reads ~/.zshrc (not .bashrc or other shells)
  • Function parsing is basic (names only, not signatures)
  • Requires internet connection (OpenCode API calls)
  • Best suited for single-line commands

Uninstall

rm ~/bin/whatcmd

Source Code

This is a Python wrapper around OpenCode that:

  • Parses your shell config with regex
  • Constructs context-aware prompts
  • Handles interactive Q&A loops
  • Provides safe command execution

See the full source.

License

MIT


Like this? Star the gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment