Command‑Line Agent Skills Runner Clone

A tiny Node.js CLI that loads local skill files and runs them with a free LLM

Welcome to another official UVF IT build guide! In the next 45 minutes you’ll create a minimal clone of Vercel’s skills CLI – a tiny command‑line tool that fetches a skill definition from a GitHub repo and feeds it to a free LLM for execution.

We’ll use plain Node.js, the open‑source sqlite3 package for a tiny skill cache, and the free Ollama API (or any local LLM endpoint) – no paid keys required.

Before you start…

Minimal Skills Runner Architecture
YOUR MACHINE fetch skill store meta run prompt CLI (Node.js) git clone (temp) SQLite cache Ollama LLM endpoint http://localhost:11434
1

Phase 1: Scaffold the project

First we create a clean folder for the clone, turn it into a Node.js module, and pull in the tiny libraries we’ll need. sqlite3 gives us a lightweight file‑based DB, while axios will let us talk to the Ollama HTTP API. All of this runs locally, no extra configuration required.

mkdir skills-runner && cd skills-runner
npm init -y
npm install sqlite3 axios
2

Phase 2: Model – SQLite cache for skills

We now set up a tiny SQLite database that remembers which skill repo we last fetched and where it lives on disk. Two tables are enough: repos stores the GitHub URL, and skills stores the local path and a timestamp. Helper functions let the rest of the CLI read/write these rows without worrying about raw SQL.

db.js

const sqlite3 = require('sqlite3').verbose();
const path = require('path');

// Open (or create) a file‑based DB in the project root
const db = new sqlite3.Database(path.resolve(__dirname, 'skills.db'));

// Initialise tables – run once at startup
db.serialize(() => {
  db.run(`CREATE TABLE IF NOT EXISTS repos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    url TEXT UNIQUE NOT NULL
  )`);
  db.run(`CREATE TABLE IF NOT EXISTS skills (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    repo_id INTEGER NOT NULL,
    local_path TEXT NOT NULL,
    fetched_at INTEGER NOT NULL,
    FOREIGN KEY(repo_id) REFERENCES repos(id)
  )`);
});

function upsertRepo(url) {
  return new Promise((resolve, reject) => {
    db.run(`INSERT OR IGNORE INTO repos (url) VALUES (?)`, [url], function (err) {
      if (err) return reject(err);
      db.get(`SELECT id FROM repos WHERE url = ?`, [url], (e, row) => e ? reject(e) : resolve(row.id));
    });
  });
}

function storeSkill(repoUrl, localPath) {
  return upsertRepo(repoUrl).then(repoId => {
    const now = Date.now();
    return new Promise((resolve, reject) => {
      db.run(
        `INSERT INTO skills (repo_id, local_path, fetched_at) VALUES (?,?,?)`,
        [repoId, localPath, now],
        function (err) { err ? reject(err) : resolve(); }
      );
    });
  });
}

function getLatestSkill(repoUrl) {
  return new Promise((resolve, reject) => {
    db.get(
      `SELECT s.local_path FROM skills s JOIN repos r ON s.repo_id = r.id WHERE r.url = ? ORDER BY s.fetched_at DESC LIMIT 1`,
      [repoUrl],
      (err, row) => err ? reject(err) : resolve(row ? row.local_path : null)
    );
  });
}

module.exports = { storeSkill, getLatestSkill };
3

Phase 3: Core – fetch skill & call LLM

In this step we pull the skill repository into a temporary directory, read the skill's prompt.md file, and send it to a local Ollama instance. Using simple-git lets us clone without worrying about authentication, and the HTTP POST to /api/generate returns the model's answer. We keep everything async so the CLI stays responsive, and we write the response to stdout for the user to see.

runSkill.js

#!/usr/bin/env node
const path = require('path');
const os = require('os');
const fs = require('fs').promises;
const simpleGit = require('simple-git');
const fetch = require('node-fetch');

async function main() {
  const [repo, skillFile = 'prompt.md'] = process.argv.slice(2);
  if (!repo) {
    console.error('Usage: runSkill.js <owner/repo> [skillFile]');
    process.exit(1);
  }
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skill-'));
  const git = simpleGit();
  await git.clone(`https://github.com/${repo}.git`, tmpDir, { '--depth': 1 });
  const skillPath = path.join(tmpDir, skillFile);
  const prompt = await fs.readFile(skillPath, 'utf8');
  const response = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: 'llama3', prompt })
  });
  const data = await response.json();
  console.log(data.response.trim());
  // optional cleanup
  // await fs.rm(tmpDir, { recursive: true, force: true });
}

main().catch(err => { console.error('Error:', err.message); process.exit(1); });
4

Phase 4: Run locally

Now we make the JavaScript entry point executable and try it against a real skill repository. The chmod +x command marks the file as runnable, and we invoke it with the repo identifier. If everything is wired correctly you should see the LLM's generated answer printed to your terminal.

chmod +x runSkill.js
# Example: run the "hello-world" skill from the official repo
./runSkill.js vercel-labs/skills examples/hello-world/prompt.md | head -n 5
5

Phase 5: Verify & extend

Check the first few lines of output – they should be a coherent response from Ollama, confirming the clone works end‑to‑end. Next, you can experiment by swapping the model field in runSkill.js to another Ollama model, or add a tiny helper that lists cached clones for faster reuse. The listCache.js script scans the temporary folder base and prints any previously cloned repos, giving you a quick view of what’s stored locally.

listCache.js

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');

const base = path.join(os.tmpdir(), 'skill-');
fs.readdir(base, (err, files) => {
  if (err) {
    console.error('No cached skills found.');
    process.exit(0);
  }
  console.log('Cached skill directories:');
  files.forEach(f => console.log(' -', f));
});

Verifying the Stack

CheckCommand / Why it matters
npm init succeeded and Node version ≥ 18node -v && npm ls
SQLite cache created without errornode -e "require('./db'); console.log('ok')"
Running the CLI prints a LLM response./runSkill.js vercel-labs/skills examples/hello-world/prompt.md | head -n 5

You did it!

You now have a functional, free‑tier clone of Vercel’s skills CLI. Swap the Ollama model, add more agents, or publish your own npm package – the sky’s the limit!