A few days ago, I decided to stop reading about AI APIs and integrate one myself.
I wanted a project that would force me to write code, make engineering decisions, and learn by building. After exploring a few ideas, I settled on something simple: a command-line tool that takes plain English and generates Linux commands using an LLM.
That project became devops-ai , a Python CLI tool powered by Google’s Gemini API. It took me about three days to build and taught me a lot about API integration, prompt design, CLI development, and error handling.
The Problem
I spend a lot of time in the terminal, and I regularly find myself searching for commands I’ve forgotten. Sometimes it’s the syntax. Sometimes it’s a flag. Sometimes I know exactly what I want to do but can’t remember the command that gets me there.
I started wondering whether I could stay inside the terminal and describe what I wanted instead.
For example:
python3 devops_ai.py "find all files larger than 100mb"
returns:
find / -type f -size +100M
That became the core idea behind the project.
Building the First Version
The first step was choosing an API provider. I went with Gemini because Google offers a free tier through AI Studio, which made it easy to experiment without worrying about usage costs.
The initial setup was straightforward:
Create a virtual environment
Install dependencies
Generate a Gemini API key
Store the key in a
.envfileConnect the application to Gemini
The first version of the script simply accepted a prompt, sent it to Gemini, and printed the response. My goal wasn’t to build a polished application immediately. I wanted proof that the idea worked before investing time in improving the structure.
One of the first issues I ran into was output quality. Gemini often returned explanations, markdown formatting, and additional context. That works well in chat applications, but it isn’t ideal when you’re expecting a shell command.
I solved this by introducing a system prompt that instructed Gemini to behave like a Linux and DevOps expert and return only raw commands. That change immediately improved the responses and gave me my first practical lesson in prompt engineering.
import os
from dotenv import load_dotenv
import google.generativeai as genai
# Load the API key from .env file
load_dotenv()
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
print("Error: GEMINI_API_KEY not found in .env file")
exit(1)
# Configure the Gemini client
genai.configure(api_key=api_key)
model = genai.GenerativeModel("gemini-1.5-flash")
# The system context — tells the AI what role it's playing
SYSTEM_PROMPT = """
You are a Linux and DevOps expert assistant.
When the user describes what they want to do, respond with ONLY the shell command that achieves it.
No explanation, no markdown, no backticks. Just the raw command.
Example:
User: show disk usage sorted by size
Response: du -sh * | sort -rh
"""
def ask_gemini(user_input):
prompt = f"{SYSTEM_PROMPT}\n\nUser: {user_input}"
response = model.generate_content(prompt)
return response.text.strip()
# Main entry point
if __name__ == " __main__":
user_input = input("What do you want to do? ")
result = ask_gemini(user_input)
print(f"\nCommand: {result}\n")
Turning It Into a Real CLI Tool
The project started feeling more like a proper tool when I replaced the basic input flow with Python’s argparse library.
I also added a second feature: command explanation.
Instead of only generating commands, the tool can now explain existing commands:
python3 devops_ai.py --explain "netstat -tulpn"
To support both workflows, I reorganized the application around three functions:
ask_gemini()— handles API communicationget_command()— generates commands from natural languageexplain_command()— explains shell commands
Splitting responsibilities made the code easier to understand and easier to extend as the project grew.
import os
import argparse
from dotenv import load_dotenv
from google import genai
# Load the API key from .env file
load_dotenv()
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
print("Error: GEMINI_API_KEY not found in .env file")
exit(1)
# Configure the Gemini client (new SDK style)
client = genai.Client(api_key=api_key)
# Prompt for generating commands
COMMAND_PROMPT = """
You are a Linux and DevOps expert assistant.
When the user describes what they want to do, respond with ONLY the shell command that achieves it.
No explanation, no markdown, no backticks. Just the raw command.
Example:
User: show disk usage sorted by size
Response: du -sh * | sort -rh
"""
# Prompt for explaining commands
EXPLAIN_PROMPT = """
You are a Linux and DevOps expert assistant.
When the user provides a shell command, explain clearly what it does in 2-4 sentences.
Break down each part of the command so a junior engineer can understand it.
No markdown formatting, just plain text.
"""
def ask_gemini(prompt):
response = client.models.generate_content(
model=CONFIG['model'],
contents=prompt
)
return response.text.strip()
def get_command(user_input):
prompt = f"{COMMAND_PROMPT}\n\nUser: {user_input}"
return ask_gemini(prompt)
def explain_command(command):
prompt = f"{EXPLAIN_PROMPT}\n\nCommand: {command}"
return ask_gemini(prompt)
def main():
# Set up the CLI argument parser
parser = argparse.ArgumentParser(
prog="devops-ai",
description="AI-powered CLI tool for Linux and DevOps commands"
)
# Positional argument — the user's query
parser.add_argument(
"query",
type=str,
help="Describe what you want to do e.g. 'list all running docker containers'"
)
# Optional flag — explain mode
parser.add_argument(
"--explain",
action="store_true",
help="Explain what a command does instead of generating one"
)
args = parser.parse_args()
if args.explain:
print(f"\nExplaining: {args.query}\n")
explanation = explain_command(args.query)
print(f"{explanation}\n")
else:
print(f"\nGenerating command for: {args.query}\n")
command = get_command(args.query)
print(f"Command: {command}\n")
if __name__ == " __main__":
main()
Handling Failures
Once the happy path worked, I started thinking about failure scenarios.
I wanted the tool to handle common problems gracefully, so I added:
API key validation
Retry logic
Timeout handling
Empty-response checks
API-specific error messages
I also created a config.py file to centralize settings such as the model name, retry count, and timeout values.
To verify everything worked, I deliberately broke the application by renaming the .env file and triggering API failures. Those tests helped me improve the messages users see when something goes wrong.
Examples include:
Empty response received from API
API Error: Check your key and try again
def ask_gemini(prompt):
retries = 0
while retries < CONFIG['max_retries']:
try:
response = client.models.generate_content(
model = CONFIG['model'],
contents = prompt
)
if not response.text or not response.text.strip():
print('Received empty response from model. Retrying ...')
retries+=1
time.sleep(2)
continue
return response.text.strip()
except genai_errors.APIError as e:
print(f'API error: {e}')
print('There is an issue with your API key or request. Please check your API key.\n')
sys.exit(1)
except Exception as e:
retries+=1
print(f'Unexpected error (attempt {retries}/{CONFIG["max_retries"]}): {e}. Retrying ...')
if retries < CONFIG['max_retries']:
print('Retrying in two seconds...')
time.sleep(2)
else:
print('Max retries reached. Please check your API key and network connection.')
sys.exit(1)
Publishing the Project
The final stage involved preparing the project for GitHub.
I:
Initialized Git
Verified
.envwas ignoredGenerated a requirements file
Wrote a README
Added setup instructions and usage examples
Published the repository
Writing the documentation was a useful reminder that shipping a project involves more than getting the code to work. Other developers need enough context to understand, install, and use what you’ve built.
What I Learned
This project started as an API integration exercise, but it ended up teaching me much more.
A few takeaways stand out:
Prompt design directly affects application behavior.
argparsemakes a huge difference when building CLI tools.Configuration becomes easier to manage when it’s centralized.
Even small projects benefit from retries, timeouts, and clear error messages.
Building a project exposes knowledge gaps much faster than reading about one.
The tool itself is still evolving, and there are plenty of improvements I want to make. For now, I’m happy to have something working, documented, and published.
Most importantly, I’m happy to continue building and sharing what I learn along the way.
Adejuwonvictor
/
AI-Powered-CLI-Tool
a command-line tool that takes natural language input and generates shell commands or scripts
devops-ai
An AI-powered command-line tool that converts plain English into Linux and DevOps shell commands Built with Python and the Google Gemini API.
What It Does
- Converts plain English descriptions into shell commands
- Explains what any shell command does with the
--explainflag - Retries automatically on network failures
- AI model is configurable via a single config file
Demo
$ python3 devops_ai.py "find all files larger than 100mb"
Command: find / -type f -size +100M
$ python3 devops_ai.py --explain "ps aux | grep python"
The ps aux command lists all currently running processes with details
like CPU and memory usage. The output is piped into grep which filters
and shows only lines containing the word python.
Tech Stack
- Python 3
- Google Gemini API (
gemini-2.5-flash-lite) python-dotenvargparse
Setup
1. Clone the repository
git clone https://github.com/Adejuwonvictor/AI-Powered-CLI-Tool
cd devops-ai
2. Create and activate a virtual environment
python3 -m…


Top comments (0)