Why Build a Terminal-Based AI Coding Agent?
In 2026, AI coding agents have moved beyond IDE plugins into the terminal, where developers spend most of their time. Projects like oh-my-pi (trending on GitHub with 9,500+ stars) prove that a well-designed terminal agent can hash-anchor edits, leverage LSP, manage subagents, and integrate browser tools — all without leaving the command line.
This tutorial walks you through building a minimal but functional AI coding agent for the terminal from scratch. By the end, you will have a working prototype that can read files, execute shell commands, and generate code using an LLM API.
Step 1: Set Up the Project Structure
Start by creating a clean project directory and initializing a Node.js project. We use TypeScript for type safety.
mkdir terminal-ai-agent
cd terminal-ai-agent
npm init -y
npm install typescript @types/node openai chalk commander
npx tsc --init
Update your tsconfig.json to enable strict mode and output to a dist/ folder:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
}
}
Step 2: Define the Agent Core
The agent needs a central loop that receives user input, calls the LLM, and executes the resulting actions. Create src/agent.ts:
import { OpenAI } from 'openai';
import * as fs from 'fs/promises';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
const SYSTEM_PROMPT = `You are a terminal coding agent. You can:
1. Read files (action: "read", path: "...")
2. Write files (action: "write", path: "...", content: "...")
3. Edit files (action: "edit", path: "...", search: "...", replace: "...")
4. Run commands (action: "shell", command: "...")
Always respond with a JSON object containing an "actions" array and a "message" for the user.
Be precise. Never guess file contents. Always read before editing.`;
export class TerminalAgent {
private client: OpenAI;
constructor(apiKey: string) {
this.client = new OpenAI({ apiKey });
}
async process(userInput: string): Promise<string> {
const response = await this.client.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: userInput },
],
temperature: 0.1,
});
const reply = response.choices[0]?.message?.content || '';
let parsed: { actions?: any[]; message?: string };
try {
parsed = JSON.parse(reply);
} catch {
return reply;
}
let output = parsed.message || '';
for (const action of parsed.actions || []) {
output += '\\n' + await this.executeAction(action);
}
return output;
}
private async executeAction(action: any): Promise<string> {
switch (action.action) {
case 'read':
return await fs.readFile(action.path, 'utf-8');
case 'write':
await fs.writeFile(action.path, action.content, 'utf-8');
return `Wrote ${action.path} (${action.content.length} bytes)`;
case 'shell':
const { stdout, stderr } = await execAsync(action.command);
return stderr || stdout;
default:
return `Unknown action: ${action.action}`;
}
}
}
Step 3: Build the CLI Interface
Now create the interactive terminal UI in src/cli.ts:
import { Command } from 'commander';
import * as readline from 'readline';
import chalk from 'chalk';
import { TerminalAgent } from './agent';
const program = new Command();
program
.name('ai-agent')
.description('AI Coding Agent for the Terminal')
.requiredOption('-k, --api-key <key>', 'OpenAI API key')
.parse();
const agent = new TerminalAgent(program.opts().apiKey);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log(chalk.green('Terminal AI Agent - type your request or "exit"'));
const ask = () => {
rl.question('> ', async (input) => {
if (input.toLowerCase() === 'exit') {
rl.close();
return;
}
if (!input.trim()) {
ask();
return;
}
process.stdout.write('Thinking...\\n');
const result = await agent.process(input);
console.log(result);
ask();
});
};
ask();
Step 4: Compile and Run
Build the project and launch the agent:
npx tsc
node dist/cli.js --api-key $OPENAI_API_KEY
Try it out with a request like:
> Create a Python file called hello.py that prints "Hello, World!" and then runs it.
The agent will generate JSON actions to write the file and execute it, then show you the output — all in the terminal.
Step 5: Add Key Enhancements
A production-ready agent needs several improvements over this minimal prototype:
Hash-Anchored Edits
Instead of naive string replacement, use content hashes to ensure edits apply to the correct file version. This prevents race conditions when multiple tools modify the same file.
// Verify file hash before editing
const currentHash = crypto
.createHash('sha256')
.update(content)
.digest('hex');
if (currentHash !== expectedHash) {
throw new Error('File changed since read - re-read before editing');
}
LSP Integration
Connect to a Language Server Protocol to get real-time diagnostics, type checking, and autocomplete suggestions before the agent commits changes:
| Feature | Without LSP | With LSP |
|---|---|---|
| Syntax errors | Discovered at runtime | Caught before save |
| Import resolution | Manual checking | Auto-validated |
| Refactoring safety | Risky | Server-verified |
| Code navigation | Text search only | Semantic goto-def |
Subagent Delegation
For complex tasks, spawn isolated subagents that handle specific concerns (testing, documentation, code review) and report back to the main agent:
interface Subagent {
id: string;
task: string;
context: 'isolated' | 'fork';
timeout: number;
}
// Example: spawn a test runner subagent
const testAgent = {
id: 'test-runner',
task: 'Run pytest and report failures',
context: 'isolated',
timeout: 60,
};
Step 6: Best Practices for Terminal AI Agents
- Read before writing — Never assume file contents. Always read the current state before generating edits.
- One action at a time — Let the agent complete and confirm one step before moving to the next. This reduces cascading errors.
- Low temperature — Keep temperature between 0.0 and 0.2 for coding tasks. Creativity is the enemy of correctness here.
- Validate shell commands — Before executing, have the agent explain what the command does. Consider a dry-run mode for dangerous operations.
- Persist context — Store conversation history and file states so the agent can resume sessions after terminal restarts.
- Use structured output — JSON action schemas make it easy to parse, validate, and replay agent decisions.
What Comes Next?
From this foundation, you can add browser automation, multi-file refactoring, Git integration, and even multi-agent coordination. The terminal is becoming the primary interface for AI-assisted development — and building your own agent is the best way to understand how these systems actually work.
The full source code for this tutorial is available as a starting template. Extend it, break it, and make it yours.