Tooling
CLI
On this page
Install
Node 20 or newer. The package is @nanoflags/cli; the command is nanoflags.
npx @nanoflags/cli --helpPublished on npm with provenance from the public nanoflags-sdk repository (Apache-2.0).
The only runtime dependency is typescript, used to read source files for refs scan and cleanup and loaded only by those commands.
Authentication
| Variable / flag | Description |
|---|---|
NANOFLAGS_KEY / --key | A CI key (nf_ci_…: export, scenarios, code references, reading the published revision) or an admin key (nf_adm_…: all of that, plus publish). SDK keys are refused by these endpoints. |
NANOFLAGS_BASE_URL / --base-url | Edge URL. Default https://edge.nanoflags.io. Must be https; plain http only for localhost. |
NANOFLAGS_SDK_KEY / --sdk-key | Optional, only for eval --source published: a server SDK key to read the bundle SDKs receive. |
- A key belongs to one environment of one project; commands act on that environment.
- Prefer the environment variables: command-line arguments are visible in process lists and shell history. The key is sent only as an
Authorizationheader to the base URL and is never printed. - Use a CI key in CI. It cannot publish, so a leaked CI key cannot change what your users get.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | A check failed: a scenario failed, or a publish was refused (any other refusal of a request also exits 1) |
| 2 | Usage, configuration or key problem: missing or rejected key (401), key of the wrong kind (403), bad arguments, refused cleanup |
| 3 | NanoFlags could not be reached, answered with a server error, or does not offer the endpoint |
Global options
| Option | Description |
|---|---|
--key <key> | CI or admin key. Default $NANOFLAGS_KEY. |
--sdk-key <key> | Server SDK key for eval --source published. Default $NANOFLAGS_SDK_KEY. |
--base-url <url> | Default $NANOFLAGS_BASE_URL or https://edge.nanoflags.io. |
--export <file> | Read a saved nanoflags export instead of calling the API (eval, flags list, refs scan, cleanup): works offline. |
-h, --help · -v, --version | Help and version. |
nanoflags export
nanoflags export [--out <file>]GET /v1/admin/export. Prints the project as JSON: flags (with permanent, archived, …), segments, environments, and the draft configuration of every environment. --out writes a file instead. CI and admin keys.
nanoflags flags list
nanoflags flags list [--json]Table of the project's flags: key, type, purpose, lifetime (permanent/temporary), client exposure, and on/off in the draft of the key's environment (or archived). --json prints the flag list as JSON.
nanoflags flags create
nanoflags flags create <key> --type boolean|string|number|json [--default <json>] [--description <text>] [--name <text>] [--json]POST /v1/admin/flags. Creates a flag in the key's project. Admin key only. Nothing is published: every environment's draft starts with the default value, and SDKs see the flag after the next publish. The creation is recorded in Activity with the key's name.
--typedefaults toboolean;--defaultdefaults tofalsefor boolean flags and is required for the other types.--defaultis JSON and must match the type. For string flags a value that is not JSON is the string itself (--default blue).- An existing key is refused with
CONFLICT(exit 1). Purpose, owner, review date and client exposure are set in the dashboard.
$ NANOFLAGS_KEY=nf_adm_… nanoflags flags create new-checkout --description "New checkout flow"
Created new-checkout (boolean, default false).
Every environment's draft now has it (development, staging, production). Nothing was published.nanoflags eval
nanoflags eval <flag> [--context <json>] [--source draft|published] [--fallback <json>] [--environment <key>] [--json]Evaluates one flag locally with the NanoFlags engine (the code the SDKs and the edge run) and prints the value, the reason and the trace.
$ nanoflags eval checkout-v2 --context '{"key":"u1","signedIn":true,"region":"US"}'
checkout-v2 = false
reason: rule_match · rule r1 (#1) · outside rollout (bucket 95.27)
source: draft of staging
trace:
…--source draft(default): the draft from the export.--environmentpicks another environment's draft.--source published: the published revision of the key's environment, read with the CI or admin key (GET /v1/ci/published), or with a server SDK key fromNANOFLAGS_SDK_KEY.--environmentis not accepted: a key reads its own environment. Nothing published yet → exit 2.- An unknown flag is not an error: the fallback (
--fallback, defaultnull) is printed with reasonflag_not_foundand the command exits 0. --jsonprints the full evaluation result with its source.
nanoflags scenarios run
nanoflags scenarios run [--source draft|revision] [--commit <sha>] [--run-url <url>] [--json]POST /v1/ci/scenarios/run. NanoFlags evaluates every scenario marked "include in CI" against the draft (default) or the published revision, records the run, and returns the results. The command prints a table and exits 1 when any scenario fails.
- In GitHub Actions,
--commitand--run-urldefault fromGITHUB_SHAand the run's URL, and a Markdown summary is appended toGITHUB_STEP_SUMMARY. - Runs on the draft are what a "require scenarios" environment counts before a publish; see CI.
$ NANOFLAGS_KEY=nf_ci_… nanoflags scenarios run --source draft
RESULT SCENARIO FLAG EXPECTED ACTUAL REASON
pass US signed-in gets v2 checkout-v2 true true rule_match
FAIL EU stays on v1 checkout-v2 false true rule_match
1 passed, 1 failed · source draft · checksum 3f2a… · published r12nanoflags publish
nanoflags publish --expected-revision <n> [--note <text>] [--json]POST /v1/admin/publish: publishes the draft of the key's environment. Admin key only. --expected-revision is the revision you believe is published now; if someone else published in the meantime nothing happens.
| Refusal (exit 1) | Meaning |
|---|---|
DESTINATION_CHANGED | The environment is at another revision than expected. Review, then retry with the new number. |
NO_CHANGES | The draft equals the published configuration. |
CHANGE_REQUIRED | The environment requires a reviewed change. Publish from the dashboard; keys cannot bypass review. |
SCENARIOS_REQUIRED | The environment requires passing scenarios on the current draft: run nanoflags scenarios run --source draft first. |
$ NANOFLAGS_KEY=nf_adm_… nanoflags publish --expected-revision 12 --note "enable checkout v2 for staff"
Published r13 (checksum 9c41…).nanoflags refs scan
nanoflags refs scan [dir] --repo <owner/name> [--commit <sha>] [--dry-run] [--json] [--max-file-size <bytes>]Finds flag references in .ts .tsx .js .jsx .mjs .cjs files under dir (default .) and uploads them with PUT /v1/ci/code-refs, replacing the references previously stored for that repository.
A reference is a call whose callee name is boolean, string, number, json, evaluate, useFlag, getBooleanValue, getStringValue, getNumberValue, getObjectValue or a …Details variant, and whose first argument is a string literal equal to a flag key of the project.
- Files are parsed with the TypeScript compiler; nothing is executed. Not found: keys built at run time, keys passed through variables or constants, wrappers with other names.
- Skipped:
node_modules,dist,build,.next,.git,.d.tsfiles, symbolic links, and files above 1 MB (--max-file-size). At most 5,000 references are uploaded. - Uploaded per reference: flag key, relative path, line, and the source line (up to 240 characters). Check that this is acceptable for your code before enabling it.
--dry-runprints the references and uploads nothing (no--reponeeded).--repo/--commitdefault fromGITHUB_REPOSITORY/GITHUB_SHA.
nanoflags cleanup
nanoflags cleanup checkout-v2 --value true src # show the diff (dry run)
nanoflags cleanup checkout-v2 --value true src --write # edit the files in placeFor a flag that is permanently resolved to one value, rewrites the code that still reads it. The default is a dry run that prints a unified diff.
| Pattern | Result |
|---|---|
if (<read>) A else B, also negated, in else if chains, combined with &&/|| | The branch that runs; the other one is removed. |
<read> ? a : b | a or b. |
<read> && x, <read> || x, and the reverse in a condition | x, or the constant. |
<read> === "literal" / !== | Folded, then simplified as above. |
const on = <read>; followed by uses of on | Every use is treated like the read itself; the declaration is removed (kept when exported). |
| Anything else (let, arguments, object properties, JSX attributes, loops) | The read is replaced with the literal. |
Limits, by design
- It never commits, pushes, opens a pull request or merges, and runs no other program. Review the diff, run your tests, commit yourself.
- It refuses flags marked permanent and values whose type does not match the flag (exit 2).
- It lists every reference it did not transform, with the reason:
evaluate()/…Details(), un-awaitedget…Value(), optional calls, reads whose callee or arguments contain calls, accessors of another type,useFlagwith a non-literal fallback. - Only literal flag keys are recognised, as in
refs scan. - It does not delete code made unreachable after a
return/throw(those places are listed), does not remove imports or helpers that became unused, and does not reformat: run your linter and formatter afterwards. - If a rewrite would leave a file with new syntax errors, that file is left untouched and reported. It works on syntax, one file at a time, without type information: a proposal for a person to review, not a proof.