Take a newer version of the SDK
The design system, the API client and the proxy live in your repository as source, so an upgrade is a diff rather than an install. Scaffold the new version beside yours, compare three directories, and finish on npm run check && npm test.
There is no npm update to run. Nothing in your package.json points back at this repository —
the design system, the API client and the proxy were copied into your project as source, and the
CLI that put them there was gone the moment it finished. That is what stops anything we publish
from breaking your app, and it is also the cost: an upgrade is a diff you look at and apply, not
a version number you bump.
Two ways to get that diff. Scaffold the new version into a scratch directory and compare the
trees, which is the one below. Or read the changelog and hand-apply what you want — reasonable
when you have edited the vendored code heavily and a mechanical diff would be mostly your own
changes coming back at you. CHANGELOG.md ships inside the published package, so it travels with
every npx run.
What is yours and what is ours
Three directories in your app are copies of directories in the SDK repository. Every file in them
was copied unchanged; the only thing left behind is the packages' own unit tests, which would
otherwise hand you failing vitest runs for code you did not write.
| In the SDK | In your app | You import it as |
|---|---|---|
packages/ui/src | src/vendor/ui | ~ui |
packages/api-client/src | src/vendor/api | ~api |
packages/vite-plugin-proxy/src | vendor/chatfuel-proxy | relative paths |
Everything else in the app is either yours or generated. Your module code lives in
src/modules/<id>/. The shell around it — App.tsx, ShellHost.tsx, Switcher.tsx,
BrandMark.tsx, client.ts, useWorkspaces.ts, index.css, src/lib/, and the
vite.config.ts, server/, api/ and Dockerfile at the root — is a template you were handed
a copy of and may have edited since. Two files under src/modules/ are written by the wizard
rather than by a person: index.ts, the registry, and navGroups.tsx, the menu, which the
wizard filters down to the modules you picked.
That split is the whole reason the upgrade is tractable. The vendored trees you can replace wholesale if you never touched them. The shell files you read a diff of. Your modules you leave alone.
Read which version installed you
The app records no version of its own, but each installed skill does. The wizard writes a sidecar next to every skill directory it copies:
interface SkillSidecar {
module: string;
wizardVersion: string;
installedAt: string;
}const sidecar: SkillSidecar = {
module: moduleId,
wizardVersion: WIZARD_VERSION,
installedAt: new Date().toISOString(),
};Each module's skill installs as chatfuel-<id>, so read any one of them:
cat .claude/skills/chatfuel-bookings/chatfuel.skill.jsonIf you chose a global skills install they are under ~/.claude/skills/ or ~/.agents/skills/
instead. Compare what you read against what is published:
npx @chatfuel/wizard@latest --versionThe sidecar is also what makes a re-run safe rather than destructive: when a skill directory is already there, the wizard reads the sidecar, tells you which version put it there and when, and replaces it only if you say yes.
Scaffold the new version beside yours
The wizard refuses a directory that exists and is not empty, so it cannot overwrite your app even by accident. Point it somewhere new:
CHATFUEL_TOKEN=<your token> npx @chatfuel/wizard@latest \
--yes --dry-run \
--dir ../chatfuel-next \
--modules bookings,contacts,livechatPass the same --modules list your app has — ls src/modules is that list. --yes answers every
question, which also means no deploy, no GitHub push, no agent handoff and no dev server at the
end. A run with no questions cannot ask for a token either, so it needs a working
CHATFUEL_TOKEN in the environment and says CHATFUEL_TOKEN is not set rather than opening a
prompt nobody can answer. --dry-run stops it before it creates anything on your Chatfuel or
Supabase account. It does still install the scratch app's dependencies, which is the minute this
step costs.
The three vendored trees do not depend on which modules you picked, so if all you want is a design-system diff, one small module in the list is enough.
Diff the three trees
diff -ru chatfuel-app/src/vendor/ui chatfuel-next/src/vendor/ui
diff -ru chatfuel-app/src/vendor/api chatfuel-next/src/vendor/api
diff -ru chatfuel-app/vendor/chatfuel-proxy chatfuel-next/vendor/chatfuel-proxyAn empty diff means that tree is current. A diff that is entirely ours — you never edited these files — is a directory you can replace wholesale. A diff with your edits mixed into it is the case the vendoring was for: you own those files, and which side of each hunk wins is your decision, not ours.
Then the two generated files, which are comparable because a person did not write either:
diff -u chatfuel-app/src/modules/index.ts chatfuel-next/src/modules/index.ts
diff -u chatfuel-app/src/modules/navGroups.tsx chatfuel-next/src/modules/navGroups.tsxAnd the shell, where the diff is a reading exercise rather than a copy: src/App.tsx,
src/ShellHost.tsx, src/lib/, vite.config.ts, server/, api/ and package.json.
Leave src/modules/<id>/ out of it for every module you have edited. That code is yours, and a
newer version of it is not an upgrade — it is somebody else's app.
Read the changelog for what a diff cannot apply
A file diff shows a renamed environment variable but not that your Supabase project needs a migration, and not that saved links now point at addresses the app no longer serves. The changelog's Breaking section is where those live. What one has looked like in practice, 0.2.0:
The wizard asks for a workspace, not a bot. An app scaffolded by 0.1.0 was pinned to the one
bot id chosen at setup. The change is in the shell and the environment — VITE_CHATFUEL_WORKSPACE_ID
instead of a bot id, and a bot picker in the top bar — so applying it by hand means taking the new
src/App.tsx, src/Switcher.tsx and src/useWorkspaces.ts, then setting the new variable. Taking
the shell diff and leaving the variable is the failure mode: the app builds and opens on nothing.
The scaffolded app has real URLs. /livechat, not #/livechat. This one comes free with the
shell diff — src/lib/route.ts rewrites an old address the first time it is opened, so links in
invite mail and bookmarks keep working — but every host has to serve the SPA fallback, which is
a change in vite.config.ts, server/entry.ts, the Dockerfile and vercel.json rather than in
your module code.
auth: an account is no longer one bot. This is the one no diff can do for you. The app half
arrives with the shell files; the data half is a migration you run on your own Supabase project,
supabase/migrations/0002_chatfuel_multi_bot.sql, after 0001. The wizard applies it when it has
a Supabase access token; otherwise you paste it into the SQL editor. Every migration in the
scaffold is idempotent and applied in name order, so re-running one over a project that already
has it is safe.
The habit worth keeping: read the Breaking section first, decide what you are taking, then diff. Diffing first tells you what changed and never why.
Take the new skills
The skills are what your coding agent reads, and they go stale in a way nothing in the app does — a skill describing a route that moved is worse than no skill. Re-running the wizard reinstalls them: an existing directory is replaced only after it tells you which version is there and you confirm.
If you switch coding agents at the same time, the layouts differ and neither CLI reads the other's
directory — Claude Code loads .claude/skills/ and CLAUDE.md, Codex loads .agents/skills/ and
AGENTS.md. A single run that starts with neither CLI on PATH writes Claude's layout for want of
a better guess, and relayoutSkills moves the installed directories over if you then accept the
Codex install at the handoff. It moves them rather than copying, and takes the vacated directories
with it only while they are empty — a global install shares ~/.claude with everything else you
keep there. Pass --agent claude or --agent codex to settle it up front instead.
Verify
From the app directory:
npm run check && npm testcheck is tsc --noEmit over the app and a second pass over the scripts; test is the suite that
ships with the app, including the render smoke test every module carries. A vendored tree that
half-landed shows up here as a type error, and a shell file taken without the file it now imports
shows up as a module that renders nothing.
Then start it and click into each module:
npm run devnpx @chatfuel/wizard doctor is the other half of the check — it reports Node, the package
manager, the coding agent and the bundled content, and it is the fastest way to find out that the
CLI you ran is not the one you thought you ran.
What to read next
Vendoring is the decision this page is the consequence of. Agent skills covers what the skill directories hold and why they are versioned separately from the code. The CLI reference lists every flag the scratch run above uses.
Put it in a repository
The app directory holds a live Chatfuel token, so the first push is a secret question before it is a git question. What the wizard's GitHub step checks, how to do it by hand, and how to make git push the deploy command.
What is in the reference
Every command, flag, wizard step, environment variable and generated script, as lists you can check a value against.