The single CLI for CipherStash. It handles authentication, project initialization, EQL database lifecycle (install, upgrade, validate, push, migrate), schema building, and encrypted secrets management. Install it as a devDependency alongside the runtime SDK @cipherstash/stack.
npm install -D stash
npx stash auth login # authenticate with CipherStash
npx stash init # scaffold, introspect, install EQL, hand off to your agentstash init runs the whole setup as one flow: authenticate, resolve DATABASE_URL, introspect your database and let you pick which columns to encrypt, install dependencies, install the EQL extension, and finish by handing off to your local coding agent. At the end it presents a four-option menu:
- Hand off to Claude Code — copies the per-integration set of skills (
stash-encryption,stash-<integration>,stash-cli) into.claude/skills/, writes.cipherstash/context.jsonandsetup-prompt.md, then launchesclaudeinteractively. - Hand off to Codex — copies the same skills into
.codex/skills/, writes a sentinel-managedAGENTS.md(durable doctrine), plus.cipherstash/context files, then launchescodex. - Use the CipherStash Agent — runs the in-house wizard (
@cipherstash/wizard). - Write AGENTS.md — for editor agents (Cursor / Windsurf / Cline) that don't auto-load skill directories. Writes a single
AGENTS.mdwith the doctrine plus the relevant skill content inlined under a sentinel block, and stops.
A project-specific action plan is written to .cipherstash/setup-prompt.md regardless of which option you pick — it tells the agent exactly what's already done and what's left, with the right commands for your package manager and ORM. The matching context (selected columns, env keys, paths, versions) is at .cipherstash/context.json.
If neither claude nor codex is on PATH, init still writes the rules files and prints install instructions — your progress is never wasted.
npx stash auth login
└── npx stash init ← introspects DB, installs EQL, hands off to your agent
└── Agent edits schema files / generates migrations
└── npx stash db push ← when ready to roll out further changes
stash covers authentication, initialization, EQL install/upgrade/validate/push/migrate, schema introspection, and a stash wizard subcommand that thin-wraps @cipherstash/wizard. The wizard package itself is a separate npm install — kept out of the stash bundle so the agent SDK doesn't bloat the CLI.
stash.config.ts is the single source of truth for database-touching commands. Create it in your project root:
import { defineConfig } from 'stash'
export default defineConfig({
databaseUrl: process.env.DATABASE_URL!,
client: './src/encryption/index.ts',
})| Option | Required | Default | Description |
|---|---|---|---|
databaseUrl |
Yes | — | PostgreSQL connection string |
client |
No | ./src/encryption/index.ts |
Path to your encryption client file |
The CLI loads .env files automatically before reading the config, so process.env references work without extra setup. The config file is resolved by walking up from the current working directory.
Commands that consume stash.config.ts: db install, db upgrade, db push, db validate, db status, db test-connection, schema build. db install will scaffold stash.config.ts for you if it's missing.
Set up CipherStash end-to-end: authenticate, introspect your database, install dependencies, install EQL, and hand off the rest to your local coding agent.
npx stash init [--supabase] [--drizzle]| Flag | Description |
|---|---|
--supabase |
Use the Supabase-specific setup flow |
--drizzle |
Use the Drizzle-specific setup flow |
What init does, in order:
- Authenticate — re-uses an existing token if found, otherwise opens the browser device-code flow.
- Resolve
DATABASE_URL— flag → env →supabase status→ interactive prompt → hard-fail. The same resolverdb installuses. - Generate the encryption client — connects to your database, lists tables, and prompts you to multi-select which columns to encrypt. Writes
./src/encryption/index.tswith the right shape for the detected ORM (Drizzle / Supabase / plain Postgres). Falls back to a placeholder if the database has no tables yet. - Install dependencies —
@cipherstash/stack(runtime) andstash(dev), with a confirmation prompt. - Install EQL — runs
stash db installagainst the resolved URL after a y/N confirm. - Hand off — four-option menu (Claude Code / Codex / CipherStash Agent / write
AGENTS.md). See the Quickstart section above for what each option writes and spawns.
The full pipeline state — integration, columns, env-key names, paths, versions — is captured in .cipherstash/context.json. The action plan at .cipherstash/setup-prompt.md tells whichever agent picks up next what's already done and what's left.
CIPHERSTASH_WIZARD_URL overrides the gateway endpoint for the rulebook fetch. Useful for local-dev against a wizard gateway running on localhost.
Authenticate with CipherStash using a browser-based device code flow.
npx stash auth loginSaves the token to ~/.cipherstash/auth.json. Database-touching commands check for this file before running.
Launch the CipherStash AI wizard. Thin wrapper around @cipherstash/wizard — the wizard ships as a separate npm package so the agent SDK stays out of the stash bundle, but you don't need to remember a second tool name.
npx stash wizard [...flags]Any flags after wizard are forwarded verbatim to the wizard package. On the first run the package manager downloads the wizard (~5s); subsequent runs are instant.
Manage end-to-end encrypted secrets.
npx stash secrets <subcommand> [options]| Subcommand | Description |
|---|---|
set |
Store an encrypted secret |
get |
Retrieve and decrypt a secret |
get-many |
Retrieve and decrypt multiple secrets (2–100) |
list |
List all secrets in an environment |
delete |
Delete a secret |
Flags:
| Flag | Alias | Description |
|---|---|---|
--name |
-n |
Secret name (comma-separated for get-many) |
--value |
-V |
Secret value (set only) |
--environment |
-e |
Environment name |
--yes |
-y |
Skip confirmation (delete only) |
Examples:
npx stash secrets set -n DATABASE_URL -V "postgres://..." -e production
npx stash secrets get -n DATABASE_URL -e production
npx stash secrets get-many -n DATABASE_URL,API_KEY -e production
npx stash secrets list -e production
npx stash secrets delete -n DATABASE_URL -e production -yConfigure your database and install CipherStash EQL extensions in a single command. Run this after npx stash init.
When stash.config.ts is missing, the command auto-detects your encryption client file (or asks for the path) and writes the config before installing. Supabase and Drizzle are detected from your DATABASE_URL and project files, so the matching flags default on. Install uses bundled SQL for offline, deterministic runs.
npx stash db install [options]| Flag | Description |
|---|---|
--force |
Reinstall even if EQL is already installed |
--dry-run |
Show what would happen without making changes |
--supabase |
Supabase-compatible install (no operator families + grants Supabase roles) |
--exclude-operator-family |
Skip operator family creation |
--drizzle |
Generate a Drizzle migration instead of direct install |
--latest |
Fetch the latest EQL from GitHub |
--name <value> |
Migration name (Drizzle mode, default: install-eql) |
--out <value> |
Drizzle output directory (default: drizzle) |
The --supabase flag uses a Supabase-specific SQL variant and grants USAGE, table, routine, and sequence permissions on the eql_v2 schema to the anon, authenticated, and service_role roles.
Good to know: Without operator families,
ORDER BYon encrypted columns is not supported. Sort application-side after decrypting results as a workaround. This applies to both--supabaseand--exclude-operator-familyinstalls.
Upgrade an existing EQL installation to the version bundled with the package (or the latest from GitHub).
npx stash db upgrade [options]| Flag | Description |
|---|---|
--dry-run |
Show what would happen without making changes |
--supabase |
Use Supabase-compatible upgrade |
--exclude-operator-family |
Skip operator family creation |
--latest |
Fetch the latest EQL from GitHub |
The install SQL is idempotent and safe to re-run. If EQL is not installed, the command suggests running npx stash db install instead.
Push your encryption schema to the database. Only required when using CipherStash Proxy. If you use the SDK directly with Drizzle, Supabase, or plain PostgreSQL, skip this step.
npx stash db push [--dry-run]| Flag | Description |
|---|---|
--dry-run |
Load and validate the schema, print as JSON. No database changes. |
When pushing, the CLI loads the encryption client from stash.config.ts, runs schema validation (warns but does not block), maps SDK types to EQL types, and upserts the config row in eql_v2_configuration.
SDK to EQL type mapping:
SDK dataType() |
EQL cast_as |
|---|---|
string / text |
text |
number |
double |
bigint |
big_int |
boolean |
boolean |
date |
date |
json |
jsonb |
Validate your encryption schema for common misconfigurations.
npx stash db validate [--supabase] [--exclude-operator-family]| Rule | Severity |
|---|---|
freeTextSearch on a non-string column |
Warning |
orderAndRange without operator families |
Warning |
| No indexes on an encrypted column | Info |
searchableJson without dataType("json") |
Error |
The command exits with code 1 on errors (not on warnings or info). Validation also runs automatically before db push.
Run pending encrypt config migrations.
npx stash db migrateGood to know: This command is not yet implemented.
Show the current state of EQL in your database.
npx stash db statusReports EQL installation status and version, database permission status, and whether an active encrypt config exists in eql_v2_configuration (relevant only for CipherStash Proxy).
Verify that the database URL in your config is valid and the database is reachable.
npx stash db test-connectionReports the database name, connected role, and PostgreSQL server version.
Build an encryption client file from your database schema using DB introspection.
npx stash schema build [--supabase]Connects to your database, lets you select tables and columns to encrypt, asks about searchable indexes, and generates a typed encryption client file.
Reads databaseUrl from stash.config.ts.
Use --drizzle with npx stash db install to add EQL installation to your Drizzle migration history instead of applying it directly. --drizzle is auto-detected when your project has drizzle-orm, drizzle-kit, or a drizzle.config.* file, so you usually don't need to pass it explicitly.
npx stash db install --drizzle
npx drizzle-kit migrateHow it works:
- Runs
npx drizzle-kit generate --custom --name=<name>to create an empty migration. - Loads the bundled EQL SQL (or fetches from GitHub with
--latest). - Writes the EQL SQL into the generated migration file.
With a custom name or output directory:
npx stash db install --drizzle --name setup-eql --out ./migrations
npx drizzle-kit migratedrizzle-kit must be installed in your project (npm install -D drizzle-kit). The --out directory must match your drizzle.config.ts.
Before installing EQL, the CLI verifies that the connected role has:
CREATEon the database (forCREATE SCHEMAandCREATE EXTENSION).CREATEon thepublicschema (forCREATE TYPE public.eql_v2_encrypted).SUPERUSERor extension owner privileges (forCREATE EXTENSION pgcrypto, if not already installed).
If permissions are insufficient, the CLI exits with a message listing what is missing.
import {
defineConfig,
loadStashConfig,
EQLInstaller,
loadBundledEqlSql,
downloadEqlSql,
} from 'stash'Type-safe identity function for stash.config.ts:
import { defineConfig } from 'stash'
export default defineConfig({
databaseUrl: process.env.DATABASE_URL!,
client: './src/encryption/index.ts',
})Finds and loads the nearest stash.config.ts, validates it with Zod, applies defaults, and returns the typed config:
import { loadStashConfig } from 'stash'
const config = await loadStashConfig()
// config.databaseUrl — validated non-empty string
// config.client — defaults to './src/encryption/index.ts'Programmatic access to EQL installation:
import { EQLInstaller } from 'stash'
const installer = new EQLInstaller({ databaseUrl: process.env.DATABASE_URL! })
const permissions = await installer.checkPermissions()
if (!permissions.ok) {
console.error('Missing permissions:', permissions.missing)
process.exit(1)
}
if (!(await installer.isInstalled())) {
await installer.install({ supabase: true })
}| Method | Returns | Description |
|---|---|---|
checkPermissions() |
Promise<PermissionCheckResult> |
Check required database permissions |
isInstalled() |
Promise<boolean> |
Check if the eql_v2 schema exists |
getInstalledVersion() |
Promise<string | null> |
Get the installed EQL version |
install(options?) |
Promise<void> |
Execute the EQL install SQL in a transaction |
Install options: excludeOperatorFamily, supabase, latest (all boolean).
Load the bundled EQL install SQL as a string:
import { loadBundledEqlSql } from 'stash'
const sql = loadBundledEqlSql()
const sql = loadBundledEqlSql({ supabase: true })
const sql = loadBundledEqlSql({ excludeOperatorFamily: true })Download the latest EQL install SQL from GitHub:
import { downloadEqlSql } from 'stash'
const sql = await downloadEqlSql() // standard
const sql = await downloadEqlSql(true) // no operator family variant@cipherstash/stack is the runtime SDK. It stays lean with no heavy dependencies like pg and ships in your production bundle. stash is a devDependency: it handles database tooling and schema lifecycle at development time. Think of it like Drizzle Kit — a companion tool that prepares the database while the runtime SDK handles queries.