Minimal AI Coding Assistant CLI

Build a tiny TypeScript CLI that answers coding questions using Pi’s built‑in AI provider

Welcome to another official UVF IT guide! In the next 45 minutes you’ll spin up a minimal AI coding assistant that runs entirely in your terminal. We’ll use the free‑tier Pi toolkit (TypeScript) and Node’s built‑in SQLite for a tiny persistence layer – no paid APIs required.

This clone mirrors the core of Pi’s interactive mode: you type a prompt, the agent replies, and the conversation is saved locally. Feel free to extend it with your own skills or themes once the basics are working.

Before you start

Minimal AI Assistant Architecture
LOCAL RUNTIME EXTERNAL SERVICES store convo request completion CLI (TypeScript) pi‑coding‑agent SQLite DB Pi Built‑in AI
1

Phase 1: Scaffold the project

Create a fresh folder for the assistant, initialise a Node project, and pull in the Pi coding‑agent package along with SQLite support. This gives us a TypeScript playground and a tiny database engine without any external services.

We also add development helpers so we can run TypeScript directly from the command line, keeping the setup minimal and beginner‑friendly.

mkdir pi-assistant && cd pi-assistant
npm init -y
npm i pi-coding-agent sqlite3
npm i -D typescript ts-node @types/node
2

Phase 2: Define a tiny SQLite model

We’ll store each user prompt and the AI’s reply in a single table called conversations. A small helper module abstracts opening the database and creating the table if it doesn’t exist yet – perfect for a quick demo.

The module exports two async functions: one to insert a new record and another to fetch the full chat history, both returning plain JavaScript objects.

src/db.ts

import sqlite3 from 'sqlite3';
import { open, Database } from 'sqlite';

let db: Database<sqlite3.Database, sqlite3.Statement> | null = null;

export async function getDb(): Promise<Database> {
  if (db) return db;
  db = await open({
    filename: './assistant.db',
    driver: sqlite3.Database,
  });
  await db.exec(`CREATE TABLE IF NOT EXISTS conversations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    role TEXT NOT NULL,
    content TEXT NOT NULL,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
  );`);
  return db;
}

export async function addMessage(role: 'user' | 'assistant', content: string) {
  const database = await getDb();
  await database.run('INSERT INTO conversations (role, content) VALUES (?, ?)', role, content);
}

export async function getHistory() {
  const database = await getDb();
  return database.all<any[]>('SELECT role, content, timestamp FROM conversations ORDER BY id');
}
3

Phase 3: Wire up Pi agent and CLI

In this step we connect the command‑line interface to Pi’s built‑in AI provider. The CLI will accept a question as a command‑line argument, forward it to Pi, and print the answer back to the console. At the same time we log both the user query and the AI response into a local SQLite database so we can review the conversation later. This gives us a tiny but functional chat loop without any external services.

src/index.ts

import { Pi } from 'pi';
import Database from 'better-sqlite3';

// Initialise Pi with its default provider (no API keys needed)
const pi = new Pi();

// Open (or create) a SQLite DB to store the conversation
const db = new Database('chat.db');
// Simple table: id, role ('user'|'assistant'), content, ts
db.exec(`CREATE TABLE IF NOT EXISTS convo (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  role TEXT NOT NULL,
  content TEXT NOT NULL,
  ts DATETIME DEFAULT CURRENT_TIMESTAMP
);`);

// Helper to log a message
function logMessage(role: string, content: string) {
  const stmt = db.prepare('INSERT INTO convo (role, content) VALUES (?, ?)');
  stmt.run(role, content);
}

// Grab the question from the command line (everything after the script name)
const question = process.argv.slice(2).join(' ');
if (!question) {
  console.error('Usage: node src/index.js "Your coding question"');
  process.exit(1);
}
logMessage('user', question);

// Ask Pi and handle the async response
pi.ask(question).then(answer => {
  console.log('AI:', answer.trim());
  logMessage('assistant', answer.trim());
}).catch(err => {
  console.error('Error contacting Pi:', err);
});
4

Phase 4: Run locally

Now that the source is ready we compile the TypeScript into plain JavaScript and execute the CLI with a sample query. The compilation step ensures type safety and produces a runnable file in the dist folder. Running the script should display the AI’s answer and create/append to chat.db.

npm install pi better-sqlite3
npx tsc --outDir dist src/index.ts
node dist/index.js "How do I reverse a string in TypeScript?"
5

Phase 5: Verify and extend

After the first run, open the SQLite database to confirm that both the user question and the AI response were stored. Running a second query will add more rows, demonstrating that the conversation history grows over time. You can later extend this by adding a SELECT that fetches the last N messages and feeds them back to Pi for context‑aware answers.

sqlite3 chat.db "SELECT id, role, content FROM convo ORDER BY id;"

Verifying the Stack

CheckCommand / Why it matters
Project builds without errorsnpx tsc finishes with no output
CLI prints an AI responseRunning node dist/index.js "What is a closure?" shows "AI:" followed by text
SQLite contains both user and assistant rowssqlite3 chat.db "SELECT COUNT(*) FROM convo;" returns 2

You did it!

Your minimal AI coding assistant is now running locally. Experiment by adding custom skills or swapping the built‑in provider for an external LLM when you’re ready.