Skip to content

feat!: run Rumi without a Meta account, and set it up in two commands (v2.0.0) - #82

Open
Jazy1 wants to merge 1 commit into
mainfrom
feat/channel-driver-onboarding
Open

feat!: run Rumi without a Meta account, and set it up in two commands (v2.0.0)#82
Jazy1 wants to merge 1 commit into
mainfrom
feat/channel-driver-onboarding

Conversation

@Jazy1

@Jazy1 Jazy1 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Rumi no longer needs a Meta account to run

The messaging channel is now pluggable. The default links your own WhatsApp by QR the way WhatsApp Web does, so a clone goes from git clone to a working conversation in about fifteen minutes — no Business account, no app review, no waiting. rumi graduate moves you to an official number when you're ready, and every teacher, conversation and past assessment carries over, because Rumi identifies people by phone number rather than by channel.

Setup stopped being an eleven-step document and became two commands.

./install.sh     # tools, dependencies, the `rumi` command
rumi setup       # five guided steps
rumi start

Release: 2.0.0. Three breaking changes, all small, all listed below with the one-line migration.


The idea that made the Meta-only features work

A WhatsApp Flow is only a renderer. The logic lives in the endpoint (bot/shared/routes/*-endpoint.js) in a uniform shape:

INIT → { screen, data } → data_exchange(screen, screenData) → …

So the sandbox needed a second renderer, not a second implementation. The new text-flow engine drives those same endpoint functions over an ordinary chat conversation — which is why /settings, /video, reading assessment and class setup all work on a channel that has no Flows at all. Navigate-style Flows (no endpoint) synthesize the nfm_reply webhook, so whatsapp-bot.js's dispatch and flow-response.handler.js run untouched.

The field names are a contract, so they're pinned by tests against their real consumers rather than assumed.


⚠️ Breaking changes

Change Why Migration
npm run setup launches the wizard (was doctor.js) The name should do the obvious thing Use npm run doctor for the preflight — identical output
.env is read from the repo root, not the working directory cd bot && npm start loaded zero variables and aborted with "Missing REQUIRED env var(s)" on a fully configured deployment Move a bot/.env up one level. Railway is unaffected — its Procfile already runs from root
REQUIRED_VARS is core-only; the channel's vars come from CHANNEL_REQUIRED_VARS[CHANNEL_DRIVER] A sandbox channel needs no Meta credentials Existing Meta deployments need no change — the driver is inferred as meta when the four Meta vars are present

CHANNEL_STATE_DIR also resolves against the repo now. That only affects the new sandbox driver, but it's the reason a bot started from bot/ registered a second WhatsApp device and re-synced endlessly until WhatsApp invalidated the first.


What's new

A two-layer CLI. install.sh does the mechanical bootstrap and offers to run the wizard; rumi does the rest.

Command What it does
rumi setup Connect Rumi to your accounts. --reconfigure re-asks everything
rumi start Start Rumi
rumi status Is Rumi running, which number it answers as, what's switched on
rumi doctor Every connection in detail, with where to get anything missing
rumi pair Link (or re-link) WhatsApp
rumi graduate Move to an official WhatsApp Business number

The wizard is built around four commitments, and they explain most of its code:

  1. Nothing is asked by its variable name. "Where should Rumi keep its memory", not SUPABASE_URL.
  2. Every value is checked while the person who typed it is still there — using the same probes rumi doctor runs, so "configured" and "working" can't drift apart.
  3. Progress is saved per step. Ctrl+C is a legitimate way to leave.
  4. Anything already working isn't asked about twice. A re-run takes seconds and leaves .env byte-identical.

Field-shape validation with the specific correction. The expensive setup failures aren't typos — they're pasting a value that's perfectly well-formed for something else:

  • Supabase's anon key instead of service_role — both are eyJ… JWTs on the same page, and the anon key can't see past RLS, so the bot starts cleanly and behaves as though the database were empty. The validator decodes the token's role claim and tells you to click Reveal.
  • A phone number in Meta's PHONE_NUMBER_ID. Graph answers "Object with ID does not exist", naming neither the field nor the mistake.
  • Another vendor's sk-… in OPENROUTER_API_KEY — it names which vendor's key it recognised.
  • The Supabase dashboard URL instead of the API URL; Upstash's https:// endpoint as REDIS_URL.

The one step that can't be automated is named rather than glossed over. Supabase exposes no API for arbitrary SQL, so the exec_sql helper the schema applies through must be pasted in once. The wizard distinguishes the three states a project can be in — already set up / helper missing / ready for schema — because "no tables yet" and "no way to create them" look identical from outside and need opposite instructions. When the helper is missing it prints the two lines and links straight to that project's SQL editor.

Also: the Baileys sandbox driver (QR pairing; text, reactions, typing, images, audio, documents, video, stickers; an inbound adapter that normalizes a socket event into the shape Meta's webhook produces); rumi graduate validating credentials before touching .env and retiring rather than deleting the old session; rumi status.


Fixes

Most of these were pre-existing and affected Meta deployments too. Each failed inside a try/catch that made it look transient.

Bug User-visible consequence
redisService.setNX / setexWithCeiling never existed No quiz could ever be delivered; every image message failed
quiz_class_* replies had no handler Picking a class after /quiz did nothing
Five services bypassed llm-client.js for OPENAI_API_KEY Those features were dead on an OpenRouter-only deployment
quiz_sessions missing six columns CREATE TABLE IF NOT EXISTS is a no-op on an existing table, so the columns only ever reached fresh installs
doctor green-ticked an OpenRouter key with no credit The worst kind of preflight — it sends you hunting for a bug in the bot
Feature videos + passage backgrounds produced relative URLs The bot asked "want to see how? 🎥", the teacher said yes, nothing arrived
Reading assessments leaked artifacts Every run left an .ogg of a child's voice and a report PDF on disk, forever
A failure message claimed "our team has been notified" Nobody had been
A failed voice note apologised three times

Four of these now have conformance guards so they can't return silently.


Test plan

  • 170 suites / 1997 tests pass locally
  • Pass under CI's own condition — root suite before cd bot && npm ci. Three suites would have failed on first push (fluent-ffmpeg reached transitively; two tests read bot/node_modules); fixed with mapped mocks and install-independent assertions
  • Fresh-clone run, recorded end to end: .env deleted, session retired, ./install.shrumi setup → QR → conversation. 6 min 13 s, against a 30-minute target
  • Re-run on a configured deployment: all five steps skip as ticks, two keypresses, .env byte-identical by checksum
  • Every command exercised with exit codes: --version, --help, unknown command, status, doctor, pair (correctly refuses while the bot holds the session), setup, graduate (rejects bad credentials and changes nothing), and all of them from inside bot/ to prove the cwd fix
  • Live WhatsApp round trips: Hi, /menu, /status, /settings1 (drives the real Flow endpoint through two screens as chat), /video, cancel
  • Secret scan clean across all 113 changed files; no personal paths

Not verified: the 12-hour quiz report (a timer), pronunciation scoring (AZURE_SPEECH_KEY), video generation (KIE_API_KEY), lesson plans (GAMMA_API_KEY).


Docs

README.md and SETUP.md lead with the two-command path; the manual walkthrough stays as the production reference. Both now say plainly that you need a second phone number to test from — Rumi answers as your number, so messaging it from the same account looks exactly like a broken bot.

The /setup skill documents both front doors: the human wizard, and the agent-driven "set me up" flow. The agent path calls the wizard's own modules (validators, the .env patcher, doctor's probes, the schema bootstrap) so the two can't drift, and the skill is explicit that rumi setup, rumi pair and rumi graduate are interactive TTY programs an agent must not launch — it would hang at the first prompt.

CLAUDE.md gains the architecture fact an agent was missing: the channel is pluggable, and you require the index, never a driver.

🤖 Generated with Claude Code

Rumi's messaging channel is now pluggable. The default links your own
WhatsApp by QR the way WhatsApp Web does, so a clone goes from `git clone`
to a working conversation in about fifteen minutes with no Business
account, no app review and no waiting. `rumi graduate` moves you to an
official number when you're ready, and every teacher, conversation and
past assessment carries over — Rumi identifies people by phone number,
not by channel.

The key insight for Meta-only features: a WhatsApp Flow is only a
*renderer*. The endpoint holds all the logic in a uniform shape, so the
sandbox needed a second renderer, not a second implementation. The new
text-flow engine drives those same endpoints over chat, which is why
/settings, /video, reading assessment and class setup work on a channel
that has no Flows.

Setup stopped being an eleven-step document and became `./install.sh`
then `rumi setup` — a five-step wizard that asks in plain language rather
than by variable name, checks every value against the real service as you
type it, writes each answer to .env immediately, and skips whatever
already works on a re-run. Plus `rumi start|status|doctor|pair|graduate`.

Most of the fixes here were pre-existing and affected Meta deployments
too; each failed inside a try/catch that made it look transient.
`redisService.setNX`/`setexWithCeiling` never existed, so no quiz could
ever be delivered and every image failed. `quiz_class_*` replies had no
handler. Five services bypassed llm-client. `quiz_sessions` was missing
six columns on any pre-existing database. `doctor` showed a green tick
for an OpenRouter key with no credit. Four paths resolved against the
working directory rather than the repo, which is how `cd bot && npm start`
loaded zero env vars and how a bot started from bot/ registered a second
WhatsApp device and re-synced until WhatsApp invalidated the first.

170 suites / 1997 tests, verified both locally and under CI's own
condition (root suite before `cd bot && npm ci`).

BREAKING CHANGE: `npm run setup` now launches the interactive wizard; use
`npm run doctor` for the preflight it used to run. `.env` is read from the
repo root rather than the process working directory — move a `bot/.env`
up one level (Railway is unaffected). `REQUIRED_VARS` is core-only, with
the channel's own variables in `CHANNEL_REQUIRED_VARS[CHANNEL_DRIVER]`;
existing Meta deployments need no change, since the driver is inferred as
`meta` when the four Meta variables are present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Jazy1 Jazy1 self-assigned this Aug 6, 2026
@Jazy1 Jazy1 added the enhancement New feature or request label Aug 6, 2026
@Jazy1 Jazy1 closed this Aug 6, 2026
@Jazy1 Jazy1 reopened this Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant