@hysterical/cli
v0.1.1
Published
CLI for Hysteria CMS — deploy schemas, manage tokens
Readme
@hysterical/cli
CLI for Hysteria CMS. Deploy schemas, spin up sandbox environments, create auth tokens.
Installation
From within the monorepo:
yarn setup # builds the CLI along with the client SDKTo use globally outside the monorepo:
npm install -g @hysterical/cli
# or
npx @hysterical/cli <command>Environment variables
Set these to avoid passing flags on every command:
HYSTERIA_URL=http://localhost:3001 # backend URL (default: http://localhost:3001)
HYSTERIA_TOKEN=hyst_... # auth token
HYSTERIA_STUDIO_URL=http://localhost:3002 # studio URL used by sandbox (default: http://localhost:3002)Commands
deploy
Validate and deploy a schema to a dataset.
hysteria deploy -p <projectId> -f <file> [options]| Flag | Description | Default |
|---|---|---|
| -p, --project <id> | Project ID — required | — |
| -f, --file <path> | Schema file (.ts, .js, or .json) — required | — |
| -d, --dataset <name> | Target dataset | production |
| --url <url> | Backend URL | $HYSTERIA_URL or http://localhost:3001 |
| --token <token> | Admin token | $HYSTERIA_TOKEN |
| --dry-run | Print resolved schema without deploying | — |
| --preview | Show schema diff + impact counts without deploying | — |
Schema files can be TypeScript, JavaScript (ESM), or JSON. TypeScript files are compiled on the fly via esbuild — no separate build step needed. The file must export a default schema object.
The schema is validated before deployment. If validation fails, errors are printed with their field paths and the command exits non-zero without contacting the backend.
Examples:
# Deploy to production
hysteria deploy -p proj-abc123 -f schemas/my-site.ts
# Preview breaking changes and impact before deploying
hysteria deploy -p proj-abc123 -f schemas/my-site.ts --preview
# Deploy to a staging dataset
hysteria deploy -p proj-abc123 -f schemas/my-site.ts -d staging
# Print the compiled schema without deploying
hysteria deploy -p proj-abc123 -f schemas/my-site.ts --dry-run
# Different backend
hysteria deploy -p proj-abc123 -f schemas/my-site.ts --url https://cms.mycompany.com
# Inline credentials
hysteria deploy -p proj-abc123 -f schemas/my-site.ts --token hyst_abc123...--preview output shows breaking changes with document counts before you commit:
Fetching current schema from proj-abc123/production... ok
Schema diff — proj-abc123/production
~ post
✗ body: array → block · 12 documents affected
+ category: string (new)
page (REMOVED — 5 documents)
+ settings new type
⚠ 3 breaking changes · 17 documents at risk
Existing data that no longer matches the schema will appear in Content Health.
Consider adding migrations to your schema file to fix affected documents.
Run without --preview to deploy.Validation errors exit with a descriptive list:
Schema validation failed with 2 error(s):
types[0].fields[3].to: "reference" fields require a non-empty "to" array
types[1].name: Duplicate type name "post"Migrations
Schema files can include a migrations array to automatically update existing documents when deploying. Migrations run before the new schema is deployed so the data is in the correct shape by the time the schema change takes effect.
const schema = {
migrations: [
// Rename a field across all documents of that type
{ type: 'rename', docType: 'post', from: 'body', to: 'content' },
// Set a field to a fixed value (e.g. backfill a new required field)
{ type: 'set', docType: 'post', field: 'status', value: 'draft' },
// Remove a field that no longer exists in the schema
{ type: 'unset', docType: 'post', field: 'legacySlug' },
],
types: [
// ...
],
}
export default schemaMigration types:
| Type | Required fields | Effect |
|---|---|---|
| rename | docType, from, to | Copies from → to, removes from, on all docs of docType |
| set | docType, field, value | Sets field to value on all docs of docType |
| unset | docType, field | Removes field from all docs of docType |
Migrations only affect documents that have the field set; documents where the field is absent are skipped.
The migrations key is stripped from the schema before it is deployed to the backend — it never appears in the deployed schema.
sandbox
Create an ephemeral sandbox dataset on the running backend, deploy a schema to it, and optionally seed it with documents copied from an existing dataset. Use this to test schema changes against real content before deploying to production.
hysteria sandbox -p <projectId> -f <file> [options]| Flag | Description | Default |
|---|---|---|
| -p, --project <id> | Project ID — required | — |
| -f, --file <path> | Schema file to deploy — required | — |
| --from <dataset> | Copy documents from this dataset into the sandbox | production |
| --no-seed | Skip copying documents (empty sandbox) | — |
| --name <name> | Custom sandbox dataset name | sandbox-xxxxxx |
| --url <url> | Backend URL | $HYSTERIA_URL or http://localhost:3001 |
| --studio-url <url> | Studio URL to open | $HYSTERIA_STUDIO_URL or http://localhost:3002 |
| --token <token> | Auth token | $HYSTERIA_TOKEN |
How it works:
- Validates the schema file
- Creates a new dataset on the backend (e.g.
sandbox-k7x2m9) - Deploys the schema to it
- Exports all documents from
--fromand imports them (two-pass to handle cross-document references) - Prints the Studio URL to navigate to
- Waits — press Ctrl+C (or send SIGTERM) to delete the sandbox dataset and exit
The sandbox dataset is automatically cleaned up on exit. If the process is killed unexpectedly, delete the dataset manually via the Studio settings or the API.
Examples:
# Minimal — seeds from production by default
hysteria sandbox -p proj-abc123 -f schemas/my-site.ts
# Seed from a staging dataset
hysteria sandbox -p proj-abc123 -f schemas/my-site.ts --from staging
# Empty sandbox (no documents)
hysteria sandbox -p proj-abc123 -f schemas/my-site.ts --no-seed
# Named sandbox (useful for CI or parallel testing)
hysteria sandbox -p proj-abc123 -f schemas/my-site.ts --name preview-pr-42Example output:
Loading schema... ok
Creating sandbox "sandbox-k7x2m9"... ok
Deploying schema... ok (9 types)
Exporting documents from "production"... 129 documents
Pass 1/2 50/129 100/129 129/129
Pass 2/2 50/129 100/129 129/129 ok
┌───────────────────────────────────────────────────────────────────────┐
│ Sandbox ready │
│ http://localhost:3002/projects/proj-abc123/sandbox-k7x2m9 │
│ │
│ When satisfied, deploy to production: │
│ hysteria deploy --project proj-abc123 --file schemas/my-site.ts │
│ │
│ Press Ctrl+C to teardown │
└───────────────────────────────────────────────────────────────────────┘
^C
Deleting sandbox-k7x2m9... donetoken
Create an auth token on a running Hysteria instance.
hysteria token -n <name> [options]| Flag | Description | Default |
|---|---|---|
| -n, --name <name> | Token name — required | — |
| --permissions <level> | read, write, or admin | read |
| --dataset <name> | Scope token to a specific dataset (omit for wildcard access) | — |
| --url <url> | Backend URL | $HYSTERIA_URL or http://localhost:3001 |
| --token <token> | Admin token used to authenticate the request | $HYSTERIA_TOKEN |
The token value is printed once and never stored — save it immediately.
Permission levels:
| Level | Access |
|---|---|
| read | GROQ queries, asset reads, SSE listen |
| write | All reads + mutations, asset upload, pipeline, releases |
| admin | All writes + schema deploy, token/webhook management |
Examples:
# Create a read token (e.g. for a public frontend)
hysteria token -n frontend-read --permissions read
# Create a write token scoped to one dataset
hysteria token -n editorial-prod --permissions write --dataset production
# Create an admin token (requires existing admin token to authenticate)
hysteria token -n ci-deploy --permissions admin --token hyst_existingadmin...First-time setup (fresh database, no tokens exist yet — dev mode is open):
hysteria token -n admin --permissions adminSchema file format
The deploy and sandbox commands accept .ts, .js, or .json files. TypeScript is recommended.
// schemas/my-site.ts
const schema = {
i18n: { // optional — enables document-level localization
languages: [
{ id: 'en', title: 'English' },
{ id: 'fr', title: 'Français' },
],
defaultLanguage: 'en',
},
types: [
{
name: 'post',
type: 'document',
title: 'Post',
i18n: true, // opt this type into document-level i18n
previewUrl: '/posts/{slug}', // Studio preview button pattern
groups: [
{ name: 'content', title: 'Content' },
{ name: 'meta', title: 'Meta' },
],
fields: [
{ name: 'title', type: 'string', required: true, group: 'content' },
{ name: 'slug', type: 'slug', required: true, group: 'content' },
{ name: 'body', type: 'array', of: [{ type: 'block' }, { type: 'image' }], group: 'content' },
{ name: 'author', type: 'reference', to: [{ type: 'author' }], group: 'meta' },
{ name: 'status', type: 'select', options: ['draft', 'review', 'published'], group: 'meta' },
],
},
{
name: 'author',
type: 'document',
title: 'Author',
fields: [
{ name: 'name', type: 'string' },
{ name: 'bio', type: 'text' },
{ name: 'avatar', type: 'image' },
],
},
],
}
export default schemaSee the main README for the full field type reference.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (or clean Ctrl+C exit from sandbox) |
| 1 | Validation error, network error, or non-2xx response from backend |
