Run it in CI, or without a terminal
--yes answers every question the wizard asks except the credentials, which have to be in the environment — and it refuses two steps outright.
Every question the wizard asks has a non-interactive answer, and --yes takes it. The credentials
are the exception, because a command line is not the place for them: they come from the environment
or the run stops.
Stopping is the design. The prompt library exits the process on end-of-input, so a scripted run
that fell through to a question nobody could answer would report success and leave nothing behind.
A run that has questions and finds nothing attached to the terminal says so before it spends
anything, and one whose CHATFUEL_TOKEN is missing says that and exits 1. Those are the versions
you can put in a pipeline.
Ask the machine what it can see
npx @chatfuel/wizard doctorOne row each: Node against the floor, the outbound proxy it will use, which coding agents are on
PATH, whether the content is a published package or a repository checkout, how many files its
lock pins, whether the cache is writable, how many modules are ready, which commit following the
branch lands on and how it got there, and one real file fetched from that commit and checked
against its digest. A missing agent is a warning; anything else fails and the command exits 1.
Run it as the first job of the pipeline. It asks nothing and creates nothing, so a red row here is a machine problem rather than a credentials problem, and you find out before spending a token check on it.
Put the credentials in the environment
| Name | What needs it |
|---|---|
CHATFUEL_TOKEN | Always. There is no flag for it. Generate one at the token page. |
SUPABASE_ACCESS_TOKEN | Only with the auth module, and only if you are not passing --supabase-token. |
SUPABASE_SERVICE_ROLE_KEY | Only on the manual Supabase path, where the wizard makes no API call and so cannot read the key itself. Under --yes it is used with a warning naming the project it is about to be written for — nothing here can tell whether it belongs to that one. |
ADMIN_PASSWORD | Only with the admin module, and only if you want to choose it. A run that finds neither the flag nor the variable generates a strong one, writes it to .env and prints it once at the end. |
The token is used on the first attempt in place of the prompt, and then checked against the API.
Under --yes there is no second attempt: a token that is missing, or one the API rejects, ends the
run with a sentence naming which of the two happened.
Pass these as environment variables rather than as flags. --supabase-token and
--admin-password take the same values, and a flag's value is in ps output for every user on the
machine and in the shell's history file — the wizard warns once, by name, when you use one anyway.
An export typed at a prompt lands in the history too, so source a file instead, keep it 0600,
and let CI inject its own secrets:
set -a; . ./secrets.env; set +a # exported for this shell, typed into no historyAnswer the rest with flags
--yes alone gives you a standalone app in ./chatfuel-app with every ready module except the
opt-in ones, skills in the project, the proxy answering its own origin only, and the Chatfuel
workspace picked for you. The flags that are worth setting anyway:
| Flag | Why in CI |
|---|---|
--dir <path> | The default is ./chatfuel-app relative to where you started. A directory that exists and is not empty is refused outright. |
--modules <ids> | --yes skips opt-in modules, so auth and admin arrive only if you name them. An unknown id ends the run and lists the ready ones. |
--workspace <id> | Taken silently when the account owns exactly one. With several and the auth module, --yes refuses — which workspace pays is not a question to answer on somebody's behalf. |
--agent <id> | With both CLIs on PATH, --yes takes the first one it detected instead of asking. Name the one whose skills layout you want. |
--app-name / --logo | The name otherwise falls back to the workspace's title; the mark otherwise stays the shipped one, because --yes never asks for a logo. |
--signup <mode> | On a Supabase project the run creates, --yes leaves sign-up open with email addresses taken on trust, and warns. confirm-email or closed is the choice made deliberately. |
--allowed-origins <list> | --yes leaves the proxy answering its own origin only. Name the others if a second front end you run has to call it. |
--embed is the only thing that chooses embed mode — --yes on its own stays standalone. The
full table, including what each flag falls back to, is the CLI
reference.
Some things --yes does without asking, worth knowing before you point it at a machine you care
about: it adds .env to .gitignore and says so, installs no coding agent when none is present,
starts no dev server at the end, takes the Business plan unless --pricing names another, prints
its checkout link and moves on, and leaves admin recovery links off.
One thing it refuses rather than assumes: an app preset from a catalog named only by
CHATFUEL_APPS_REPO. A preset is trusted code and that variable can predate the command by months,
so --apps-repo is what says the catalog is meant — it is fetched with a warning — and the
environment variable alone stops the run.
The one thing it will not do is replace a skill directory the app's lock does not record as the
wizard's. In embed mode the skills go into your project, so an unattended run in a repository
that already has a chatfuel-* skill leaves it exactly as it is, says so, and records it as kept —
which is also what stops a later update from touching it. The closing summary lists any it left.
Name the Supabase project up front
The auth module is the one module with a second system behind it, and under --yes it needs to
be told which project — a token on its own does not say. There are exactly two accepted shapes:
SUPABASE_ACCESS_TOKEN (or --supabase-token) plus one of:
--supabase-project <ref> # a project that already exists
--supabase-create <name> # make one, or reuse the one already called that--supabase-create reusing by name is what keeps a repeated pipeline from spending the account's
other free project on a duplicate. Two projects with that name is an error naming both refs rather
than a guess.
Add --supabase-org <slug> when the token sees more than one organization — under --yes that is
otherwise a refusal that lists the slugs. --supabase-region defaults to the recommended one.
Neither shape present and the run stops before the scaffold writes anything, with a hint naming
every flag that would have worked. A --yes run also never creates a project it was not explicitly
told to create — that is a second, deliberate backstop behind the same rule.
Get the calls out of the network
Node's own fetch ignores HTTP_PROXY and HTTPS_PROXY unless the process was started with a
flag nobody types, so the wizard reads them itself — https_proxy, HTTPS_PROXY, http_proxy,
HTTP_PROXY, all_proxy, ALL_PROXY, lowercase winning, honouring NO_PROXY, with loopback
always exempt.
export HTTPS_PROXY=http://proxy.example:8080What has to be reachable, and when:
| Host | For |
|---|---|
panel.chatfuel.com | Always. |
api.github.com | Always, to resolve the branch the content comes from — and again to push. A run that cannot reach it installs the content the wizard shipped with and says so. |
raw.githubusercontent.com | The content itself, on any run that is not working from a cache it already filled. |
registry.npmjs.org | Installing anything. |
api.supabase.com | The auth module's access-token path. |
api.vercel.com | Deploying. |
github.com | Cloning the apps catalog, with --app. |
doctor prints the proxy it resolved on its own row, with any credentials in it masked, which is
the cheapest way to find out that the variable is spelled differently than you think.
Rehearse
Two flags, and they stop different things.
--plan writes nothing at all. Every question is still asked and every read-only call still made —
the token check, the workspace list, the Supabase lookups — and then the run prints each file it
would have created, with its path: the app directory, the vendored trees, the migrations, the
.gitignore line and .env, the skills, .chatfuel/lock.json, the install, and the agent's two
files. In embed mode the same, against your own project, which is the mode where it matters most: a
"preview" that created src/chatfuel/ and dropped a lock in would have converted somebody's
repository into a wizard-managed app. It implies --dry-run.
--dry-run stops account assets and keeps the files. The app is still written to disk, its lock
beside it and its dependencies installed; what it holds back are the calls that change something on
an account — the Chatfuel trial checkout, the Supabase project creation, the database migrations and
the auth-config patches. Each is printed as the call it would have made. A project it would have
created does not exist, so there are no keys to read and .env carries empty Supabase values. An
account that owns no workspace gets a stand-in that exists nowhere else, and every step that would
spend it is guarded the same way.
So --plan tells you your flags parse and your tokens work, for the price of nothing, and
--dry-run tells you the scaffold builds, for the price of a directory you delete afterwards. Both
still fill the wizard's own content cache, which is not your project.
Run it, and check what came out
CHATFUEL_TOKEN="$CHATFUEL_TOKEN" \
SUPABASE_ACCESS_TOKEN="$SUPABASE_ACCESS_TOKEN" \
npx @chatfuel/wizard \
--yes \
--dir ./chatfuel-app \
--modules auth,livechat,contacts,deals \
--workspace 8f3c1d2e-4a5b-6c7d-8e9f-0a1b2c3d4e5f \
--agent claude \
--app-name "Acme Concierge" \
--logo ./brand/acme.svg \
--supabase-create acme-concierge \
--signup confirm-email \
--app-url https://app.acme.comA failed run exits 1 with the wizard's own sentence and its hint, so the pipeline step goes red on its own; an interrupted one exits 130, having removed the directory it had created. A successful one leaves an app with its dependencies installed:
cd chatfuel-app
npm run checktsc passing over the whole app and its scripts is the check that means the registry, the modules
and the vendored trees all agree. Then read .env: every variable the run resolved is in it, and it
is the file the deployment's configuration is pushed from.
Two things will not be there, on purpose. No deployment, and no GitHub repository: both
steps are interactive-only and skipped under --yes, without a TTY, and under --dry-run. A
deployment is a public URL with your Chatfuel token behind it, and a repository under your account
holds the source of an app wired to your bot; neither is something a flag should be able to cause.
Both are ordinary commands afterwards — npm run deploy, and git init && git add -A && git commit -m "Initial commit" && gh repo create --source . --push.
Next: deploy for the step --yes would not take, or
the CLI reference for every flag and its fallback.
Replace the Chatfuel token
npx @chatfuel/wizard auth rewrites one line of one file. The rest of the job is getting that value to wherever the proxy actually reads it.
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.