@stetcms/cli
Stet from the terminal: log in or out, generate the typed content client, publish your tracking plan, and check which organization a key belongs to.
@stetcms/cli does what the Vite plugin does, without
Vite: generate the content client, publish the tracking plan, and tell you
which account and organization you are working against. That makes it the tool
for CI, for scripts, and for projects that do not build with Vite at all.
npm install @stetcms/clipnpm add @stetcms/cliyarn add @stetcms/clibun add @stetcms/cliOr run it without installing:
npx stet generate
Two ways to authenticate
The commands split by what they need, and it is worth knowing which is which:
| Kind | Commands | Credential |
|---|---|---|
| Organization-scoped | generate, sync, org |
An API key: --key / STET_API_KEY (org uses --api-key) |
| Account-scoped | login, logout, whoami |
A session from stet login, stored on this machine |
An API key belongs in CI. A login belongs on your laptop. Neither substitutes
for the other. init needs neither.
Commands
stet init
Writes a stet.config.ts to start from. The only command that talks to
nothing: no key, no session, no network, so it is the first thing you can run.
npx stet init
--config <path>?string
Where to write the config.
string./stet.config.ts--output <path>?string
Where the generated content client should go. Written into the config.
stringsrc/stet.gen.ts, or stet.gen.ts without a src directory--force?boolean
Overwrite an existing config.
booleanThe file it writes sets output and carries the
analytics block commented out, so the shape is there to
uncomment when you want a tracking plan:
import { defineStet } from '@stetcms/config';
// import { defineAnalytics, event } from '@stetcms/analytics';
// import { z } from 'zod';
export default defineStet({
output: 'src/stet.gen.ts',
// A typed tracking plan, published with `stet sync`.
// analytics: defineAnalytics({
// events: { signup: event({ plan: z.enum(['free', 'paid']) }) },
// }),
});
There is no apiKey in it on purpose: a key written into a committed file is a
key in your git history. init prints the STET_API_KEY export instead, along
with the install command for your package manager. It does not touch your
package.json.
The rest of what it prints depends on the project. One that builds with Vite is
told to install the plugin and add stet() to
vite.config.ts; anywhere else — Next.js, another bundler, a CI checkout — is
told to install @stetcms/client and run stet generate,
because the plugin would do nothing there.
Without --force an existing config is never replaced, and detection uses the
same candidates the plugin resolves, so writing to
the default location cannot shadow a src/stet.config.ts with a new file at the
root. An explicit --config goes exactly where you point it, which can be
somewhere that resolver never looks; when it is, the command warns that the file
it just wrote will go unread.
stet generate
Generates the typed content client from your content model — the same codegen
@stetcms/vite runs, so the output is byte-identical.
stet generate
--url <origin>?string
Stet server origin. Defaults to $STET_ORIGIN or the hosted app.
string--key <api-key>?string
Organization API key. Defaults to $STET_API_KEY.
string--output <path>?string
Where the generated module goes.
stringsrc/stet.gen.ts--config <path>?string
Path to stet.config.ts. Auto-detected by default.
string--if-key?boolean
Succeed without generating when no API key is set.
booleanfalseOptions settle against stet.config.ts through the
same ladder the plugin uses,
so the CLI writes exactly where your dev server does.
Unlike the plugin, generate fails loudly: no key or an unreachable API
exits non-zero. A build that silently kept a stale client is right for a dev
server and wrong for CI.
--if-key softens exactly one of those failures, for build scripts like
stet generate --if-key && next build that must also pass where no
STET_API_KEY exists and the committed generated client is the one to build
against. Without a key it says so and succeeds; with one, an unreachable API or
a rejected key still fails.
stet sync
Publishes the analytics tracking plan from stet.config.ts, so its events can
be charted in Stet before anyone has fired one.
stet sync
--url <origin>?string
Stet server origin. Defaults to $STET_ORIGIN or the hosted app.
string--key <api-key>?string
Organization API key. Defaults to $STET_API_KEY.
string--config <path>?string
Path to stet.config.ts. Auto-detected by default.
stringExits non-zero when the config declares no analytics. See
syncTrackingPlan for what publishing
does to events you deleted.
stet login
Logs in from this machine with the OAuth device flow: the CLI shows a code, opens your browser, and waits for you to approve it.
stet login
--url <origin>?string
Stet server origin. Defaults to $STET_API_URL or the hosted app.
string--no-open?boolean
Print the verification link instead of opening a browser.
booleanThe session token is written to ~/.config/stet/auth.json (or
$XDG_CONFIG_HOME/stet/auth.json), owner-readable only.
stet whoami
Shows the account the stored session belongs to, its active organization, and the server.
stet whoami
stet whoami --json
--json omits the session token, so the output is safe in logs and pipelines.
stet logout
Clears the session stored on this machine. The command also succeeds when no session is stored, so it is safe to run repeatedly.
stet logout
stet org
Shows the organization your API key is scoped to — the quickest check that a key in CI is the key you meant.
stet org
--api-key <key>?string
Organization API key. Defaults to $STET_API_KEY.
string--url <url>?string
Origin of the Stet deployment.
string--json?boolean
Print the organization as JSON.
booleanPrints the name, slug, id and server, plus the billing plan when that lookup succeeds — a billing failure never hides the organization. An invalid or revoked key exits non-zero with a message saying so.
In CI
Codegen needs the model, which needs a key. Give CI one and run generate
before the build:
- run: npx stet generate
env:
STET_API_KEY: ${{ secrets.STET_API_KEY }}
You can skip this entirely if you build with Vite: the plugin already
regenerates on buildStart, reading the same environment variable.