Skip to content
NanoFlags
Start free

Tooling

CLI

Export a project, evaluate flags locally with the same engine the SDKs use, run scenario checks in CI, publish, upload flag code references, and remove resolved flags from code.

Install

Node 20 or newer. The package is @nanoflags/cli; the command is nanoflags.

sh
npx @nanoflags/cli --help

Published 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 / flagDescription
NANOFLAGS_KEY / --keyA 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-urlEdge URL. Default https://edge.nanoflags.io. Must be https; plain http only for localhost.
NANOFLAGS_SDK_KEY / --sdk-keyOptional, 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 Authorization header 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

CodeMeaning
0Success
1A check failed: a scenario failed, or a publish was refused (any other refusal of a request also exits 1)
2Usage, configuration or key problem: missing or rejected key (401), key of the wrong kind (403), bad arguments, refused cleanup
3NanoFlags could not be reached, answered with a server error, or does not offer the endpoint

Global options

OptionDescription
--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, --versionHelp and version.

nanoflags export

sh
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

sh
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

sh
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.

  • --type defaults to boolean; --default defaults to false for boolean flags and is required for the other types.
  • --default is 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.
sh
$ 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

sh
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.

sh
$ 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. --environment picks 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 from NANOFLAGS_SDK_KEY. --environment is not accepted: a key reads its own environment. Nothing published yet → exit 2.
  • An unknown flag is not an error: the fallback (--fallback, default null) is printed with reason flag_not_found and the command exits 0.
  • --json prints the full evaluation result with its source.

nanoflags scenarios run

sh
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, --commit and --run-url default from GITHUB_SHA and the run's URL, and a Markdown summary is appended to GITHUB_STEP_SUMMARY.
  • Runs on the draft are what a "require scenarios" environment counts before a publish; see CI.
sh
$ 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 r12

nanoflags publish

sh
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_CHANGEDThe environment is at another revision than expected. Review, then retry with the new number.
NO_CHANGESThe draft equals the published configuration.
CHANGE_REQUIREDThe environment requires a reviewed change. Publish from the dashboard; keys cannot bypass review.
SCENARIOS_REQUIREDThe environment requires passing scenarios on the current draft: run nanoflags scenarios run --source draft first.
sh
$ NANOFLAGS_KEY=nf_adm_… nanoflags publish --expected-revision 12 --note "enable checkout v2 for staff"
Published r13 (checksum 9c41…).

nanoflags refs scan

sh
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.ts files, 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-run prints the references and uploads nothing (no --repo needed). --repo/--commit default from GITHUB_REPOSITORY/GITHUB_SHA.

nanoflags cleanup

sh
nanoflags cleanup checkout-v2 --value true src          # show the diff (dry run)
nanoflags cleanup checkout-v2 --value true src --write  # edit the files in place

For 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.

PatternResult
if (<read>) A else B, also negated, in else if chains, combined with &&/||The branch that runs; the other one is removed.
<read> ? a : ba or b.
<read> && x, <read> || x, and the reverse in a conditionx, or the constant.
<read> === "literal" / !==Folded, then simplified as above.
const on = <read>; followed by uses of onEvery 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-awaited get…Value(), optional calls, reads whose callee or arguments contain calls, accessors of another type, useFlag with 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.