A 45‑minute beginner’s guide to a tiny TypeScript‑based AI coding helper
Welcome to another official UVF IT build where we demystify a cool open‑source product. In this guide you’ll spin up a stripped‑down version of the OpenCode coding agent using only Node.js, TypeScript and a local SQLite database – no paid APIs required.
We’ll walk through scaffolding, a tiny prompt‑to‑code core, a simple Express UI, and how to run and verify it locally. By the end you’ll have a working clone you can tinker with and extend.
sqlite3 package – no external DB server.Create a fresh npm workspace with TypeScript and install the tiny dependencies we need. This gives us a clean folder, a package.json, and a ready‑to‑run TypeScript compiler.
We’ll also add sqlite3 for the database and ts-node so we can execute TypeScript files directly without a separate build step.
mkdir opencode-clone && cd opencode-clone
npm init -y
npm i typescript ts-node @types/node sqlite3
npx tsc --init --rootDir src --outDir dist --esModuleInterop true --resolveJsonModule true
mkdir srcWe store generated snippets with a title and code body. The DB is created on first run, so you don’t need any manual migration steps.
The helper module wraps the SQLite calls in async functions, making it easy for the rest of the app to add and fetch snippets.
src/db.ts
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
const DB_PATH = 'data.db';
export async function getDb() {
const db = await open({ filename: DB_PATH, driver: sqlite3.Database });
await db.exec(`CREATE TABLE IF NOT EXISTS snippets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
code TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`);
return db;
}
export async function addSnippet(title: string, code: string) {
const db = await getDb();
await db.run('INSERT INTO snippets (title, code) VALUES (?, ?)', title, code);
await db.close();
}
export async function getSnippets() {
const db = await getDb();
const rows = await db.all('SELECT id, title, code, created_at FROM snippets ORDER BY created_at DESC');
await db.close();
return rows;
}In this step we add the heart of our clone: a tiny generateCode function that pretends to understand a prompt and returns a simple TypeScript snippet. The function is deliberately simple so you can see the data flow without needing an actual LLM.
We expose this logic through an Express POST route /generate. The route receives JSON {prompt:string} and replies with {code:string}. This keeps the API contract identical to the real OpenCode service, making future swaps easy.
src/server.ts
import express from 'express';
import cors from 'cors';
const app = express();
app.use(cors());
app.use(express.json());
// Very small stub that turns a prompt into a TS function
function generateCode(prompt: string): string {
const safeName = prompt.replace(/\W/g, '_') || 'generatedFunction';
return `function ${safeName}() {\n // TODO: implement based on "${prompt}"\n}\n`;
}
app.post('/generate', (req, res) => {
const {prompt} = req.body;
if (!prompt) {
return res.status(400).json({error: 'Prompt required'});
}
const code = generateCode(prompt);
res.json({code});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`🚀 server at http://localhost:${PORT}`));Now we give ourselves a minimal front‑end so we can interact with the API without leaving the terminal. A single HTML file contains a textarea for the prompt, a button to submit, and a <pre> block that shows the returned snippet.
The script uses fetch to POST the prompt to /generate and then injects the returned code into the page. No build step is required – just open the page in a browser and you’re ready to test.
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Mini Coding Agent</title>
<style>
body {font-family: Arial, sans-serif; margin: 2rem;}
textarea {width: 100%; height: 80px;}
pre {background:#f4f4f4; padding:1rem; overflow:auto;}
</style>
</head>
<body>
<h1>Mini Coding Agent</h1>
<textarea id="prompt" placeholder="Describe the function you need"></textarea><br/>
<button id="run">Generate</button>
<h2>Result:</h2>
<pre id="output">(code will appear here)</pre>
<script>
document.getElementById('run').onclick = async () => {
const prompt = document.getElementById('prompt').value.trim();
if (!prompt) return alert('Enter a prompt');
const res = await fetch('/generate', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({prompt})
});
const data = await res.json();
document.getElementById('output').textContent = data.code || 'Error';
};
</script>
</body>
</html>With server and UI in place, start the application using ts-node. The Express server will serve the static HTML file and the generation endpoint, so visiting http://localhost:3000 loads the UI instantly.
Enter a natural‑language description like "add two numbers" and hit Generate. You should see a tiny TypeScript function appear in the <pre> area. From here you can replace generateCode with a real LLM call, add authentication, or store snippets in a database.
npm install express cors ts-node @types/express @types/cors
npx ts-node src/server.ts| Check | Command / Why it matters |
|---|---|
| Server starts without errors | npm run start (or the ts-node command) prints "🚀 server at http://localhost:3000" |
| UI loads at http://localhost:3000 | Opening the URL in a browser shows the Mini Coding Agent page with textarea and button |
| Generated code appears after entering a prompt | After typing a prompt and clicking Generate, the <pre> element displays a TypeScript function matching the input |
Your minimal OpenCode clone is up and running. Feel free to replace generateCode with a real LLM call, add authentication, or persist more metadata. Happy hacking!