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:

FeatureWithout LSPWith LSP
Syntax errorsDiscovered at runtimeCaught before save
Import resolutionManual checkingAuto-validated
Refactoring safetyRiskyServer-verified
Code navigationText search onlySemantic 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.