AI

Building an AI Habit and Productivity Tracker with TypeScript, SQLite, and Cron

DD
Ankur Ishwar
8 min read Updated Sep 7, 2026
Building an AI Habit and Productivity Tracker with TypeScript and SQLite

Why Every Mobile Habit App Fails You

When I was spending one full year studying alone in my room to break into software development, staying disciplined was a brutal daily battle. Some days I would code for nine hours straight. Other days, feeling overwhelmed by self-doubt, isolation, and zero income, I would barely open my laptop. To stay on track, I downloaded half a dozen habit-tracking apps on my phone.

Every single one of them followed the same flawed playbook: you tick a box, see a fire emoji, and watch your streak counter go from day four to day five. But the moment you get sick, travel, or spend twelve hours resolving an emergency bug, you miss a day. Your streak resets to zero. Your momentum breaks, the app sends guilt-tripping push notifications, and two weeks later you delete the app.

Static checkbox apps fail because they have zero context. They treat a genuine personal emergency the same as watching reels for four hours. What you actually need is an honest, objective accountability system that tracks your progress, analyzes trends over time, and delivers constructive, no-nonsense feedback at night.

Here is how you can build a private, self-hosted AI productivity coach in TypeScript using SQLite, node-cron, and Discord webhooks for ₹0.

The Accountability Architecture

This system runs silently in the background on your local machine or a cheap cloud instance:

[Daily CLI Check-in]  -> Log tasks, study hours, and blockers
                                  |
                                  v
┌─────────────────────────────────────────────────────────────┐
│ SQLite Database (better-sqlite3)                            │  --> Fast, zero-config local storage
└─────────────────────────────────────────────────────────────┘
                                  |
┌─────────────────────────────────┴───────────────────────────┐
│ 21:30 Daily Scheduled Trigger (node-cron)                   │
└─────────────────────────────────────────────────────────────┘
                                  |
                                  v
[Analytics Engine]     -> Calculate completion rate & historical consistency
                                  |
                                  v
[AI Coach Evaluator]   -> Structured Zod prompt generates direct feedback
                                  |
                                  v
[Discord Dispatcher]   -> Sends formatted daily review to your private channel

Step 1: Setting Up the SQLite Schema

We use better-sqlite3 for fast, synchronous local data access. It requires no external database servers and stores everything in a single portable file on your disk:

// src/db/schema.ts
import Database from 'better-sqlite3';

export const db = new Database('habits.sqlite');

// Turn on Write-Ahead Logging for speed and concurrency
db.pragma('journal_mode = WAL');

db.exec(`
  CREATE TABLE IF NOT EXISTS habits (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    target_frequency TEXT NOT NULL DEFAULT 'daily',
    category TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  );

  CREATE TABLE IF NOT EXISTS habit_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    habit_id INTEGER NOT NULL,
    completed_date DATE NOT NULL,
    notes TEXT,
    duration_minutes INTEGER DEFAULT 0,
    FOREIGN KEY (habit_id) REFERENCES habits(id) ON DELETE CASCADE,
    UNIQUE(habit_id, completed_date)
  );

  CREATE TABLE IF NOT EXISTS daily_reviews (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    review_date DATE NOT NULL UNIQUE,
    score_percentage REAL NOT NULL,
    ai_coaching_feedback TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  );
`);

Step 2: Tracking Check-Ins and Daily Summaries

Next, build a small service to record your completed goals and aggregate your daily numbers:

// src/services/habit-tracker.ts
import { db } from '../db/schema';

export interface DaySummary {
  dateString: string;
  totalHabits: number;
  completedHabits: number;
  completionRate: number;
  habitDetails: Array<{ name: string; completed: boolean; notes: string | null }>;
}

export class HabitService {
  public logHabit(habitName: string, date: string, notes = '', durationMinutes = 0): void {
    const habit = db.prepare('SELECT id FROM habits WHERE name = ?').get(habitName) as { id: number } | undefined;
    if (!habit) {
      throw new Error(`Habit "${habitName}" is not registered in database.`);
    }

    const stmt = db.prepare(`
      INSERT INTO habit_logs (habit_id, completed_date, notes, duration_minutes)
      VALUES (?, ?, ?, ?)
      ON CONFLICT(habit_id, completed_date) DO UPDATE SET
        notes = excluded.notes,
        duration_minutes = excluded.duration_minutes
    `);
    stmt.run(habit.id, date, notes, durationMinutes);
  }

  public getDaySummary(targetDate: string): DaySummary {
    const allHabits = db.prepare('SELECT id, name FROM habits').all() as Array<{ id: number; name: string }>;
    const logs = db.prepare(`
      SELECT habit_id, notes FROM habit_logs WHERE completed_date = ?
    `).all(targetDate) as Array<{ habit_id: number; notes: string | null }>;

    const logMap = new Map(logs.map(l => [l.habit_id, l.notes]));
    const details = allHabits.map(h => ({
      name: h.name,
      completed: logMap.has(h.id),
      notes: logMap.get(h.id) ?? null
    }));

    const completedCount = details.filter(d => d.completed).length;
    const rate = allHabits.length > 0 ? (completedCount / allHabits.length) * 100 : 0;

    return {
      dateString: targetDate,
      totalHabits: allHabits.length,
      completedHabits: completedCount,
      completionRate: Math.round(rate),
      habitDetails: details
    };
  }
}

Step 3: The Structured AI Coaching Evaluator

At 9:30 PM every evening, pass your day's records to an LLM evaluator. We use Zod to enforce structured outputs so the model gives us exact fields instead of generic rambling:

// src/services/ai-coach.ts
import { z } from 'zod';
import OpenAI from 'openai';
import { DaySummary } from './habit-tracker';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export const DailyCoachingSchema = z.object({
  verdictScore: z.number().min(1).max(10),
  candorReview: z.string(),
  primaryTomorrowAdjustment: z.string()
});

export type DailyCoachingReport = z.infer<typeof DailyCoachingSchema>;

const COACH_SYSTEM_PROMPT = `You are a tough, supportive senior software engineer mentoring a junior developer.

RULES:
1. Be direct, brotherly, and practical. Skip corporate motivational fluff.
2. If tasks were skipped, identify the blocker and propose a small change for tomorrow.
3. Keep the feedback under 150 words.`;

export async function evaluateDailyPerformance(summary: DaySummary): Promise<DailyCoachingReport> {
  const prompt = `
Date: ${summary.dateString}
Completion Rate: ${summary.completedHabits}/${summary.totalHabits} (${summary.completionRate}%)

Habit Breakdown:
${summary.habitDetails.map(h => `- ${h.name}: ${h.completed ? 'COMPLETED' : 'SKIPPED'}${h.notes ? ` (Note: ${h.notes})` : ''}`).join('\n')}

Provide your honest assessment in JSON format matching the schema: {"verdictScore": number, "candorReview": string, "primaryTomorrowAdjustment": string}`;

  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      { role: 'system', content: COACH_SYSTEM_PROMPT },
      { role: 'user', content: prompt }
    ],
    response_format: { type: 'json_object' }
  });

  const content = response.choices[0].message.content || '{}';
  return DailyCoachingSchema.parse(JSON.parse(content));
}

You can validate and test your JSON coaching schemas with our free JSON Formatter.

Step 4: Dispatching Reports via Discord Webhook

Set up a small daemon script using node-cron that runs every night at 21:30 and dispatches an embed directly to your private Discord channel:

// src/daemon.ts
import cron from 'node-cron';
import { HabitService } from './services/habit-tracker';
import { evaluateDailyPerformance } from './services/ai-coach';

const habitService = new HabitService();
const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL || '';

async function dispatchDiscordEmbed(dateStr: string, report: any, summary: any) {
  if (!DISCORD_WEBHOOK_URL) return;

  const payload = {
    embeds: [{
      title: `Daily Accountability Review: ${dateStr}`,
      color: summary.completionRate >= 80 ? 0x22c55e : 0xef4444,
      fields: [
        { name: 'Completion', value: `${summary.completedHabits}/${summary.totalHabits} (${summary.completionRate}%)`, inline: true },
        { name: 'Rating', value: `${report.verdictScore} / 10`, inline: true },
        { name: 'Senior Feedback', value: report.candorReview },
        { name: 'Focus for Tomorrow', value: report.primaryTomorrowAdjustment }
      ],
      footer: { text: 'Personal Developer Coach' }
    }]
  };

  await fetch(DISCORD_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
  });
}

// Trigger every night at 9:30 PM (21:30)
cron.schedule('30 21 * * *', async () => {
  const today = new Date().toISOString().split('T')[0];
  console.log(`[*] Generating review for ${today}...`);

  try {
    const summary = habitService.getDaySummary(today);
    const coachingReport = await evaluateDailyPerformance(summary);
    await dispatchDiscordEmbed(today, coachingReport, summary);
    console.log('[+] Review sent to Discord.');
  } catch (err) {
    console.error('[-] Daily cron review failed:', err);
  }
});

console.log('[*] Developer Coach daemon started. Waiting for 21:30 trigger...');

Running this script locally costs practically zero rupees because gpt-4o-mini calls cost fractions of a cent. You can monitor your token usage with our Token Counter and check out more practical guides on our Free Developer Tools page.

Real career growth comes from showing up consistently, acknowledging bad days without giving up, and focusing on small daily wins. Build your own tools, track what matters, and keep coding. Start tonight.

Found this useful?
View all articles

Keep Reading

Related Articles

Learn with Dropout Developer

Build real software with AI

Step-by-step learning paths, vibe coding tutorials, and certified developer programs designed for the modern engineer.