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.
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. |
| /howto | The getting-started walkthrough on how to fill your Fanad. |
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. |
| Send a photo | A photo with a caption files a task with the photo attached. |
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.
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. |
| /start | Start task N, showing your original words plus its steps. |
| /done | Finish task N (or a few: done 1 2 3), or a bare done finishes what you just started. |
| /drop | Clear task N off your list, archiving it as dropped. |
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. |
| yes / no / smaller / done / not today | Steer it: yes to start, done if finished, smaller for lighter, no/not today to pass. |
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. |
Where to get help
Friendly, tappable references whenever you need them.
| Command | What it does |
|---|---|
| /guide | Open the topic-guide hub, or guide <topic> for a deep dive on one feature. |
| /commands | The tappable command reference, split into sections that expand in place. |
| c (/menu) | Pop the tappable command menu; works mid-question to back out. |
Part two
Core
The everyday depth: filing precisely, breaking tasks into steps, jotting notes, dates, rewards, 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. |
| /today (x) | File a task due by the end of today (e.g. x call the pharmacy). |
| /tasks all | Show every open task as a ranked, paginated flat list. |
| /tasks today | Show only the tasks due by the end of today. |
| /whatdo today | The same one-thing suggestion, scoped to tasks due today. |
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 … |
| unstep / remove step | Remove step N (or unstep all); the list renumbers. |
| start (stepping) | Starting a stepped task walks you through its steps one at a time. |
| done / done N / done all | Tick off steps: done for the next, done 2 3 for a few, done all finishes the task. |
| stop / pause | Leave a stepping session without finishing; 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. |
| /notes | Show your note inbox. |
| /recall (r) | Find a note by meaning rather than exact words. |
| /promote | Turn note N into a real task (carrying any attached photo over). |
| /forget (/delete) | Delete note N from your inbox. |
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. |
| on <when> | on sunday 6pm sets both a deadline and a one-time reminder at that moment. |
| remind me … at <time> | A one-time nudge that pings once and leaves the task on your list. |
| /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. |
| /cal | Add dated task N to your own calendar via a downloadable .ics file. |
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.
Rewards & knowing yourself
Let finishing feel good, and see what Fanad has quietly learned about your patterns.
| Command | What it does |
|---|---|
| /reward | Ask what reward fits, so finishing feels good (prompts you to save one if you have none). |
| /me (/dossier) | Show what Fanad has learned — completion rate, favored categories, usual mood. |
| /summary | A narrative recap of your activity, optionally scoped (today, this week, last week). |
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. |
| /unlock | Clear the lock so Fanad sorts each task on its own again. |
Sleeping & reviving
Tasks untouched for about three weeks quietly go to sleep so the list stays scannable. Nothing is lost — bring them back anytime.
| Command | What it does |
|---|---|
| /sleeping | Show tasks that drifted off to sleep after ~3 weeks untouched. |
| /revive | Bring a sleeping task back onto your list (e.g. /revive 1, or a few: /revive 1 2 3). |
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). |
| n t d k s r g x w | Lead with one letter: n=note, t=task, d=done, k=drop, s=step, r=recall, g=guide, x=today, w=whatdo. |
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. |
| optin / optout <module> | Turn a module (lists/notes/metrics/vouch/notebook/timer) on or off; opt-out keeps your data. |
Notes, Lists, Metrics, Vouch, Notebooks, and Timer 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. |
| /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. |
| out / top / next / prev / del / rename / exit | Move around inside lists: up a level, to all lists, page, delete, rename, or leave. |
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). |
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. |
| /template <name> | Drop a fresh copy of a saved template onto your list, reset to unchecked. |
| /templates | List your saved templates. |
| /template retire <name> | Delete a saved template. |
Metrics & diet
Track any number you like, and log meals with an LLM calorie estimate you confirm. (Opt-in.)
| Command | What it does |
|---|---|
| /track /measure /tally /chart | Define metrics, log summed or one-off readings, and see a daily tally against optional targets. |
| /eat | Estimates a food's calories/protein/carbs/fat, confirms with you, then logs it so undo removes them together. |
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. |
| notebook | Where am I? Lists your notebooks with tappable switch buttons — plus a “back to main” chip when you're inside one. |
| notebook <name> | Switch into that notebook — a new name creates it on the spot (notebook work). Keep names under 40 characters. |
| notebook main | Back to your default space (home, exit, and out work too). Notebooks never nest. |
| notebook rename <old> <new> | Rename one of your notebooks. |
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). |
| 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. |
| vouch | Who you've vouched in so far. |
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.
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). |
| /wakelist | List your scheduled check-ins. |
| /wake off <id> | Remove a check-in by id. |
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
Node 24+ and a local model, then a handful of npm scripts. All state lives in one SQLite file.
| Command | What it does |
|---|---|
| Node >= 24 | Pinned because Fanad uses the built-in node:sqlite module (unflagged on 24+). |
| 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/reward 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. |
| 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, Vouch, Notebooks, and Timer are per-user opt-in and off by default.
| 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 module toggle (web) | The per-user on/off for the Metrics & diet module (off by default). |
| Notebooks switch/create (web) | List, switch into, and create the account's isolated sub-user spaces; hidden when the module is off. |
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. A registration isn't usable until its own 2FA is scanned and verified; each account is a fully separate tenant. |
| 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. |
| AUTH_MODE | Env default for the mode before any in-app choice; the dropdown's stored value wins thereafter. |
| 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. |
| 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. |
| GET /api/setup | Reports whether SETUP_MODE is active so the UI shows or hides backup/restore controls. |
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.