--help. The CLI targets your sandbox environment unless you pass --prod — see Environments & authentication.
Every command reads KELVIQ_* variables from .env.local and then .env in the working directory before it runs, so a project’s env file works without exporting anything. A variable already set in your shell always wins. Two global flags control this:
Output is colorized on interactive terminals (green creates, yellow updates, red archives) and automatically plain when piped. Disable colors explicitly with the global
--no-color flag or the NO_COLOR environment variable.
kelviq init
Writes a starterkelviq.config.ts to the current directory.
Each template is a complete catalog that
kelviq push accepts as written, using the typed builders from @kelviq/cli/config — see the format reference:
saas— a product with a free plan and a Pro plan billed monthly or yearly, gating a Boolean feature and a Customizable limit.usage— aMETERfeature with the details the API requires, a free plan with a hard monthly allowance, and a Pro plan with an included quantity plus a tiered pay-as-you-go overage price.license— a Pro plan whose orders issue license keys valid on up to three devices.
kelviq login
Stores a Server API key for the CLI to use.
What it does:
- Prints the dashboard’s API keys page URL and tries to open it in your default browser — already switched to the environment you’re logging into (sandbox by default, production with
--prod), so the key on screen is the key the CLI expects. - Prompts you to paste the key. In an interactive terminal each pasted character shows as
*— so you can see the paste landed — and aReceived a N-character key.line confirms the length. The key itself is never echoed or logged. - Stores it in
~/.config/kelviq/config.jsonwith owner-only permissions (0600).
login does not check the key. Run kelviq env --check afterwards to confirm the API accepts it. Kelviq has no key-introspection endpoint yet, so the CLI can’t show which organization the key belongs to.
For CI and other non-interactive contexts, skip login entirely and set an environment variable instead — see Environments & authentication.
kelviq logout
Removes stored API keys.logout only touches the stored config file. Keys supplied via environment variables are unaffected — unset those in your shell or CI configuration.
Removing a key locally does not revoke it. To revoke a key, delete it from the API keys page in the dashboard.
kelviq env
Shows which environment and keys the CLI is configured with, and which env files it read.
Without
--check, key rows show set or missing only and nothing is sent to the API. With --check, each configured key gets one GET /catalog/products/ call against its own environment’s host and a result line:
ok means the key was accepted. rejected (HTTP 401 or 403) almost always means the key belongs to the other environment. The command exits non-zero when any configured key fails, or when no key is configured, so a script or agent can gate on it. --check --json adds sandboxCheck and prodCheck objects ({ ok, status, httpStatus?, message? }) to the JSON document.
kelviq pull
Fetches your remote catalog and writes it as akelviq.config.ts.
What it fetches: all products, all features, the latest version of each plan, and each plan’s prices and entitlements. Every cross-reference in the generated file uses
identifier slugs, never UUIDs — the file is stable across environments that share identifiers.
On success, pull prints the path it wrote and a warnings list for anything it couldn’t represent in the config format (for example, plan fields like links, countries, and files are dropped, with one warning each; a plan’s license settings are pulled into the file). Warnings are informational — the file it wrote is still valid.
On failure — missing key, network error, unexpected API response — pull writes pull failed: <reason> to stderr and exits non-zero without touching the output file.
kelviq push
Deploys akelviq.config.ts to a Kelviq environment: fetches the current remote catalog, computes exactly what would change, shows you the diff, and applies it only after confirmation.
The diff shows what actually changes, value to value —
~ update plan "pro": name "Pro" → "Pro Plan" — so the confirmation gate is reviewable at a glance.
Scripting with --json
kelviq push --dry-run --json prints exactly one JSON document: {environment, direction, changes, summary, operations, prune, publish, featureDrift, errors, warnings}. The changes boolean makes drift checks a one-liner in CI:
--json is valid only with --dry-run (JSON mode never mixes with prompts or writes), always exits 0 on a successful render, and emits {"error": …} documents on failures. The shape is a documented, versioned contract — see the repo README.
What push writes — and in what order
Push converges the environment toward your config in dependency order: products → features → plans → entitlements → prices → publish → prune. Cross-references you wrote as identifiers are resolved to UUIDs automatically, including references to resources created earlier in the same run. Everything push writes is a draft by default. Plan creates land in draft; updating a published plan creates a new draft version. Nothing a customer sees changes until a plan is explicitly published. On a mid-run failure, push stops immediately, reports exactly what was applied and what wasn’t, and exits non-zero — there’s no rollback, but because the writes were drafts, a half-applied push has no customer impact. Push stamps amanaged-by: kelviq-cli entry into the metadata of every feature and plan it creates (merged with any metadata you declare). That marker is how --prune later distinguishes CLI-managed resources from everything else — you’ll see it in the dashboard, and it’s safe to leave alone.
Prices are all-or-nothing per plan
A plan with aprices array in config always pushes that complete array, replacing the plan’s entire remote price set. A plan without a prices key never touches remote prices at all. Omitting prices means “leave pricing alone,” not “this plan is free.”
Prices are compared on the fields your config sets. A field you omit — freeTrial, taxBehavior, a charge’s paymentType or reset — is left to the server’s default and never counts as a difference, so a hand-authored price diffs clean after its first push.
METER features are checked before anything is written
The API rejects aMETER feature created without featureDetails (featureSubType and units). Push checks for this while building the plan and stops before any write, naming the feature and the shape to add — see feature().
Features can’t be updated — by design
The Kelviq API deliberately treats feature attributes as immutable; renaming a feature is a dashboard operation. If a feature in your config differs from its remote counterpart, push stops before writing anything and lists the drifted fields. A feature’s identifier and type can’t change: to change them, create a new feature, then runkelviq pull and push again. Edit the name in the dashboard.
Publishing
--publish publishes the plans the run just changed, after a second, separate confirmation listing them. A bare --publish never migrates existing customers — they stay on their current entitlements and pricing until you deliberately pass --migrate-features and/or --migrate-pricing, each of which spells out its consequence before you confirm. --yes skips prompts; it never substitutes for passing a migration flag explicitly.
Pruning
Remote-only resources — things that exist in the environment but not in your config — are normally just listed as warnings.--prune archives them, under strict conditions:
- Only plans and features carrying the
managed-by: kelviq-climarker are ever archived. Anything created in the dashboard, by another tool, or before marker support is listed as skipped with the reason — never touched. - Products are never pruned, ever.
- Archiving is the platform’s soft-delete — nothing is destroyed.
- Pruning production requires typing the environment name at a dedicated prompt (or
--allow-prod-prunein CI).--yesalone never unlocks it.
kelviq promote
Moves your sandbox catalog to production in one command — the same engine and safety rails aspush, with sandbox as the source instead of a local file.
Promote reads and writes no local file — it fetches the sandbox catalog in memory and reconciles production toward it, matching resources by identifier (which is why the config format bans per-environment UUIDs). It needs both keys configured: sandbox to read, production to write.
Everything you know about push applies: drafts by default, a confirmation showing the full diff first, feature drift between the environments stops the run with dashboard guidance, and prices replace whole-set per plan. The intended workflow is always
--dry-run first:
kelviq status
Shows the live catalog of an environment without writing a config file: products, features, and the latest version of every plan with its price and entitlement counts. Read-only — the check to run after a push to confirm what landed.
A plan with no prices is flagged, since it can’t be bought. The API reports a plan’s version number rather than a draft/published state, so that’s what
status shows.
kelviq webhook
Registers the app’s webhook endpoint and gets the signing secret into the app’s env file without it ever appearing on screen.kelviq webhook add
add is idempotent on the URL: an endpoint that already exists for the same URL is reused instead of registered twice, and its events are extended when the ones you pass aren’t all covered. With neither --write-secret nor --print-secret, the secret stays hidden and the output tells you how to get it.
kelviq webhook list
Lists endpoints with their URL, state, id and events. Secrets are never shown.--prod and --json as above.
kelviq discount
Creates and lists discount codes. Only enabling or disabling a code can change after creation, socreate restates everything and asks before writing.
kelviq discount create
Discount codes need a Merchant of Record organization.
kelviq discount list
Lists codes with amount, duration and state.--search <text> filters by name or code; --prod and --json as above.