migrace /remind na sqlite
This commit is contained in:
@@ -1,71 +1,92 @@
|
||||
---
|
||||
name: remind
|
||||
description: >-
|
||||
Create recurring reminders for tasks. Use when the user wants to set up a
|
||||
reminder for something they need to do regularly, or when they mention tasks
|
||||
they keep forgetting. Also handles listing and removing reminders. Triggers on
|
||||
words like "remind", "reminder".
|
||||
---
|
||||
# /remind
|
||||
|
||||
# Remind
|
||||
Create, list, edit, enable, disable, and remove recurring or one-time reminders.
|
||||
|
||||
Create, list, and manage recurring reminders for tasks.
|
||||
## How it works
|
||||
|
||||
## CRUD Script
|
||||
- **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`.
|
||||
|
||||
All mutations to `reminder.yaml` go through `scripts/remind_edit.py` (paths in this skill are relative to the skill directory).
|
||||
## Commands
|
||||
|
||||
Run via: `uv run scripts/remind_edit.py <subcommand>`
|
||||
### Create a reminder
|
||||
|
||||
Subcommands:
|
||||
```
|
||||
/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
|
||||
```
|
||||
|
||||
- **`list`** — prints JSON `{"reminders": [...]}`.
|
||||
- **`add --text "..." --cron "EXPR" [--cron "EXPR"]`** — add recurring reminder; validates cron syntax.
|
||||
- **`add --text "..." --at "ISO_DATETIME" [--at "ISO_DATETIME"]`** — add one-time reminder(s); `--at` is repeatable.
|
||||
- **`add --text "..." --at "ISO" --cron "EXPR"`** — combine one-time and recurring times in one entry.
|
||||
- **`add --text "..." --random-times-per-day N --random-window "HH:MM-HH:MM" [--random-days "1-5"] [--random-from "YYYY-MM-DD"] [--random-until "YYYY-MM-DD"]`** — random but deterministic times: fires `N` times per day at random moments inside the window. Use when the user wants something a few times a day without a fixed clock time (e.g. "remind me to drink water a few times during the day"). `--random-days` is a cron day-of-week filter; `--random-from` / `--random-until` bound the active period. Minimum gap between fires is a fixed constant in `scripts/random_times.py`. Combinable with `--at` / `--cron`.
|
||||
- **`remove --keyword "..."`** — removes by case-insensitive substring match. Returns error JSON if 0 or >1 matches.
|
||||
The LLM parses natural language and calls `remind_edit.py add` with the appropriate flags:
|
||||
|
||||
All outputs are JSON. Errors go to stderr with non-zero exit code.
|
||||
- `--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]`
|
||||
|
||||
## Create Workflow
|
||||
### List reminders
|
||||
|
||||
1. **Identify the task** — What does the user want to be reminded about? If unclear, ask.
|
||||
2. **Check for duplicates** — Run `remind_edit.py list` and compare existing reminder texts against the new one. If a similar reminder already exists:
|
||||
- Show the user the existing reminder
|
||||
- Ask whether they really want a duplicate, or want to modify the existing one
|
||||
- Only proceed if the user explicitly confirms
|
||||
3. **Determine frequency** — Ask how often the reminder should fire. Suggest common options:
|
||||
- Every N minutes/hours/days
|
||||
- Specific time of day (e.g. "every weekday at 9am")
|
||||
- Specific day of week/month
|
||||
- One-time at a specific datetime
|
||||
- A few times a day at random moments (use the `--random-*` flags)
|
||||
4. **Create the cron expression(s) or `at` field** — Map user input to cron syntax for recurring reminders, or ISO datetime for one-time reminders.
|
||||
5. **Add via script** — Run a single `add` call combining all times (see CRUD Script for the exact flags). **Never call `add` multiple times for the same task** — put all times into one call.
|
||||
6. **Confirm** — Show the user what was created (text, schedule).
|
||||
```
|
||||
/remind list
|
||||
```
|
||||
|
||||
## List Workflow
|
||||
Calls `remind_edit.py list` → JSON with all active reminders and their schedules.
|
||||
|
||||
1. Run `uv run remind_edit.py list` and parse the JSON output.
|
||||
2. Present all reminders in a table with columns: number, task, schedule.
|
||||
3. Convert each schedule to human-readable text **in the user's language** (e.g. "every day at 9:00", "every Tuesday at 9:00"). For a `random` block, describe it like "5× a day at random between 9:00–21:00, Mon–Fri" (include `days`/`from`/`until` only if present).
|
||||
4. If `reminders` is empty, say so.
|
||||
### Edit a reminder
|
||||
|
||||
## Remove / Done Workflow
|
||||
```
|
||||
/remind edit keyword --text "new text"
|
||||
/remind edit keyword --replace-schedules --cron "0 10 * * *"
|
||||
```
|
||||
|
||||
1. Run `uv run remind_edit.py remove --keyword "..."`.
|
||||
2. If exit code is non-zero, read the error JSON:
|
||||
- `"no match"` → tell the user no reminder matches the keyword.
|
||||
- `"ambiguous"` → show the matches and ask the user to be more specific.
|
||||
3. If success, confirm what was removed.
|
||||
Calls `remind_edit.py edit --keyword <keyword>`. Keyword is matched case-insensitively against reminder text. Ambiguous matches are rejected.
|
||||
|
||||
## Rules
|
||||
### Enable / Disable
|
||||
|
||||
- **Respond to the user in their own language** (e.g. Czech) — this skill is written in English, but user-facing messages adapt to the user's language.
|
||||
- **Never edit `reminder.yaml` directly** — no `edit_file`, `write_file`, or any direct write. All mutations go exclusively through `scripts/remind_edit.py`.
|
||||
- **Read via the `list` subcommand** — never read the YAML file directly; always `remind_edit.py list`.
|
||||
- Always confirm the reminder text and frequency with the user before creating.
|
||||
- When listing, always show a human-readable schedule.
|
||||
- Completed or removed reminders are deleted from `reminder.yaml` entirely — no `done` field, no `status` field.
|
||||
- Timezone is always `Europe/Prague` unless the user explicitly requests otherwise.
|
||||
```
|
||||
/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.
|
||||
|
||||
Reference in New Issue
Block a user