VS CodeTutorial··8 min read

How I Built a VS Code Extension That Shows What My AI Actually Learned

A step-by-step guide to building a status bar extension — from yo code to seeing your widget light up in the editor. Full source code included.

Why I built this

I run Cachly, an AI memory layer that lets coding assistants (Copilot, Claude, Cursor) remember what they learned across sessions. The MCP server stores lessons like “don’t use ${var,,} on macOS — use trinstead” and recalls them automatically before the AI makes the same mistake twice.

The problem: I had no visibility into what the brain actually knew. Was it learning? How many lessons? How many tokens was it saving me?

So I built a VS Code extension. Here’s exactly how — including the parts that bit me.

MCP 0.5.4 note: Sessions are now fully automatic. The brain starts tracking on the first tool call and saves a summary on editor close. No manual session_start / session_end needed.

Step 1: Scaffold the extension

You need Node.js 20+ and the VS Code Extension Generator:

npm install -g yo generator-code
yo code

Choose New Extension (TypeScript), name it cachly-brain, enable strict TypeScript. Delete the hello-world command in src/extension.ts— we won’t need it.

Step 2: Define the manifest

The package.json is the heart of every VS Code extension:

{
  "name": "cachly-brain",
  "displayName": "Cachly Brain",
  "version": "0.5.2",
  "engines": { "vscode": "^1.85.0" },
  "activationEvents": ["onStartupFinished"],
  "main": "./dist/extension.js",
  "contributes": {
    "commands": [
      { "command": "cachly.showBrainHealth", "title": "Cachly: Show Brain Health" },
      { "command": "cachly.showLessons",     "title": "Cachly: Show Lessons" }
    ],
    "configuration": {
      "title": "Cachly Brain",
      "properties": {
        "cachly.apiKey":          { "type": "string", "default": "", "description": "Your Cachly API key" },
        "cachly.instanceId":      { "type": "string", "default": "", "description": "Your Brain instance UUID" },
        "cachly.refreshInterval": { "type": "number", "default": 300, "description": "Refresh interval (seconds)" }
      }
    }
  }
}

Key decisions

Use onStartupFinished — it activates after VS Code is ready, not blocking startup. Never use "*" unless you absolutely must. Configuration properties appear in the Settings UI automatically — users get a nice form instead of editing JSON.

Step 3: Create the status bar widget

import * as vscode from 'vscode';

let statusBarItem: vscode.StatusBarItem;
let refreshTimer: NodeJS.Timeout | undefined;

export function activate(context: vscode.ExtensionContext) {
  statusBarItem = vscode.window.createStatusBarItem(
    vscode.StatusBarAlignment.Right, 100
  );
  statusBarItem.command = 'cachly.showBrainHealth';
  statusBarItem.tooltip  = 'Cachly Brain Health — click for details';
  context.subscriptions.push(statusBarItem);

  context.subscriptions.push(
    vscode.commands.registerCommand('cachly.showBrainHealth', showHealthPanel),
    vscode.commands.registerCommand('cachly.refreshBrain', updateStatusBar),
  );

  startRefreshLoop();
}

What I learned the hard way

Always push to context.subscriptions. Otherwise your disposables leak and VS Code complains on reload. Use StatusBarAlignment.Right — the left side is crowded with Git/problems indicators. The command property is just a string — it must match what you registered in package.json AND in registerCommand(). Typos mean silent failures.

Step 4: Fetch data with zero dependencies

import * as https from 'https';
import * as http  from 'http';

function apiGet(url: string, apiKey: string): Promise<unknown> {
  return new Promise((resolve, reject) => {
    const parsedUrl = new URL(url);
    const mod = parsedUrl.protocol === 'https:' ? https : http;

    const req = mod.get(url, {
      headers: { 'Authorization': `Bearer ${apiKey}`, 'Accept': 'application/json' },
      timeout: 5000,
    }, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try   { resolve(JSON.parse(data)); }
        catch { resolve(null); }
      });
    });
    req.on('error', reject);
    req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
  });
}

VS Code extensions run in Node.js, not a browser. Using the built-in http/https modules gives you control over timeouts and avoids polyfill issues across VS Code versions — with zero added dependencies.

Step 5: The refresh loop

function startRefreshLoop() {
  if (refreshTimer) clearInterval(refreshTimer);

  const config     = vscode.workspace.getConfiguration('cachly');
  const apiKey     = config.get<string>('apiKey', '');
  const instanceId = config.get<string>('instanceId', '');

  if (!apiKey || !instanceId) {
    statusBarItem.text = '$(brain) Cachly: not configured';
    statusBarItem.show();
    return;
  }

  updateStatusBar();
  const interval = config.get<number>('refreshInterval', 300) * 1000;
  refreshTimer = setInterval(() => updateStatusBar(), interval);
}

// Restart loop when the user saves new settings
vscode.workspace.onDidChangeConfiguration((e) => {
  if (e.affectsConfiguration('cachly')) startRefreshLoop();
});

Without the onDidChangeConfiguration listener, users have to reload VS Code after updating their API key. Bad UX — two lines fix it.

Step 6: Rich detail panel (Markdown, no webview)

async function showHealthPanel() {
  const health = await fetchBrainHealth();

  const lines = [
    '# 🧠 Cachly Brain Health\n',
    '| Metric | Value |',
    '|--------|-------|',
    `| Status  | ${health.status === 'healthy' ? '✅' : '❌'} |`,
    `| Lessons | **${health.lessons}** |`,
    `| Recalls | **${health.totalRecalls}** |`,
    `| Tokens Saved | ~${health.estimatedTokensSaved} |`,
  ];

  const doc = await vscode.workspace.openTextDocument({
    content: lines.join('\n'),
    language: 'markdown',
  });
  await vscode.window.showTextDocument(doc, { preview: true });
}

No webview needed — Markdown renders natively with tables, headers, and emoji. Preview mode avoids cluttering the editor with permanent tabs. Users can also copy-paste the health report.

Step 7: Package and install

npm install -g @vscode/vsce
vsce package
# → cachly-brain-0.5.2.vsix

code --install-extension cachly-brain-0.5.2.vsix

Packaging gotchas

Node version matters — vsce 3.x requires Node 20+. Create a .vscodeignore to exclude src/, node_modules/, and tsconfig.json from the package — otherwise your VSIX bloats from ~15 KB to several MB.

Step 8: Publish to the Marketplace

vsce login cachly
vsce publish

You need a Visual Studio Marketplace publisher and a Personal Access Token with Marketplace scope. The first publish takes a few minutes to appear — Marketplace listing for the Cachly Brain extension is coming soon.

The satisfying result

After all this, you open VS Code and see 🧠 Brain: 42 lessons in the status bar. Click it and you get a full report: which bugs the AI fixed, how many times each lesson was recalled, estimated token savings.

Seeing your own widget in the editor you use every day — not a web app in a tab, but inside your tool — is deeply satisfying.

Try it yourself.Install the Cachly Brain extension, set your instance ID and API key, and watch your AI’s lessons accumulate in real time.