Track habits. Journal your life. Let AI hold you accountable.
HabitMind is a full-stack personal productivity platform combining daily journaling, streak-based habit tracking, AI-powered mood analysis, a multi-agent accountability coach, and computer-vision habit verification — all in one cohesive experience.
- Features
- Tech Stack
- Getting Started
- Core Features
- AI Features
- API Reference
- Database Schema
- Environment Variables
- Contributing
- License
| Feature | Description |
|---|---|
| 📓 Journaling Engine | Timestamped, editable daily long-form entries with rich text support |
| ✅ Habit Tracker | Create habits, mark completions, and track streaks with full history |
| 📊 Progress Visualisation | Calendar and history views colour-coded by completion status |
| 🎭 The Mood Ring | AI sentiment and theme extraction from every journal entry |
| 🤖 Accountability Coach | Dual-agent system that reads your journals and calls out your excuses |
| 📷 Proof of Work | Upload a photo to verify habit completion — or get roasted by AI |
| 🕵️ Paranoia Mode | Random words get [REDACTED]. Hover to reveal. The AI won't explain why. |
Frontend
- React 18 · Tailwind CSS · Recharts · React Calendar
Backend
- Node.js · Express · MongoDB · Mongoose
AI / ML
- Google Gemini API (
gemini-1.5-pro) for mood analysis and multi-agent coaching - Google Gemini Multimodal (
gemini-1.5-pro-vision) for image-based habit verification and Paranoia redaction
Infrastructure
- AWS S3 / Cloudflare R2 for image uploads
node-cronfor scheduled Auditor agent background scans
- Node.js 18+
- MongoDB (local or MongoDB Atlas)
- Google Gemini API key
- S3-compatible storage bucket (for photo uploads)
# Clone the repository
git clone https://github.com/your-username/habitmind.git
cd habitmind
# Install dependencies
npm install
# Configure environment variables
cp .env.example .env
# Fill in your credentials (see Environment Variables below)
# Start the development server
npm run devThe app will be available at http://localhost:3000.
A distraction-free editor for daily long-form entries.
- Entries are automatically timestamped on creation and fully editable afterwards
- Rich text support — bold, italic, lists
- Past entries accessible via the Calendar view
- Each save asynchronously triggers the Mood Ring analysis pipeline
Create and manage personal habits from your dashboard.
- Add habits with a name and optional description (e.g. "Read 20 pages", "Morning meditation")
- Daily checklist view to mark habits complete
- Streak logic calculates
currentStreakandlongestStreakper habit
Streak Rules:
- Completing habit on day D increments the streak only if day D−1 was also completed
- Missing a day resets
currentStreakto 0 longestStreakis only ever updated upward
- Calendar View — each date is colour-coded: green (all complete), yellow (partial), red (none)
- Click any date to view that day's journal entry and habit status
- History Feed — reverse-chronological list of past entries with mood tags
Every time a journal entry is saved, it is passed asynchronously to the Gemini API. The model returns:
- Sentiment — a primary emotional tone (
Positive,Anxious,Reflective,Lethargic,Hopeful, etc.) - Key Themes — 2–5 thematic tags (
Work Stress,Good Sleep,Family,Exercise, etc.)
These are stored back to the entry and surfaced in the Emotional Dashboard at /mood:
- Sentiment Timeline — line/bar chart showing mood over the past 7–14 days
- Theme Frequency Cloud — most common themes in the period
- Weekly Summary — a generated narrative paragraph describing your emotional arc for the week
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
async function analyseJournalEntry(entryId, text) {
const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
const result = await model.generateContent(
`You are a mood analysis engine. Return ONLY valid JSON.
Analyse the following journal entry and extract:
{ "sentiment": string, "themes": string[] }
Journal entry: ${text}`
);
const { sentiment, themes } = JSON.parse(result.response.text());
await JournalEntry.findByIdAndUpdate(entryId, { sentiment, themes });
}A two-agent pipeline that generates personalised, contextually targeted motivational messages.
Runs on a cron job every 6 hours. Scans all habit completion records and fires a payload to Agent B for any habit missed for 2 or more consecutive days, including:
- Habit name and days missed
- User's last 5 journal entries (raw text)
cron.schedule('0 */6 * * *', async () => {
const brokenStreaks = await Habit.find({ daysMissed: { $gte: 2 } })
.populate({
path: "userId",
populate: { path: "journalEntries", options: { sort: { date: -1 }, limit: 5 } }
});
for (const habit of brokenStreaks) await invokeEnforcer(habit);
});Receives the Auditor's payload, cross-references the user's journal entries, and generates a highly personalised message.
"You missed 'Morning Run' for the third day in a row. Interesting. Because two days ago you wrote — and I'm quoting directly here — 'I really want to finish that half-marathon before summer.' Bold of you. Very bold. The race is in 11 weeks. The couch you're on right now does not have a finish line."
| Mode | Behaviour |
|---|---|
modal |
Full-screen takeover on next app load |
toast |
Persistent toast notification |
inbox |
Simulated email inbox UI at /coach/inbox |
Habits flagged as Proof Required replace the standard checkbox with a photo upload prompt.
- User uploads a photo from their device or camera
- Image is sent to the Gemini Multimodal endpoint alongside the habit name and description
- Gemini evaluates whether the photo constitutes valid proof
- Verified → habit marked complete, streak increments, image saved to storage
- Rejected → habit not marked complete, AI returns a sarcastic rejection message
Example interactions:
| Habit | Upload | Result |
|---|---|---|
| "Eat a healthy meal" | Photo of a salad | ✅ Verified. Streak continues. |
| "Eat a healthy meal" | Photo of a pizza box | ❌ "Bold choice submitting this as evidence…" |
| "Read 20 pages" | Photo of open book | ✅ Verified. |
| "Read 20 pages" | Photo of Netflix | ❌ "An open browser is not a book. Rejected." |
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
async function verifyHabitProof(habitName, habitDescription, imageBase64, mimeType = "image/jpeg") {
const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
const result = await model.generateContent([
{
inlineData: { data: imageBase64, mimeType }
},
`Habit: "${habitName}". Description: "${habitDescription}".
Does this image prove the habit was completed?
Respond ONLY with JSON: { "verified": boolean, "message": string }
If not verified, make the message sarcastic and specific to what you see in the image.`
]);
return JSON.parse(result.response.text());
}Enable from Settings. When active, an AI agent periodically scans the rendered page text and wraps selected words in a [REDACTED] black bar. Hover to reveal.
- The selection logic is intentionally arbitrary — proper nouns, adjectives, the occasional article
- Paranoia level:
Low/Medium/MAXIMUM CLEARANCE - A 🔒 badge in the top-right corner indicates Paranoia Mode is active
"Today was a [REDACTED] day. I went to the [REDACTED] and had [REDACTED] for lunch.
Feeling [REDACTED] about the week ahead."
The agent does not explain its choices.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/habits |
List all habits for authenticated user |
POST |
/api/habits |
Create a new habit |
PATCH |
/api/habits/:id/complete |
Mark habit complete (or initiate proof upload) |
GET |
/api/journal |
List journal entries |
POST |
/api/journal |
Create journal entry (triggers mood analysis) |
PUT |
/api/journal/:id |
Edit journal entry |
GET |
/api/mood/dashboard |
Aggregated mood data for past N days |
GET |
/api/coach/messages |
Retrieve accountability coach messages |
POST |
/api/proof/verify |
Submit image for vision verification |
GET |
/api/streaks/:habitId |
Get streak data for a specific habit |
// User
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true, select: false },
habits: [{ type: mongoose.Schema.Types.ObjectId, ref: "Habit" }],
journalEntries:[{ type: mongoose.Schema.Types.ObjectId, ref: "JournalEntry" }],
coachMessages: [{ type: mongoose.Schema.Types.ObjectId, ref: "CoachMessage" }],
}, { timestamps: true });
// Habit
const habitSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
name: { type: String, required: true },
description: String,
proofRequired: { type: Boolean, default: false },
currentStreak: { type: Number, default: 0 },
longestStreak: { type: Number, default: 0 },
completions: [{ type: mongoose.Schema.Types.ObjectId, ref: "Completion" }],
}, { timestamps: true });
// Completion
const completionSchema = new mongoose.Schema({
habitId: { type: mongoose.Schema.Types.ObjectId, ref: "Habit", required: true },
date: { type: Date, required: true },
proofUrl: String,
});
// Journal Entry
const journalEntrySchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
content: { type: String, required: true },
sentiment: String,
themes: [String],
}, { timestamps: true });
// Coach Message
const coachMessageSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
habitName: { type: String, required: true },
message: { type: String, required: true },
read: { type: Boolean, default: false },
}, { timestamps: true });Create a .env file in the root directory:
# Database
MONGODB_URI="your mongoDB Database URL"
# Google Gemini
GEMINI_API_KEY="AIza..."
# Storage (S3-compatible — works with AWS S3 or Cloudflare R2)
STORAGE_BUCKET="habitmind-proofs"
STORAGE_REGION="us-east-1"
STORAGE_ACCESS_KEY="..."
STORAGE_SECRET_KEY="..."
STORAGE_ENDPOINT="https://s3.amazonaws.com"
# App
JWT_SECRET="your-jwt-secret"
PORT=5000
CLIENT_URL="http://localhost:3000"
CRON_SECRET="your-cron-secret"Contributions are welcome. Please open an issue before submitting a pull request for anything beyond minor bug fixes.
# Run tests
npm test
# Lint
npm run lint
# Type check
npm run type-checkBranch conventions:
| Branch | Purpose |
|---|---|
main |
Stable production code |
dev |
Integration branch |
feature/your-feature-name |
Individual feature branches |
This project is licensed under the MIT License.
Built with obsessive attention to streaks and the Gemini API.
Your journal entries are private. The AI reads them anyway — to help you, of course.
⭐ Star this repo if HabitMind helped you build a better routine.