The assistant that actually remembers you β without burning your token budget.
NudgeBot is a self-hosted AI assistant (React + Express) built around one core idea: your conversation history belongs to you and shouldn't cost you a fortune in tokens.
While other assistants like OpenClaw re-inject the full conversation history into every request β leading to exponentially growing token costs β NudgeBot compresses and stores context on GitHub, then injects only a concise summary into the system prompt. No vector databases, no paid cloud services, no token floods.
| Feature | NudgeBot | OpenClaw / typical assistants |
|---|---|---|
| Cross-session memory | β GitHub-backed, compressed | β In-memory only, lost on restart |
| Token efficiency | β Compact summary injected once | β Full history re-sent every request |
| Memory storage | β Private GitHub repo (free, versioned) | β Local files, no history, no versioning |
| GitHub context manager | β First-class, auto-creates repo | β Not available |
| On-demand MCP servers | β Per-user, lazy start | |
| Google Jules integration | β Delegate coding tasks + auto PR | β Not available |
| Email notifications | β Resend + recurring schedule | β Not available |
| Self-hosted | β
One npm start |
β Yes |
| Multi-channel (WhatsApp, Slackβ¦) | β Web only | β Yes |
NudgeBot trades multi-channel breadth for deep GitHub integration and token frugality. If you primarily use the web interface and care about memory without paying for it in tokens, NudgeBot is the better fit.
# Clone and install
git clone https://github.com/QuenumGerald/NudgeBot
cd NudgeBot
npm install
# Configure (see Variables section below)
cp backend/.env.example backend/.env
# Generate a secure JWT secret
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# Add the generated secret to backend/.env:
# JWT_SECRET=<generated-secret>
# JWT_EXPIRES_IN=12h
# Run
npm run devApp available at http://localhost:3000.
Login: Use POST /api/auth/login with your ADMIN_PASSWORD to get a JWT token, then include it in requests with Authorization: Bearer <token>.
Requirements: Node.js 20+ Β· npm 10+
Most assistants either forget everything on restart, or re-inject the full history on every request (expensive). NudgeBot instead:
- Summarizes each session into key decisions + active actions + topics
- Stores the compressed context as JSON in a private GitHub repo (
nudgebot-context) - On the next session, injects only the compact summary into the system prompt β not hundreds of messages
No third-party memory service. No vector DB. Just a GitHub repo you already have.
Auto-creates the repo if GITHUB_CONTEXT_REPO is not set β resolves your login via the GitHub API and creates {login}/nudgebot-context (private, auto-init).
The agent is a LangGraph StateGraph that loops LLM β tools β LLM until no more tool calls are needed. Supported providers: DeepSeek, OpenRouter, OpenAI (configurable per user in Settings).
| Category | Tool | What it does |
|---|---|---|
| Workspace | create_project_workspace |
Creates a sandboxed subfolder per project under NUDGEBOT_WORKDIR |
| Files | create_file |
Creates or appends to a file (workspace-restricted) |
read_file |
Reads a file | |
list_directory |
Lists a directory | |
delete_file |
Deletes a file | |
| Shell | execute_command |
Runs a shell command in the workspace (15s timeout) |
| Scheduling | schedule_task |
Schedules a one-off or recurring task via BlazerJob |
list_tasks |
Lists active scheduled tasks | |
cancel_task |
Cancels a task by ID | |
| Web | web_fetch |
Fetches the content of a URL (HTML stripped, JSON pretty-printed) |
send_email |
Sends an email immediately via Resend | |
| Date/Time | get_date_time |
Returns current date/time with timezone support |
| Notes | save_note |
Saves a persistent note to the GitHub context repo |
list_notes |
Lists all saved notes | |
read_note |
Reads a note by title | |
| AI Coding | run_jules_session |
Launches a Google Jules session β returns PR URL |
NudgeBot can hand off development tasks to Google Jules (Google's AI coding agent). The run_jules_session tool:
- Takes a prompt, a GitHub repo (
owner/repo) and a base branch - Streams Jules's progress updates in real time
- Returns the Pull Request URL created automatically by Jules
User: "Add pagination to the /api/users endpoint in repo myorg/myapi"
NudgeBot: [runs Jules] β PR created: https://github.com/myorg/myapi/pull/42
Requires: JULES_API_KEY + GITHUB_PERSONAL_ACCESS_TOKEN
MCP servers start only when a user enables them in Settings. No global startup, no credential errors for services you don't use.
| Integration | Tools exposed | Required env vars |
|---|---|---|
| Web Fetch | Fetch any URL | β |
| GitHub | Repos, issues, PRs, code search | GITHUB_PERSONAL_ACCESS_TOKEN |
| Google Calendar | Read/create/update events | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN |
| Jira | Tickets, sprints, projects | JIRA_API_TOKEN, JIRA_EMAIL, JIRA_URL |
| Confluence | Pages, spaces, search | CONFLUENCE_API_TOKEN, CONFLUENCE_EMAIL, CONFLUENCE_URL |
| Render | Deployments, services, logs | RENDER_API_KEY |
| Netlify | Sites, deploys, DNS | NETLIFY_AUTH_TOKEN |
Each user's enabled integrations are stored in the GitHub-backed store. The MCP client is cached per userId:integrations key and invalidated on Settings change.
Each MCP server exposes dozens of tools automatically to the LLM β for example, enabling GitHub gives the agent create_issue, search_repos, create_pull_request, etc. without writing any code.
Schedule email notifications via POST /api/notifications/:userId:
{
"recipient_email": "you@example.com",
"subject": "Daily standup reminder",
"body": "Time for standup!",
"send_at": "2026-04-17T09:00:00Z",
"recurrence_interval_minutes": 1440,
"max_runs": 30
}- One-off: set
send_at, omitrecurrence_interval_minutes - Recurring: add
recurrence_interval_minutes(minutes between runs) + optionalmax_runs - Worker reconciles pending notifications on startup (survives restarts)
- Powered by Resend
- JWT authentication on all
/apiroutes (configurable expiry viaJWT_EXPIRES_IN)- Login:
POST /api/auth/loginwith{ "password": "ADMIN_PASSWORD" } - Returns:
{ "message": "Login successful", "user": { "id": 1, "email": "admin" }, "token": "jwt_token_here" } - Use token in header:
Authorization: Bearer <token> - Protected routes:
/api/chat,/api/settings,/api/notifications
- Login:
- Helmet β secure HTTP headers out of the box
- Rate limiting β 300 req/15min per IP by default (
RATE_LIMIT_MAX) - Strict CORS β only origins listed in
CORS_ORIGINare allowed - Path traversal protection β all file tools are restricted to
NUDGEBOT_WORKDIR - Per-user MCP isolation β each user's integrations run in their own cached client
The Settings page lets each user configure:
- LLM provider + model + API key β overrides server env vars per user
- MCP integrations β toggle each service on/off with instant cache invalidation
- Memory β view stats (decisions, actions, topics, message count), delete individual sections, force-save to GitHub, or clear everything
Create backend/.env:
# Server
PORT=3000
CORS_ORIGIN=https://your-domain.com
RATE_LIMIT_MAX=300
# Auth
ADMIN_PASSWORD=your-admin-password
JWT_SECRET=your-long-random-secret
JWT_EXPIRES_IN=12h
# To generate a secure JWT_SECRET, run:
# node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# This creates a 128-character hex string suitable for production use
# LLM (user can override in Settings)
LLM_PROVIDER=deepseek
LLM_MODEL=deepseek-chat
DEEPSEEK_API_KEY=
DEEPSEEK_BASE_URL=https://api.deepseek.com
OPENROUTER_API_KEY=
OPENAI_API_KEY=
# Persistence
NudgeBot uses a **zero-dependency GitHub-backed store**. No SQLite or PostgreSQL is required. All users, settings, and notifications are synced to a private JSON file in your GitHub account.
# Agent workspace
NUDGEBOT_WORKDIR=./workspace
# Google Jules
JULES_API_KEY=
# Email notifications
RESEND_API_KEY=
RESEND_FROM_EMAIL=notifications@your-domain.com
# GitHub (Master access)
GITHUB_TOKEN= # Master PAT (repo scope) - used for memory, settings, and MCP
GITHUB_REPO= # e.g. mylogin/nudgebot-context (auto-created if blank)
# MCP: GitHub (Optional fallback)
GITHUB_PERSONAL_ACCESS_TOKEN=
# MCP: Jira
JIRA_API_TOKEN=
JIRA_EMAIL=
JIRA_URL=
# MCP: Confluence
CONFLUENCE_API_TOKEN=
CONFLUENCE_EMAIL=
CONFLUENCE_URL=
# MCP: Google Calendar
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REFRESH_TOKEN=
GOOGLE_SERVICE_ACCOUNT_JSON= # optional, stringified JSON
# MCP: Render
RENDER_API_KEY=
# MCP: Netlify
NETLIFY_AUTH_TOKEN=NudgeBot/
βββ frontend/ # React + Vite
β βββ src/pages/ # Home (chat), Settings
βββ backend/
βββ src/
βββ lib/
β βββ agent/
β β βββ graph.ts # LangGraph StateGraph
β β βββ tools.ts # 16 built-in tools (files, shell, web, email, notes, Julesβ¦)
β β βββ mcp.ts # On-demand MCP, per-user cache
β βββ githubContextManager.ts # GitHub persistence (read/write/auto-create)
β βββ renderSessionManager.ts # In-memory sessions + hourly auto-save
β βββ notifications.ts # Resend worker + recurrence logic
β βββ githubStore.ts # In-memory store synced to GitHub (replaces SQLite)
β βββ db.ts # Re-export from githubStore
βββ routes/
β βββ auth.ts # POST /api/auth/login|register
β βββ chat.ts # POST /api/chat (SSE stream)
β βββ settings.ts # GET/POST /api/settings
β βββ notifications.ts # CRUD /api/notifications
β βββ memory.ts # GET/DELETE /api/memory/:userId/...
βββ middleware/
β βββ auth.ts # requireAuth (JWT)
βββ server.ts
Browser β POST /api/chat (JWT required)
β Load user settings from GitHubStore (LLM config + enabled integrations)
β Load compressed context from GitHub (previous sessions)
β getAgent(provider, model, apiKey, integrations, userId, previousContext)
β setupMCP(integrations, userId) β lazy, per-user, cached
β Merge 16 built-in tools + MCP tools
β LangGraph: LLM β tools β LLM β β¦ β final answer
β SSE: { type: "thinking" } β { type: "delta", content } β { type: "done" }
npm run build
npm startServes frontend static assets + API from a single Express process on PORT.
The repository includes a render.yaml configuration file. Connect your GitHub repo to Render and it will automatically detect and use this configuration.
Manual setup steps:
- Push your code to GitHub
- Go to Render dashboard β New + β Web Service
- Connect your GitHub repository
- Render will detect
render.yamland pre-fill the configuration - Set the sensitive environment variables (marked
sync: falsein render.yaml) - Deploy
If you prefer manual configuration, follow these steps:
- A Render account (free tier available)
- GitHub repository connected to Render
Set these in your Render dashboard:
# Server
PORT=3000
CORS_ORIGIN=https://your-app.onrender.com
RATE_LIMIT_MAX=300
# Auth
ADMIN_PASSWORD=your-admin-password
JWT_SECRET=your-long-random-secret
JWT_EXPIRES_IN=12h
# LLM
LLM_PROVIDER=deepseek
LLM_MODEL=deepseek-chat
DEEPSEEK_API_KEY=your-deepseek-api-key
DEEPSEEK_BASE_URL=https://api.deepseek.com
# GitHub (Master access)
GITHUB_TOKEN=your-github-pat
GITHUB_REPO=your-username/nudgebot-context
# MCP: GitHub (Optional fallback)
GITHUB_PERSONAL_ACCESS_TOKEN=
# MCP integrations (optional)
JIRA_API_TOKEN=
JIRA_EMAIL=
JIRA_URL=
CONFLUENCE_API_TOKEN=
CONFLUENCE_EMAIL=
CONFLUENCE_URL=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REFRESH_TOKEN=
RENDER_API_KEY=
NETLIFY_AUTH_TOKEN=cd frontend && npm install && npm run build && cd ../backend && npm install && npm run buildcd backend && npm start- Render uses ephemeral file systems β use
/datafor persistent storage - GitHub context persistence is recommended for cross-session memory
- Set
GITHUB_CONTEXT_REPOto avoid auto-creation on every deploy - Free tier has spin-down after 15min inactivity β first request may be slow
