npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kelviq/cli

v0.1.3

Published

Pricing-as-code CLI for Kelviq — pull, push, and promote your catalog as typed TypeScript

Readme

Kelviq CLI

Pricing-as-code for Kelviq — a CLI that lets you define products, features, and plans as typed TypeScript instead of clicking through a dashboard, and sync that definition against a real Kelviq environment.

Author or pull a typed kelviq.config.ts, inspect what environment you're pointed at, then preview and apply changes with kelviq push — a dry-run diff, confirmation, ordered writes, and optional --publish/--prune — against a live environment. kelviq promote runs that same reconciliation sandbox → production in one command.

Install

npm install -g @kelviq/cli
kelviq --help

Commands

Everything else (catalog browse commands, browser-handoff login) is not built yet — see "What's not here yet" below.

| Command | What it does | |---|---| | kelviq init | Writes a starter kelviq.config.ts to the current directory. | | kelviq login | Pastes a Kelviq Server API key from the dashboard and stores it locally. | | kelviq logout | Removes a stored API key. | | kelviq env | Shows which environment and keys are configured — no live validation. --json for scripting. | | kelviq pull | Fetches the remote catalog and writes it to kelviq.config.ts. | | kelviq push | Diffs kelviq.config.ts against an environment, confirms, and applies drafts-only writes. --dry-run --json for scripting. | | kelviq promote | Diffs the sandbox catalog against production and applies the same way — no local config file involved. |

kelviq init                    # write kelviq.config.ts in the current directory
kelviq login                   # paste a sandbox key (default env)
kelviq login --prod            # paste a production key
kelviq logout --all            # remove both stored keys
kelviq env                     # show configured environment + key status
kelviq pull                    # pull the sandbox catalog into kelviq.config.ts
kelviq pull --prod --force     # pull production, overwriting an existing file
kelviq push --dry-run          # preview what push would change, no writes
kelviq push --yes              # push to sandbox non-interactively
kelviq promote --dry-run       # preview sandbox -> production

Run kelviq push --help / kelviq promote --help for the full flag set: --publish, --migrate-features, --migrate-pricing, --prune, --allow-prod-prune, --verbose, --json.

kelviq login prints the dashboard's API-keys URL, deep-linked to the environment mode matching the target (sandbox by default, production with --prod), and tries to open it in your default browser, then prompts for the key on stdin (an interactive terminal masks each character as * while you type or paste, then reports how many characters it received). Kelviq has no key-introspection endpoint, so neither login nor env can confirm a key is actually valid or show which org it belongs to — env reports only what's configured locally.

kelviq.config.ts

kelviq init writes this starter file:

// kelviq.config.ts — pricing-as-code for Kelviq.
// Defines products, features, and plans as typed, versioned TypeScript.
// Sync this file with your Kelviq account using `kelviq pull` / `kelviq push`.
import { product, feature, plan } from '@kelviq/cli/config';

export const app = product({
  identifier: 'my-app',
  name: 'My App',
  taxCode: 'saas',
});

export const seats = feature({
  identifier: 'seats',
  name: 'Seats',
  type: 'CUSTOMIZABLE',
});

export const pro = plan({
  identifier: 'pro',
  name: 'Pro',
  product: 'my-app',
  entitlements: [{ feature: 'seats', value: 5 }],
  prices: [
    {
      priceType: 'PAID',
      currency: 'USD',
      // Full charge shape: see Kelviq docs → api-reference/plans/update-plan-prices
    },
  ],
});

Every cross-reference in the format (plan.product, entitlement.feature) is a stable identifier slug, never a UUID — the API sometimes wants UUIDs (plan creation takes a product UUID; entitlement attachment takes feature UUIDs), but that resolution is sync's job, not something you write by hand. The plan lifecycle (draft → publish) is deliberately absent from this format: it describes desired state, not deployment state.

Fields like taxCode, type, priceType, taxBehavior, and reset are open enums: known values (like the ones above) get editor autocomplete, but the API may accept additional values — some require enabling from the Kelviq dashboard first. The CLI passes any value it doesn't recognize through unchanged; the server validates it on push.

kelviq pull generates this same shape from a live environment's catalog instead of you writing it by hand — the two are meant to be interchangeable starting points.

Environments

Every Kelviq org has a sandbox environment; the CLI targets it by default. Pass --prod on login/pull/push to target production instead (promote always targets production — no flag needed). Keys are stored per-environment (~/.config/kelviq/config.json, mode 0600, created via kelviq login) or supplied via KELVIQ_SANDBOX_SERVER_API_KEY / KELVIQ_SERVER_API_KEY (env vars win over the stored file). There is deliberately no environment where an unqualified command touches production by accident.

Sandbox and production also talk to different API hosts:

| Environment | Default host | Override env var | |---|---|---| | Sandbox | https://sandboxapi.kelviq.com/api/v1 | KELVIQ_SANDBOX_BASE_URL | | Production | https://api.kelviq.com/api/v1 | KELVIQ_BASE_URL |

The two overrides are independent — setting one doesn't affect the other. kelviq env prints both resolved URLs.

Scripting: --json output

kelviq env --json and kelviq push --dry-run --json / kelviq promote --dry-run --json each emit a single JSON document on stdout and nothing else — safe to pipe into jq or parse in CI. This is a public, versioned contract: the field names below are exact, and changing them is a breaking change.

kelviq env --json

{
  "environment": "sandbox",
  "sandboxUrl": "https://sandboxapi.kelviq.com/api/v1",
  "prodUrl": "https://api.kelviq.com/api/v1",
  "sandboxKey": "set",
  "prodKey": "missing"
}

sandboxKey/prodKey are always the literal string "set" or "missing" — never any part of the actual key value. environment is always the literal "sandbox"env has no --prod flag of its own; it always reports both keys' status together.

kelviq push --dry-run --json / kelviq promote --dry-run --json

--json is valid only together with --dry-run — it never mixes with the interactive confirmation prompts or an actual write. Passing it without --dry-run exits 1 with a one-line explanation before any fetch happens. Colors and the fetch spinner (see below) are forced off in --json mode regardless of FORCE_COLOR/TTY, and stderr carries nothing but a fatal error.

{
  "environment": "sandbox",
  "direction": "push",
  "changes": true,
  "summary": {
    "creates": 1,
    "updates": 0,
    "priceReplaces": 0,
    "entitlementChanges": 0,
    "archives": 0,
    "skipped": 0,
    "remoteOnly": 0,
    "warnings": 0
  },
  "operations": {
    "products": { "create": [], "patch": [] },
    "features": { "create": [] },
    "plans": { "create": [], "patch": [] },
    "entitlements": { "add": [], "update": [], "delete": [] },
    "prices": { "bulkReplace": [] }
  },
  "prune": null,
  "publish": null,
  "featureDrift": [],
  "errors": [],
  "warnings": []
}
  • environment: "sandbox" or "production" — the write target. promote always reports "production" (its only possible target); direction is what distinguishes the two commands ("push" or "promote").
  • changes: whether a REAL run of this same invocation (i.e. without --dry-run) would write or archive anything. The exit code is always 0 on a successful render, whether or not changes is true — check this field in a script, not the process exit code, e.g. test "$(kelviq push --dry-run --json | jq -r .changes)" = "false".
  • operations: the exact op groups push/promote compute internally (products/features/plans/entitlements/prices), serialized as-is — a stable, documented contract, never reshaped for display. A same-run pending reference (e.g. a plan referencing a product this same run also creates, before it has a real UUID) serializes as its placeholder object verbatim, e.g. { "pendingProductIdentifier": "my-app" } or { "pendingFeatureIdentifier": "seats" } — never resolved or dropped.
  • prune: null unless run with --prune; otherwise the full computed archive/skip plan: { "archive": { "plans": [...], "features": [...] }, "skipped": { "plans": [...], "features": [...] } }. summary.skipped (the count of remote-only resources that matched but carry no managed-by: kelviq-cli marker) is always populated regardless of --prune; prune itself and summary.archives are not.
  • publish: null unless run with --publish --dry-run; otherwise { "plans": [...identifiers], "updateFeatures": boolean, "updatePricing": boolean } — the plans that WOULD be published this run.
  • featureDrift / errors: the same hard-abort data the text renderer shows before refusing to proceed — empty arrays on a normal run.
  • warnings: the raw, unaggregated list (every dropped-price-field warning individually) — never collapsed the way the text renderer's "Warnings (N):" section groups them per plan by default. --verbose is a text-mode-only concept; JSON output always gives you the raw list.

On a load/fetch/feature-drift error, the command exits non-zero and emits a smaller, separate document instead of the shape above:

{ "error": "push failed: Missing KELVIQ_SANDBOX_SERVER_API_KEY. ...", "featureDrift": [] }

featureDrift is populated only for an actual feature-drift abort (the same condition that aborts a normal run before any write); every other error (a missing key, a network failure, an unresolved config reference) reports featureDrift: [].

Push and promote

push deploys a kelviq.config.ts to an environment; promote does the same sandbox → production, reusing the exact same diff/apply/publish engine with no local config file involved. Both default to a dry-run-first, confirm-before-write posture: --dry-run previews with zero writes, non-interactive sessions require --yes, and production pruning (--prune) has its own dedicated gate (--allow-prod-prune or a typed confirmation) that --yes alone never satisfies. New plans are created as drafts and stay unpublished unless you pass --publish, price changes replace a plan's full price list rather than patching individual entries, and --prune only ever archives resources carrying the CLI's own managed-by: kelviq-cli marker — nothing unmanaged is touched.

What's not here yet

  • Catalog browse commands (list/show products, features, plans without a full pull) — not built yet.
  • Browser-handoff login (whoami, org display) — needs pairing-flow and key-introspection endpoints that don't exist on the platform yet; login is paste-key only for now.

Development

The full push/promote reconciliation design is documented in DESIGN-push.md.

Run from a checkout instead of the published package:

npm install
npm run build
node dist/cli.js --help

Or link it onto your PATH for a real kelviq command during development:

npm link
kelviq --help
npm run dev         # run the CLI from source (tsx), e.g. `npm run dev -- pull`
npm test             # run the test suite (vitest)
npm run lint         # eslint
npm run typecheck    # tsc --noEmit, no output
npm run snapshot     # refresh src/generated/openapi.json from the live docs URL
npm run build        # snapshot + tsc + chmod dist/cli.js

npm run snapshot (and therefore npm run build, and therefore prepublishOnly) fetches https://docs.kelviq.com/api-reference/openapi.json by default and writes it to src/generated/openapi.json — no sibling checkout required, so build works from any machine with network access. The response is validated (parses as JSON, has openapi/paths keys, meets a minimum size) before it overwrites the existing snapshot; a degenerate response leaves the snapshot untouched and exits non-zero.

For offline or pre-deploy work, npm run snapshot -- --from-local (or KELVIQ_SPEC_SOURCE=local npm run snapshot) falls back to copying ../docs/api-reference/openapi.json from a sibling checkout of the docs repo, failing fast if that checkout is absent. Prefer the default (live) source when possible — a local checkout can sit on a stale branch, so a "no drift" result against it proves nothing about the deployed spec.

Publish via npm run publish:stable (or npm run publish:beta for a --tag beta prerelease). scripts/publish.sh guards the release: it checks you're on a clean main working tree and logged in to npm as kelviq, runs lint and typecheck, prints a summary, and asks for interactive confirmation before calling npm publish. It does not run tests or build itself — prepublishOnly (npm run snapshot && npm test && npm run build) does that automatically at publish time against a freshly fetched live OpenAPI spec. A successful stable publish also creates an annotated v<version> git tag and prints (without running) the follow-up commands to push it.