# /remind Create, list, edit, enable, disable, and remove recurring or one-time reminders. ## How it works - **Storage**: SQLite (`db/reminders.sqlite`) — atomic transactions, no YAML races. - **Schema**: `reminders` (text, enabled, timezone, timestamps, soft-delete) + `schedule_at` / `schedule_cron` / `schedule_random` + `reminder_fires` (dedup + audit). - **Sender**: `remind_send.py` runs every minute from the user crontab. Reads SQLite, finds due fires, sends to Telegram, logs delivery. - **Deduplication**: Every delivery is recorded in `reminder_fires` with status `delivered`/`failed`. One-time `at` reminders fire exactly once; cron and random fire once per computed slot. - **Audit**: All mutations and deliveries are logged to `log/reminder.log`. ## Commands ### Create a reminder ``` /remind drink water every day at 9:00 /remind stand up every weekday at 9:30 /remind buy milk at 2026-06-15T18:00 /remind stretch randomly 2 times between 08:00 and 20:00 /remind občanka pana Přibyla once daily at random time between 8:00 and 21:00 ``` The LLM parses natural language and calls `remind_edit.py add` with the appropriate flags: - `--text "..."` - `--cron "0 9 * * *"` (repeatable) - `--at "2026-06-15T18:00:00"` (repeatable) - `--random-times-per-day N --random-window HH:MM-HH:MM [--random-days DOW] [--random-from YYYY-MM-DD] [--random-until YYYY-MM-DD]` ### List reminders ``` /remind list ``` Calls `remind_edit.py list` → JSON with all active reminders and their schedules. ### Edit a reminder ``` /remind edit keyword --text "new text" /remind edit keyword --replace-schedules --cron "0 10 * * *" ``` Calls `remind_edit.py edit --keyword `. Keyword is matched case-insensitively against reminder text. Ambiguous matches are rejected. ### Enable / Disable ``` /remind disable keyword /remind enable keyword ``` ### Remove a reminder ``` /remind remove keyword ``` Soft delete (sets `deleted_at`). Hard delete happens only via direct DB access. ## Files | File | Purpose | |------|---------| | `skills/remind/scripts/db.py` | Schema, connection factory (`get_db`), `init_db()`, audit `log_operation()` | | `skills/remind/scripts/remind_edit.py` | CRUD CLI: `list`, `add`, `remove`, `edit`, `enable`, `disable` | | `skills/remind/scripts/remind_send.py` | Sender: reads SQLite, finds due fires, sends Telegram, dedups | | `skills/remind/scripts/random_times.py` | Deterministic random time generator (seeded by text + date) | | `scripts/migrate_yaml_to_sqlite.py` | One-shot migration from old `reminder.yaml` to SQLite | | `skills/remind/tests/` | pytest suite: `test_db.py`, `test_remind_edit.py`, `test_remind_send.py`, `test_random_times.py` | ## Crontab ``` * * * * * uv run /home/nanobot/.nanobot/workspace/skills/remind/scripts/remind_send.py >> /home/nanobot/.nanobot/workspace/log/reminder_cron.log 2>&1 ``` ## Environment - `REMIND_DB` — override SQLite path (used in tests). - `python3` is required; `python` is not available in this runtime. ## Design decisions - **SQLite WAL mode** — readers don't block writers. - **Soft delete** — preserves history and foreign-key integrity. - **Deterministic random** — same text + date always yields same times, so dedup works across restarts. - **JSON output** — both edit and send scripts emit structured JSON for easy LLM parsing. - **No YAML** — eliminated race conditions, manual string construction, and fragile parsing.