Building AI agents that operate directly in the terminal offers huge productivity gains for developers. In this guide, we will provide hands-on, hands-on instructions on how to build a custom CLI AI agent based on a local LLM. 1. Analysis of CLI AI Agent Ecosystem Currently, there are several solutions in the CLI AI agent market: Key tools: Aider: GitHub Copilot-based, real-time code modification function Continue.dev: VSCode-based, handle complex tasks OpenCode: Open source, simple coding help Custom Scripts: Customize with your own script Current problems: Most tools rely on cloud API Poor performance when running locally Lack of complex tooling features Cost issues 2. Setting up local LLM API endpoint locally To run LLM, follow these steps: 1. Install LM Studio: # macOS brew install lm-studio # or download directly wget https://github.com/lmstudio-ai/LMStudio/releases/latest/download/LMStudio-MacOS.dmg Enter fullscreen mode Exit fullscreen mode 2. Run local model: # Download model and run lm-studio –model “Nous-Hermes-2-Mistral-7B-DPO.Q4_K_M.gguf” Enter fullscreen mode Exit fullscreen mode 3. API server settings: # Install Ollama curl -fsSL https://ollama.com/install.sh | sh # Download model ollama pull mistral # Run API server ollama serve Enter fullscreen mode Exit fullscreen mode 3. Building a simple Python CLI agent Now let’s create a basic CLI agent: # ai_agent.py import os import json import subprocess from typing import Dict, List, Any import openai class TerminalAIAgent: def __init__(self, model=”ollama/mistral”): self.model = model self.client = openai.OpenAI( base_url=”http://localhost:11434/v1″, api_key=”ollama” ) self.conversation_history = () def run_command(self, command: str) -> str: “””Execute the command and return the result””” try: result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=30 ) return result.stdout + result.stderr except Exception as e: return f”Error: {str(e)}” def get_context(self) -> str: “””Collect current working directory information””” pwd = os.getcwd() files = os.listdir(pwd) return f”Working directory: {pwd}\nFiles: {‘, ‘.join(files(:10))}” def chat(self, user_input: str) -> str: “””Conversation with AI””” self.conversation_history.append({ “role”: “user”, “content”: user_input }) # System prompt system_prompt = “”” You are a helpful AI assistant that helps developers with coding tasks. Your responses should be concise and actionable. You can execute shell commands and modify files. “”” messages = ( {“role”: “system”, “content”: system_prompt}, {“role”: “user”, “content”: f”Context: {self.get_context()}”}, ) + self.conversation_history response = self.client.chat.completions.create( model=self.model, messages=messages, temperature=0.3 ) ai_response = response.choices(0).message.content self.conversation_history.append({ “role”: “assistant”, “content”: ai_response }) return ai_response # Usage if __name__ == “__main__”: agent = TerminalAIAgent() print(“Start AI Agent (type ‘exit’ to exit)”) while True: user_input = input(“\n> “) if user_input.lower() == ‘exit’: break response = agent.chat(user_input) print(response) Enter fullscreen mode Exit fullscreen mode 4. Integrate with tmux Integrate with terminal multiplexers to improve workflow: # Create a tmux session tmux new-session -d -s ai_agent # Run the agent within a session tmux send-keys -t ai_agent “python ai_agent.py” Enter Enter fullscreen mode Exit fullscreen mode tmux script: # tmux_ai.sh #!/bin/bash SESSION=”ai_agent” # Check whether session exists if ! tmux has-session -t $SESSION 2>/dev/null; then tmux new-session -d -s $SESSION tmux send-keys -t $SESSION “python ai_agent.py” Enter fi tmux attach -t $SESSION Enter fullscreen mode Exit fullscreen mode 5. Custom tool development Code search tool: # tools/code_search.py import os import re from typing import List, Dict class CodeSearchTool: def __init__(self, root_dir: str = “.”): self.root_dir = root_dir def search_in_files(self, pattern: str, file_extensions: List(str) = None) -> List(Dict): “””Search for a pattern within a file””” results = () if file_extensions is None: file_extensions = (‘.py’, ‘.js’, ‘.ts’, ‘.java’, ‘.cpp’) for root, dirs, files in os.walk(self.root_dir): for file in files: if any(file.endswith(ext) for ext in file_extensions): file_path = os.path.join(root, file) try: with open(file_path, ‘r’, encoding=’utf-8′) as f: content = f.read() matches = re.finditer(pattern, content) for match in matches: results.append({ ‘file’: file_path, ‘line’: content(:match.start()).count(‘\n’) + 1, ‘context’: self.get_context(content, match.start()) }) except Exception: continue return results def get_context(self, content: str, position: int, context_lines: int = 3) -> str: “””Context Extraction””” lines = content.split(‘\n’) line_num = content(:position).count(‘\n’) start = max(0, line_num – context_lines) end = min(len(lines), line_num + context_lines + 1) return ‘\n’.join(lines(start:end)) # Usage example search_tool = CodeSearchTool() results = search_tool.search_in_files(r”def\s+(\w+)\s*\(“) print(json.dumps(results, indent=2)) Enter fullscreen mode Exit fullscreen mode Git tools: # tools/git_tool.py import subprocess import json from typing import Dict, List class GitTool: def get_status(self) -> Dict: “””Check Git status””” try: result = subprocess.run((‘git’, ‘status’, ‘–porcelain’), capture_output=True, text=True) return { ‘status’: result.stdout.strip(), ‘has_changes’: bool(result.stdout.strip()) } except Exception as e: return {‘error’: str(e)} def get_branch_info(self) -> Dict: “””Branch information””” try: branch = subprocess.run((‘git’, ‘branch’, ‘–show-current’), capture_output=True, text=True).stdout.strip() return {‘branch’: branch} except Exception as e: return {‘error’: str(e)} def commit_changes(self, message: str) -> Dict: “””Commit changes””” try: subprocess.run((‘git’, ‘add’, ‘.’), capture_output=True) result = subprocess.run((‘git’, ‘commit’, ‘-m’, message), capture_output=True, text=True) return { ‘success’: True, ‘output’: result.stdout, ‘error’: result.stderr } except Exception as e: return {‘success’: False, ‘error’: str(e)} # Usage example git_tool = GitTool() status = git_tool.get_status() print(json.dumps(status, indent=2)) Enter fullscreen mode Exit fullscreen mode 6. Context window management Context management for handling large code bases: python # context_manager.py import os import hashlib from typing import List, Dict, Set class ContextManager: def __init__(self, max_tokens: int = 8000): self.max_tokens = max_tokens self.token_cache = {} def calculate_tokens(self, text: str) — π₯ **Get the full guide on Gumroad**: https://gumroad.com/l/auto ($5) Enter fullscreen mode Exit fullscreen mode
Source link


