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.
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/nodeWe’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');
}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);
});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?"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;"| Check | Command / Why it matters |
|---|---|
| Project builds without errors | npx tsc finishes with no output |
| CLI prints an AI response | Running node dist/index.js "What is a closure?" shows "AI:" followed by text |
| SQLite contains both user and assistant rows | sqlite3 chat.db "SELECT COUNT(*) FROM convo;" returns 2 |
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.