@sheepit-ai/cli
v0.9.1
Published
Sheepit CLI — codegen Flag constants, manage releases, run platform operations from the terminal
Maintainers
Readme
@sheepit-ai/cli
Generate type-safe flag constants and run Sheepit operations from your terminal.
Command-line tool for the Sheepit platform. Generate typed flag constants for your codebase, and manage flags, rollouts, releases, events, and crashes from your terminal or CI pipeline. The binary is sheepit.
Why it exists
Flag names are strings, and a typo in a string silently never matches, so the feature stays off and nobody notices. The CLI fetches your live flag definitions and writes typed constants, so a wrong name is a compile error instead of a dead feature. It also exposes the read and write operations you would otherwise click through in the dashboard, which makes them scriptable in CI.
Install
# project-scoped, for npm scripts and CI
npm install -D @sheepit-ai/cli
# or run without installing
npx @sheepit-ai/cli initA global install is available through the unscoped sheepit-ai alias:
npm install -g sheepit-ai
sheepit --helpQuick start
With a project-scoped install, prefix these with npx.
# 1. log in (opens the browser; stores a profile in ~/.sheepit/credentials.json)
sheepit login
# 2. inspect your project
sheepit flags list
# 3. typed flag constants for your codebase
sheepit init # writes sheepit.config.json, including apiUrl
export SHEEPIT_API_KEY=lp_dev_... # codegen reads its key from this variable
sheepit codegen # writes ./src/generated/flags.ts by defaultThen reference flags by constant instead of string:
import { Sheepit } from "@sheepit-ai/sdk-js";
import { Flags } from "./generated/flags";
const sheepit = Sheepit.init({ apiKey: "lp_pub_..." });
const showBeta = sheepit.flag(Flags.show_beta_ui, false); // a misspelled key fails to compileCommands
| Command | Description |
| ---------------------------------------------------------------------------- | ----------------------------------------------------- |
| sheepit init | Create sheepit.config.json (interactive) |
| sheepit codegen | Regenerate typed flag constants from the dashboard |
| sheepit login / logout <profile> | Browser login (PKCE) or --key; remove a profile |
| sheepit whoami / use / profiles | Show, switch, or list saved profiles |
| sheepit env use <slug> / show / clear | Set, show, or clear the profile's default environment |
| sheepit flags list / get / kill / restore | Inspect flags; activate or clear a kill switch |
| sheepit rollouts list / get / start / pause / advance / complete | Manage staged rollouts |
| sheepit releases list / get / health / create | Inspect and register releases |
| sheepit events names / query | List event types; run a time-series query |
| sheepit crashes list / get / reports | Inspect crash groups and reports (Apple SDK) |
| sheepit audit tail | Recent audit-log entries |
| sheepit integrations github list / link / unlink | Manage GitHub repo links |
| sheepit feedback "<message>" | Send feedback to the Sheepit team |
| sheepit help [topic] | Show help for a topic |
| sheepit doctor --url <SDK URL> --publishable-key <key> | Check a forwarder in front of a self-hosted install |
Output is a table when stdout is a terminal and JSON when piped. Force either with --json or --output table.
Default environment
sheepit env use staging saves a default environment for the active profile (one optional
environment field per profile in ~/.sheepit/credentials.json; @sheepit-ai/mcp reads and writes the
same field, so a default set here also applies to your MCP tools). sheepit env show prints what
applies and sheepit env clear drops the default, so calls use the key's own environment again.
A login never picks one, and a re-login keeps the default you already saved.
The default applies only to environment-bound data. Today that is releases list and
releases create; flags are universal and ignore it. For those commands the environment resolves,
first match wins:
- the per-call flag (
--environment-id <uuid>) - the profile's default environment
- the API key's own environment (Production for a login key; applied by the server)
When neither 1 nor 2 is set the CLI sends no environment at all, so the request is identical to
what an older CLI sends. releases create prints the environment it used, and says when that came
from your default.
sheepit ci release --environmentstays an assertion: the release lands in its API key's own environment, and the flag only fails the run if you named a different one. The default is ignored there and CI keys have no saved profile anyway.- 🔴 The environment is part of
ci release's revision identity. Changing the key (or its environment) between two runs of one pipeline creates a second release instead of reusing the first. - The credentials file is replaced atomically (temp file in the same directory, mode 0600, then
rename). Two shells changing it at once is still last-writer-wins;
env showreads the value back.
Connecting to a self-hosted install
sheepit login signs in to Sheepit Cloud unless something tells it otherwise. To point it at
your own install, pass the origin your browser uses — one flag, not two:
sheepit login --host https://sheepit.acme.internalOr state the install once and run plain sheepit login. Login uses the first of these it
finds and prints which one:
--url/--hostSHEEPIT_API_URL, e.g.https://sheepit.acme.internal/apiapiUrlin the config file in the current directory (sheepit.config.json, or the first.sheepitrc*found), whichsheepit initwrites. Login asks you to confirm it first, because a config file can come from someone else's repo. Without a terminal it refuses it.- Sheepit Cloud. Login prints a notice naming
--hostbefore it opens the browser.
Any flag (--host, --url or --web-url) turns off the variable and the config file.
Login also refuses a dashboard and an API that belong to different installs, because
signing in on one and redeeming the code on the other hands the second a credential.
A self-hosted install is a single origin: the dashboard is at / and the API is behind
/api on the same host, so --host sets both and records them in the profile. Everything
after login — flags, rollouts, events, codegen — then talks to your install.
sheepit whoami shows web_url for a self-hosted profile so you can tell it apart from a
hosted one.
Your install is never brokered through sheepit.ai. The CLI authenticates directly against
the instance you name, the same way glab auth login --hostname does — which is what keeps
an air-gapped install working and keeps your logins out of our infrastructure.
If your reverse proxy does not follow that layout, --url and --web-url still override
each origin individually:
sheepit login --host https://sheepit.acme.internal --url https://sheepit-api.acme.internalFor a headless or CI login, --key still works and honours --host and SHEEPIT_API_URL the
same way. With neither and no terminal it refuses, rather than send the key to Sheepit Cloud or
to a host named by a config file nobody confirmed:
sheepit login --host https://sheepit.acme.internal --key lp_sec_... --name acmeChecking the forwarder in front of the SDK listener
If your SDK traffic reaches the install through your own forwarder (a load-balancer rule,
nginx, a framework rewrite), sheepit doctor checks it end to end and exits 1 if anything
fails. Here --url is the address your SDK calls, not the API origin login takes:
sheepit doctor --url https://app.example.com/_s --publishable-key "$PUBLISHABLE_KEY" \
[--secret-key-env SECRET_KEY_VAR] [--expect-ip <your public IP>] [--origin https://app.example.com]It uses no saved profile and talks to --url only. sheepit help doctor lists the checks.
Configuration
sheepit.config.json holds the project slug, the API URL, and codegen targets. It contains no secrets, so commit it:
{
"projectSlug": "my-app",
"apiUrl": "https://api.sheepit.ai",
"codegen": {
"platforms": ["web", "ios", "server"],
"outputs": {
"typescript": "./src/generated/flags.ts",
"swift": "./ios/Generated/Flags.swift"
}
}
}| Field | Type | Notes |
| ------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------- |
| projectSlug | string (required) | Must match the slug in your dashboard |
| apiUrl | string | Used by codegen when SHEEPIT_API_URL is unset. sheepit init writes it. No default |
| codegen.platforms | ("web" \| "ios" \| "android" \| "server")[] | Platforms to generate for (Android SDK: coming soon) |
| codegen.outputs | { typescript?, swift?, kotlin? } | Output paths. Without outputs, codegen writes ./src/generated/flags.ts |
.sheepitrc and .sheepitrc.json are also read, in that order after sheepit.config.json.
| Environment variable | Purpose |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SHEEPIT_API_KEY | Key used instead of any saved profile (lp_dev_… or lp_sec_…). Required for sheepit codegen, which does not read saved profiles. |
| SHEEPIT_API_URL | API URL. Required whenever SHEEPIT_API_KEY is set (otherwise the command fails; codegen also accepts apiUrl from the config file). There is no default, so a self-hosted key is never sent to Sheepit Cloud by omission. Sheepit Cloud: https://api.sheepit.ai. |
| SHEEPIT_ENVIRONMENT_ID | Default for releases create --environment-id |
| SHEEPIT_RELEASE_ENVIRONMENT | Default for releases create --environment (an environment slug, such as production). Set this or SHEEPIT_ENVIRONMENT_ID, not both. Deliberately not SHEEPIT_ENVIRONMENT, which @sheepit-ai/server reads as its runtime environment |
CI usage
Fail the build if a developer forgot to regenerate flag constants:
- name: Check flag codegen is in sync
env:
SHEEPIT_API_KEY: ${{ secrets.SHEEPIT_API_KEY }}
# Required. Omit it and the run fails rather than silently defaulting to
# Sheepit Cloud — which is what would send a self-hosted key to us.
# (codegen can drop this if sheepit.config.json already carries `apiUrl`.)
SHEEPIT_API_URL: https://api.sheepit.ai
run: |
npx @sheepit-ai/cli codegen
git diff --exit-code src/generated/flags.tsDraft flags (created but not yet published) are emitted too, marked with a DRAFT doc comment,
so creating one in the dashboard changes the generated file just as an active flag does.
sheepit flags list includes drafts as well; use --status active when you need the flags SDKs
actually receive.
Register a release from GitLab CI
Works on GitLab.com and self-managed GitLab alike: the job calls the Sheepit API, so the runner
needs outbound HTTPS to your API URL and nothing has to reach your GitLab. Store a project
secret key (lp_sec_…) as a masked, protected CI/CD variable named SHEEPIT_API_KEY. Set
SHEEPIT_API_URL as a CI/CD variable next to it (https://api.sheepit.ai, or your
self-hosted install), not in the job's variables:. GitLab ranks project and group CI/CD variables
above .gitlab-ci.yml ones, so a job-level URL is silently replaced by any group-level
SHEEPIT_API_URL, which on a self-hosted install could send the key to Sheepit Cloud; keeping
the URL beside the key leaves one value to check. Make it protected
too: whoever can change it on an unprotected branch can point the key at their own server. Pin
the version you tested (npx @sheepit-ai/cli@<version>) so a pipeline never changes behaviour on its own.
sheepit ci release (CLI 0.8.0 or later; hosted Sheepit or BYOC 0.5.0+) is one step that
registers the release and the build it ships, and binds the two. The binding is what
per-release health is measured through: Sheepit counts the sessions and crashes of exactly that
build, matching telemetry by its platform, version and build number, so the app sends no ids.
Today's SDKs do not declare crash capture in that telemetry, so once its telemetry resolves, a
bound release with sessions reports capture_unverified: sessions and errors are counted, but no crash-free percentage is
given. Product and per-platform health, measured by platform and app version, do not depend on it.
register-release-web:
stage: deploy
needs: [deploy-web]
image: node:22
environment: production # also records a deployment
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # not on merge-request pipelines
script:
- npx @sheepit-ai/[email protected] ci release --platform web --environment production
register-release-ios:
stage: deploy
needs: [upload-testflight]
image: node:22
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- |
npx @sheepit-ai/[email protected] ci release --platform ios --channel beta \
--version "$APP_MARKETING_VERSION" --build-number "$APP_BUILD_NUMBER" \
--environment production --artifact build/App.ipa- Safe to re-run. Retrying the job, re-running the pipeline or running a new pipeline on the
same commit registers nothing twice: every record has an id derived from the release identity,
and the command reads before it writes. A new pipeline adds only a new deployment; a retried
job's deployment is deduplicated by the API. The JSON on stdout says, per record, whether it
was
created,reusedor (deployments)recorded. - It only adopts what it would have written. The ids are derivable by anyone holding a key
of the project, so before reusing an existing record the command checks that it names this
build, environment, version, build number and commit, and was written by
ci release. If not, it exits non-zero and changes nothing: that version is blocked until someone looks, but no release ever reports another build's data. Any key of the project can still register a matching record first with its own pipeline link, so keep secret keys to the people and pipelines you trust with production. - Give a job without
environment:one anyway if you want scheduled health. A release stayscreateduntil a deployment is recorded, and the periodic health snapshot skipscreatedreleases (sheepit releases health <id>computes it on demand either way). - The release lands in the API key's own environment, because that is the only environment
its binding can be written to.
--environment(orSHEEPIT_RELEASE_ENVIRONMENT) is an assertion: when it names another environment the command exits non-zero before writing anything. A pipeline that releases to staging and to production gives each job the key of its environment. The environment is printed to stderr on every run. - One release per platform. A product with web, server, iOS and Android repos registers one
release from each repo's pipeline.
--platformtakesweb,ios,ipados,macos,tvos,watchos,androidorserver(backendis still accepted and stored asserver);allis refused, because no telemetry ever reports it. SheepitKit reports the device the app runs on, so register an Apple build under every platform it runs on: a universal iPhone and iPad app is two runs,--platform iosand--platform ipados. SheepitKit before 0.8.0 reports every Apple device asios. --versionand--build-numbermust equal what the app reports, or no event is attributed to the release: the SDK'sreleasefor web and server — which defaults to the commit SHA here, asCI_COMMIT_SHA, and which the server SDKs read fromCI_COMMIT_SHA,GITHUB_SHA,VERCEL_GIT_COMMIT_SHAorSHEEPIT_RELEASEon their own — and the marketing version plus build number (CFBundleShortVersionString/CFBundleVersion,versionName/versionCode) on Apple platforms and Android, where--build-numberis required.APP_MARKETING_VERSIONandAPP_BUILD_NUMBERabove stand for however your pipeline exposes them. Both are capped at 64 characters, the same bound the SDKs report, except SheepitKit, which sends at most 63 bytes of UTF-8 (its crash report's limit): keep an Apple--versionwithin that. A web or server release named by a tag still records its commit.- The same version and build from a different commit is refused, not overwritten: give every build its own build number.
- The app needs no ids from CI. The API names the build of every event from the platform,
version and build number the SDK already reports, using the same id this command registers,
so the job does not pass anything to the app. The app only has to report the right version
(see the SDKs'
releaseoption). Telemetry that arrives before this job runs, or from a pipeline that cannot reach the API, joins the build once it is registered. Its health readscapture_unverified(sessions and errors counted, no crash-free rate) until the app's SDK declares that it captures crashes. --application <name>is only for a project that ships two apps on one platform. Telemetry names builds of the default application, so a build registered under another name is measured only from telemetry that sends its runtime ids, and the command says so on stderr. Without it, the application is shown under the CI project path.--artifact <file>(repeatable) records the file's SHA-256 against the build.- The command reads the commit, branch, pipeline id and URL, and the job's
environment:from GitLab's predefined variables (GitHub Actions works too, without a deployment). Outside CI, pass--commit-sha. --dry-runreads (it needs the key) and writes nothing; it prints what it would create.
sheepit releases create remains for a release without a build: it is not idempotent (a
re-run creates a second release), never writes a binding, and takes --environment <slug> or
--environment-id <uuid> for any environment of the project.
Keys
| Key | Use |
| ----------------------- | ---------------------------------------------------------------------------------------------- |
| Developer, lp_dev_… | Read-only: codegen, flags list, and other reads. Fits CI. |
| Secret, lp_sec_… | Everything, including writes such as flags kill and rollout control. Keep it out of clients. |
| Publishable, lp_pub_… | Not usable: the endpoints the CLI calls reject publishable keys. |
FAQ
Where are profiles stored? ~/.sheepit/credentials.json, mode 0600. Switch with sheepit use <profile> or per command with --profile <name>.
Can I manage more than one project? Yes. Each project has its own sheepit.config.json. Commands other than codegen can use profiles for different keys and hosts (--profile); codegen ignores profiles and always uses SHEEPIT_API_KEY with SHEEPIT_API_URL or the config file's apiUrl.
Does the CLI send telemetry? Yes. After each project command (not the login/profile commands or help) it posts one $cli_command_run event to the API it is using, carrying the command name, success, duration, argument count, and error code, never argument values.
Is there a programmatic API? Output is JSON when piped, for tools like jq. For in-process use, install @sheepit-ai/sdk-js or @sheepit-ai/server.
Requirements
Node.js 20+.
License
MIT. Copyright (c) 2026 GoaTech AI LLC. See LICENSE.
