Update an app to newer content
npx @chatfuel/wizard@latest update reads the lock in your app, overwrites what you never touched, and hands you the files where your edits and ours landed on the same lines.
There is no npm update to run. Nothing in your package.json points back at this repository —
the design system, the API client, the proxy and the schema were copied into your project as
source, and the CLI that put them there was gone the moment it finished.
What makes an update possible anyway is .chatfuel/lock.json, written into the app at scaffold
time. For every file it records where the bytes came from, what they hashed to when they landed,
and what upstream held at that moment. So the question an update has to answer — has anybody edited
this since? — is a digest comparison rather than a guess, and the answer decides each file on its
own.
npx @chatfuel/wizard@latest update --dry-run # what would change
npx @chatfuel/wizard@latest update # do itThe command moves the app to the content the wizard's branch holds now, not to whatever the published package was built against — that commit is only the floor a resolution has to descend from. So running it twice in a week does something the second time.
What it decides, file by file
| Upstream | On disk | What happens |
|---|---|---|
| Unchanged | Anything | Skipped. |
| Changed | Still the bytes the wizard wrote | Overwritten, no question asked. |
| Changed | Edited since, or deleted | Left exactly as it is, reported as a conflict. |
| Changed | The wizard rewrote it on the way in | A conflict too — the upstream bytes alone would drop the edit that makes the app run, so the lock names the transforms to re-apply. |
| Gone upstream | Anything | Skipped, and named. Usually a rename, which arrives here as a disappearance; the app keeps its copy for good. |
| Generated here | Anything | Never written. It has no upstream to compare against — see step 5. |
Conflicts are the normal outcome, not a failure. They are the files where your work and ours landed on the same lines, and the command exits 0 with them outstanding.
Three apps it will not touch at all: one with no lock, one scaffolded from a repository checkout — which pins a commit nothing outside that machine can resolve — and one built from a different content repository than the wizard you are running carries.
Commit what you have
git status --porcelainThe update refuses to write into a tree with uncommitted changes, and the refusal is the point: an
update is a pile of overwrites, and git checkout . undoes all of them at once — but only while
there is nothing else uncommitted to lose. Without the check, its writes would mix with your unsaved
work and neither could be taken back on its own. A directory that is not a git repository is refused
for the same reason, with the three commands that fix it.
See what it would change
npx @chatfuel/wizard@latest update --dry-runNothing is written. What you get is the commit range, then the lists: what would be updated, the conflicts with the reason beside each one, anything gone upstream, anything whose generated client would need regenerating, and a count of the skips broken down by why. The first two lists and the last are the whole story; the middle two are named rather than counted, because a number is not something anybody can act on.
Add --json for the machine-readable form — from and to, the wizard versions at each end,
the release notes for the range between them, and the three lists, with upstream's copy of every
conflicting file fetched into the wizard's cache and its path recorded as theirs. That fetch
happens under --dry-run too, and it is the one thing that does: it lands in the cache, not in
your app, and a plan whose conflicts have no upstream side to open is a plan nothing can act on.
The notes are the changelog between the two wizard versions, so they are empty on an update that moved content under one version — the common case. When they are there, they are the only thing that says why upstream changed, which is the difference between a merge and a guess.
Take the safe half
npx @chatfuel/wizard@latest updateThis overwrites the files nobody touched, moves the pin in the lock, and leaves every conflict alone. Each file is fetched into the wizard's cache and checked against its digest before it is copied, and each destination is re-checked on the way in: a directory on the path to it that turns out to be a symlink pointing out of the app stops the run rather than being written through.
The lock is rewritten with what was actually copied, whether the run finished or not — an interrupted update records the files it had already written, so the next one does not read them as edits you made.
Even an update with no files to copy moves the pin, which is what makes the next one ask about the right range.
Resolve the conflicts
Every app the wizard writes carries a chatfuel-update skill, whatever modules were picked,
because it is about the app's relationship to the wizard rather than about anything the app does.
Ask your coding agent to update the app and that skill takes the conflicts from here: it reads the
plan as JSON, opens each conflicting file beside upstream's copy of it, and merges them one at a
time with the release notes in hand.
The rule it works under is worth knowing, because it is what keeps the result reviewable: it may
change the files the CLI listed under conflicts and nothing else. Not the files the CLI wrote
itself, not the skipped ones, not the lock, and not .env. The one file it may create is
src/operationDocs.ts, for an app scaffolded before that barrel existed — update cannot add a
file the app never had, and the three hosts it just overwrote import it.
How each reason is resolved:
| Reason | What to do |
|---|---|
edited here | Merge upstream's change into your file. Keep your intent; take their fix. |
wizard rewrote it | Start from upstream's copy and re-apply the transforms the lock names. Copying it over the file as it stands is what drops the transform and breaks the app. |
deleted here | Decide. Nobody should resurrect a file you removed on purpose. |
Regenerate the client
The typed GraphQL client under src/vendor/api/generated/ is marked generated in the lock, so the
update never writes one — but both of its inputs, the schema and the operation documents, are files
it does move. When it has moved either, it prints a fourth list, regenerate the client, naming the
inputs, and then this:
npm run codegenThe generator is 265 packages the app does not otherwise need, so it is not one of its dependencies:
the first run prints the exact install line, with the versions the vendored client was generated
with, and stops. Run that line, then npm run codegen again.
The whole directory changing is what a correct regeneration looks like, not a conflict. Commit it together with the documents above it — a client that does not match the documents beside it is worse than an old one. An app embedded in a host project ships no generator at all, and the command says so instead of naming one.
Say what you settled
npx @chatfuel/wizard@latest update --resolved src/vendor/ui/Button.tsxThis records the merge in the lock, and it is not optional. Left unsaid, the lock still describes each file as it was before you merged it, so every future update reports the same conflict again and fetches upstream's copy again, for work that was already done.
Only a file the update is actually holding back may be settled, and every path is judged before any is recorded — one bad path refuses the whole call and names all of them, so nothing is half-recorded after a merge you have already made. Pass only the files you really resolved: one you left for somebody else stays a conflict, which is correct. A file you settled by leaving it deleted drops out of the lock entirely.
Check the app still runs
npm run check && npm testcheck is tsc --noEmit over the app and a second pass over its scripts; test is the suite that
ships with the app, including the render smoke test every module carries. A build that fails on a
field or a type name that does not exist is a regeneration that did not run, or one that ran before
the merges were finished. After that, the break is in the files you touched.
Then start it and click into each module:
npm run devWhat it never touches
- A file the app has never had. The lock maps your files to their origins, not the trees they came from, so an added upstream file has no place to land that the command could work out on its own. That is the skill's job, with the release notes in hand.
- Anything the lock marks generated — the typed client, the module registry, the nav table,
src/operationDocs.ts, andsrc/config/app.ts, which is your deployment's own settings and is recorded that way for exactly this effect. The defaults it overrides sit beside it insrc/config/defaults.ts, which is a plain copy and does keep receiving updates. .env. It is not in the lock and never will be.- A skill directory the wizard did not write. One that was already there when the app was scaffolded is recorded as kept, and the update skips it too — which is why the scaffold's closing summary named it at the time.
What to read next
Where the content comes from is the mechanism behind the commit this page keeps naming. Vendoring is the decision that makes an update a merge rather than a version bump, and Agent skills covers what else is in those directories. Every flag is on the CLI reference.
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, manifest field, proxy route, error code and generated script, as lists you can check a value against.