CLI Setup Command
avsb init sets up A vs B in the project you are standing in, and finishes by
proving it works rather than telling you what to do next.
npx @avsbhq/cli initIn one run it:
- reads your
package.jsonand works out which framework you use, - links an existing project or creates one,
- writes the SDK key into the environment file that framework actually reads,
- names the install command for your package manager (or runs it),
- writes an example file that reads a real flag,
- offers to generate TypeScript types for your flags,
- waits for your app's first check-in and prints what it saw.
What it writes, and what it will not touch
avsb init writes at most four files, all inside the directory you point it at:
| File | When |
|---|---|
An environment file (.env.local, .env) | The framework has one, and the variable is not already set |
.gitignore | Inside a git repository, when git does not already ignore that environment file. One line naming the file is added under a comment saying avsb init put it there |
An example file (avsb-example.tsx and friends) | It does not already exist |
| Generated flag types | Only with --codegen, or after saying yes to the prompt |
Two rules it never breaks:
- It never overwrites. A variable already set is left alone, even when it holds a different key, and the run says so. An example file that already exists is left alone too.
- It never invents a credential. It uses the login
avsb loginsaved on your machine, or theAVSB_TOKENenvironment variable.
Running it twice is safe: the second run leaves the environment file byte-identical, lists it under Left alone rather than Written, and simply waits for a check-in again.
Frameworks it knows
Detection reads your dependencies. Pass --framework to override it.
| Detected | Package | Variable | File |
|---|---|---|---|
| Next.js | @avsbhq/next | AVSB_SDK_KEY | .env.local |
| Nuxt | @avsbhq/nuxt | NUXT_PUBLIC_AVSB_SDK_KEY | .env |
| SvelteKit | @avsbhq/svelte | PUBLIC_AVSB_SDK_KEY | .env |
| React, Vue, Solid, Svelte built with Vite | @avsbhq/react, @avsbhq/vue, @avsbhq/solid, @avsbhq/svelte | VITE_AVSB_SDK_KEY | .env.local |
React built with Create React App (react-scripts) | @avsbhq/react | REACT_APP_AVSB_SDK_KEY | .env.local |
Vue built with Vue CLI (@vue/cli-service) | @avsbhq/vue | VUE_APP_AVSB_SDK_KEY | .env.local |
| React, Vue, Solid, Svelte with any other bundler | as above | AVSB_SDK_KEY, to rename | .env.local |
| Angular | @avsbhq/angular | none | none |
| Node.js | @avsbhq/node | AVSB_SDK_KEY | .env |
No package.json | none | none | none |
For browser frameworks the bundler decides the variable name, because the
bundler decides which variables reach browser code: Vite exposes VITE_
variables, Create React App REACT_APP_, and Vue CLI VUE_APP_. The run says
which bundler it found and which name that gave, for example
React (found react in package.json, key in REACT_APP_AVSB_SDK_KEY: found react-scripts, and Create React App only exposes REACT_APP_ variables to the browser, installs with npm). With none of the three, avsb init writes
AVSB_SDK_KEY and says so in its Next list: rename it to the prefix your
bundler exposes, or pass the key to the provider directly.
Angular has no environment-file convention, so the key goes into the example where you bootstrap the client. That is expected: an SDK key is a public identifier, safe to ship in a browser bundle.
A directory with no package.json has nothing to install into, so those steps
are skipped and the run says so. What happens next depends on the project, not
the directory: a web experiments project is pointed at the snippet tag, and a
feature flags project still gets its SDK key, its typed flags, and the wait for
the first check-in, because the application it belongs to may well live
somewhere else.
Flags
| Flag | Default | Meaning |
|---|---|---|
-p, --project <project> | prompt | Link a project: PRJ-42, 42, or a dashboard URL |
--create <name> | prompt | Create a feature flags project with this name |
-e, --env <environment> | production | Which environment's SDK key to write |
--framework <framework> | detected | next, nuxt, angular, svelte, vue, solid, react, node, none |
--flag <key> | first flag in the project | Flag the example reads |
-d, --dir <path> | . | Project directory to set up |
--install | off | Run the install command instead of printing it |
--no-env-file | off | Do not write an environment file |
--no-example | off | Do not write the example |
--example-path <path> | framework default | Where the example goes |
--codegen | prompt | Generate flag types as part of setup |
--codegen-output <path> | ./src/generated/flags.ts | Where generated types go |
--wait <seconds> | 120 | How long to wait for the first check-in |
--no-wait | off | Do not wait at all |
--require-heartbeat | off | Exit non-zero when no check-in arrives |
--no-input | off | Never prompt; use flags and defaults only |
--json, --quiet | off | See Machine-readable output |
Every prompt has a flag, so the command works unattended. With --no-input,
--json, no terminal, or CI set, nothing is asked: it takes the documented
default, or stops and names the flag it needed.
The proof at the end
Every A vs B SDK reports in after it successfully loads its configuration. That
report is what avsb init waits for, and the last line is printed only when two
things were actually observed: a check-in newer than this run arrived for that
key, and the flag in your example is in the configuration that key serves. A
check-in that was already recorded when the wait began does not count, even one
from a minute earlier: only one that arrives while the CLI is watching (or in
the first few seconds of the wait) proves this setup, so an app that has since
stopped cannot pass for a working one.
sdk_production_ttqm0eaj4vth1krcb2xn checked in and is serving 'checkout_v2' for Next.js 8s after setupThe line says "checked in and is serving" rather than "evaluated" on purpose. The evaluation happens inside your process, where the CLI cannot see it, so the line claims only what was actually observed. The line under it says what the environment serves for that flag when no targeting rule matches.
If nothing arrives before the timeout, everything above it is still written and the run says exactly that, followed by the four things that are usually wrong, most likely first:
- Nothing in the environment is turned on yet. A new environment has no
configuration for the SDK to load until something in it is published, and
the SDK only checks in after it loads one. Turn the flag on with
avsb flags toggle <key> --env production --on. - The app never started.
- The key in the code is a different one.
- Outbound requests to the CDN or the platform are blocked.
! No check-in from Production in 120s. Everything above is written; only the proof is missing. Turn a flag on with `avsb flags toggle checkout_v2 --env production --on`. An environment where nothing has been turned on has no configuration for the SDK to load, and the SDK checks in only after it loads one. Start your app once, then run `avsb init --wait 120` again to watch for it.The "Next" list says the same about a flag that exists but is not turned on in the environment you set up, with the exact command.
A timeout is not a failure exit code, because the setup succeeded. Pass
--require-heartbeat in CI if you want the run to fail without the proof. It
cannot be combined with --no-wait: the two cancel each other out, so the pair
is refused as a usage error (exit 2) rather than failing on a wait that never
happened.
With --quiet, the proof line is the only thing printed on success. Warnings
and the timeout guidance still go to stderr.
In a script
avsb init --project PRJ-42 --env staging --no-input --no-wait --jsonThe JSON envelope carries the project, the environment, the detected framework, every step with the reason it ran or was skipped, the files written, and the check-in result:
{ "ok": true, "command": "init", "data": { "project": { "shortId": 42, "name": "Checkout", "type": "FEATURE_FLAG", "created": false }, "environment": { "key": "staging", "name": "Staging", "sdkKey": "sdk_staging_..." }, "framework": { "id": "next", "label": "Next.js", "detectedFrom": "next", "packageManager": "pnpm" }, "flag": { "key": "checkout_v2", "exists": true, "served": { "variationKey": "on", "value": true } }, "steps": [ { "id": "env", "action": "write", "detail": "Write AVSB_SDK_KEY into .env.local." } ], "files": { "env": "/app/.env.local", "envOutcome": "created", "example": null, "codegen": null }, "install": { "command": "pnpm add @avsbhq/next", "ran": false, "ok": false }, "heartbeat": { "waited": false, "seen": false, "at": null, "sdkType": null, "elapsedMs": 0 }, "proof": null }}Teaching your coding agent
avsb agents-md installWrites an A vs B section into CLAUDE.md and AGENTS.md in your repository: the
credential vocabulary, the CLI commands, how a flag is read, and the rules that
keep an agent from inventing flag keys or putting a token in application code.
The section sits between markers, so re-running the command replaces it instead of adding a second copy, and everything you wrote around it is untouched.
| Flag | Meaning |
|---|---|
-d, --dir <path> | Repository directory to write into |
--claude | Only CLAUDE.md |
--agents | Only AGENTS.md |
--print | Print the section instead of writing it |
-p, --project <project> | Project to name in the section |
-e, --env <environment> | Environment to name in the section |
--json, --quiet | See Machine-readable output |
It makes no network calls and needs no login: the section is built from what is already in your repository.
Related
- CLI Tool for every other command
- CLI Authentication for tokens and CI
- CLI Codegen for typed flags
- Credentials for what each key is and is not