Part one
Quickstart
The handful of moves that are 90% of Fanad. If you only read one part, read this one.
- ① A statement becomes a task on your list.
- ② A question runs a command.
- ③ Answering Fanad's own open question is taken as that answer.
That's the whole grammar — and the leading slash is always optional, so /mood 🙂 and mood 🙂 do the same thing.
Fanad is a command pad, not a chatbot. It reads each message and files or runs it — it doesn't reply to you in paragraphs. So jot one thing per line: paste a whole ChatGPT-style paragraph and the entire blob becomes a single task. New here? The web app plays a 20-second 🎬 How Fanad works demo on your first visit — replay it anytime from the 🎬 button at the top.
The Rules of Three
The rules live in chat too — ask for them any time.
| Command | What it does |
|---|---|
| /rules | Show the Rules of Fanad — the rules of three.
example/rules ✨ The Rules of Fanad ask of me these rules of three… ① Make a statement, and I’ll add it to my list. ② Ask a question, and I’ll see what I can do. ③ Answer my question, and so shall it be. 🌱 A “no” is never the end in Fanad — we’ll find something the right size, or nothing at all. 🙂 And show me how you feel, anytime, with an emoji. |
| /howto | The getting-started walkthrough on how to fill your Fanad.
examplehowto ✨ How to fill your Fanad Use Fanad like an extra memory, or a little notebook. Just wander — through your home, your day, your head — and jot down anything you’d like to change, accomplish, experience, clean up, or tear down. Don’t sort it; that’s my job. … |
Just talk to capture
Say what's on your mind in your own words and Fanad files it as a task, quietly picking a category and effort. You unload; Fanad organizes.
| Do this | What happens |
|---|---|
| Capture a task by talking | Make a plain statement and Fanad files it as a task in your own words.
examplethe gutters need clearing ✓ Filed: “the gutters need clearing” · Home · medium |
| Send a photo | A photo with a caption files a task with the photo attached.
example(a photo, captioned) fix the fence gate ✓ Filed: “fix the fence gate” · Home · medium |
| Paste a link | Paste a URL and Fanad fetches the page’s title once, names the task after it, and makes the task title a clickable link. Add your own words and they stay the title — still linked.
examplehttps://example.com/how-to-descale-a-kettle ✓ Filed: “How to descale a kettle” · Home · trivial |
Organizing is Fanad's job, not yours. Fanad keeps your own words as the task title and silently picks the category and effort — you never have to file, tag, or tidy anything by hand.
To flesh out a pasted link, your own server fetches that one page directly, once,
when you file it — just its title and description, nothing tracked or stored elsewhere. It only
follows public http(s) addresses (never your local network), and if a page can’t be
reached the task still files exactly as you typed it. An admin can turn the whole thing off with
LINK_PREVIEW=off.
See and clear your list
Your open tasks, always numbered 1..N for whatever's on screen. Finish what's done, clear what isn't yours anymore.
| Command | What it does |
|---|---|
| /tasks | Show your open tasks — grouped by category, or counts to drill into when there are many.
example/tasks 🌊 You’ve got 23 open tasks — too many to list at once. Tap a kind or difficulty below to see its top 7 — or grab 📅 Today / 📋 All. Home (8)Admin (6)📅 Today📋 All
|
| /start | Start task N, showing your original words plus its steps.
example/start 3 ▶ Started: “bake sourdough”. |
| /view | Open task N’s full detail — your original words, the fuller read, and its steps — with its action buttons, all without starting it. Tap a row’s 👁 /view_N link, or type /view 3 (/details works too). From there 🪜 Steps edits the checklist without starting.
example/view 3 👁 “renew the passport” · Admin · medium · ⏳ due friday 📄 renew the passport before the italy trip Steps (0/2): ☐ 1. dig out the old one ☐ 2. book a photo booth |
| unstart | Put the task you started back — not started, not finished, nothing lost. Bare unstart means the one in progress; unstart 2 targets row N. There’s a ⏸ Unstart button on the started card too.
exampleunstart ⏸ Put “bake sourdough” back on your list — not started, not lost. |
| undo | Take back the last thing Fanad did — a capture you didn’t mean (the task vanishes as if never filed), a done/drop/snooze/start, a logged portion or metric, a fresh timer, a list item. It works everywhere, remembers the last ~20 undoable things from the past day (newest first — say it again to keep going), and tells you exactly what it took back. When there’s nothing it can take back, it says so.
exampledone 3 ✓ Done: “bake sourdough”. undo ↩ Not done after all — “bake sourdough” is back on your list. |
| /done | Finish task N (or a few: done 1 2 3), or a bare done finishes what you just started.
example/done 1 2 ✓ Done: “call the pharmacy”, “water the plants”. |
| /drop | Clear task N off your list, archiving it as dropped.
example/drop 4 Removed “relist the old couch” from your list. |
Positions restart at 1 on every listing and every page. done 3 always means the third row you last saw — never a hidden database id.
One thing to do now
When the list feels like a lot, ask for a single next step, sized to your time, energy, and mood — never a wall.
| Command | What it does |
|---|---|
| /whatdo | Fanad suggests one thing to do right now, sized to you.
example/whatdo 💡 How about “clear the gutters”? — you usually finish home things by afternoon (“yes” to start · “done” if it’s finished · “no” · “smaller”) yesdonenosmaller
|
| yes / no / smaller / done / not today | Steer it: yes to start, done if finished, smaller for lighter, no/not today to pass.
exampleno Okay, that’s fine. Want something smaller, or done for now? smaller 💡 Sure — here’s a smaller one: How about “water the plants”? (“yes” to start · “done” if it’s finished · “no” · “smaller”) |
Refusing a suggestion never fails. Fanad simply offers something smaller, or nothing at all — and after a few passes it gently offers to reword or break the task down rather than push.
Tell Fanad how you feel
Drop a mood anytime; it gently softens and sizes what Fanad suggests for a while.
| Command | What it does |
|---|---|
| /mood | Set how you feel with an emoji (mood 🙂) or a word (mood overwhelmed); it steers suggestion size.
examplemood 😴 Mood set: 😴 |
Where to get help
Friendly, tappable references whenever you need them. Prefer a story to a reference? The how-to articles walk one real use case each, start to finish.
| Command | What it does |
|---|---|
| /guide | Open the topic-guide hub, or guide <topic> for a deep dive on one feature.
exampleguide ✨ The Fanad guide — tap a topic and I’ll keep it short. 📝 Capturing💡 What to do🪜 Steps🗓 Reminders
|
| /commands | The tappable command reference, split into sections that expand in place.
example/commands Everything I can do — tap a section: ✨ ▶ Tasks➕ Capture & steps⏰ Reminders & calendar❔ Help
|
| /manual (h) | Ask this manual a free-form question — /manual <question> or just h <question> — and the answer comes strictly from what’s written here, kept short. It won’t answer anything beyond the manual.
exampleh how do I set a reminder? End the task with a time — “…on friday 3pm” sets a deadline plus a one-time reminder. See “Deadlines, reminders & calendar”. |
| c (/menu) | Pop the tappable command menu; works mid-question to back out.
examplec Everything I can do — tap a section: ✨ ▶ Tasks➕ Capture & steps🧩 Modules❔ Help
|
Part two
Core
The everyday depth: filing precisely, breaking tasks into steps, jotting notes, dates, and letting stale tasks rest.
Filing tasks your way
Capture happens by just talking, but you can be explicit when you want a category, a due date, or a quick "today".
| Command | What it does |
|---|---|
| /task (/task:category) | Explicitly file a task, optionally with a category, deadline, or inline priority.
example/task:health book a check-up ✓ Filed: “book a check-up” · Health · low |
| /today (x) | File a task due by the end of today (e.g. x call the pharmacy).
examplex call the pharmacy ✓ Filed: “call the pharmacy” · Errands · trivial · ⏳ due today |
| /tasks all | Show every open task as a ranked, paginated flat list.
example/tasks all 1. call the pharmacy · Errands · trivial · ⏳ due today ▶ /start_1 · ✓ /done_1 · 👁 /view_1 2. clear the gutters · Home · medium ▶ /start_2 · ✓ /done_2 · 👁 /view_2 3. renew the passport · Admin · medium · ⏳ due friday ▶ /start_3 · ✓ /done_3 · 👁 /view_3 · 📅 /cal_3 |
| /tasks today | Show only the tasks due by the end of today.
example/tasks today Nothing due today. 🌱 (try /tasks for everything) |
| /whatdo today | The same one-thing suggestion, scoped to tasks due today.
example/whatdo today 💡 How about “call the pharmacy”? (“yes” to start · “done” if it’s finished · “no” · “smaller”) yesdonenosmaller
|
Steps: breaking a task down
Add steps under a task and Fanad walks you through them one at a time with tappable checkboxes.
| Command | What it does |
|---|---|
| step (s) / substep / subtask | Add a step under your last task, or under a listed task with step N …
examplestep feed the starter ✓ Step 1 added to “bake sourdough”: feed the starter |
| unstep / remove step | Remove step N (or unstep all); the list renumbers.
exampleunstep 2 🗑 Removed step 2. |
| start (stepping) | Starting a stepped task walks you through its steps one at a time.
examplestart ▶ Started: “bake sourdough”. ☐ 1. feed the starter ☐ 2. shape the loaf ☐ 3. bake at 230C |
| done / done N / done all | Tick off steps: done for the next, done 2 3 for a few, done all finishes the task.
exampledone ☑ 1. feed the starter ☐ 2. shape the loaf ☐ 3. bake at 230C done all ✅ All 3 steps done. ✓ Done: “bake sourdough”. |
| stop / pause | Leave a stepping session without finishing; steps stay saved.
examplestop Paused — 2 steps left on “bake sourdough”. Steps stay saved. |
Notes: a place for the rest
Jot things you want to recall later, kept separate from your task list. Find them by meaning, not exact words. (Opt-in.)
| Command | What it does |
|---|---|
| /note (n) | Jot something to recall later, kept separate from tasks.
examplenote the spare key is under the blue pot 📝 Noted. |
| /notes | Show your note inbox.
example/notes 📝 3 waiting: 1. the spare key is under the blue pot 2. wifi guest password is sunflower42 3. paint the hallway ceiling (“/promote 3” → task · “/forget 3” — or a few: “forget 1 2 3” — → delete) |
| /recall (r) | Find a note by meaning rather than exact words.
exampler where’s the spare key • the spare key is under the blue pot |
| /promote | Turn note N into a real task (carrying any attached photo over).
example/promote 3 ✓ Promoted to a task: “paint the hallway ceiling”. |
| /forget (/delete) | Delete note N from your inbox.
example/forget 2 🗑 Deleted that note. |
Deadlines, reminders & calendar
Dates gently lift a task's ranking. Reminders fire once. For anything recurring, hand it to your own calendar — Fanad won't nag on a schedule.
| Command | What it does |
|---|---|
| by <when> | End a task with by friday to set a due date that lifts ranking and retires after it passes.
examplerenew the passport by friday ✓ Filed: “renew the passport” · Admin · medium · ⏳ due friday |
| on <when> | on sunday 6pm sets both a deadline and a one-time reminder at that moment.
examplecall mom on sunday 6pm ✓ Filed: “call mom” · Social · trivial · 🔔 sunday 6pm |
| remind me … at <time> | A one-time nudge that pings once and leaves the task on your list.
exampleremind me to take the bins out at 8pm ✓ Filed: “take the bins out” · Home · trivial · 🔔 8pm …then, at 8:00 pm: 🔔 Reminder: “take the bins out” — it’s time. |
| /timer <how long> | Timer module (opt-in): a one-shot ding — timer 12 min pasta; bare timer lists, timer off 1 cancels. Nothing lands on your list.
exampletimer 12 min pasta ⏰ Timer set — 12 min — pasta · rings 6:42 pm. (“timer” shows it · “timer off” cancels) …12 minutes later: ⏰ Ding — 12 min is up: pasta. ✕ Cancel timer
|
| /cal | Add dated task N to your own calendar via a downloadable .ics file.
example/cal 3 📅 Add “renew the passport” to your calendar (hands you an .ics file to open — recur it there if you like) |
Recurrence is nagging, and nagging is stress, so Fanad has none. A dated task instead gives you an "add to calendar" .ics so you make it recur in your own calendar, on your terms. Reminders fire exactly once.
Knowing yourself
See what Fanad has quietly learned about your patterns.
| Command | What it does |
|---|---|
| /me (/dossier) | Show what Fanad has learned — completion rate, favored categories, usual mood.
example/me ✨ What I’ve learned about you • Finished 24 tasks — about 60% of what you added. • You lean into: Home (10, mostly by afternoon) · Admin (6) • Your usual mood: 🙂 • Set aside: 3 snoozed · 2 let go (I use this to suggest the right thing at the right time.) |
| /summary | A narrative recap of your activity, optionally scoped (today, this week, last week).
example/summary today Today you finished “call the pharmacy” and “water the plants”, added two admin tasks, and kept the evening clear. 🌱 |
Locking categories
Doing a batch on one theme? Pin a category or effort so Fanad stops guessing each one.
| Command | What it does |
|---|---|
| /lock | Pin a category and/or difficulty for the next tasks you add; a new word mints that category.
example/lock gardening 🆕 New category “gardening” — added for good. 🔒 Locked to gardening — new tasks skip the guessing. “/unlock” to clear. |
| /unlock | Clear the lock so Fanad sorts each task on its own again.
example/unlock 🔓 Unlocked — I’ll sort each task on its own again. |
Snoozing, sleeping & reviving
Set-aside tasks are never a black hole. Tasks you 😴 Snooze hide until their wake time (/snoozed shows them, /unsnooze 1 brings one back early), and tasks untouched for about three weeks quietly go to sleep so the list stays scannable. Nothing is lost — bring any of it back anytime.
| Command | What it does |
|---|---|
| /snoozed | Show the tasks you tucked away with 😴 Snooze, and when each one wakes on its own.
example/snoozed 😴 1 snoozed: 1. paint the shed · Home · medium · wakes tomorrow 16:36 (“/unsnooze 1” to bring one back now · a few: “/unsnooze 1 2 3”) |
| /unsnooze | Bring a snoozed task back before its timer (e.g. /unsnooze 1, or a few: /unsnooze 1 2 3). Bare /unsnooze shows the snoozed list.
example/unsnooze 1 ☀️ Unsnoozed 1 task — back on your list. |
| /sleeping | Show tasks that drifted off to sleep after ~3 weeks untouched.
example/sleeping 💤 2 sleeping (untouched for a while): 1. reorganize the garage 2. learn some spanish (“/revive 1” to bring one back · a few: “/revive 1 2 3”) |
| /revive | Bring a sleeping task back onto your list (e.g. /revive 1, or a few: /revive 1 2 3).
example/revive 1 ☀️ Revived 1 task — back on your list. |
Once a day, anything left untouched for about three weeks quietly goes to sleep — kept out of both your listings and your suggestions, so the pad never rots into a guilt archive. Revive any of it whenever you like.
Photos & shortcuts
Little conveniences: resend an attached photo, and lead a message with a single letter to skip the command.
| Command | What it does |
|---|---|
| /pic | Resend the photo attached to task N (rows with a photo show a tappable 📷 /pic_N link).
example/pic 3 📷 fix the fence gate (sends the photo back with that caption) |
| n t d k u s r g x j h w | Lead with one letter: n=note, t=task, d=done, k=drop, u=undo (bare), s=step, r=recall, g=guide, x=today, j=journal, h=manual, w=whatdo.
examplen the wifi password is sunflower42 📝 Noted. w 💡 How about “clear the gutters”? (“yes” to start · “done” if it’s finished · “no” · “smaller”) |
Turning modules on & off
A new account sees only Tasks. Turn on the extras you want, whenever you want; turning one off hides but never deletes your data.
| Command | What it does |
|---|---|
| modules | Show your optional modules and tap to turn each on or off.
examplemodules 🧩 Your modules — tap one to turn it on or off. Tasks are always on. ⚪ Notes — off🟢 Lists — on⚪ Metrics — off⚪ Diet — off⚪ Timer — off
|
| optin / optout <module> | Turn a module (notes/lists/metrics/diet/vouch/notebook/timer/journal/batches/ha) on or off; opt-out keeps your data.
exampleoptin lists ✓ Lists on. Make nestable lists with /lists and “/list <name>”. optout lists ✓ Lists hidden. Your lists are kept — “optin lists” brings them back. |
Notes, Lists, Metrics, Diet, Medication, Vouch, Notebooks, Timer, Journal, Batches, and Home Assistant are all off by default so a fresh account sees only Tasks. Turn on exactly what you want; opting a module back out only hides its data, it never deletes it.
Part three
Advanced
For when you want more control: nestable lists, templates instead of recurrence, metrics, notebooks, and a look under the hood at how suggestions are chosen.
Lists & nesting
A separate outliner for anything that isn't a task — checklists, packing lists, anything nestable. (Opt-in.)
| Command | What it does |
|---|---|
| /lists | Open your nestable lists; at the top, type a name or /list <name> to start one.
example/lists 📑 Your lists · 2 items 1. Groceries · /sub_1 2. Trip packing · /sub_2 (type a name to start a list · /sub_1 to open one · “exit” to leave) example/list Groceries 📑 Groceries This list is empty. Type an item to add it, or “out” to go back. 🌱 (type to add an item · /sub_1 to open one · “out” · “top” · “del 1” · “rename 1 …” · “exit”) |
| /list <name> | Create a new top-level list, or add an item when a list is open. |
| /sub_N | Descend into list item N as its own sub-list; /sub_N text quick-adds a child.
example/sub_1 📑 Groceries › Produce · 3 items 1. apples · /sub_1 2. bananas · /sub_2 3. spinach · /sub_3 (type to add an item · /sub_1 to open one · “out” · “top” · “del 1” · “rename 1 …” · “exit”) |
| out / top / next / prev / del / rename / exit | Move around inside lists: up a level, to all lists, page, delete, rename, or leave.
exampleexit Closed your lists. 🌱 (“/lists” opens them again) |
Notes recall, grounding & photos
Fanad finds your real notes by meaning — and only ever surfaces notes and tasks that actually exist. It never invents a row.
| Feature | What it does |
|---|---|
| Semantic note recall | Finds your real notes by embedding-cosine meaning plus keyword match — only actual stored notes. |
| Notes inbox + photos | A caption-less photo waits in your notes for recall; a captioned photo becomes a task. |
| Closed-world id allow-list | Rejects any suggested task that isn't in your real tasks, so Fanad can never surface an invented one. |
Retrieval decides what exists; the model may only order and rephrase a closed set of your own real rows. An id allow-list backstop rejects any invented task, so Fanad can't hand you homework you never wrote down.
Guessing steps
The one place Fanad draws on general know-how: once a task is started, it can guess a first-draft checklist — always labeled a guess, always yours to edit.
| Command | What it does |
|---|---|
| /guess | Once a task is started, Fanad guesses an editable step checklist from general know-how (add via step, remove via unstep).
example/guess 💡 A guess at the steps for “bake sourdough” (my own best guess, not from your notes — edit freely): ☐ 1. feed the starter ☐ 2. mix and rest the dough ☐ 3. shape the loaf ☐ 4. bake at 230C Tick off with “done” / “done 2” / “done all” · add “step …” · remove “unstep 2”. |
Everywhere else Fanad never invents. /guess is the single sanctioned place it draws on the model's general know-how — and the result is always surfaced as an explicit, disposable, fully-editable guess, never as fact.
Templates
The calm alternative to recurring tasks: save a task's shape and steps, then drop a fresh copy whenever you need it.
| Command | What it does |
|---|---|
| /template N <name> | Save listed task N as a reusable blueprint (shape + steps), never a deadline or priority.
example/template 3 sourdough 🗂 Saved template “sourdough” (4 steps). Start a fresh copy anytime: “/template sourdough”. |
| /template <name> | Drop a fresh copy of a saved template onto your list, reset to unchecked.
example/template sourdough 📋 Fresh copy on your list: ✓ Filed: “bake sourdough” · Home · medium (4 steps) — say “start” to walk through it. |
| /templates | List your saved templates.
example/templates 🗂 Your templates: • sourdough — “bake sourdough” (4 steps) (“/template <name>” → a fresh copy · “/template retire <name>” → remove) |
| /template retire <name> | Delete a saved template.
example/template retire sourdough Retired the “sourdough” template. |
Metrics
Track any number you like — water, pages, push-ups — and see a daily tally and a chart against optional targets. (Opt-in.)
| Command | What it does |
|---|---|
| track <name> <number> | Log a value that adds up over the day (water, push-ups). The metric is created on first use.
example/track water 3 Logged water: 3. • water: 3 |
| measure <name> <number> | Record a one-off reading (weight, blood pressure) — the tally shows the latest value instead of a daily sum. |
| metric add <name> [unit] [sum|avg|last|max|min] | Define a metric up front with a unit and how a day's readings combine — e.g. metric add weight kg last. |
| tally [name] | Today's numbers against their targets — every metric, or just the one you name. Metrics sitting at zero (or a measurement with no reading yet) are left out of the full list to keep it short; name one to see it regardless.
example/tally Today: • calories: 1450 / 2000 kcal • water: 5 • weight: 182 lbs |
| chart <name> [range] | A picture of the history, sent as an image: tracked metrics draw one bar per day, measured ones a line of readings. The default range is the last 30 days; add one word for another — 7d, 30d, 90d, ytd, today, yesterday, this_week, last_week, this_month.
example/chart water 7d 📈 water · the past 7 days (sends a chart image — one bar per day, with your target as a dashed line if you set one) |
| undo | Take back the last tracked entry — undo is app-wide, so it pops whatever Fanad did last (see Tasks). |
Diet & foods
Calorie logging the way it actually works: weigh the food, multiply by a per-ounce density — or skip the scale entirely and teach a food (or a whole meal) its typical serving. Each food’s number is confirmed once — after that it’s canonical and never re-guessed. One calories number, no macros. (Opt-in — its own module, separate from Metrics, though both share the calories metric.)
The day changes at 2:00 am, not midnight — a late snack at 1 am still counts toward the evening’s day. Days follow your timezone: the server adopts the timezone of your weather location (Settings → Weather), so set that if Fanad runs on a hosted box — the web Diet view warns you when the server’s clock and your device disagree. (The TZ environment variable overrides it.)
| Command | What it does |
|---|---|
| eat <amount> <food> | Logs weight × the food’s cal/oz (oz, g, or lb). A food I’ve never seen gets one guessed density you confirm or correct; piece and serving foods count instead (eat 2 eggs, eat 2 skyr) — a bare eat <food> logs one piece or one typical serving. No amount on a weighed food and I’ll ask for it.
exampleeat 4 oz chicken breast chicken breast ≈ 45 cal/oz (my guess) — 4 oz ≈ 180 cal. Reply “yes” to save & log, send the right cal/oz (a bare number), or “no” to skip. yes ✓ chicken breast saved at 45 cal/oz — I’ll reuse it from now on. Logged 4 oz chicken breast: 180 cal. • calories: 180 / 2000 kcal eat 6 oz chicken breast Logged 6 oz chicken breast: 270 cal. • calories: 450 / 2000 kcal |
| eat <food> <n> cal | Know the calories already? Your own count always wins — no guess, no question. With no amount, that number becomes the food’s typical serving: saved once, so a bare eat <food> logs it from then on, and saying it again with a new number updates the serving. Add an amount instead and an unknown food’s density is derived and saved from your numbers. A known weighed or piece food (or recipe) is never rewritten by a one-off.
exampleeat olives 80 cal ✓ olives saved at 80 cal/serving — I’ll reuse it. Logged olives: 80 cal. • calories: 530 / 2000 kcal eat olives Logged olives: 80 cal. • calories: 610 / 2000 kcal eat 4 oz mac salad 240 cal ✓ mac salad saved at 60 cal/oz — I’ll reuse it. Logged 4 oz mac salad: 240 cal. • calories: 850 / 2000 kcal |
| eat <a, comma, list> | Threw a plate together? List the parts, separated by commas — each amount binds to its own item, so eat 8 oz chicken, half a pepper, 5 mushrooms is 8 oz of the chicken, not of the whole plate. Known foods price from your library; the rest get one batched guess. It logs as a single one-off entry — nothing is saved for reuse (that’s what save meal is for). An all-known plate logs straight away; if anything had to be guessed you get one yes/number/no question first.
exampleeat 8 oz chicken breast, half a red pepper, 5 white mushrooms 8 oz chicken breast, half a red pepper, 5 white mushrooms ≈ 425 cal (8 oz chicken breast 360 · half a red pepper ~20 guess · 5 white mushrooms ~45 guess). “yes” logs it, a number sets the total, or “no” to skip. yes Logged 8 oz chicken breast, half a red pepper, 5 white mushrooms: 425 cal. • calories: 425 / 2000 kcal |
| eat whatever [day] | A cheat day, a fast, a travel day — take a day off the record. It shows in a different colour on the graph and drops out of your average, so one deliberate blow-out (or a skipped day) never masquerades as a tracked result. You can still log food on it if you like. Bare eat whatever marks today; add a day to mark one after the fact — yesterday, a weekday (eat whatever tuesday is the most recent Tuesday), 3 days ago, or a date (7/20, jul 20). eat whatever off — with or without a day — puts it back on the books. (In the web Diet view, the food log’s 🍕 eat-whatever button does the same for whichever day you’re viewing.)
exampleeat whatever 🍕 Today’s an eat-whatever day — off the record. I’ll tint it on the graph and leave it out of your averages. (“eat whatever off” puts it back on the books.) eat whatever 7/20 🍕 Mon Jul 20 is an eat-whatever day — off the record. I’ll tint it on the graph and leave it out of your averages. (“eat whatever 7/20 off” puts it back on the books.) |
| save meal <name> <what’s in it> [<n> cal] eat meal <name> | Eat the same breakfast every day? Save it once — a one-word name, then what’s in it. State the total and it saves silently; leave the total off and Fanad prices it from your foods (one batched guess covers the unknowns) behind a single yes/number/no question. eat meal <name> — or just eat <name> — logs one serving. foods lists meals with their contents; food del removes one; re-saving overwrites.
examplesave meal breakfast 2 eggs, skyr, toast 450 cal ✓ breakfast saved at 450 cal/serving (2 eggs, skyr, toast). “eat breakfast” logs it. eat breakfast Logged breakfast: 450 cal. • calories: 450 / 2000 kcal save meal lunch 2 eggs, skyr, toast lunch ≈ 380 cal (2 eggs 140 · skyr 140 · toast ~100 guess). “yes” saves it, a number sets the total, or “no” to skip. yes ✓ lunch saved at 380 cal/serving (2 eggs, skyr, toast). “eat lunch” logs it. |
| foods food add/set/del/show | Your food library. food add <name> <cal> sets a density directly (per-oz unless you say /g, /piece, or /serving); food set corrects one by listing number or name.
examplefood add olives 47 ✓ olives — 47 cal/oz. Log it with: eat 4 oz olives foods 🥗 Your foods: 1. chicken breast — 45 cal/oz 2. olives — 47 cal/oz (“food set 2 48” corrects one · “food del 2” removes it) |
| recipe new <name> | Build a recipe conversationally: an amount + ingredient per line, then the dish’s cooked weight — total calories ÷ cooked oz becomes the recipe’s own cal/oz, so a portion logs like any food.
examplerecipe new chili 🍲 Building chili. what’s in it? e.g. “16 oz chicken breast” (an amount + ingredient per line · “cooked 28 oz” sets the dish weight · “done” finishes · “cancel” discards) 16 oz chicken breast ✓ chicken breast 16 oz (720 cal) — next? done What does the finished dish weigh, in oz? (weigh the whole pot minus the pot) 28 🍲 chili: 720 cal ÷ 28 oz cooked = 25.7 cal/oz. Log it with: eat 8 oz chili |
| recipe <name> = … | The one-line version: comma-separated ingredients (each already in your foods) ending @ <oz> cooked. Redefines the recipe wholesale.
examplerecipe chili = 16 oz chicken breast, 8 oz olives @ 28 oz cooked 🍲 chili: • chicken breast — 16 oz @ 45 cal/oz = 720 cal • olives — 8 oz @ 47 cal/oz = 376 cal = 1096 cal ÷ 28 oz cooked = 39.1 cal/oz. Log it with: eat 8 oz chili |
| recipes recipe show/del <name> | List your recipes, see one’s ingredients and math, or remove it. (Past logs keep their calories.) |
| weight <n> target <n> | Log today’s body weight, and set the daily calorie goal every tally is measured against.
exampleweight 182 ⚖️ 182 lbs logged. target 1800 🎯 Daily target set to 1800 kcal. • calories: 450 / 1800 kcal |
| undo | Take back the last logged portion — undo is app-wide, so it pops whatever Fanad did last (see Tasks). |
Medication
A logger, not medical advice. Medication is a calm “did I take it?” journal: you tell it what you take, tap when you take it, and see today’s doses and your streak. It records only what you type — it never guesses a dose, never offers drug or interaction information, and never suggests changes. For anything about your medications, ask your pharmacist or doctor. (Opt-in — its own module. Each med is tracked on its own, kept out of the general Metrics tally so it doesn’t clutter your numbers.)
The day changes at 2:00 am, not midnight, so a late bedtime dose still counts toward the right day — the same rollover Diet and the charts use. A reminder you set fires on the wall-clock minute you picked and only once a day, and it stays quiet once that template’s meds are already logged.
| Command | What it does |
|---|---|
| med add <name> [dose] | Add a medication. The dose is a free-text note (“5mg”, “1 tablet”) — Fanad never fills it in for you. med list shows your catalog with today’s ☑/☐. |
| med <name> | Log a dose taken today (med amlodipine). A med you haven’t added yet is created on first use. Take it twice a day? Log it twice. undo takes back the last dose. |
| med template <name> = <m1>, <m2> | Group a time of day (med template morning = amlodipine, metformin). Fanad then asks if you want a daily reminder — reply a time like 8am, or no. |
| med <template> · med all | med morning logs everything in that template at once; med all logs every scheduled med you haven’t taken yet today. Meds already done are left as they are. |
| meds | Today’s adherence: each template with a ☑ or ☐ per med. This is the one-tap view of “what’s left today?”. |
| med chart <name> [range] | Your adherence trend for one med (med chart amlodipine 90d). Works whether or not the Metrics module is on. |
| med template <name> remind <time|off> | Set or clear a template’s daily reminder without the prompt (med template morning remind 8am · … remind off). med templates lists them with their times. |
| med del <name> · med template del <name> | Remove a med or a template. A removed med keeps its dose history, so med chart <name> still works. |
example
med add amlodipine 5mg
💊 Added amlodipine (5mg). Log a dose with “med amlodipine”.
med template morning = amlodipine, metformin
💊 Saved “morning”: amlodipine, metformin.
Want a daily reminder? Reply a time like “8am” (or “no”).
8am
🔔 I’ll remind you about morning every day at 08:00.
med morning
✅ Logged morning: amlodipine, metformin.
Today: 2/2 scheduled meds taken.
💊 Medication is an adherence journal, not medical advice, and never suggests doses. Talk to your pharmacist or doctor about your medications.
Journal & trends
A named daily checklist + note that Fanad reads back over weeks — its heaviest AI feature. Good for spotting slow problems: a food that doesn’t sit right, sleep that slips, even a pet’s on-again-off-again limp. (Opt-in.)
| Command | What it does |
|---|---|
| journal new <name> | Start a named journal — keep several (one for you, one for the dog). Bare journal lists yours; journal use <name> picks the default.
examplejournal new food 📔 Started “food”. Give it a daily checklist with “journal template <template>” (see /templates), or just “entry” + “journal note <text>” for a note-only journal. |
| journal template | Snapshot one of your saved templates as the journal’s daily checklist. It’s a copy — editing the template later never rewrites the journal; re-run this to re-snapshot.
examplejournal template morning-checks ✓ “food” now uses the “morning-checks” checklist (3 items) — snapshotted, so editing the template later won’t touch this journal. Tomorrow’s entry uses it; “entry” starts today’s. |
| /entry check | Open today’s entry (one per day — asking again shows the same one) and tick items by number, or tap the ☐ buttons. uncheck 2 unticks. An unticked box is data, never guilt.
exampleentry 📔 food — 2026-07-09 (fresh entry) Checklist 0/3: 1. ☐ walk the dog 2. ☐ no dairy breakfast 3. ☐ meds (“check 1 2” ticks items · “journal note <text>” adds to the day) check 1 3 📔 food — 2026-07-09 Checklist 2/3: 1. ☑ walk the dog 2. ☐ no dairy breakfast 3. ☑ meds |
| journal note | Add to today’s free-text note (creates the entry if needed; repeats stack up). The note is where trends hide — jot symptoms, food, mood. Shortcut: j note …
examplej note had dairy at lunch, headache by 3pm 📝 Added to today’s “food” note. (“entry” shows the day.) |
| journal today week month | AI summaries of what you logged: journal today / yesterday for a day, journal week / month roll days up. Each night Fanad quietly files the finished day’s summary, so month-old detail is never re-read raw.
examplejournal week 📅 food · 2026-W28 You kept 5 of 7 days. Adherence held around 80% — dairy turned up in three notes, headaches in two, both in the back half of the week. |
| journal trends | The long look: gentle correlations across weeks, with their evidence, phrased as things worth watching — never a diagnosis.
examplejournal trends 🧭 food — trends One pattern might be worth watching: headaches turned up on 4 of the 5 days that followed dairy. Tentative — the data is still thin. 🩺 These are patterns in what you logged, not medical advice — a real conversation with a doctor (or vet) is the next step if one worries you. |
| journal delete | Erase a journal and all of its entries + summaries. Asks you to confirm first — a bare “yes” never deletes; only an explicit “delete” does. |
How-to: running a trend journal, start to finish
The journal only pays off as a small daily habit, so here’s the whole loop once, end to end — using the classic case it was built for: “is it the dairy?” (Swap in sleep, a medication, or your dog’s limp — the shape is identical.)
- Turn it on: optin journal. (It’s per-person, like every module.)
- Build the daily checklist as a template. File a task (“morning checks”), give it steps (step walk the dog · step no dairy breakfast · step meds), then save it: /template 1 morning-checks. Track whatever you want to correlate later — the “no dairy” item is the experiment.
- Point a journal at it: journal new food, then journal template morning-checks. The steps are copied in — editing the template later won’t rewrite your journal.
- Each day, two small moves: entry opens today (tap the boxes, or check 1 2), and the note carries the story: j note had dairy at lunch, headache by 3pm. Skipped a day? Fine — a gap is data too.
- Let the nights do the heavy lifting. Fanad summarizes each finished day overnight, and weeks/months are built from those summaries — so the AI stays fast and cheap no matter how old the journal gets.
- Read it back: journal week after a few days, journal month once it’s been running, and journal trends for the long look. Trends name their evidence (“headaches on 4 of the 5 days after dairy”) and stay tentative on thin data.
- Act on it like a scientist, not a patient. A pattern is a hypothesis: tighten the checklist (“no dairy after noon”), keep logging, and take a real question to a doctor — or the vet, if the journal is journal new pepper and the mystery is the morning limp.
Honest by design: summaries and trends only ever describe what you logged — Fanad never invents days, and the not-medical-advice line is part of every trends reply on purpose.
Batches
Track each run of something you make again and again — a sourdough, a kombucha, a batch of soap. Write the directions once as a template; every run gets its own numbered checklist snapshotted from it, a dated log of how it went, and a closing verdict — so batch #7 can actually beat batch #3. A run is a working copy: tweak its steps as you learn, then batch save to graduate the winners into a new, auto-numbered recipe version that the next run starts from (a dud version can be rejected back out). No AI and no reminders: a batch only moves when you say so. (Opt-in.)
| Command | What it does |
|---|---|
| batch new <name> | Open a run. The steps are copied from the same-named template — a snapshot, so editing the template later never touches a batch already going. Run again to open another (two crocks at once is fine); bare batches lists your processes.
examplebatch new sourdough 🧪 Batch #1 of “sourdough” is open — 3 steps snapshotted from the template. 🧪 sourdough — batch #1 (fresh) · opened 7/13 Steps 0/3: 1. ☐ feed the starter 2. ☐ mix and autolyse 3. ☐ bake at 450 (“batch check 1 2” ticks steps · “batch log <text>” adds a line · “batch done <how it went>” closes it) |
| batch batch check | Bare batch shows the current open run; batch check 1 2 ticks steps by number (batch uncheck 2 unticks), or tap the ☐ buttons. The explicit batch prefix keeps a plain check free for the journal.
examplebatch check 1 2 🧪 sourdough — batch #1 · opened 7/13 Steps 2/3: 1. ☑ feed the starter 2. ☑ mix and autolyse 3. ☐ bake at 450 |
| batch add edit rm | Tweak the run’s steps as you learn: batch add cold-proof overnight appends a step, batch edit 2 autolyse 45 min rewords step 2 (keeping its tick), batch rm 5 removes it. The run is a working copy — change it freely; only open runs are editable.
examplebatch edit 2 autolyse 45 min 🧪 sourdough — batch #1 · opened 7/13 Steps 2/3: 1. ☑ feed the starter 2. ☑ autolyse 45 min 3. ☐ bake at 450 |
| batch save | Graduate the run’s current steps into a new recipe version — auto-numbered (sourdough #2, #3…), so you never manage names. The original is never overwritten; batch new sourdough just starts from your latest version from now on.
examplebatch save 🌱 Saved “sourdough” as new template version “sourdough #2” (3 steps, reset). The original is untouched — “batch new sourdough” now starts from this version. |
| batch log | Add a dated line to the run’s diary — days or weeks of them. This is where the story lives: what you fed it, how it smelled, what you’d change.
examplebatch log smells lively, nice rise overnight 🧪 sourdough — batch #1 · opened 7/13 Steps 2/3: 1. ☑ feed the starter 2. ☑ mix and autolyse 3. ☐ bake at 450 📓 Log: 7/13 — smells lively, nice rise overnight |
| batch done | Close the run with its outcome. Bare batch done asks how it turned out (say skip for none); the verdict is what makes the next run better.
examplebatch done tangy, best crumb yet 🏁 Closed “sourdough” #1. |
| batch history | Every run of a process, newest first — dates, steps done, and each verdict, so a good batch is reproducible.
examplebatch history sourdough 🗂 sourdough — 2 runs: #2 · 7/13→7/13 · 3/3 steps · 🏁 tangy, best crumb yet #1 · opened 7/13 · 1/3 steps · still open 🌱 versions: #1 (original) · #2 ← latest |
| batch versions | The recipe lineage of a process — every saved version, which one is the latest (what a new run starts from), and any that are rejected.
examplebatch versions sourdough 🌱 “sourdough” recipe versions:
#1 — original · 3 steps
#2 — 4 steps · ← latest
(“batch reject sourdough # |
| batch reject unreject | A version that turned out badly? batch reject sourdough #2 pulls it from the lineage, so batch new sourdough falls back to the last good version. Reversible — batch unreject sourdough #2 brings it back. Rejecting targets the recipe version, not a run; your run history is untouched.
examplebatch reject sourdough #2 ✗ Rejected “sourdough #2” — dropped from the lineage. “batch new sourdough” now starts from “sourdough”. (“batch unreject sourdough #2” restores it.) |
| batch delete | Erase a process and all of its runs + logs. Asks you to confirm first — a bare “yes” never deletes; only an explicit “delete” does. |
The whole loop, once: optin batches → file a task, give it steps, save it (/template 1 sourdough) → batch new sourdough → tick steps, batch log …, and batch add/edit/rm as you learn → batch save to keep the winning steps as the next version → batch done … to file the verdict. Each run refines the recipe; a dud version is batch reject-ed out. A batch is a run, not a recurring chore — there are no reminders, on purpose.
Home Assistant: ring the house
Home Assistant has no reminders of its own that survive a restart — Fanad does. With this module on, the timers and reminders you already set here also ring the house: a voice satellite speaks the ding, a script you name can flash lights or sound a siren, and your phone gets a push through the HA companion app. You can also talk straight to HA (ha turn off the kitchen light) and push a dated task onto a house calendar. Fanad never reads your house — no sensors, no presence; the house is an output, not an input. (Opt-in.)
| Command | What it does |
|---|---|
| ha <command> | Anything after ha goes straight to Home Assistant’s own assistant, and its answer comes back — lights, covers, questions, whatever your HA can do.
exampleha turn off the kitchen light 🏠 Turned off the light |
| /ha | Status: is HA reachable, which ring outputs are enabled, and whether the last ring got through. Owners see the host and targets; everyone else just the health.
exampleha 🏠 Home Assistant ✓ Connected — HA 2026.7 · Home Ring outputs: announce (1) · script fanad_alarm Calendar: calendar.house (“ha test” rings the outputs · “ha <command>” talks to HA) |
| ha test | Ring every enabled output right now — the satellite speaks a test line, the script runs, the push arrives — so you know the wiring works before a real timer needs it. |
| ha cal <n> | Push dated task n onto the house calendar (the 📅 /cal_N reply also grows a 🏠 button). Needs HA’s Local Calendar integration and a calendar picked in Settings.
exampleha cal 3 🏠 Sent “dentist” to the HA calendar. |
Setup (owner, once): in HA open your profile → Security and create a long-lived access token; paste the HA URL + token in Settings → Home Assistant and pick the outputs — voice satellites to announce on, an optional script (it receives kind and title variables — wire a siren or lights there), and any companion-app notify services. For the calendar push, add the Local Calendar integration in HA first. The token is stored encrypted, like every other secret. Then optin ha and ha test.
Dashboards: let Home Assistant read Fanad
The other direction: GET /api/ha/summary is one read-only JSON bundle built for HA dashboards — open tasks, due today, overdue, cleared today, the next deadline and ding, plus a block per module you have on (calories today, journal entries, timers…). It carries counts and timestamps only: no task text leaves Fanad unless you add ?titles=1, because entity states flow into HA’s recorder, logbook, and whatever voice assistant you wired up. “Today” rolls at Fanad’s 02:00 logical day, not midnight. Pair it with a read-only token (Settings → Security → Terminal client tokens → tick Read-only, or fanad token --read-only) — that token can never post chat or change anything, so the credential sitting in your HA config is harmless. A minimal HA REST sensor:
rest:
- resource: http://YOUR_FANAD_HOST:8787/api/ha/summary
headers:
Authorization: Bearer YOUR_READ_ONLY_TOKEN
scan_interval: 300
sensor:
- name: Fanad tasks due today
value_template: "{{ value_json.tasks.due_today }}"
- name: Fanad overdue
value_template: "{{ value_json.tasks.overdue }}"
- name: Fanad next deadline
device_class: timestamp
value_template: "{{ value_json.tasks.next_deadline }}"
Live updates: GET /api/stream (SSE, same token) emits a counts event whenever these numbers change — poll on that instead of a timer if your integration can. The payload carries version: 1; the shape only changes with that number.
Speed Dial: give someone a few house buttons
Like the old phone speed dial. You (the owner) give another Telegram person a pad of numbers 0–9, and set each number to one specific Home Assistant command you write. They send the number — or tap it — and only that command runs. It’s how you hand a houseguest, a kid, or a roommate a handful of house controls without giving them the run of your whole Home Assistant. A person with a pad never gets the free-text ha <command> — their pad is their only line to the house, and since they only ever send a digit, their words never reach HA. (Owner-managed; needs the Home Assistant connection set up.)
Each person can be one of three kinds, your choice per account:
- Full account + pad — a normal Fanad user (their own tasks and chat) who also has the pad.
- Limited to speed dial — the account can do nothing but its 0–9 pad: no tasks, no chat, no other commands. Anything else they send just shows the pad again. A limited account also can’t vouch anyone in — the pad is its whole reach, so it can’t grow the guest list around its own limit.
- Local (no Telegram) — a household name, not a Telegram handle: for a family member who doesn’t use Telegram at all. A local account is a pad and nothing else — it’s always speed-dial-only, is never let through the Telegram gate, and is used entirely through its remote-control link (below).
| Command | What it does |
|---|---|
| 1–9 | The person with a pad sends a bare digit to fire that number’s command.
example1 🏠 1️⃣ Kitchen lights → Turned off the light |
| 0 · pad | Show my pad — the numbers with labels and tappable buttons. 0 is the always-there “show my pad” key, so a bare 0 never fires slot 0 (use dial 0 or tap it for that). Fanad also shows a new pad-holder their pad alongside the reply to their first message. |
| dial <n> | Fire number n the unambiguous way (handy if they’re also using Fanad for other things — and the only way to fire slot 0). |
| sd @user <n> = <command> | Owner. Set number n for that person to a Home Assistant command — the same free text you’d type after ha. Add a label with Label | command.
examplesd @sam 1 = Kitchen | turn off the kitchen lights ✓ @sam 1️⃣ Kitchen — turn off the kitchen lights |
| sd @user limit on|off | Owner. Lock that account to speed dial only (on), or let it be a full account that still has the pad (off). |
| sd @user test <n> | Owner. Fire that number against the house yourself, to check it works. |
| sd @user · sd | Owner. Show one person’s pad, or list every pad you’ve set up. |
Setup (owner): in Settings → Access, every allowed Telegram account is a row you can expand. Add a person by @username (they can then reach the bot), open their row, fill the numbers you want, tick Limit to speed dial only if you want them locked to it, and Save. You can program a pad before the person has ever messaged — it links to them on their first message. Numbers run against the one Home Assistant connection you set in the Channels tab, so set that up first. Each person’s row has a 🖨 Sheet button that prints a hand-out card of their numbers — give it to them like an old speed-dial card.
One button, on and off. A light usually needs two commands — one to turn it on, one to turn it off. Rather than spend two numbers on it, give a single number a second OFF command (the optional field beside the main one when you expand a row). That number becomes a toggle: press it to turn the device on, press it again to turn it off, and so on. Fanad remembers which way it’s set, so the number behaves the same everywhere — the Telegram digit, the tappable pad, and a shared remote-control link all flip the same light. Leave the OFF field blank and the number stays a plain one-shot. The Test off button beside Test lets you try the off command before you save, and a toggle is marked on / off on the printable sheet so the guest knows one button does both. (Fanad can’t read the device back from Home Assistant, so a toggle button never shows a live on/off state — it just alternates each press.)
Share a remote-control link (no Telegram needed). Speed dial is really for your guests, and a guest shouldn’t have to install Telegram to flip a light. In a person’s row, under Share a remote-control link, pick how long it should last (1, 7, or 30 days), optionally note who it’s for, and Generate link. You get a plain web link like https://your-fanad/r/… — text it to the guest and they open a simple page of just their buttons. No login, no account, no app. Tapping a button runs that same owner-authored command, so the link can only ever do what the pad can do — nothing else in Fanad, and still no free text to the house. The link expires on its own and you can Revoke it any time from the same row. (Copy the link when it’s shown — for your security it isn’t stored and can’t be shown again; generate a fresh one if you lose it. For a link to work away from your home network, set a Site URL in Settings → Security.)
Web login must be on. A share link lives at your Fanad address, and the promise is “only these buttons”. That only holds if everything else at that address is locked, so Fanad won’t generate a link until you’ve turned on web login (Settings → Security) — and a link stops working the moment login is turned back off, so a guest can never reach the buttons while the rest of the app is open.
Local accounts: house buttons for family without Telegram. Grandma isn’t installing Telegram, and she shouldn’t have to. In Settings → Access, switch the add-account row to Local (no Telegram) and type a name — grandma, not an @handle. That makes an account that is only a pad: fill her numbers like any other row, then generate her remote-control link and send it to her (or bookmark it on her phone’s home screen) — that link is how she uses it. Because the link is her whole way in, a local account is the one place you may pick Never expires when generating it; you can still revoke it from her row any moment, and removing the account kills the pad and every link with it. A local name never opens your Telegram bot to anyone — even a Telegram user with the very same @username stays a stranger — and the 🖨 Sheet card adapts its instructions for her (“open your link”, not “message the bot”).
Notebooks
Step into an isolated sub-space with its own tasks, notes, and lists — separate from your main one. Like a fresh account you can walk into and back out of. (Opt-in.)
| Command | What it does |
|---|---|
| optin notebook | Turn Notebooks on (off by default). Opting back out returns you to your main space; nothing is deleted.
exampleoptin notebook ✓ Notebooks on. Open a fresh space with “notebook <name>”, list them with “notebook”, head home with “notebook main”. |
| notebook | Where am I? Lists your notebooks with tappable switch buttons — plus a “back to main” chip when you're inside one.
examplenotebook 📓 Notebooks — a separate, private space for tasks, notes & lists. • work • garden — you’re here Switch or create: “notebook <name>” · back home: “notebook main” · rename: “notebook rename <old> <new>”. 📓 work📖 Back to main
|
| notebook <name> | Switch into that notebook — a new name creates it on the spot (notebook work). Keep names under 40 characters.
examplenotebook garden ✓ Made a new notebook 📓 garden and switched you in — a clean space for tasks, notes & lists. “notebook main” takes you home. |
| notebook main | Back to your default space (home, exit, and out work too). Notebooks never nest.
examplenotebook main 📖 You’re already in your main space. |
| notebook rename <old> <new> | Rename one of your notebooks.
examplenotebook rename garden allotment ✓ Renamed to 📓 allotment. |
| notebook retire <name> | Hide a notebook you're done with. Nothing is deleted — it just leaves your listings, its reminders go quiet, and its name frees up for a fresh notebook. Retiring the space you're in returns you to main.
examplenotebook retire allotment 🗄 Retired 📓 allotment. Everything in it is kept, just hidden. “notebook recover allotment” brings it back. |
| notebook retired | See your retired notebooks, each with a tappable recover chip.
examplenotebook retired 🗄 Retired notebooks — hidden, not deleted. Recover one to bring it (and everything in it) back: • allotment ♻️ allotment
|
| notebook recover <name> | Bring a retired notebook back, with everything in it. If a live notebook has taken its name meanwhile, it returns under a numbered name (“allotment 2”) — rename it afterwards if you like.
examplenotebook recover allotment ✓ Recovered 📓 allotment — it’s back in your notebooks. Switch to 📓 allotment
|
Everything you say lands in whichever space you're standing in — say notebook if you're unsure where you are. A notebook is yours alone (nobody else can reach it), your module choices carry across all your spaces, and reminders still find you in the same chat.
Vouching people in
Fanad is invite-only by endorsement: anyone already in can vouch a friend in, and the record remembers who let in whom. (Opt-in — already on for the owner.)
| Command | What it does |
|---|---|
| optin vouch | Turn vouching on for yourself (off by default; the owner has it on already).
exampleoptin vouch ✓ Vouch on. Add someone with “vouch @username”. |
| vouch @username | Let someone you trust message this bot — their Telegram @username, or on Slack just @-mention them. You're on record as who let them in.
examplevouch @sam_gardner ✅ Vouched. @sam_gardner can message me now — they’ll get in next time they write. You’re on record as who let them in. |
| vouch | Who you've vouched in so far.
examplevouch 🤝 You’ve vouched in 2: @sam_gardner, @old_pal_pete Add someone with “vouch @username” — they’ll be able to message me, and you’ll be on record as who let them in. |
There's no un-vouch in chat: the owner revokes from web Settings, and revoking someone also revokes everyone they vouched in. Vouches are per-platform — letting someone into Telegram doesn't open Slack. A vouched-in friend lives in chat only — if the admin has set a Site URL and turned on web login, they can send /web for a one-time link that opens the web UI signed in as themselves (see Web login).
Check-ins
Ask for a gentle, once-a-day nudge at a time that suits you — a check-in, never a stream of pings.
| Command | What it does |
|---|---|
| /wake | Set a gentle check-in at a time of day (e.g. /wake 8:30).
example/wake 8:30 ⏰ I'll check in at 08:30 (tomorrow). (“/wakelist” · “/wake off 1”) …next morning at 8:30: 💡 A gentle nudge: how about “water the plants”? (reply /whatdo when you're ready) |
| /wakelist | List your scheduled check-ins.
example/wakelist ⏰ Check-ins: #1 08:30 (“/wake off <id>” to remove) |
| /wake off <id> | Remove a check-in by id.
example/wake off 1 Removed check-in #1. |
How the suggestions think
/whatdo isn't random. Fanad retrieves your own tasks, scores them on real signals, then lets the model pick one with an honest reason — and a "no" just reshapes the offer.
| Piece | What it does |
|---|---|
| RAG suggestion engine | Retrieves your open tasks, prefilters a shortlist, then the model chooses the single best next one with an honest reason. |
| Learned affinity scoring | Nudges a task from your real outcomes (done/refused/dropped and 👍/🙁) per category and time of day. |
| Context-fit scoring | Nudges a task by whether now matches the day-part, hour, and weather it was noted in. |
| Deadline urgency boost | Lifts a live-dated task as its due date nears, without ever being an absolute override. |
| Anti-repetition + refusal penalties | Down-weights recently-shown tasks and ones you tend to refuse now; never re-offers the just-declined pick. |
| Grooming reshapers | After repeated refusals, offers to reword a task or break it into first steps — inventing nothing. |
Mood, brain-state & sizing
Your mood steers how big a suggestion is, so a hard day gets an easier ask.
| Piece | What it does |
|---|---|
| Mood-to-energy sizing | Infers energy from your last mood (low/sad/sick/hungry → lighter offers) to soften suggestions for ~6h. |
| Brain-state awareness | A designed nervous-system gate to down-shift to low-demand offers when dysregulated; not yet in code — the shipped proxy is mood sizing. |
Reactions & the honest-by-design behavior
Fanad acks your message with a quick two-step reaction, and holds two promises: it never invents, and it lets stale tasks rest.
| Behavior | What it does |
|---|---|
| Reactions on your message | A two-step reaction: 👀 on arrival, then a decision emoji (mood, ✍ for a note, else 🫡; 🤬 on error). |
| Auto-sleep of stale tasks | Once a day, long-untouched tasks go to sleep and are also excluded from suggestions. |
| Honest recommendation reasons | Every suggestion's reason is the model's own or a deterministic phrase grounded in real signals — never fabricated. |
Part four · host only
Admin & setup
For whoever runs the box: installing, wiring up surfaces, bringing your own local model, and the flags that guard privacy and persistence.
How private is it? The four tiers
Privacy isn't one switch — it's three choices: where Fanad is hosted, how you talk to it, and which model does the thinking. The setup below can land you anywhere on this scale, so pick the column you're comfortable with before you wire things up.
| Your setup | Full privacy | Mostly private | Performance over privacy | Convenience |
|---|---|---|---|---|
| You talk to it via | Your own web app, over your private tunnel | Telegram | Telegram | Telegram |
| Hosted on | Your machine | Your machine | Your machine | A cloud server you rent |
| The AI model | Local (LM Studio / Ollama) | Local (LM Studio / Ollama) | A cloud AI provider | A cloud AI provider |
| Notes & data live | On your machine | On your machine | On your machine | On the cloud host |
| Who can see your words | Only you | You + Telegram | You + Telegram + the AI provider | Telegram + the AI provider + your host |
| Good when | Your notes are sensitive and privacy comes first. | You want Telegram's ease, but keep data & AI at home. | You want the strongest model and will trade some privacy for it. | You just want the easiest start and privacy isn't the concern. |
Telegram bot messages aren't end-to-end encrypted, so Telegram can see what you send its bot — that's why the web app (reached over your own tunnel, browser-to-server) is the private surface. A cloud model only sees your prompts when LLM_ALLOW_CLOUD is on (off by default); see Cloud & privacy boundary below.
Install & run
Pick your path: a double-click Windows installer with everything bundled, a one-line npx bootstrap, or a checkout and a handful of npm scripts. All state lives in one SQLite file.
The Happy Path installer is a friendly, start-to-finish guide — download the Windows app, set up a local model, run the wizard, hook up a Telegram bot, and reach it from anywhere. This section is the reference behind it.
| Path | What it does |
|---|---|
| FanadSetup.exe (Windows) | The easiest start: an installer with its own bundled Node runtime — nothing to install first, no admin rights, nothing added to PATH. It adds "Fanad Setup" and "Start Fanad Server" to the Start Menu, and the first launch opens the setup wizard in your browser. The installer isn't code-signed yet, so SmartScreen warns once: click More info → Run anyway. Uninstalling keeps your data and settings. |
| npx github:NTBooks/Fanad | One-liner for technical users on any platform (needs Node 24+): copies the app into ./fanad, opens the setup wizard, installs dependencies, builds, and starts. |
| installer.bat + run.bat | From a Windows checkout with Node 24+ installed: installer.bat opens the setup wizard and writes .env; run.bat installs, builds, and starts the server. |
From a checkout, the npm scripts:
| Command | What it does |
|---|---|
| Node >= 24 | Pinned because Fanad uses the built-in node:sqlite module (unflagged on 24+). The Windows installer ships its own copy, so this only matters for the checkout and npx paths. |
| npm run dev | Runs the server with --watch and auto-loads .env for local development. |
| npm start | Starts the production server (Express API + built frontend) on PORT (default 8787). |
| npm run build | Builds the web/ React app into static assets served in production. |
| npm run web:dev | Runs the Vite dev server for the web frontend (proxies /api to the Node server). |
| npm test | Runs the built-in node --test suite. |
| npm run reindex | Re-embeds every task/note for all users after switching embed providers. |
Surfaces & channels
Three surfaces, one shared brain. Both bots reach out via outbound connections — no public URL or inbound ports. Access is fail-closed: strangers get silence.
| Surface | What it does |
|---|---|
| Telegram bot | The primary surface: text/photo capture, inline menus, two-step reaction acks, via grammY long-polling. |
| Telegram setup | Paste a @BotFather token and allowed usernames into web Settings; the server validates and starts the bot. |
| Slack bot | Optional second channel at Telegram parity via Bolt Socket Mode: Block Kit buttons, mrkdwn, .ics uploads. |
| Slack setup | Paste xoxb- bot token and xapp- app-level token into web Settings; secrets stored encrypted. |
| Web app | A React chat UI with Settings, a your-data browser, reactions, and scroll-back history. |
| Web reach | The built frontend is served by the same Express server; reach it over LAN at http://<host>:8787 or via Tailscale. |
| /web (chat → browser) | A Telegram/Slack user asks the bot for a one-time link that opens the web UI signed in as them — no password to invent. Needs the Site URL set and web login on (see Web login). |
| Mac mini always-on deploy | A copy-pasteable guide to run Fanad + LM Studio on an always-on Mac mini via launchd/pm2 with Tailscale. |
Bring your own local LLM
Local is the default and the privacy boundary. Run LM Studio or Ollama separately with both a chat and an embedding model loaded.
| Setting | What it does |
|---|---|
| LM Studio (local, default) | Default local provider at http://127.0.0.1:1234/v1; auto-uses the loaded chat model. |
| Ollama (local) | Alternative local provider at http://127.0.0.1:11434/v1, selectable in Settings. |
| BYO local model requirement | Run LM Studio/Ollama with BOTH a chat and an embedding model loaded, or chat/embeddings error. |
| LLM setup in Settings | Configure provider, base URL, chat/embed models, and (for cloud) an API key from the web UI; keys never returned. |
| LLM_PROVIDER | Selects the chat provider (lmstudio|ollama|openai|gemini|anthropic); default lmstudio. |
| EMBED_PROVIDER | Selects the embeddings provider independently (lmstudio|ollama|openai|gemini); default lmstudio. |
Everything stays on your own machine with a local model doing the thinking — no cloud, no account, no telemetry, nothing leaving the box. That boundary is what makes an honest note possible.
Cloud providers & the privacy boundary
Cloud is off by default and hard-blocked at the factory. LLM_ALLOW_CLOUD is the real boundary — the UI gate is only the friendly front.
| Flag | What it does |
|---|---|
| LLM_ALLOW_CLOUD | Master flag unlocking cloud providers in Settings and on the write path; OFF by default, enforced by a 403 too. |
| Provider cloud hard-block | Cloud providers are refused at the provider factory unless LLM_ALLOW_CLOUD is on. |
| OPENAI_API_KEY / _CHAT_MODEL / _EMBED_MODEL | BYO OpenAI credentials + model overrides; only usable when LLM_ALLOW_CLOUD is on. |
| GEMINI_API_KEY / _CHAT_MODEL / _EMBED_MODEL | BYO Google Gemini credentials + model overrides; gated by LLM_ALLOW_CLOUD. |
| ANTHROPIC_API_KEY / _CHAT_MODEL | BYO Anthropic Claude chat credentials + model (no embeddings); gated by LLM_ALLOW_CLOUD. |
| LMSTUDIO_BASE_URL / OLLAMA_BASE_URL | Base URL of the local server; blank falls back to a provider-aware default. |
| LMSTUDIO_CHAT_MODEL / _EMBED_MODEL | The exact chat and embedding model ids on the local server. |
| LMSTUDIO_API_KEY | API key for the local server (LM Studio ignores it; defaults to 'lm-studio'). |
Channel & weather credentials
Bot tokens and weather keys, all stored encrypted and configured from Settings, not .env.
| Setting | What it does |
|---|---|
| TELEGRAM_BOT_TOKEN | Telegram bot token from @BotFather; enables Telegram via outbound long-polling. Blank disables it. |
| SLACK_BOT_TOKEN / _APP_TOKEN / _SIGNING_SECRET | Slack credentials: xoxb- bot token plus xapp- app token (Socket Mode) or a signing secret (HTTP/Events). |
| WEATHER_PROVIDER | Weather backend selector (default open-meteo, which needs no key). |
| OPENWEATHER_API_KEY | API key for the OpenWeather provider (only if not the keyless open-meteo). |
| Web Settings (Telegram) | Sets the bot token + allowed username and (re)starts Telegram; the token is stored encrypted. |
| Web Settings (Slack) | Sets the Slack tokens/mode and allow-list, then (re)starts Slack; secrets stored encrypted. |
| Web Settings (Weather) | Sets the location and unit and immediately refreshes conditions from Open-Meteo. |
| Web Settings (LLM) | Configures provider, base URL, models and API key from the UI; cloud rejected when the flag is off. |
Secret encryption (KEK)
Every stored secret is encrypted at rest. The off-box KEK is the only thing defending secrets against box theft.
| Setting | What it does |
|---|---|
| KEK envelope encryption | AES-256-GCM encryption of stored secrets so nothing is ever plaintext; decrypted only on read. |
| KEK | The off-box env encryption key; deleted from process.env at boot and the only defense against box theft. |
| KEK_FILE | Overrides the on-box bootstrap-key file path (default <dataDir>.kek) to move it off the backup set. |
| Secret re-key migration | Lifts every bootstrap-encrypted secret to the env KEK once one arrives, then retires the bootstrap key. |
Feature toggles & opt-in
Tasks are the always-on core. Notes, Lists, Metrics, Diet, Vouch, Notebooks, Timer, Journal, Batches, and Home Assistant are per-user opt-in and off by default. As the owner you can also enable or disable each module for the whole deployment — release features over time, or gate one — a global layer that sits above each person's own opt-in.
| Setting | What it does |
|---|---|
| optin / optout / modules | Per-user opt-in for optional modules (all OFF by default; Tasks always-on) via chat or web. |
| Web feature-toggle checkboxes | Returns and sets the acting user's module state; turning Notebooks off drops them to their main space. |
| Metrics & Diet toggles (web) | Separate per-user on/offs for Metrics and Diet (each off by default); Diet logs land on the calories metric either way. |
| Notebooks switch/create (web) | List, switch into, and create the account's isolated sub-user spaces; hidden when the module is off. |
| System modules (Settings → Modules) | Owner-only: enable or disable each module for the whole deployment. A disabled module is hidden and unavailable for everyone but you (so you can preview it before releasing); this global switch sits above each user's own opt-in. |
| system · system enable|disable <module> | The same deployment-wide switches from chat (owner only): system shows the board, system disable journal gates one. |
Access: vouch & impersonation
Grow the whitelist by endorsement, and — on a single-operator host only — act as any user.
| Setting | What it does |
|---|---|
| Telegram auth | Fail-closed: owner claims on first contact, plus a @username allowlist and social vouches; strangers dropped. |
| Slack auth | Same fail-closed control keyed on the Slack user id, with platform-namespaced vouches. |
| Vouch list + cascade-revoke (web) | Lists who endorsed whom and soft-revokes a handle plus everyone in the subtree they vouched. |
| USER_IMPERSONATION | Host-only flag letting the web UI act as any user via X-Fanad-User; default OFF, keep off on multi-user deploys. The server prints a loud warning banner on every boot while it's on. |
| Impersonation picker (web) | Lists accounts so the operator can switch which user the web acts as; empty unless USER_IMPERSONATION is on. |
| Acting-user resolver | The single seam mapping X-Fanad-User to an acting user, defaulting to root so a bad header can't escalate. When web login is on, the session decides instead and the header is ignored. |
| Web local access | By default the web app has no login (auth mode none); it acts as root and is meant for LAN/Tailscale only. Turn on web login below for anything networked. |
Web login (Settings → Security)
An opt-in login for the web UI: username + password + a mandatory authenticator (TOTP) code, enrolled by scanning a QR. Set everything up while the mode is still off, then flip the dropdown — it refuses to enable until the credentials are complete, so you can't lock yourself out. Telegram/Slack channels are unaffected.
| Setting | What it does |
|---|---|
| Auth mode (none | simple) | The in-app dropdown. none = open web UI (today's trust model); simple = every web visitor signs in. Only the root user can change Settings while login is on. |
| Web login account | Root's username + password (scrypt-hashed at rest). Changing the password signs out every other session. |
| Two-factor (required) | Scan the QR with any authenticator app, verify a code to finish. The secret is stored encrypted under the KEK; a re-enroll keeps the old authenticator working until the new one is proven. |
| Allow new users to register | Shows "Create an account" on the login screen — and adds a "sign up in your browser" link to the public /demo page, so people without Telegram can join. Each account is a fully separate tenant. Normally a registration isn't usable until its own 2FA is scanned and verified; but while demo mode (demo signup on) is active these browser signups skip 2FA and drop straight in — and are asked to set up an authenticator if you later turn demo mode off. Per-IP limits and the MAX_WEB_DEMO_ACCOUNTS cap keep a public box from being flooded. |
| Web IP allowlist | Optional, works in either mode: restrict the whole web UI to listed IPs/CIDR ranges. Loopback always passes and /api/health stays open (platform healthchecks). Needs TRUST_PROXY behind a reverse proxy. |
| Site URL (Advanced) | The public address of this server (e.g. https://fanad.example.com) — the base the /web links below point at. Blank (the default) keeps /web off. Deployment-specific, so it doesn't ride the setup-mode settings backup. |
| /web | In a Telegram/Slack DM: a one-time link (10-minute, single-use) that opens the web UI signed in as that chat user — the authorized DM is the proof of identity, so chat-only users never need a password. The link lands on a page with a single button; tapping the button is what signs you in (so a chat app's link preview can't spend the link for you). Requires the Site URL and login mode simple — while either is missing, only the owner is pointed at the Settings switch; for everyone else the word behaves like any other and simply files as a task. The root operator is always refused (2FA can't be bypassed from chat).
example/web 🔗 Your one-time sign-in link: https://fanad.example.com/web/3fK…9Qw It opens a page with one button — tap it and Fanad opens in your browser, already signed in as you. It works once and expires in 10 minutes — run /web again whenever you need a fresh one. |
| cmd (terminal client) | In any chat — web, or an authorized Telegram/Slack DM: mints a long-lived claim token for the terminal client and replies with the ready-to-paste connect command — npx github:NTBooks/Fanad <server> <token>, which runs in any terminal with Node.js 24+ (npx fetches the client from the repo, so no checkout or pre-downloaded source is needed). Admin opt-in, default off: nothing mints and no token authenticates until the owner ticks “Enable the terminal client” (Settings → Security → Terminal client tokens); flipping it back off instantly disables every outstanding token. While it's off, only the owner is told about the switch — for everyone else cmd is just a word and files as a task. The token is shown once (only its hash is stored), acts as your account for chat only (never Settings), and expires in 90 days. A DM counts because it's already your vouched-in, authorized surface — the same proof of identity /web uses to sign you in. Manage or revoke tokens in the same Settings section, or on the server box with fanad token --list / --revoke. A read-only variant (tick Read-only when minting, or fanad token --read-only) can only GET — it's the credential for Home Assistant dashboards and anything else that should read your numbers but never write (see Home Assistant).
examplecmd 💻 Your terminal connect command — shown ONCE (only its hash is stored): npx github:NTBooks/Fanad https://fanad.example.com fnd1_05qO…Z71o Paste it into any terminal with Node.js 24+ installed; npx fetches the client from the repo, so no checkout or pre-downloaded source is needed. The token acts as you, chat only — never Settings — and expires after the window you pick when minting (90 days by default, or never for always-on credentials like a Home Assistant dashboard). Manage or revoke: Settings → Security → Terminal client tokens. |
| token (read-only) | The self-service way to get the credential a Home Assistant dashboard needs, straight from chat — no web panel. Send token (also create token / new token) and Fanad shows a warning and asks yes/no; reply yes and it mints a read-only, never-expiring token scoped to your account, shown once. Unlike cmd (which mints a full, 90-day terminal-client token), this one can only GET /api/ha/summary — it can never post chat or change anything. Any authorized user can mint their own; it follows the same Enable the terminal client switch as cmd (while it's off, a non-owner's “token” is just a task). Because the raw token arrives over chat, copy it and delete the message. Revoke anytime in Settings → Security → Terminal client tokens.
exampletoken 🔑 Mint a read-only access token? This creates a long-lived token that lets an outside app (like a Home Assistant dashboard) READ your Fanad numbers. It can only read, never expires until you revoke it, and covers only your data. Reply “yes” to mint it, or “no” to cancel. yes 🔑 Your read-only token — shown ONCE (only its hash is stored): fnd1_9tQ…4kZ Point a dashboard at https://fanad.example.com/api/ha/summary with the header Authorization: Bearer <token>. It never expires and can only read. Copy it now, then delete this message. |
| AUTH_MODE | Env default for the mode before any in-app choice; the dropdown's stored value wins thereafter. |
| SITE_URL | Env default for the Site URL before any in-app choice; the saved Settings value wins thereafter (same precedence as AUTH_MODE). |
| AUTH_RESET | Break-glass: set to 1 and restart to force login off (credentials and 2FA preserved) after a lockout — lost phone or lost KEK. Unset it afterwards. |
| TRUST_PROXY | Set to 1 (or a hop count) behind Coolify/Traefik so the IP allowlist and login rate-limit see the real client address. |
Deletion, retention & data
Deletion truly deletes — unless you first turn on a retention export. Browse and edit your own data anytime.
| Setting | What it does |
|---|---|
| /requestdeletion | Confirm-gated full erase of a user's account and data.
example/requestdeletion ⚠️ Delete everything? This permanently erases ALL of your data — every task, note, message, mood, metric, reminder, template, and the preferences I’ve learned about you. It cannot be undone. Type DELETE to confirm, or anything else to cancel. never mind Okay — nothing was deleted. Your data is safe. 🌱 |
| Data retention toggle | Turns on a full zip export of a user's data before /requestdeletion erases it; OFF by default. |
| Retention export zip | Snapshots every row (including each notebook) into a timestamped zip before the wipe. |
| Data Browser ('Your data') | A user-scoped browser over a whitelist of tables (app_settings excluded) for transparency and in-place edit/delete. |
Diagnostics & logs
Optional panels for the operator. Both expose sensitive content over the unauthenticated local API, so both are off by default.
| Endpoint / setting | What it does |
|---|---|
| GET /api/health | Unauthenticated liveness probe — booleans only (ok, secrets encrypted, persist mounted, LLM reachable). Deployment detail (paths, KEK source, impersonation) is logged at startup instead of served here. |
| AI Activity Log viewer + toggle | Tails every LLM call (purpose/prompt/reply/latency) plus the /whatdo decision; live DB toggle, OFF by default. |
| DEBUG_LOG | Tees console logs into a ring buffer served to the web debug panel; never enable in production. |
| Debug Log viewer | Serves the captured server-log ring buffer to the web debug panel, only when DEBUG_LOG is set. |
Persistence & deployment env
In production Fanad fails fast if its data volume isn't mounted, so the DB and KEK never land on ephemeral storage.
| Setting | What it does |
|---|---|
| PERSIST_DATA | Names the persistent volume (default /persist) where the DB + KEK live; prod boot fails fast if unmounted. |
| DATA_DIR | Explicit override for where the DB + KEK live; the escape hatch that bypasses the persist fail-fast. |
| NODE_ENV | When 'production', activates the persist fail-fast and force-disables SETUP_MODE. |
| PORT | TCP port the Express server listens on (default 8787). |
| SESSION_SECRET | Unused — web login sessions are opaque random tokens stored hashed in the DB, so no signing secret is needed. |
Setup mode & config portability
Move a whole config between servers — but only in setup mode, because the backup contains decrypted secrets.
| Setting | What it does |
|---|---|
| SETUP_MODE | Unlocks settings backup/restore; the backup holds decrypted secrets, so it's force-disabled in production. |
| Settings backup / restore | Export/import all settings (decrypted, re-encrypted on restore) to move a config; only when SETUP_MODE is on. The web-login config (mode, credentials, IP allowlist) deliberately never rides along, and neither does the Site URL (it names the old server's address). |
| GET /api/setup | Reports whether SETUP_MODE is active so the UI shows or hides backup/restore controls. |
Instance backup & migrate (BACKUP_MODE)
Move the whole installation — database (every user's tasks, notes, messages), settings, photos, retention archives — to another server as one zip, or keep it as a full backup. Unlike the setup-mode settings backup above, this works in production: migrating off a live box is the point. It's gated by an env flag, so exposing it is a deliberate, restart-level decision.
| Setting / step | What it does |
|---|---|
| BACKUP_MODE | Env flag (default off). When set, Settings → Data & privacy → Backup offers the download. Turn it on for the migration, then back off — the backup is your entire database in one file. |
| Include encryption key | Checkbox (default off) adding the on-box key file (data.kek) to the zip so it's self-contained. Excluded, stored secrets (API keys, bot tokens, login 2FA) can't be read on the new server until you move data.kek yourself — the safer default. If your secrets use an env KEK instead, the checkbox is moot: set the same KEK on the destination (the backup never contains it). |
| Restore: setup wizard | On a fresh install, drag the zip onto the first-run setup wizard's drop zone before finishing the form. Everything lands before first boot; an existing data dir is renamed aside (data.pre-restore-<timestamp>), never deleted. Already ran setup? Delete .env and run the wizard again. |
| Restore: headless / Coolify | npm run restore -- backup.zip (a wrapper for node server/scripts/restore-backup.js) does the same on a server with no wizard. Stop the app first; env (DATA_DIR / PERSIST_DATA / KEK) is respected as at boot. |
| Version safety | A backup from an older Fanad migrates forward on first boot. A database from a newer Fanad refuses to boot with a clear error (no down-migrations) — update the destination and start again. |
| Lockout recovery | If the restored install has web login on but its secrets can't decrypt yet (key not moved), 2FA can't verify — set AUTH_RESET=1 to force login off, fix the key, then re-enable. |
Security: while BACKUP_MODE is on with web login off, anyone who can reach the server can download the backup (the web layer has no other auth — the same single-operator trust model as impersonation). Enable web login before exposing a networked deployment, and treat the zip like a password vault: it IS your data, and with the key included, your secrets too.
Connecting your channels — step by step
Fanad reaches you over Telegram and/or Slack. Both connect outbound — no public URL, no inbound ports — and you paste the tokens into web Settings, where they're stored encrypted (see Secret encryption). Here's how to get them.
Telegram — a couple of minutes
- In Telegram, open a chat with @BotFather and send
/newbot. - Give it a display name, then a username ending in
bot(e.g.my_fanad_bot). - BotFather replies with an HTTP API token like
123456789:AA…. Copy it. - In Fanad, open web Settings → Telegram, paste the token, and add your own Telegram @username to the allow-list. Save.
- Fanad validates the token and starts the bot over grammY long-polling. Message your bot and send
/howtoto begin.
Access is fail-closed — the owner is claimed on first contact and everyone else stays
silent until allow-listed or vouched. No webhook or open port is needed; the bot polls Telegram
outbound.
Slack — optional, ~5–10 minutes
Fanad's Slack channel runs in Socket Mode (also outbound). You create a small Slack app and hand Fanad two tokens.
- Go to api.slack.com/apps → Create New App → From scratch; name it and choose your workspace.
- Socket Mode: open the Socket Mode page and enable it. When prompted, generate an
app-level token with the
connections:writescope — this is thexapp-…token. Copy it. - Bot scopes: under OAuth & Permissions → Bot Token Scopes, add exactly
what Fanad uses:
chat:write,files:write(the calendar.ics),reactions:write,im:history, andim:write. - Events: under Event Subscriptions, enable events and subscribe to the bot event
message.imso Fanad receives your DMs. Button clicks arrive over Socket Mode automatically. - Install: under Install App, install to your workspace and copy the
Bot User OAuth Token — the
xoxb-…token. - In Fanad, open web Settings → Slack, paste the
xoxb-bot token and thexapp-app-level token, add your Slack user to the allow-list, and save. DM the bot to confirm.
Paste tokens into the web Settings UI rather than .env — they're encrypted with the KEK.
The env vars TELEGRAM_BOT_TOKEN, SLACK_BOT_TOKEN, and
SLACK_APP_TOKEN exist only as a first-boot bootstrap fallback.
Part five
Common questions
Quick answers to the things people actually ask — each links to the full story above.
How do I get a chart or graph of a metric?
Turn Metrics on (optin metrics), log a few values with track or measure, then send chart <name> — the bot replies with a chart image of the last 30 days. Add one word for another range: chart water 7d, 90d, ytd, this_week, this_month. Tracked metrics draw a bar per day; measured ones a line of readings. See Metrics.
How do I put a task on my calendar?
Any dated task shows a 📅 /cal_N link — tap it (or send /cal 3) and Fanad hands you an .ics file that drops the task into your own calendar app. See Deadlines, reminders & calendar.
Can Fanad make a task repeat every week?
No — on purpose. There are no recurring tasks: recurrence is nagging, and nagging is stress. Instead, export the dated task with /cal and set the repeat in your own calendar, on your terms. See Routines without recurrence. If you make the same thing over and over (a bread, a brew) and want to track each run, that’s Batches — still no reminders, just a record per run.
How do I set a quick kitchen timer?
Turn the Timer module on (optin timer) and send timer 12 min pasta — a one-shot ding that never lands on your task list. Bare timer lists what's running; timer off 1 cancels.
Can I use Fanad in a web browser?
Yes — send /web to the bot and it mints a one-time link that opens the web UI already signed in as you. The host has to have web login and a Site URL configured; see Web login.
On a wide screen the web chat floats two quiet side panels in the space beside the column: a shortcut legend on the left (the single-letter commands plus the one-tap commands — tapping a letter drops it into the message box) and a status panel on the right with your module toggles, the task you’re working on, upcoming timers & reminders, and today’s notebook / mood / date. Both show only the modules you’ve opted into, and a single » button at the top right hides or shows the whole layer.
How do I let a friend or my partner use the bot?
Send vouch @their_username (turn the module on with optin vouch first). They get their own private account the moment they message the bot — your tasks stay yours. See Vouching people in.
Why do task numbers change between listings?
Numbers are just positions in the listing you're looking at — every fresh listing renumbers from 1. Act on the numbers (or buttons) of the latest listing rather than one from earlier in the chat.
Why did starting a task pause my other one?
Deliberate: one thing at a time. Starting a task automatically pauses whatever else was started, so "started" always means the thing you're doing now, not a pile of half-open threads. Changed your mind? unstart puts the started task back without finishing it.
Where did my old tasks go?
Anything untouched for about three weeks quietly goes to sleep — out of your listings and your suggestions, but never deleted. /sleeping shows them; /revive 1 brings one back. Snoozed something and want it back early? /snoozed shows what's tucked away; /unsnooze 1 wakes it. See Snoozing, sleeping & reviving.
How do I keep work and home separate?
Notebooks: notebook work switches you into a separate space with its own tasks, notes, and lists; notebook main takes you home. (Opt-in: optin notebook.) See Notebooks.
What's the fastest way to log what I ate?
With Diet on (optin diet): eat skyr 140cal logs it and teaches Fanad that a typical serving of skyr is 140 — from then on plain eat skyr is enough. Whole meals work too: save meal / eat meal breakfast. See Diet & foods.
How do I delete everything I've ever told it?
Send /requestdeletion — after a confirmation, your account and everything in it is erased. See Deletion, retention & data.
How can I see what the AI is actually doing?
Turn on the AI activity log (web Settings → AI connection) — it records every model call's purpose, prompt, raw reply, and timing so you can watch the thinking. Off by default; see Diagnostics & logs.
How do I ask the manual a question from chat?
Send /manual <your question> (or lead with the single letter h) and Fanad answers strictly from this book — if the answer isn't in here, it says so instead of guessing.