Skip to content
ChatfuelSDK
Guides

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.

The wizard writes an ordinary React + Vite project and then leaves. Deploying it needs no repository — npm run deploy works from a bare directory and always will — so the code can be live and still sitting in an un-versioned folder on one laptop. This is the separate question.

It is a secret question before it is a git question. The app directory holds a live Chatfuel token, and that token can read and change every bot in the account it belongs to. .gitignore covers .env, but a .gitignore can be declined, a file of your own can appear between then and now, and a wrong answer here is a token on the internet.

Check what would be committed

The scaffold ships with this:

.gitignore
node_modules/
dist/
.env
.env.*
!.env.example
.DS_Store

# Vercel CLI project link (created by `npm run deploy`).
.vercel/

# Supabase CLI working directory.
.supabase/

# `.envrc` exists for one purpose — `export CHATFUEL_TOKEN=…` — and direnv users
# never think of it as a dotenv file, so the rules above do not cover it.
.envrc

# Keys, and the server log: with AUTH_RECOVERY_LINK_LOG on, the log is the only
# place a password recovery link is ever written.
*.pem
*.key
*.log

.env.example is the one .env* meant to be published — it holds the names of the variables and none of the values. Before the first push, read the index rather than the ignore file:

git add -A
git ls-files --cached |
  grep -E '(^|/)\.env|(^|/)\.vercel/|(^|/)\.aws/credentials$|(^|/)(\.npmrc|\.git-credentials|\.netrc|_netrc)$|\.(pem|key|p12|pfx|jks|keystore|ppk)$|(^|/)id_(rsa|ed25519)' |
  grep -vE '\.(example|sample|template)$'

Nothing back means nothing forbidden is staged. Then check by content as well as by name, because a token can be pasted into a file that is not called .env:

TOKEN=$(grep '^CHATFUEL_TOKEN=' .env | cut -d= -f2-)
[ -n "$TOKEN" ] && git grep --cached -l -F -e "$TOKEN"

That is the pair of checks the wizard runs itself. By name it refuses every file whose name starts with .env and does not end .example, .sample or .template — .envrc included — plus *.pem, *.key, *.p12, *.pfx, *.jks, *.keystore, *.ppk, id_rsa*, id_ed25519*, .npmrc, .git-credentials, .netrc, _netrc, an .aws/credentials, and anything under .vercel/.

By content it searches for the values the run itself holds, read out of the scaffold's own variable declarations rather than a list somebody has to remember to extend: the Chatfuel token, the Supabase secret key, the Supabase access token, and every module variable declared secret — ADMIN_PASSWORD and PUBLISHING_SECRET among them. Anything shorter than 16 characters is skipped. The Supabase URL, the anon key and the workspace id are declared not secret and are never searched for: all three are shipped to the browser by design, and a gate that cries wolf gets switched off.

The patterns go to git in a file rather than on a command line, which every process on the machine can read. And a scan that could not run stops the push instead of blessing it — a broken index and a clean tree both come back as no output, and reading the first as the second is how a live token gets published.

Push it

Say yes to Put this app on GitHub? — a question the run asks after the deploy and before the agent handoff, because gh auth login prints a one-time code and waits for a browser, and after the handoff an agent session may already own the terminal.

It never runs under --yes, --dry-run or --plan, and never without a TTY. A repository under somebody's GitHub account, holding the source of an app wired to their bot, is not something a non-interactive flag should be able to cause.

If the deploy step found that Vercel has no GitHub connection, that comes next, before the name is asked for: the wizard says so, prints the page where you grant it, and asks Added it?. It is in front of the name because everything past that prompt costs something — a commit, a repository under your account, a push — while at vercel git connect, where the refusal would otherwise arrive, all three have already happened. Answering no carries straight on: this is a convenience, not a gate.

What happens then: it asks for a name and rewrites it the way GitHub would have silently (letters, digits, dot, dash and underscore), asks Who can see it?, and prepares the local repository — git init, HEAD on refs/heads/main, everything staged, the two scans above, and a first commit. A directory that is already inside another repository is left alone; one that already has an origin is reported rather than taken over, unless that remote is empty, which is the wizard's own push interrupted halfway and gets offered again. If a scan finds something, the .git it created a moment ago is removed again — in a repository that was already there, the index is reset instead, so the staged copy of the offending file is not left one blind git commit from the publication that was just prevented.

A directory that already had commits gets a third check, because a push publishes every commit and not just the new one: every path the history holds on any branch, and the contents of the blobs behind them. That content scan is bounded — 20 000 blobs, 1 MB each, 256 MB in total — and it says how many of the repository's committed files it actually read whenever a bound was reached, because a bounded scan that found nothing is a different promise from a clean history. A hit is reported and the push is offered anyway, defaulting to no; under --yes the answer is no and the run says to rewrite the history or rotate those credentials first.

For the GitHub side it wants gh. Already installed is free; otherwise it offers Homebrew on macOS, winget or Scoop on Windows, and the official release archive everywhere else — the only route on Linux, because every distro package manager there needs sudo and the wizard escalates privileges on nobody's behalf. Every route ends by running the binary and seeing if it answers. If there is no working gh at all, it asks for a GitHub token instead and uses the API. That fallback is for the absence of a gh, not for a gh that tried and failed: a name already taken fails identically on both paths.

Make a push deploy

Once a repository exists, connecting it means git push and npm run deploy do the same thing and you can stop remembering which:

npm run connect-git

Two things must already be true, and the script checks both before it calls the Vercel CLI, because Vercel's own error for either is a menu you did not expect. The project must be linked — .vercel/project.json, written by npm run deploy — and there must be an origin remote. An SSH remote or one carrying a username names the same repository in a form the CLI does not accept, so the script rewrites it to a plain https://host/owner/repo first.

Re-running is safe: vercel git connect on an already-connected project is a no-op that says so.

The commonest failure is not this app's: a Vercel account with no GitHub connection at all, which only you can grant, in a browser. The script prints Vercel's own words first and then the two places to fix it.

The wizard offers this step itself, but only when the deploy step already linked a project — without one there is nothing to connect the repository to.

And only when Vercel's GitHub connection is there. Still missing at that point, and it does not ask whether a push should redeploy — vercel git connect would refuse on the first call — but says what is off, where to switch it on, and the same npm run connect-git to run once you have.

Verify

npm run connect-git prints Connected — pushing to the default branch now deploys to production. Prove it rather than believe it. Change something committed that you can see from the outside — a heading in one of your modules, a token in src/vendor/ui/styles/tokens.css, the logo file in public/ — then:

git add -A && git commit -m "A change I can see" && git push

Watch the deployment appear on Vercel and open the production URL. If nothing happens, the repository is not connected and npm run connect-git says why.

Then check the repository itself for the thing that matters most:

gh api repos/{owner}/{repo}/contents/.env --jq .name

A 404 is the answer you want. Anything else means .env is in the repository and you are on the callout below.

A token that has landed in a commit is public from that moment, and rewriting history does not un-publish it — anything that fetched the repository in between still has it. Rotate it: generate a new token, then run npx @chatfuel/wizard auth in the app directory, which replaces the CHATFUEL_TOKEN line in .env and keeps every other line. Push the new value to your deployment with npm run deploy, which overwrites the variable rather than adding a second one. Then remove the file from the repository and fix the .gitignore that let it through.

Rotate the token is the full procedure for the callout above. Vercel covers what npm run deploy does and what it pushes; scripts lists every script in the app, connect-git included.

On this page