updated readme
This commit is contained in:
374
README.md
374
README.md
@@ -1,16 +1,47 @@
|
||||
# LLM Bot Framework
|
||||
# Synculous
|
||||
|
||||
A template for building Discord bots powered by LLMs with natural language command parsing.
|
||||
A routine and medication management app designed as a prosthetic for executive function. Built for people with ADHD.
|
||||
|
||||
## Features
|
||||
The app externalizes the things ADHD impairs — time awareness, sequence memory, task initiation, and emotional regulation around failure — into a guided, sequential interface with immediate feedback and zero shame.
|
||||
|
||||
- **AI-Powered Parsing**: Uses LLMs to parse natural language into structured JSON with automatic retry and validation
|
||||
- **Module Registry**: Easily register domain-specific command handlers
|
||||
- **Flask API**: REST API with JWT authentication
|
||||
- **PostgreSQL**: Generic CRUD layer for any table
|
||||
- **Discord Bot**: Session management, login flow, background tasks
|
||||
- **Notifications**: Discord webhook + ntfy support out of the box
|
||||
- **Docker Ready**: Full docker-compose setup
|
||||
## Architecture
|
||||
|
||||
```
|
||||
synculous/
|
||||
├── synculous-client/ # Next.js 16 frontend (React, Tailwind)
|
||||
├── api/ # Flask REST API
|
||||
│ ├── main.py # App entry point, auth routes
|
||||
│ └── routes/ # Domain route modules
|
||||
│ ├── routines.py # Routines CRUD + sessions
|
||||
│ ├── routine_sessions_extended.py # Pause, resume, abort, notes
|
||||
│ ├── routine_stats.py # Completion stats, streaks, weekly summary
|
||||
│ ├── routine_templates.py # Premade routine templates
|
||||
│ ├── routine_steps_extended.py # Step instructions, types, media
|
||||
│ ├── routine_tags.py # Tagging system
|
||||
│ ├── medications.py # Medication scheduling + adherence
|
||||
│ ├── preferences.py # User settings + timezone
|
||||
│ ├── notifications.py # Web push subscriptions
|
||||
│ ├── rewards.py # Variable reward system
|
||||
│ └── victories.py # Achievement detection
|
||||
├── core/ # Shared business logic
|
||||
│ ├── postgres.py # Generic PostgreSQL CRUD
|
||||
│ ├── auth.py # JWT + bcrypt authentication
|
||||
│ ├── users.py # User management
|
||||
│ ├── routines.py # Routine/session/streak logic
|
||||
│ ├── tz.py # Timezone-aware date/time helpers
|
||||
│ └── notifications.py # Multi-channel notifications
|
||||
├── scheduler/
|
||||
│ └── daemon.py # Background polling for reminders
|
||||
├── bot/ # Discord bot (optional)
|
||||
├── ai/ # LLM parser for natural language commands
|
||||
├── config/
|
||||
│ ├── schema.sql # Database schema
|
||||
│ ├── seed_templates.sql # 12 premade ADHD-designed routine templates
|
||||
│ ├── seed_rewards.sql # Variable reward pool
|
||||
│ └── .env.example # Environment template
|
||||
├── docker-compose.yml
|
||||
└── Dockerfile
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -25,226 +56,171 @@ nano config/.env
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
This starts five services:
|
||||
|
||||
```
|
||||
llm-bot-framework/
|
||||
├── api/
|
||||
│ ├── main.py # Flask app with auth routes
|
||||
│ └── routes/
|
||||
│ └── example.py # Example route module
|
||||
├── bot/
|
||||
│ ├── bot.py # Discord client
|
||||
│ ├── command_registry.py # Module registration
|
||||
│ └── commands/
|
||||
│ └── example.py # Example command module
|
||||
├── core/
|
||||
│ ├── postgres.py # Generic PostgreSQL CRUD
|
||||
│ ├── auth.py # JWT + bcrypt
|
||||
│ ├── users.py # User management
|
||||
│ └── notifications.py # Multi-channel notifications
|
||||
├── ai/
|
||||
│ ├── parser.py # LLM JSON parser
|
||||
│ └── ai_config.json # Model + prompts config
|
||||
├── scheduler/
|
||||
│ └── daemon.py # Background polling
|
||||
├── config/
|
||||
│ ├── schema.sql # Database schema
|
||||
│ └── .env.example # Environment template
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
```
|
||||
| Service | Port | Description |
|
||||
|---------|------|-------------|
|
||||
| `db` | 5432 | PostgreSQL 16 with schema + seed data |
|
||||
| `app` | 8080 | Flask API |
|
||||
| `scheduler` | — | Background daemon for medication/routine reminders |
|
||||
| `bot` | — | Discord bot (optional, needs `DISCORD_BOT_TOKEN`) |
|
||||
| `client` | 3000 | Next.js frontend |
|
||||
|
||||
## Creating a Domain Module
|
||||
## Features
|
||||
|
||||
### 1. Add Database Schema
|
||||
### Routines
|
||||
- Create routines with ordered steps (4-7 steps recommended)
|
||||
- Run sessions with a guided, one-step-at-a-time focus interface
|
||||
- Complete, skip, pause, resume, or cancel sessions
|
||||
- Swipe gestures for step completion on mobile
|
||||
- Per-step timing with visual countdown
|
||||
- Animated celebration screen on completion with streak stats and variable rewards
|
||||
|
||||
Edit `config/schema.sql`:
|
||||
### Premade Templates
|
||||
12 ADHD-designed templates ship out of the box, seeded from `config/seed_templates.sql`:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id UUID PRIMARY KEY,
|
||||
user_uuid UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
completed BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
| Template | What it's for |
|
||||
|----------|---------------|
|
||||
| Morning Launch | Getting from bed to ready. First step: just sit up. |
|
||||
| Leaving the House | The keys-wallet-phone checklist. |
|
||||
| Focus Sprint | One focused work block with environment setup first. |
|
||||
| Wind Down | Screen-first sleep transition. |
|
||||
| Quick Tidy | Fast sweep, not deep cleaning. Completable on bad days. |
|
||||
| Body Reset | Minimum viable hygiene. Zero judgment. |
|
||||
| Unstuck | For executive function paralysis. Pure two-minute-rule. |
|
||||
| Evening Reset | Set tomorrow up to be easier. |
|
||||
| Move Your Body | Not a workout. Just movement. Starts with shoes. |
|
||||
| Sunday Reset | Weekly prep so Monday doesn't ambush you. |
|
||||
| Cook a Meal | One meal, start to finish, low activation energy. |
|
||||
| Errand Run | Externalized planning + sequencing for errands. |
|
||||
|
||||
### 2. Create API Routes
|
||||
All templates follow the design framework: two-minute-rule entry points, concrete instructions, zero-shame language, 4-6 steps max.
|
||||
|
||||
Create `api/routes/tasks.py`:
|
||||
### Medications
|
||||
- Scheduling: daily, twice daily, specific days, every N days, as-needed (PRN)
|
||||
- "Today's meds" view with cross-midnight lookahead (late night + early morning)
|
||||
- Take, skip, snooze actions with logging
|
||||
- Adherence tracking and statistics
|
||||
- Refill tracking with low-quantity alerts
|
||||
- Background reminders via the scheduler daemon
|
||||
|
||||
```python
|
||||
import flask
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
### Streaks and Stats
|
||||
- Per-routine streak tracking (current + longest)
|
||||
- Weekly summary across all routines
|
||||
- Completion rate and average duration stats
|
||||
- Victory detection (comebacks, weekend completions, variety, consistency)
|
||||
- Milestone celebrations at 3, 7, 14, 21, 30, 60, 90, 100, 365 days
|
||||
|
||||
import core.auth as auth
|
||||
import core.postgres as postgres
|
||||
import uuid
|
||||
### Rewards
|
||||
- Variable reward pool seeded from `config/seed_rewards.sql`
|
||||
- Random reward on routine completion (post-completion only, never mid-routine)
|
||||
- Reward history tracking per user
|
||||
- Common and rare rarity tiers
|
||||
|
||||
def register(app):
|
||||
@app.route('/api/tasks', methods=['GET'])
|
||||
def api_listTasks():
|
||||
header = flask.request.headers.get('Authorization', '')
|
||||
if not header.startswith('Bearer '):
|
||||
return flask.jsonify({'error': 'missing token'}), 401
|
||||
token = header[7:]
|
||||
### Notifications
|
||||
- Web push notifications via VAPID
|
||||
- Discord webhooks
|
||||
- ntfy support
|
||||
- Scheduled reminders for medications and routines
|
||||
|
||||
# Get user UUID from token
|
||||
from core.auth import decodeJwtPayload
|
||||
import json, base64
|
||||
payload = token.split('.')[1]
|
||||
payload += '=' * (4 - len(payload) % 4)
|
||||
decoded = json.loads(base64.urlsafe_b64decode(payload))
|
||||
user_uuid = decoded['sub']
|
||||
### Timezone Support
|
||||
All date/time operations respect the user's local timezone:
|
||||
- The frontend sends `X-Timezone-Offset` with every API request
|
||||
- The timezone offset is also persisted to `user_preferences` for background jobs
|
||||
- Streaks, "today's meds," weekly stats, and reminders all use the user's local date
|
||||
- The scheduler daemon looks up each user's stored offset for reminder timing
|
||||
|
||||
tasks = postgres.select("tasks", {"user_uuid": user_uuid})
|
||||
return flask.jsonify(tasks), 200
|
||||
### User Preferences
|
||||
- Sound effects (default off — habituation risk)
|
||||
- Haptic feedback (default on)
|
||||
- Launch screen toggle
|
||||
- Celebration style
|
||||
- Timezone offset (auto-synced from browser)
|
||||
|
||||
@app.route('/api/tasks', methods=['POST'])
|
||||
def api_addTask():
|
||||
header = flask.request.headers.get('Authorization', '')
|
||||
if not header.startswith('Bearer '):
|
||||
return flask.jsonify({'error': 'missing token'}), 401
|
||||
token = header[7:]
|
||||
## API Overview
|
||||
|
||||
data = flask.request.get_json()
|
||||
task = postgres.insert("tasks", {
|
||||
'id': str(uuid.uuid4()),
|
||||
'user_uuid': data['user_uuid'],
|
||||
'name': data['name'],
|
||||
})
|
||||
return flask.jsonify(task), 201
|
||||
```
|
||||
All endpoints require `Authorization: Bearer <token>` except `/api/register` and `/api/login`.
|
||||
|
||||
Register it in `api/main.py`:
|
||||
### Auth
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/api/register` | Create account |
|
||||
| POST | `/api/login` | Get JWT token |
|
||||
|
||||
```python
|
||||
import api.routes.tasks as tasks_routes
|
||||
register_routes(tasks_routes)
|
||||
```
|
||||
### Routines
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/routines` | List user's routines |
|
||||
| POST | `/api/routines` | Create a routine |
|
||||
| GET | `/api/routines/:id` | Get routine with steps |
|
||||
| PUT | `/api/routines/:id` | Update routine |
|
||||
| DELETE | `/api/routines/:id` | Delete routine |
|
||||
| GET | `/api/routines/:id/steps` | List steps |
|
||||
| POST | `/api/routines/:id/steps` | Add a step |
|
||||
| PUT | `/api/routines/:id/steps/reorder` | Reorder steps |
|
||||
| POST | `/api/routines/:id/start` | Start a session |
|
||||
|
||||
### 3. Create Bot Commands
|
||||
### Sessions
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/sessions/active` | Get active or paused session |
|
||||
| POST | `/api/sessions/:id/complete-step` | Complete current step |
|
||||
| POST | `/api/sessions/:id/skip-step` | Skip current step |
|
||||
| POST | `/api/sessions/:id/pause` | Pause session |
|
||||
| POST | `/api/sessions/:id/resume` | Resume session |
|
||||
| POST | `/api/sessions/:id/cancel` | Cancel session |
|
||||
| POST | `/api/sessions/:id/abort` | Abort with reason |
|
||||
|
||||
Create `bot/commands/tasks.py`:
|
||||
### Medications
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/medications` | List medications |
|
||||
| POST | `/api/medications` | Add medication |
|
||||
| GET | `/api/medications/today` | Today's schedule with status |
|
||||
| POST | `/api/medications/:id/take` | Log dose taken |
|
||||
| POST | `/api/medications/:id/skip` | Log dose skipped |
|
||||
| GET | `/api/medications/adherence` | Adherence stats |
|
||||
| GET | `/api/medications/refills-due` | Refills due soon |
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
### Stats
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/routines/:id/stats` | Routine completion stats |
|
||||
| GET | `/api/routines/:id/streak` | Routine streak |
|
||||
| GET | `/api/routines/streaks` | All streaks |
|
||||
| GET | `/api/routines/weekly-summary` | Weekly progress |
|
||||
| GET | `/api/victories` | Achievement detection |
|
||||
|
||||
from bot.command_registry import register_module
|
||||
import ai.parser as ai_parser
|
||||
|
||||
async def handle_task(message, session, parsed):
|
||||
action = parsed.get('action')
|
||||
token = session['token']
|
||||
user_uuid = session['user_uuid']
|
||||
|
||||
# Make API calls using the bot's apiRequest helper
|
||||
from bot.bot import apiRequest
|
||||
|
||||
if action == 'list':
|
||||
result, status = apiRequest('get', f'/api/tasks', token)
|
||||
if status == 200:
|
||||
lines = [f"- {t['name']}" for t in result]
|
||||
await message.channel.send("Your tasks:\n" + "\n".join(lines))
|
||||
else:
|
||||
await message.channel.send("Failed to fetch tasks.")
|
||||
|
||||
elif action == 'add':
|
||||
task_name = parsed.get('task_name')
|
||||
result, status = apiRequest('post', '/api/tasks', token, {
|
||||
'user_uuid': user_uuid,
|
||||
'name': task_name
|
||||
})
|
||||
if status == 201:
|
||||
await message.channel.send(f"Added task: **{task_name}**")
|
||||
else:
|
||||
await message.channel.send("Failed to add task.")
|
||||
|
||||
def validate_task_json(data):
|
||||
errors = []
|
||||
if 'action' not in data:
|
||||
errors.append('Missing required field: action')
|
||||
if data.get('action') == 'add' and 'task_name' not in data:
|
||||
errors.append('Missing required field for add: task_name')
|
||||
return errors
|
||||
|
||||
register_module('task', handle_task)
|
||||
ai_parser.register_validator('task', validate_task_json)
|
||||
```
|
||||
|
||||
### 4. Add AI Prompts
|
||||
|
||||
Edit `ai/ai_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompts": {
|
||||
"command_parser": {
|
||||
"system": "You are a helpful assistant...",
|
||||
"user_template": "..."
|
||||
},
|
||||
"task_parser": {
|
||||
"system": "You parse task commands...",
|
||||
"user_template": "Parse: \"{user_input}\"\n\nReturn JSON with action (list/add/complete) and task_name."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## AI Parser Usage
|
||||
|
||||
```python
|
||||
import ai.parser as ai_parser
|
||||
|
||||
# Basic usage
|
||||
parsed = ai_parser.parse(user_input, 'command_parser')
|
||||
|
||||
# With conversation history
|
||||
history = [("previous message", {"action": "add", "item": "test"})]
|
||||
parsed = ai_parser.parse(user_input, 'command_parser', history=history)
|
||||
|
||||
# Register custom validator
|
||||
ai_parser.register_validator('task', validate_task_json)
|
||||
```
|
||||
|
||||
## Notification Channels
|
||||
|
||||
```python
|
||||
import core.notifications as notif
|
||||
|
||||
# Set user notification settings
|
||||
notif.setNotificationSettings(user_uuid, {
|
||||
'discord_webhook': 'https://discord.com/api/webhooks/...',
|
||||
'discord_enabled': True,
|
||||
'ntfy_topic': 'my-alerts',
|
||||
'ntfy_enabled': True
|
||||
})
|
||||
|
||||
# Send notification
|
||||
settings = notif.getNotificationSettings(user_uuid)
|
||||
notif._sendToEnabledChannels(settings, "Task due: Buy groceries")
|
||||
```
|
||||
### Templates, Tags, Rewards, Preferences
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/templates` | List available templates |
|
||||
| POST | `/api/templates/:id/clone` | Clone template to user's routines |
|
||||
| GET/PUT | `/api/preferences` | User settings |
|
||||
| GET | `/api/rewards/random` | Random completion reward |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `DISCORD_BOT_TOKEN` | Discord bot token |
|
||||
| `API_URL` | API URL (default: `http://app:5000`) |
|
||||
| `DB_HOST` | PostgreSQL host |
|
||||
| `DB_PORT` | PostgreSQL port |
|
||||
| `DB_NAME` | Database name |
|
||||
| `DB_USER` | Database user |
|
||||
| `DB_PASS` | Database password |
|
||||
| `JWT_SECRET` | JWT signing secret |
|
||||
| `OPENROUTER_API_KEY` | OpenRouter API key |
|
||||
| `OPENROUTER_BASE_URL` | OpenRouter base URL |
|
||||
| `AI_CONFIG_PATH` | Path to ai_config.json |
|
||||
| `DISCORD_BOT_TOKEN` | Discord bot token (optional) |
|
||||
| `API_URL` | API URL for bot (default: `http://app:5000`) |
|
||||
| `OPENROUTER_API_KEY` | OpenRouter API key (for AI parser) |
|
||||
| `POLL_INTERVAL` | Scheduler poll interval in seconds (default: 60) |
|
||||
|
||||
## Design Framework
|
||||
|
||||
Synculous follows a documented design framework based on research from 9 books on behavior design, cognitive psychology, and ADHD. The three core principles:
|
||||
|
||||
1. **Immediate Feedback** — Visual state change on tap in <0.1s. Per-step completion signals. Post-routine celebration.
|
||||
2. **One Thing at a Time** — Current step visually dominant. No decisions during execution. 4-7 steps max per routine.
|
||||
3. **Zero Shame** — No failure language. Streaks as identity markers, not performance metrics. Non-punitive everywhere.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user