# Introduction ### Welcome to Shelve Shelve is an open-source environment management platform designed to revolutionize how development teams handle their environment variables and secrets. Born from the frustration of sharing sensitive information through insecure channels, Shelve provides a secure, collaborative, and developer-friendly solution for managing environment configurations across projects and teams. ### Why Shelve? - Simple setup: one command to log in, another to inject secrets at runtime. - Team focused: built for modern development teams with collaboration and granular permissions. - Secure by default: per-project envelope [encryption](https://shelve.cloud/docs/core-features/encryption), OS-keychain token storage, [scoped API tokens](https://shelve.cloud/docs/core-features/tokens), and [audit logs](https://shelve.cloud/docs/core-features/audit-logs) on every write. - Open source: completely free and self-hostable, giving you full control over your data. - Developer experience: CLI-first, `.env`-free workflow with `shelve run`. ### Key features Environment management - Centralized variable management across projects and teams. - First-class support for `development`, `preview`, `production`, plus custom environments. - Per-project data encryption keys (DEKs) sealed by a platform KEK — rotate scope is a single project. - Secret references (`shelve://ENV/KEY`) in `.env.template` for cross-environment composition. Team collaboration - Role-based access control on teams and projects. - Scoped, expiring API tokens with optional IP allowlists. - Full audit trail of team, project, variable, and token changes. - Secure member invitations. Integrations - GitHub secrets synchronization. - One-click deployment using Coolify. - Email authentication with OTP codes. - OAuth authentication (GitHub, Google). - Runtime injection into any process, CI, or container via `shelve run`. Developer tools - CLI with `init`, `login`, `run`, `pull`, `push`, `create`, `generate`, `config`, `upgrade`. - Encrypted offline cache — work from a plane, get variables back online automatically. - AI-agent safety: `init` provisions `.cursorignore` / `.aiderignore` / `.codeiumignore` files and never exposes secrets to your coding agent. - Docker image and Vercel deploy button for self-hosting. ### Getting Started Ready to improve your environment management workflow? Head over to our [Quickstart](https://shelve.cloud/docs/getting-started/quickstart) guide to begin your journey with Shelve. ### Community and Support Shelve is built by developers for developers. We believe in the power of community and open source. Join our growing community: - Star us on [GitHub](https://github.com/hugorcd/shelve){rel=""nofollow""} - Follow updates on [Twitter](https://x.com/shelvecloud){rel=""nofollow""} - Report issues on [Github](https://github.com/hugorcd/shelve/issues){rel=""nofollow""} Whether you're a solo developer or part of a large team, Shelve adapts to your needs while maintaining security and simplicity at its core. # Quickstart ::steps{level="3"} ### Create an account Sign up for a Shelve account on [Shelve](https://app.shelve.cloud){rel=""nofollow""} :prose-icon{name="custom:shelve"} . Or if you prefer to self-host, follow the instructions in the [Self-hosting](https://shelve.cloud/docs/self-hosting/vercel) guide. Once you have an account, complete the onboarding process to create your first team (workspace). ![login](https://shelve.cloud/docs/login.png) ### Install the Shelve CLI package :::code-group{sync="pm"} ```bash [pnpm] pnpm add -D @shelve/cli ``` ```bash [yarn] yarn add -D @shelve/cli ``` ```bash [npm] npm install -D @shelve/cli ``` ```bash [bun] bun add -D @shelve/cli ``` ::: :::callout You can also install the CLI globally using the `-g / --global` flag of your package manager. ::: ### Connect the CLI to your account Run the following command in your terminal: ```bash [terminal] shelve login ``` This opens your browser so you can sign in to Shelve and authorize the CLI. Shelve creates a revocable CLI token automatically. For CI or scripts, use a [scoped API token](https://shelve.cloud/docs/core-features/tokens) with `SHELVE_TOKEN` or `shelve login --token "$SHELVE_TOKEN"` instead. :::card ::::accordion :::::accordion-item --- icon: i-lucide-badge-help label: Can I use the CLI with my self-hosted instance? --- Before running the `shelve login` command, you can set the URL of your self-hosted instance in a manually created `shelve.json` file in the root of your project. The file should look like this: ```json [shelve.json] { "url": "https://your-instance-url.com" } ``` ::::: :::::accordion-item --- icon: i-lucide-users label: Can i use the CLI with multiple accounts? --- No, the CLI only supports one account at a time. If you need to switch accounts, you will need to log out and log back in with the new account. ::::: :::::accordion-item --- icon: i-lucide-refresh-cw label: Can I use the CLI in a CI/CD pipeline, for example GitHub Actions? --- Yes. Create a scoped, expiring [API token](https://shelve.cloud/docs/core-features/tokens) and expose it as `SHELVE_TOKEN` in your CI secrets. The CLI reads it directly — no `shelve login` needed. Pair it with `shelve run -- ` to inject variables without writing a `.env` file on the runner. ::::: :::::accordion-item{icon="i-lucide-search" label="What if I forget my token?"} If you forget your token, you can always generate a new one in your account settings on the [Shelve App](https://app.shelve.cloud/user/tokens){rel=""nofollow""} . ::::: :::: ::: :::note{to="https://shelve.cloud/docs/cli/create"} You can now start using the CLI to manage your secrets. Check out the [CLI documentation](https://shelve.cloud/docs/cli) for more information. ::: :: # Audit Logs Every security-relevant action in Shelve is recorded in an append-only audit log. The feed is scoped to a team and visible from **Team → Audit logs**. ## What gets logged Among others: - `team.member.invite`, `team.member.remove` - `project.create`, `project.delete` - `environment.create`, `environment.update`, `environment.delete` - `variables.read`, `variables.create`, `variables.update`, `variables.delete` - `token.create`, `token.delete` Every entry stores the actor, IP, user agent, resource type/id, and a metadata JSON blob. The API enriches each row with human-readable labels — project and environment names, token prefix, user display name — so clients do not need extra lookups. ## API Retrieve logs via the REST API: ```bash [terminal] curl https://app.shelve.cloud/api/teams//audit-logs \ -H "Authorization: Bearer $SHELVE_TOKEN" ``` ### Query parameters ::field-group :::field{default="50" name="limit" type="number"} Number of entries to return. Between 1 and 100. ::: :::field{name="cursor" type="number"} `id` of the last entry seen on the previous page. The API returns entries with smaller ids (newest first). ::: :::field{name="action" type="string"} Filter by action name (for example `variables.create` ). Exact match. ::: :::field{name="actorType" type="string"} Filter by actor type: `user` , `token` , or `system` . ::: :::field{name="projectId" type="number"} Filter to events tied to a project (direct resource or metadata). ::: :: ### Response shape Each log entry includes enriched fields: ```json { "logs": [ { "id": 42, "action": "variables.read", "summary": "Read secrets from web-platform / production", "actor": { "type": "token", "label": "she_abc… · ci-deploy" }, "resource": { "type": "environment", "label": "web-platform / production", "href": "/my-team/projects/13" }, "client": { "label": "Shelve CLI · macOS", "icon": "lucide:terminal", "raw": "..." }, "metadata": { "projectId": 13, "projectName": "web-platform", "environmentName": "production" } } ], "nextCursor": 41, "total": 128 } ``` Pass `nextCursor` back to fetch the next page until it is `null`. ::callout{type="info"} Audit writes are fire-and-forget: they never block the originating request. If recording fails, the event is logged to the application logs and the request still succeeds. :: ## Retention Audit logs are retained indefinitely on the hosted instance. On self-hosted deployments you control the retention policy by pruning the `audit_logs` table directly. # Encryption Shelve treats every secret value as encrypted data until the very moment you need it. This page describes the encryption scheme, the key hierarchy, and what actually hits the database. ## Two-tier envelope encryption Secret values are never encrypted directly with the global server key. Instead Shelve uses an envelope scheme: ```text value ──seal──▶ ciphertext (key = DEK, per-project) DEK ──seal──▶ encryptedDek (key = KEK, platform-wide) ``` - **KEK** — *Key Encryption Key*. A single secret sourced from `NUXT_PRIVATE_ENCRYPTION_KEY` at boot. It never touches stored data directly; it is only used to seal and unseal DEKs. - **DEK** — *Data Encryption Key*. Generated server-side on a project's first write (32 random bytes, base64-encoded), sealed with the KEK, and persisted in `projects.encryptedDek`. From then on every variable on that project is encrypted with that DEK. The sealing primitive underneath is [`iron-webcrypto`](https://github.com/brc-dd/iron-webcrypto){rel=""nofollow""}, configured with authenticated encryption (`aes-256-gcm`). A tampered ciphertext fails to decrypt — there is no silent downgrade. ## Why envelope encryption? ::card-group :::card --- icon: i-lucide-zap --- #title Fast key rotation #description Rotating a project's DEK only re-encrypts that project's variables. A platform-wide rotation changes only the KEK; every sealed DEK is re-sealed once and business continues. ::: :::card --- icon: i-lucide-shield --- #title Scoped blast radius #description A leaked DEK compromises one project, not every secret on the instance. The KEK never leaves the server. ::: :::card --- icon: i-lucide-layers --- #title BYOK-ready #description The DEK layer is the seam where hardware-backed key storage (KMS, HSM, passkeys) will plug in without touching application code. ::: :: ## Backward compatibility Projects created before the envelope upgrade have no `encryptedDek` column value. Their variables keep decrypting directly with the KEK and the first new write provisions a DEK automatically. Reads tolerate mixed-state data: the service tries the project DEK first, then falls back to the KEK so no variable is left unreadable during the transition. ## What is stored in the database | Column | What it holds | | -------------------------- | ----------------------------------------------------------------- | | `variables.encryptedValue` | Sealed ciphertext of the secret value. Never plaintext. | | `projects.encryptedDek` | Project DEK, sealed by the KEK. `null` for pre-envelope projects. | | `tokens.hash` | `sha256(token)` as hex. The plaintext token is never stored. | | `tokens.prefix` | Non-secret 12-char prefix used for display and audit logs. | ## API tokens API tokens follow a different (but compatible) model: they are hashed, not encrypted. See [API Tokens](https://shelve.cloud/docs/core-features/tokens) for the full story. ## In transit Traffic to `app.shelve.cloud` uses TLS 1.3 end-to-end. The CLI pins the same endpoint and refuses downgrade. For self-hosted instances, configure HTTPS at your reverse proxy or platform. ## Self-host checklist 1. Generate a strong KEK: `openssl rand -base64 48`. 2. Set it as `NUXT_PRIVATE_ENCRYPTION_KEY` on your platform. 3. **Never** rotate the KEK in-place without re-sealing existing DEKs — existing data becomes unreadable. A safe rotation procedure is on the [roadmap](https://github.com/HugoRCD/shelve/issues){rel=""nofollow""}. 4. Back up `projects.encryptedDek` alongside `variables.encryptedValue`; losing either makes the corresponding data unrecoverable. # Environments ![environments](https://shelve.cloud/docs/environments.png) Environments are a way to group variables by context. You can create environments for different stages of your application like development, staging, production for example. Each environment can have its own set of variables, which allows you to manage your configurations more efficiently. You can create as many environments as you want. To create an environment, click on the `New Environment` button, give it a name (e.g., `preview`, `feat/218`, `staging`, `production`), and click on the `Create` button. The name must be unique across all environments. Creating an environment will automatically add a field to set a variable value for this environment. ::caution You can delete an environment but be careful, this action is irreversible and will delete all variables associated with this environment. :: # Projects ![projects](https://shelve.cloud/docs/project.png) Projects are the core of Shelve, a project is a collection of environment variables and secrets, but it can also contain other resources like quick links, files (soon), and more. ### Variables Variables are key-value pairs that store environment configurations and secrets. You can create variables for different environments like development, staging, production, or even custom environments. Check the [Variables](https://shelve.cloud/docs/core-features/variables) page for more information. ### Quick Links Quick links are shortcuts to external resources like documentation, repositories, or any other URL. You can create quick links for your project to easily access external resources without leaving Shelve in the settings tab. ### Files (soon) Files will be a new feature that allows you to store files in your project. You can upload files like certificates, images, or any other file type to your project. Files will enable you to store all your project resources in one place. ### Repository The repository serves as a link to your project's repository. You can connect your project to a GitHub repository to synchronize your secrets with GitHub secrets. By linking your project to a repository, you enhance Shelve's capabilities, enabling you to automatically synchronize your secrets with GitHub secrets, which simplifies the management of your secrets across your projects. But don't worry, your secrets are always encrypted and secure. You will benefit from a lot more features in the future ! # Teams ![teams](https://shelve.cloud/docs/teams.png) Shelve is designed to be used by teams of developers but can also be used by solo developers. A team is a group of users who work together on a project. Teams can have multiple members, each with different roles that determine their permissions within the team. ### Roles Shelve has three roles: **Owner**, **Admin**, and **Member**. - **Owner**: The user who created the team. The owner has full control over the team, members and projects. He's the only one who can delete projects, variables, environments, and the team itself. - **Admin**: Users with admin permissions can manage the team, members, and projects. They can't delete the team or change the owner. - **Member**: Users with member permissions can only view the settings of the team and projects. They can't make any changes but can create and update variables. ### Workspaces Team act as a container for projects and variables. Each team has its own workspace where you can create and manage projects and variables. Team workspaces are isolated from each other, meaning that projects and variables from one team are not accessible by another team. ### Slug A team slug is a unique identifier for a team. It is used in URLs to access the team's workspace. The team slug is automatically generated when you create a team and can be customized later but must be unique across all teams. This also the unique identifier that you can use to access the team's workspace via the CLI ```json [shelve.json] { "slug": "nuxtlabs", "project": "@nuxt/ui" } ``` ## Create a team Use the team selector and type the team name in the search field to create a new team. :video{autoPlay="true" loop="true" muted="true" src="https://shelve.cloud/docs/create-team.mp4"} # API Tokens API tokens let the CLI and any external system authenticate against Shelve. They are managed from your account settings at [app.shelve.cloud/user/tokens](https://app.shelve.cloud/user/tokens){rel=""nofollow""}. ## How tokens are stored - The plaintext value is **shown exactly once**, at creation. Copy it immediately — Shelve never stores it and cannot retrieve it later. - Only the SHA-256 hash of the token is written to the database, alongside a non-secret prefix (`she_…` — the first 12 characters). The prefix is what you see in the token list and in audit logs; it is useful for identifying a token without revealing it. - Lookups run in constant time (`timingSafeEqual` on the hash), so there is no side channel that leaks bit-by-bit comparisons. ::callout{type="info"} Tokens are produced from `crypto.randomBytes(32)` and encoded in [Crockford base32](https://www.crockford.com/base32.html){rel=""nofollow""} . They are 256 bits of entropy and cannot be brute-forced. :: ## Scoped tokens By default a token inherits your account's full access. You can narrow it when creating the token: ::field-group :::field{name="permissions" type="('read' | 'write')[]"} At least one is required. `read` grants listing and fetching, `write` grants mutations. A read-only token that tries to POST a variable receives `403 Token missing 'write' permission` . ::: :::field{name="teamIds" type="number[]"} Restrict the token to one or more teams. Requests targeting any other team return `403 Token not authorized for this team` . ::: :::field{name="projectIds" type="number[]"} Restrict the token to specific projects within the allowed teams. ::: :::field{name="environmentIds" type="number[]"} Restrict the token to specific environments. Useful for CI tokens that should only read `production` secrets, for example. ::: :: Scope enforcement happens server-side in a dedicated middleware; the token can never outgrow the scope it was issued with. ## Expiry Every token may carry an `expiresAt`. After that date the token is refused with `401 Token expired` even before it is hashed and matched. Rotating a short-lived CI token is as simple as creating a new one; no restart or config push is required. ## IP allowlist Optionally attach a list of CIDR blocks to a token. Requests originating outside those blocks are rejected with `401 Token not authorized for this network`. Both IPv4 and IPv6 notations are supported. ## Audit trail Token lifecycle events (`token.create`, `token.delete`) and every authenticated request surface in [audit logs](https://shelve.cloud/docs/core-features/audit-logs) with the token prefix, actor, IP, and user agent. When something looks wrong, revoke the token from the UI — the hash is deleted and the next request using it returns `401`. ## Sending tokens The CLI and any integration should send the token as an `Authorization: Bearer ` header. The legacy `Cookie: authToken=…` path still works for older CLI builds but returns `Deprecation: true` / `Sunset: Wed, 01 Jul 2026 00:00:00 GMT` response headers — migrate clients before that date. # Variables Variables are key-value pairs that store environment configurations and secrets. You can create variables for different environments like development, staging, production or even custom environments. Understanding how to effectively manage these variables is crucial for maintaining a secure and efficient development workflow. ## Creating Variables ### Variables Form The variables form is an incredibly powerful tool that allows you to create variables for multiple environments at the same time. You can easily import variables from a file or export them by clicking on the three dots in the top right corner. Plus, you can simply drag and drop a `.env` file (or any other supported files containing variables) or copy/paste to import variables, which will be automatically parsed and cleaned by removing comments, empty lines, and quotes. Working with variable names is made easier through several thoughtful features. As you type, the form automatically converts your keys to uppercase, following the common convention for environment variables. You'll also find a prefix button that helps you maintain consistency with framework-specific prefixes like `NUXT_PRIVATE_` or `REACT_APP_`. For generating secure values, especially useful for passwords or tokens, the Value Generator button provides random strings that meet your security requirements. ### Variable Item ![variable-item](https://shelve.cloud/docs/variable-item.png) Each variable in Shelve is designed to provide clear visibility and easy access to its information. When looking at a variable, you'll see its key, value, and environment status all clearly displayed. Values are masked by default to protect sensitive information, but you can easily reveal them using the `eye` icon when needed. The copy functionality goes beyond simple value copying. While you can copy individual fields, you can also copy the entire variable string in the format `KEY=value` by clicking on the environment name. This becomes particularly useful when you need to quickly share or transfer variables between projects. One of the most valuable features is the environment indicator system. These indicators provide an immediate visual understanding of where each variable is set. When managing large projects with multiple environments, this becomes invaluable for ensuring consistency and identifying potential missing configurations. ## Variables Selector :video{autoPlay="true" loop="true" muted="true" src="https://shelve.cloud/docs/variable-selector.mp4"} You can easily select multiple variables by clicking on each item. Once selected, you can perform bulk actions like copying, deleting, or sending them to Github Secrets if you've connected your project to a repository and install a [Github App](https://shelve.cloud/docs/integrations/github) to synchronize your secrets with Github Secrets. # Init The `init` command prepares a repository for use with Shelve and, crucially, keeps your `.env*` files out of the context of AI coding agents. Running it is a one-off setup step — run it once per project. ```bash [terminal] shelve init shelve init --cwd ./packages/my-app shelve --json init ``` ## What it does - Creates (or updates) an ignore file for every major AI agent so they can never read cached secrets: - `.cursorignore` - `.aiderignore` - `.codeiumignore` - `.continueignore` - `.aigignore` - Appends a managed block to your `.gitignore` if one is not already present, so the encrypted cache directory (`.shelve/`) and all `.env*` files stay out of version control. `.env.example` and `.env.template` are whitelisted so you can still commit references. Each ignore file is framed with `# shelve-managed-block` / `# end shelve-managed-block` markers. Running `shelve init` again only rewrites the managed block — your own entries above and below are preserved. ## Options ::field-group :::field{name="cwd" type="string"} Directory to initialize. Defaults to the current working directory. ::: :: With global `--json`, stdout returns `{ writtenFiles, skippedFiles, gitignoreUpdated }`. ## Why this matters AI coding agents (Cursor, Claude Code, Codex, Aider, Continue…) can read every file in your workspace unless you tell them otherwise. If you ever run `shelve pull` to write a `.env` to disk, an agent can trivially exfiltrate its contents into a prompt or a suggested commit. `shelve init` closes that hole in one command. ::callout{type="info"} Even with agent ignore files in place, prefer `shelve run -- ` : it pipes secrets to your subprocess through the environment and never writes them to disk in the first place. :: # Agents & automation The Shelve CLI is designed to work in **CI**, **shell scripts**, and **AI agent shells** (Cursor, Claude Code, Codex, …) without hanging on prompts or polluting logs with spinners. ::callout{type="warning"} Prefer **`shelve run -- `** over **`shelve pull`** . `run` keeps secrets in the child process memory only. `pull` writes plaintext to disk where agents can read them. :: ## Recommended workflow 1. Run **`shelve doctor --json`** (or `shelve doctor`) to validate setup. 2. Set context once with environment variables (no `shelve login` prompt required): ```bash \[terminal] export SHELVE_TOKEN="shlv_…" export SHELVE_TEAM_SLUG="my-team" export SHELVE_PROJECT="my-app" export SHELVE_DEFAULT_ENV="development" # optional export SHELVE_URL="https://app.shelve.cloud" # optional ``` 3. Run **`shelve init`** once per repo (agent ignore files + `.gitignore` block). 4. Inject secrets with **`shelve run -- pnpm dev`** (or your command). 5. Use **`--json`** when a script needs to parse output. 6. Use **`--non-interactive`** (or rely on agent/CI auto-detection) so missing flags fail fast. ## Global flags These flags can appear **before or after** the subcommand (for example `shelve run --json -- pnpm dev` or `shelve --json config`): ::field-group :::field{name="--json" type="boolean"} Success → JSON on **stdout** : `{ "ok": true, "command"?: string, "data"?: object }` . :br Errors → JSON on **stderr** : `{ "ok": false, "error": { "code", "message", "status"?, "hint"? } }` . :br Secret **values are never** included in JSON output. ::: :::field{alias="-q" name="--quiet" type="boolean"} Suppress clack intro/outro/spinners. Minimal text output only. ::: :::field{alias="-y" name="--yes" type="boolean"} Skip confirmation prompts ( `pull` , `push` when `confirmChanges` is enabled, etc.). ::: :::field{name="--non-interactive" type="boolean"} Fail with a structured error instead of prompting when required input is missing ( `MISSING_*` , `AUTH_REQUIRED` , …). ::: :::field{name="--debug" type="boolean"} Verbose debug logs. Also enabled by `SHELVE_DEBUG=1` or `DEBUG=true` . HTTP requests are logged without Authorization headers or secret values. ::: :: ### Auto non-interactive mode The CLI enters non-interactive mode when any of these is true: - `--non-interactive` is passed - `CI=true` or `CI=1` - The shell is detected as an AI agent via [`std-env`](https://github.com/unjs/std-env){rel=""nofollow""} (`cursor`, `claude`, `codex`, …) - `AI_AGENT` is set (force-detect) In agent shells, **`shelve pull` without `--yes` fails immediately** with code `AGENT_BLOCKED` instead of prompting. ## JSON output by command | Command | `data` shape | | ---------- | -------------------------------------------------- | | `doctor` | `{ healthy, checks[], exitCodes, errorCodes }` | | `config` | Merged config; `token` redacted as `"***"` | | `me` | `{ loggedIn, username?, email? }` | | `push` | `{ env, variableCount, pushed }` | | `pull` | `{ env, variableCount, file, keys[] }` — no values | | `init` | `{ writtenFiles, skippedFiles, gitignoreUpdated }` | | `login` | `{ username, email }` | | `create` | `{ name, slug, configPath }` | | `logout` | `{ loggedOut: true }` | | `generate` | `{ type, path }` | | `upgrade` | `{ previous, current, updated }` | `shelve run` inherits the child stdio. Startup errors are structured on stderr; with `--json`, a spawn event is also emitted on stderr: `{ "ok": true, "event": "child_spawned", "env", "variableCount", "keys", "command", "pid" }`. ## Exit codes | Code | Meaning | | ------------- | --------------------------------------- | | `0` | Success | | `1` | CLI, API, or validation error | | `128 + n` | Child killed by signal (`run`) | | `129` | Parent process gone / stdin EIO | | `130` / `143` | SIGINT / SIGTERM (forwarded from `run`) | ## Examples ```bash [terminal] # Machine-readable config (token redacted) shelve --json config # CI push without prompts shelve --non-interactive --yes push --env staging # Agent-safe dev server shelve run -- pnpm dev # Debug API round-trip shelve --debug run --env preview -- pnpm build ``` ## Install the agent skill Shelve publishes a single [Agent Skill](https://docus.dev/en/ai/skills){rel=""nofollow""} at `/.well-known/skills/` on [shelve.cloud](https://shelve.cloud){rel=""nofollow""} (`shelve` — CLI, platform, and sync policies): ```bash [terminal] npx skills add https://shelve.cloud ``` The skill teaches agents Shelve end-to-end: platform model (teams, tokens, UI), CLI workflows, sync policies, and security rules (prefer `run`, avoid disk writes, use `SHELVE_*` env vars). Reference files ship alongside `SKILL.md` (`cli-commands.md`, `platform.md`, `sync-policies.md`, `agent-workflows.md`). ## Local testing (contributors) From the Shelve monorepo, use the zero-network playground: ```bash [terminal] pnpm play pnpm play -- --json config ``` See the [playground README](https://github.com/HugoRCD/shelve/tree/main/playground/run){rel=""nofollow""} on GitHub. # Troubleshooting Quick fixes for the most common CLI issues. Run **`shelve doctor`** (or **`shelve --json doctor`**) first — it checks config, auth, API, cache, and agent context in one shot. ```bash [terminal] shelve doctor shelve --json doctor ``` ## Error codes | Code | Meaning | Fix | | ------------------- | ------------------------------------------- | -------------------------------------------------------------------- | | `AGENT_BLOCKED` | `pull` in an AI agent shell without `--yes` | Use `shelve run -- ` or pass `--yes` if disk `.env` is required | | `AUTH_REQUIRED` | No token | `export SHELVE_TOKEN=…` or `shelve login` | | `CONFIG_MISSING` | No `shelve.json` and missing env | Create config or set `SHELVE_TEAM_SLUG` + `SHELVE_PROJECT` | | `MISSING_ENV` | No environment selected | `--env staging` or `defaultEnv` in config | | `MISSING_INPUT` | Non-interactive mode missing a flag | Pass the flag shown in the error hint | | `FETCH_FAILED` | Network/API failure, no cache | Go online once, or use `--offline` if cache exists | | `FORBIDDEN` | Token lacks scope | Create a token with read/write for the team/project | | `PROJECT_NOT_FOUND` | Project missing | Enable `autoCreateProject` or `shelve create` | | `PUSH_BLOCKED` | Push disabled for this env | Check `sync.protectedEnvironments` or `allowPush` | | `PULL_BLOCKED` | Pull disabled for this env | Set `sync.environments..allowPull` | | `SYNC_CONFLICT` | Diverging keys and `onPushConflict: fail` | Run `shelve diff`, align values, or change policy | | `ENV_PROTECTED` | Server blocked write to protected env | Update project sync policy in Shelve settings | See also `shelve --help` for the full list of structured error codes. ## Exit codes | Code | Meaning | | ------------- | ----------------------------------------------------------- | | `0` | Success | | `1` | CLI or API error | | `128 + n` | Child killed by signal (`run`) | | `129` | Parent process gone / stdin EIO | | `130` / `143` | SIGINT / SIGTERM (often treated as clean shutdown in `run`) | ## Common symptoms ### CLI hangs waiting for input **Cause:** Interactive prompt (login, missing `--env`, confirmation). **Fix:** ```bash [terminal] export SHELVE_TOKEN=… export SHELVE_TEAM_SLUG=… export SHELVE_PROJECT=… shelve --non-interactive doctor ``` ### `shelve run dev` exits instantly **Cause:** Script resolution failed or script exits immediately. **Fix:** ```bash [terminal] shelve run --debug dev shelve run -- pnpm dev ``` ### Secrets not updated after Shelve UI change **Cause:** Old process env or stale cache. **Fix:** Restart with `shelve run`, or use `--watch --restart-on-change`. ### `pull` fails in Cursor / Claude Code **Cause:** Agent guard (`AGENT_BLOCKED`). **Fix:** Prefer `shelve run -- `. Only `shelve pull --yes` if you explicitly need a `.env` file. ### Offline / plane mode **Fix:** ```bash [terminal] # After one successful online run: shelve run --offline -- pnpm build ``` ### JSON automation parse errors - Success → **stdout**: `{ "ok": true, "data": … }` - Errors → **stderr**: `{ "ok": false, "error": { "code", "message" } }` - `run` spawn metadata → **stderr** event: `{ "ok": true, "event": "child_spawned", … }` Secret **values** are never included in JSON output. ## CI / GitHub Actions Use the composite action: ```yaml - uses: ./.github/actions/shelve-run with: token: ${{ secrets.SHELVE_TOKEN }} team-slug: my-team project: my-app env: ci command: pnpm test ``` Validate secrets before the main job: ```yaml - run: npx @shelve/cli --json doctor env: SHELVE_TOKEN: ${{ secrets.SHELVE_TOKEN }} SHELVE_TEAM_SLUG: my-team SHELVE_PROJECT: my-app ``` ## Agent skill Install the published skills: ```bash [terminal] npx skills add https://shelve.cloud ``` Catalog: `https://shelve.cloud/.well-known/skills/index.json` (skill: `shelve`). # Sync policies Sync policies live under `sync` in `shelve.json` (and can be enforced on the server per project). They answer: **should a push from my laptop overwrite production?** **Does pull replace my whole `.env` or merge?** ## Quick example ```json [shelve.json] { "$schema": "https://shelve.cloud/schema.json", "slug": "my-team", "project": "my-app", "defaultEnv": "development", "sync": { "protectedEnvironments": ["production", "preview"], "default": { "onPushConflict": "overwrite", "pullMode": "replace" }, "environments": { "development": { "sourceOfTruth": "local", "onPushConflict": "overwrite" }, "production": { "sourceOfTruth": "remote", "allowPush": false, "pullMode": "merge" } } } } ``` ## Policy fields | Field | Values | Effect | | ------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `sourceOfTruth` | `remote` \| `local` | Hint for [`shelve sync`](https://shelve.cloud/docs/cli/sync-policies#sync-command): pull when `remote`, push when `local`. | | `onPushConflict` | `overwrite` \| `skip` \| `fail` \| `prompt` | When a key exists on Shelve with a **different** value than local. Default: `overwrite`. | | `pullMode` | `replace` \| `merge` | `replace`: rewrite `.env` (legacy). `merge`: remote keys win; **local-only** keys are kept. | | `allowPush` / `allowPull` | `boolean` | Hard block with `PUSH_BLOCKED` / `PULL_BLOCKED`. | | `protectedEnvironments` | `string[]` | Sets `allowPush: false` for listed env names. | | `requireConfirmation` | `boolean` | Extra confirmation even if `confirmChanges` is false. | Per-environment overrides go in `sync.environments.`. Defaults apply via `sync.default`. ## Server policies Project **Settings → Sync policy** stores `syncPolicy` on the project. Server rules **cannot be relaxed** from `shelve.json`: if the server sets `allowPush: false`, the CLI cannot override it. Protected environments reject API writes with `ENV_PROTECTED`. ## Commands ### `shelve diff` Compare local `envFileName` with Shelve (no writes). Safe for agents with `--json` (no secret values). ```bash [terminal] shelve diff --env staging shelve --json diff --env staging shelve diff --env staging --show-values ``` ### `shelve sync` {#sync-command} Apply the effective policy for the environment: - `sourceOfTruth: remote` → pull (respects `pullMode`) - `sourceOfTruth: local` → push (respects `onPushConflict`) ```bash [terminal] shelve sync --env development shelve sync --env production --dry-run shelve sync --yes --env staging ``` `--dry-run` reports the planned action and diff without writing. ## Environment variables | Variable | Effect | | -------------------------- | -------------------------------------------------- | | `SHELVE_SYNC_ALLOW_PUSH=0` | Disables push for all environments in this process | | `SHELVE_SYNC_ALLOW_PULL=0` | Disables pull for all environments | ## Error codes | Code | Meaning | | --------------- | ------------------------------------------------------------ | | `PUSH_BLOCKED` | `allowPush: false` or protected environment | | `PULL_BLOCKED` | `allowPull: false` | | `SYNC_CONFLICT` | `onPushConflict: fail` (or `prompt` in non-interactive mode) | | `ENV_PROTECTED` | Server rejected a write to a protected environment | See [Troubleshooting](https://shelve.cloud/docs/cli/troubleshooting). ## Monorepos Put `protectedEnvironments` in the **root** `shelve.json`; package-level files can override `sync.environments` for each app. # CLI The CLI is the primary way to sync secrets between your terminal and [Shelve](https://shelve.cloud){rel=""nofollow""}. Use it to **inject** variables at runtime (`shelve run`), **push** local changes upstream, or **pull** secrets to disk when you really need a `.env` file. ::callout{type="tip"} New to AI agents or CI? Start with [Agents & automation](https://shelve.cloud/docs/cli/agents-automation) and install the published skill: `npx skills add https://shelve.cloud` :: ## Installation Install `@shelve/cli` in your project or globally. See the [Quickstart](https://shelve.cloud/docs/getting-started/quickstart) guide. ```bash [terminal] pnpm add -D @shelve/cli ``` ## Command overview | Command | Description | | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | [`run`](https://shelve.cloud/docs/cli/run) | Inject secrets into a child process (preferred) | | [`init`](https://shelve.cloud/docs/cli/init) | Agent-safe ignore files + `.gitignore` block | | [`login`](https://shelve.cloud/docs/cli/login-logout) / [`logout`](https://shelve.cloud/docs/cli/login-logout) | Manage stored credentials | | [`me`](https://shelve.cloud/docs/cli/login-logout) | Show the logged-in user | | [`push`](https://shelve.cloud/docs/cli/push-pull) / [`pull`](https://shelve.cloud/docs/cli/push-pull) | Sync secrets with Shelve | | [`diff`](https://shelve.cloud/docs/cli/sync-policies) / [`sync`](https://shelve.cloud/docs/cli/sync-policies) | Compare or apply sync policy | | [`create`](https://shelve.cloud/docs/cli/create) | Create a project + `shelve.json` | | [`config`](https://shelve.cloud/docs/cli/config) | Show merged configuration | | [`generate`](https://shelve.cloud/docs/cli/generate) | Generate `.env.example` or ESLint config | | [`upgrade`](https://shelve.cloud/docs/cli/upgrade) | Update the CLI package | | [`doctor`](https://shelve.cloud/docs/cli/troubleshooting) | Validate config, auth, API, and cache | | [`agents-automation`](https://shelve.cloud/docs/cli/agents-automation) | Global flags, JSON output, CI & agents | | [`troubleshooting`](https://shelve.cloud/docs/cli/troubleshooting) | Error codes and common fixes | ## Global flags Available on **every** command (before or after the subcommand): | Flag | Alias | Description | | ------------------- | ----- | ---------------------------------------------------- | | `--json` | | Machine-readable stdout; structured errors on stderr | | `--quiet` | `-q` | No spinners or clack UI | | `--yes` | `-y` | Skip confirmation prompts | | `--non-interactive` | | Fail instead of prompting | | `--debug` | | Verbose logs (`SHELVE_DEBUG=1`) | ## Configuration Configuration is loaded from the current working directory. Supported filenames: `shelve.json`, `shelve.config.json`, `.shelverc.json`. If none exists and a command requires config, the CLI can create `shelve.json` interactively (or fail in non-interactive mode). JSON Schema: [shelve.cloud/schema.json](https://shelve.cloud/schema.json){rel=""nofollow""} ```json [shelve.json] { "$schema": "https://shelve.cloud/schema.json", "slug": "nuxtlabs", "project": "@nuxt/ui", "defaultEnv": "development", "confirmChanges": false, "autoCreateProject": true } ``` ### Monorepo support Shelve detects monorepo roots and **merges** the root `shelve.json` with the local one (shared team `slug`, per-package `project`, etc.): ::code-tree{default-value="shelve.json"} ```json [apps/app/shelve.json] { "project": "@nuxt/app" } ``` ```json [shelve.json] { "slug": "nuxtlabs" } ``` :: Commands always run in the **current directory** — they do not automatically execute across every package in the monorepo. ## Configuration options | Option | Type | Default | Description | | ------------------- | --------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------- | | `project` | `string` | `SHELVE_PROJECT` or nearest `package.json` name | Project name | | `slug` | `string` | `SHELVE_TEAM_SLUG` | Team slug | | `token` | `string` | Keychain / `$XDG_CONFIG_HOME/.shelve` / `SHELVE_TOKEN` | API token | | `url` | `string` | `https://app.shelve.cloud` | Shelve instance URL | | `defaultEnv` | `string` | — | Default environment for `run`, `push`, `pull` | | `confirmChanges` | `boolean` | `false` | Confirm before push/pull writes | | `envFileName` | `string` | `.env` | Local env file name | | `autoUppercase` | `boolean` | `true` | Uppercase keys on push | | `autoCreateProject` | `boolean` | `true` | Create project if missing | | `sync` | `object` | — | [Sync policies](https://shelve.cloud/docs/cli/sync-policies) (per-env push/pull rules) | ## Environment variables | Variable | Description | | -------------------- | ------------------------------ | | `SHELVE_PROJECT` | Project name | | `SHELVE_TEAM_SLUG` | Team slug | | `SHELVE_TOKEN` | Authentication token | | `SHELVE_URL` | Shelve instance URL | | `SHELVE_DEFAULT_ENV` | Default environment | | `SHELVE_DEBUG=1` | Enable debug logging | | `AI_AGENT` | Force AI-agent shell detection | Credentials are stored in the **OS keychain** when available, with an XDG file fallback (`~/.config/.shelve`, mode `0600`). # Run `shelve run` spawns your command with every variable from your Shelve project already present in its environment. **No plaintext `.env` file is written by `shelve run`** — encrypted cache files (for example under `~/.shelve/cache/`) may still be created for offline use. ```bash [terminal] shelve run -- [args] ``` The double-dash is conventional — anything after `--` is passed verbatim to your command. For npm-style scripts, `shelve run dev` resolves the script from `package.json` and runs it directly (without a package-manager wrapper that prints `ELIFECYCLE` on Ctrl-C). ::callout{type="tip"} If a script exits suspiciously fast, run `shelve run --debug -- pnpm dev` to see the resolved command, or use `shelve run -- pnpm dev` to bypass script resolution. :: ## How it works 1. The CLI reads your token (keychain, XDG file, or `SHELVE_TOKEN`), resolves team / project / environment, and fetches variables (or reads the encrypted cache). 2. Variables are merged into a fresh environment map layered on `process.env`. 3. Your command is spawned with `stdio: 'inherit'`. 4. The child exit code is propagated to `shelve run`. On fetch failure, the CLI falls back to a fresh encrypted cache when available (unless `--no-cache` is set). Failures emit structured errors instead of silent exits. ## Examples ```bash [terminal] shelve run --env preview -- pnpm dev shelve run dev shelve run -- node scripts/migrate.ts --dry-run shelve --json run -- pnpm test # JSON applies to CLI messages only; child keeps normal stdio ``` ## Options ::field-group :::field{name="env" type="string"} Environment to use ( `development` , `preview` , `production` , …). Falls back to `defaultEnv` in `shelve.json` or `SHELVE_DEFAULT_ENV` . ::: :::field{name="template" type="string"} Path to a `.env.template` file with `shelve://` references and literal values (see below). ::: :::field{name="offline" type="boolean"} Use **only** the encrypted offline cache. Fails if no cache exists for this project/environment. ::: :::field{name="no-cache" type="boolean"} Disable cache reads and writes entirely. ::: :::field{default="24h" name="cache-ttl" type="duration"} Cache freshness override. Accepts `500ms` , `30s` , `15m` , `2h` , `7d` , or milliseconds. ::: :::field{name="watch" type="boolean"} Poll Shelve every ~5s for variable changes. Forwards `SIGHUP` to the child by default. ::: :::field{name="restart-on-change" type="boolean"} With `--watch` , kill and respawn the child instead of sending `SIGHUP` (required when the child reads `process.env` only once). ::: :: ## Secret references (`.env.template`) Commit a template and resolve secrets at runtime: ```bash [.env.template] NODE_ENV=production DATABASE_URL=shelve://DATABASE_URL PROD_DATABASE_URL=shelve://production/DATABASE_URL ``` - Lines without `shelve://` pass through verbatim. - `shelve://KEY` pulls `KEY` from the current environment. - `shelve:///KEY` targets another environment. Run with: ```bash [terminal] shelve run --template .env.template -- pnpm dev ``` Unresolved references are warned; the command still runs with literal placeholders. ## Encrypted offline cache After every successful fetch, Shelve writes an AES-256-GCM payload to `~/.shelve/cache/`, keyed via HKDF from your API token. - Token rotation or revocation invalidates the cache. - Tampering is detected (GCM auth tag). - Files are mode `0600`; directory `0700`. ```bash [terminal] shelve run --offline -- pnpm build ``` ## Watch mode ```bash [terminal] # Daemons that reload on SIGHUP shelve run --watch -- node server.js # Node apps that need a full respawn for new env shelve run --watch --restart-on-change -- pnpm dev ``` ## AI-agent safety Prefer `shelve run` in agent shells — secrets exist only in the child process memory. `shelve pull` is blocked in agent environments unless `--yes` is passed explicitly. See [Agents & automation](https://shelve.cloud/docs/cli/agents-automation). ## Exit behaviour | Signal / event | Behaviour | | -------------------- | ------------------------------------------ | | Child exits normally | Same exit code | | Child crashes | Exit code propagated | | Ctrl-C (`SIGINT`) | Forwarded to child tree; graceful shutdown | | `SIGTERM` / `SIGHUP` | Forwarded; 5s grace then `SIGKILL` | | Parent process gone | CLI tears down child tree (exit `129`) | | Watch reload | Child respawned or sent `SIGHUP` | Signal exits (`130`, `143`, `129`) are treated as normal shutdown in many paths. # Login and Logout ## Login ```bash [terminal] shelve login shelve login --no-browser shelve login --with-token shelve login --token "$SHELVE_TOKEN" ``` By default, `shelve login` starts a **device authorization** flow (similar to Vercel or GitHub CLI): 1. The CLI opens your browser to Shelve (or prints a URL and short code). 2. Sign in if needed, then click **Authorize CLI**. 3. Shelve creates a revocable API token named `CLI — ` (90-day expiry, full read/write) and the CLI stores it locally. You can revoke CLI tokens anytime from [app.shelve.cloud/user/tokens](https://app.shelve.cloud/user/tokens){rel=""nofollow""}. Tokens are [scopeable, expiring, and IP-bound](https://shelve.cloud/docs/core-features/tokens) when created manually from the dashboard. ### Options ::field-group :::field{name="token" type="string"} API token (skips device login). `SHELVE_TOKEN` is also accepted without running `login` . ::: :::field{name="with-token" type="boolean"} Prompt for a pasted API token instead of browser login. ::: :::field{name="no-browser" type="boolean"} Do not open a browser; print the authorization URL and user code only. ::: :: ### Where the token is stored 1. **OS keychain** — Keychain (macOS), Credential Vault (Windows), Secret Service / GNOME Keyring (Linux) via [`@napi-rs/keyring`](https://github.com/napi-rs/keyring){rel=""nofollow""}. 2. **XDG file fallback** — `$XDG_CONFIG_HOME/.shelve` (typically `~/.config/.shelve`), mode `0600`, when the keychain is unavailable. ::callout{type="info"} Legacy `~/.shelve` is migrated automatically on first read with v5+. :: ## Me ```bash [terminal] shelve me shelve --json me ``` Human mode prints the logged-in username and email. JSON mode returns `{ "loggedIn": true, "username", "email" }` or `{ "loggedIn": false }`. ## Logout ```bash [terminal] shelve logout shelve --json logout ``` Clears the keychain entry and XDG config. JSON returns `{ "loggedOut": true }`. ## Non-interactive environments Skip `shelve login` in CI and agents — set `SHELVE_TOKEN` directly: ```bash [terminal] SHELVE_TOKEN="$SHELVE_TOKEN" shelve --non-interactive run -- pnpm test ``` The CLI reads `SHELVE_TOKEN` before the keychain. Pair with scoped, expiring tokens for least privilege. ::callout{type="warning"} Never commit `SHELVE_TOKEN` . Use your CI provider's secrets store. :: ## Multiple accounts One account per machine. To switch: `shelve logout`, then `shelve login` again. # Push and Pull `push` and `pull` sync secrets between your local `.env` file and Shelve. For day-to-day development — especially in **AI agent shells** — prefer [`shelve run`](https://shelve.cloud/docs/cli/run) instead of `pull`. ## Push Upload variables from your local env file to Shelve: ```bash [terminal] shelve push shelve push --env staging shelve --non-interactive --yes push --env staging shelve --json push --env staging ``` Reads variables from `envFileName` in `shelve.json` (default `.env`). ### Options ::field-group :::field{name="env" type="string"} Target environment. Defaults to `defaultEnv` in `shelve.json` or `SHELVE_DEFAULT_ENV` . ::: :::field{name="yes" type="boolean"} Skip confirmation when `confirmChanges` is `true` in config. Also respects global `--yes` . ::: :: JSON output: `{ "env", "variableCount", "pushed" }` (no secret values). ## Pull Download variables from Shelve to your local env file: ```bash [terminal] shelve pull shelve pull --env production shelve pull --env production --yes ``` ### AI-agent guard When the CLI detects an AI agent shell (Cursor, Claude Code, Codex, …) or `AI_AGENT` is set, **`pull` fails with `AGENT_BLOCKED`** unless you pass `--yes`: ```bash [terminal] # Explicit opt-in only — secrets will be written to disk shelve pull --yes --env development ``` ::callout{type="warning"} Plaintext `.env` files can be read by AI agents. Run [`shelve init`](https://shelve.cloud/docs/cli/init) and prefer [`shelve run`](https://shelve.cloud/docs/cli/run) . :: ### Options ::field-group :::field{name="env" type="string"} Source environment. ::: :::field{name="yes" type="boolean"} Skip the agent disk-write confirmation and `confirmChanges` prompts. ::: :: JSON output: `{ "env", "variableCount", "file", "keys[]" }` — values are never included. ## Default environment Set `defaultEnv` to skip passing `--env` every time: ```json [shelve.json] { "defaultEnv": "development", "slug": "my-team", "project": "my-app" } ``` ## confirmChanges When `confirmChanges: true` in `shelve.json`, push and pull ask before writing. Skip with `--yes` or global `--yes` / `--non-interactive` automation flags. ## Sync policies Configure **who wins** when local and Shelve differ, block pushes to production, or merge on pull. See [Sync policies](https://shelve.cloud/docs/cli/sync-policies). ```bash [terminal] shelve diff --env staging shelve push --env staging shelve sync --dry-run --env production ``` # Create The `create` command enables you to initiate a new project in Shelve. When you execute the `create` command, if there is no configuration file located in the current directory, you will be asked to provide the project name (which will be automatically filled with the current package name specified in the `package.json` file), along with the project description, repository URL, and homepage URL. ```bash [terminal] shelve create ``` ### Examples ```bash [terminal] shelve create --name my-project --slug my-team shelve --non-interactive create --name my-project --slug my-team shelve --json create --name my-project --slug my-team ``` In non-interactive mode, both `--name` and `--slug` are required (or set `SHELVE_PROJECT` / `SHELVE_TEAM_SLUG`). ### Options ::field-group :::field{name="name" type="string"} The name of the project you want to create ::: :::field{name="slug" type="string"} The slug of the team to which you want the project to belong ::: :: # Config The `config` command prints the **merged** configuration Shelve CLI will use in the current directory (local + monorepo root + environment variables). ```bash [terminal] shelve config shelve --json config ``` ## Human output Prints the config object to stdout (Node inspect style). The token is shown in full — avoid sharing this output. ## JSON output With `--json`, stdout is: ```json { "ok": true, "command": "config", "data": { "slug": "my-team", "project": "my-app", "token": "***", "url": "https://app.shelve.cloud", "defaultEnv": "development", "confirmChanges": false, "envFileName": ".env", "autoUppercase": true, "autoCreateProject": true, "workspaceDir": "/path/to/project", "isMonoRepo": false, "isRoot": true } } ``` The token is always **redacted** as `"***"` in JSON mode. ## What is included - Resolved `project`, `slug`, `url`, `defaultEnv` - Credential metadata (`username`, `email`) when logged in - Monorepo detection (`isMonoRepo`, `workspaceDir`, `monorepo.paths`) - Effective flags (`confirmChanges`, `envFileName`, `sync`, …) - Resolved sync policy per environment when `sync` is configured (see [Sync policies](https://shelve.cloud/docs/cli/sync-policies)) If required fields are missing and you are in non-interactive mode, the command fails with `MISSING_SLUG`, `MISSING_PROJECT`, or `AUTH_REQUIRED` instead of prompting. See [Introduction](https://shelve.cloud/docs/cli) for the full list of config keys and `SHELVE_*` environment variables. # Generate Generate helper files from your current project context. ```bash [terminal] shelve generate shelve generate --type env-example shelve generate --type eslint shelve --json generate --type env-example ``` ## Interactive mode Without `--type`, the CLI prompts you to choose: - `.env.example` — keys from your local `.env` with placeholder values - ESLint config — fetches a starter `eslint.config.js` ## Non-interactive mode Pass `--type` (required when `--non-interactive`, CI, or agent shell is active): | `--type` | Output | | ------------- | ------------------------------------------------ | | `env-example` | `{envFileName}.example` (default `.env.example`) | | `eslint` | `eslint.config.js` | JSON success: `{ "type", "path" }`. ## Env example Generates an example file from your local env file keys — safe to commit, no real secret values: ```bash [terminal] shelve generate --type env-example ``` ## ESLint config Fetches a starter config from the Shelve templates repository. Optionally installs `eslint` and `@hrcd/eslint-config` when run interactively or when `--yes` auto-confirms; non-interactive runs skip the installs. ```bash [terminal] shelve generate --type eslint ``` # Upgrade Checks npm for a newer `@shelve/cli` version and updates the dependency in your project. ```bash [terminal] shelve upgrade shelve --json upgrade ``` ## Behaviour 1. Compares the installed version with the npm `latest` tag. 2. If already up to date, exits successfully. 3. Otherwise runs `nypm addDependency('@shelve/cli@latest')`. Global `--yes` / non-interactive mode skips any interactive steps during the upgrade flow. ## JSON output ```json { "ok": true, "command": "upgrade", "data": { "previous": "5.0.3", "current": "latest", "updated": true } } ``` When already current: `"updated": false` and `"current"` matches `"previous"`. # Github ![github](https://shelve.cloud/docs/github-apps.png) Shelve integrates with GitHub to provide a seamless experience for developers. By linking your project to a repository, you enhance Shelve's capabilities, enabling you to synchronize your secrets with GitHub secrets, which simplifies the management of your secrets across your projects. But don't worry, your secrets are always encrypted and secure. ## GitHub Apps GitHub Apps are the recommended way to integrate with GitHub because they offer more granular permissions and better security. Shelve uses GitHub Apps to provide a secure and seamless integration with GitHub. You can create a GitHub app in the integrations tab in the `User` section. After creating the GitHub app, you will be redirected to the GitHub app page where you can manage the app, install it on your repositories, set the permissions, and more. ## Secrets Send your secrets to GitHub to synchronize them with GitHub secrets. By sending your secrets to GitHub, you can use them in your CI/CD pipelines, GitHub Actions, and more. You can select the variables you want to send to GitHub and choose the repositories you want to send them to. You will first need to create a GitHub app, install it on your repositories, and link your project to a repository to send your secrets to GitHub. ::steps ### Select the variables ![variables-selector](https://shelve.cloud/docs/variables-selector.png) ### Send to GitHub ![send-to-github](https://shelve.cloud/docs/send-to-github.png) :: ::note{icon="i-lucide-rocket"} Is it planned in the future to enhance even more the integration with GitHub, stay tuned for more features! :: # Deploy on Vercel Vercel is the official recommended provider for self-hosting Shelve. This platform offers native integration with the ecosystem that Shelve uses and greatly simplifies deployment. ## Prerequisites - A [Vercel](https://vercel.com){rel=""nofollow""} account - A GitHub repository with Shelve source code - A PostgreSQL database (we recommend [Neon](https://neon.tech){rel=""nofollow""}) ## Recommended Vercel native integrations For an optimal experience, we recommend using Vercel's native integrations: - **Database**: [Neon PostgreSQL](https://vercel.com/integrations/neon){rel=""nofollow""} - **Cache/Session**: [Vercel KV (Redis)](https://vercel.com/storage/kv){rel=""nofollow""} - **Email**: [Resend](https://vercel.com/integrations/resend){rel=""nofollow""} - **AI**: [Vercel AI SDK](https://vercel.com/ai){rel=""nofollow""} if you use AI features These integrations are optimized for Vercel and offer simplified configuration. ## Quick deployment ### 1. Fork and deployment 1. Fork the Shelve repository to your GitHub account 2. Connect your repository to Vercel 3. Vercel will automatically detect that it's a Nuxt application ### 2. Environment variables configuration Before the first deployment, you need to configure environment variables. Go to your Vercel project settings > Environment Variables. ## Required environment variables These variables are **mandatory** for Shelve to work: ```bash # PostgreSQL database DATABASE_URL=postgresql://username:password@host:port/database # Security (generate random keys of 32+ characters) NUXT_SESSION_PASSWORD=your-32-character-minimum-session-password NUXT_PRIVATE_ENCRYPTION_KEY=your-32-character-minimum-encryption-key ``` ### Security keys generation You can generate secure keys with this command: ```bash # Generates a 64-character key openssl rand -base64 48 ``` ## Database configuration Shelve requires a PostgreSQL database. We recommend [Neon](https://neon.tech){rel=""nofollow""} for its excellent Vercel integration, but you can use any PostgreSQL provider. ### Option 1: Using Vercel-Neon integration (Recommended) 1. Use the [Vercel-Neon integration](https://vercel.com/integrations/neon){rel=""nofollow""} directly 2. The integration will automatically configure the `DATABASE_URL` environment variable 3. No manual configuration needed ### Option 2: Manual configuration 1. Create a PostgreSQL database with any provider ([Neon](https://neon.tech){rel=""nofollow""}, [Supabase](https://supabase.com){rel=""nofollow""}, [Railway](https://railway.app){rel=""nofollow""}, etc.) 2. Copy the PostgreSQL connection URL 3. Add it as `DATABASE_URL` variable in Vercel project settings ## Authentication configuration Shelve supports multiple authentication methods. **You need at least one authentication method** configured for users to create accounts and log in. ### Option 1: Email authentication (Recommended) Configure email service variables below to enable OTP-based authentication via email. ### Option 2: OAuth authentication Configure OAuth providers below for social authentication. ### GitHub OAuth To enable GitHub authentication: ```bash # GitHub OAuth (both variables are required together) NUXT_OAUTH_GITHUB_CLIENT_ID=your_github_client_id NUXT_OAUTH_GITHUB_CLIENT_SECRET=your_github_client_secret ``` ### Google OAuth To enable Google authentication: ```bash # Google OAuth (both variables are required together) NUXT_OAUTH_GOOGLE_CLIENT_ID=your_google_client_id NUXT_OAUTH_GOOGLE_CLIENT_SECRET=your_google_client_secret ``` ## Email service (Required for email authentication) For email-based authentication using OTP codes: ```bash # Resend API key (required for email authentication) NUXT_PRIVATE_RESEND_API_KEY=re_your_resend_api_key NUXT_PRIVATE_SENDER_EMAIL=noreply@yourapp.com ``` ::callout{type="warning"} **Important** : These variables are now required if you want to enable email authentication. Without them, users can only authenticate via OAuth providers. :: ## Optional environment variables ### Administration and security ```bash # Admin emails (comma-separated) NUXT_PRIVATE_ADMIN_EMAILS=admin@yourapp.com,admin2@yourapp.com # Allowed origins for CORS (comma-separated) NUXT_PRIVATE_ALLOWED_ORIGINS=https://yourapp.com,https://www.yourapp.com ``` ## Advanced GitHub integration To use GitHub integration (secrets synchronization), you need to configure a GitHub App: ### 1. Create a GitHub App 1. Go to your GitHub organization settings 2. Create a new GitHub App with the following permissions: **Repository permissions:** - Actions: Read - Administration: Read - Contents: Read - Metadata: Read - Secrets: Read and write - Variables: Read and write - Webhooks: Read and write **Organization permissions:** - Secrets: Read and write - Variables: Read and write ### 2. Configure private key ```bash # GitHub private key (PKCS#8 base64 format) NUXT_PRIVATE_GITHUB_PRIVATE_KEY=your_base64_encoded_private_key ``` ::callout{type="warning"} The GitHub private key must be converted from PKCS#1 to PKCS#8 format, then base64 encoded. Use this command: ```bash openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private-key.pem | base64 -w 0 ``` :: ## Automatic validation Shelve automatically validates environment variables configuration at startup. If a required variable is missing or invalid, the application will display a detailed error. Vercel deployment logs will show you: - ✅ GitHub OAuth: enabled/disabled - ✅ Google OAuth: enabled/disabled - ✅ Email Service: enabled/disabled ## Deployment Once all variables are configured: 1. Commit and push your changes (if necessary) 2. Vercel will automatically deploy your application 3. Your Shelve instance will be accessible at the URL provided by Vercel ## Custom domain configuration 1. In your Vercel project settings, add your domain 2. Configure DNS according to Vercel instructions 3. Update `NUXT_PRIVATE_ALLOWED_ORIGINS` with your new domain ## Monitoring and logs Use Vercel tools to monitor your deployment: - **Functions**: API routes performance - **Analytics**: Traffic and performance - **Logs**: Real-time debugging # Environment variables This page documents all environment variables available in Shelve, their usage and configuration. ## Required variables These environment variables are **mandatory** for Shelve to function: ### `DATABASE_URL` - **Type**: String (PostgreSQL URL) - **Required**: ✅ Yes - **Description**: Connection URL to your PostgreSQL database - **Format**: `postgresql://username:password@host:port/database` - **Example**: `postgresql://user:pass@db.example.com:5432/shelve` ::callout{type="info"} **Recommendation** : Use [Neon](https://neon.tech){rel=""nofollow""} with [Vercel integration](https://vercel.com/integrations/neon){rel=""nofollow""} for simplified configuration. :: ### `NUXT_SESSION_PASSWORD` - **Type**: String - **Required**: ✅ Yes - **Constraint**: Minimum 32 characters - **Description**: Secret key to sign and encrypt user sessions - **Generation**: `openssl rand -base64 48` ### `NUXT_PRIVATE_ENCRYPTION_KEY` - **Type**: String - **Required**: ✅ Yes - **Constraint**: Minimum 32 characters - **Description**: Key Encryption Key (KEK). Used to seal the per-project Data Encryption Keys (DEKs) that actually protect variable values. See [Encryption](https://shelve.cloud/docs/core-features/encryption) for the full model. - **Generation**: `openssl rand -base64 48` ::callout{type="warning"} **Important** : Never modify these keys after first deployment, it would make existing data inaccessible. A safe KEK rotation flow (re-sealing all DEKs) is on the roadmap. :: ## Authentication configuration Shelve now supports multiple authentication methods. **You need to configure at least one method** for users to be able to create accounts and log in: ### Option 1: Email authentication only (Recommended) - Configure `NUXT_PRIVATE_RESEND_API_KEY` and `NUXT_PRIVATE_SENDER_EMAIL` - Users can sign up and log in using their email with OTP codes - No OAuth setup required ### Option 2: OAuth only - Configure at least one OAuth provider (GitHub or Google) - Users authenticate via OAuth providers - No email service required ### Option 3: Both methods (Maximum flexibility) - Configure both email service and OAuth providers - Users can choose their preferred authentication method - Recommended for production environments ## Authentication variables Shelve supports multiple authentication methods. You need **at least one authentication method** configured: ### Email authentication (OTP) For email-based authentication using one-time passwords (OTP), you need to configure email service variables below. ### OAuth authentication (Optional) OAuth providers offer an alternative authentication method alongside email authentication. ### GitHub OAuth To enable GitHub authentication, configure these two variables together: #### `NUXT_OAUTH_GITHUB_CLIENT_ID` - **Type**: String - **Required**: 🔶 Optional (must be paired with CLIENT\_SECRET) - **Description**: Client ID of your GitHub OAuth App - **How to get**: [GitHub Developer Settings](https://github.com/settings/developers){rel=""nofollow""} #### `NUXT_OAUTH_GITHUB_CLIENT_SECRET` - **Type**: String - **Required**: 🔶 Optional (must be paired with CLIENT\_ID) - **Description**: Client Secret of your GitHub OAuth App - **How to get**: [GitHub Developer Settings](https://github.com/settings/developers){rel=""nofollow""} ### Google OAuth To enable Google authentication, configure these two variables together: #### `NUXT_OAUTH_GOOGLE_CLIENT_ID` - **Type**: String - **Required**: 🔶 Optional (must be paired with CLIENT\_SECRET) - **Description**: Client ID of your Google OAuth App - **How to get**: [Google Cloud Console](https://console.cloud.google.com/){rel=""nofollow""} #### `NUXT_OAUTH_GOOGLE_CLIENT_SECRET` - **Type**: String - **Required**: 🔶 Optional (must be paired with CLIENT\_ID) - **Description**: Client Secret of your Google OAuth App - **How to get**: [Google Cloud Console](https://console.cloud.google.com/){rel=""nofollow""} ## GitHub integration variables ### `NUXT_PRIVATE_GITHUB_PRIVATE_KEY` - **Type**: String (Base64) - **Required**: 🔶 Optional - **Description**: Private key of your GitHub App for advanced integration - **Usage**: Automatic secrets synchronization with GitHub - **Format**: PKCS#8 encoded in base64 ::callout{type="info"} **Required setup** : This variable requires a GitHub App configured with comprehensive permissions including Secrets, Variables, and more. See the [Vercel documentation](https://shelve.cloud/docs/self-hosting/vercel#advanced-github-integration) for the complete permissions list. :: #### How to get this key 1. Create a GitHub App in your organization 2. Download the private key (.pem format) 3. Convert it to PKCS#8 format and encode in base64: ```bash openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private-key.pem | base64 -w 0 ``` ## Email service variables ### `NUXT_PRIVATE_RESEND_API_KEY` - **Type**: String - **Required**: ⚠️ Required for email authentication (OTP) - **Format**: Must start with `re_` - **Description**: Resend API key for sending emails including OTP codes - **Usage**: OTP authentication, welcome emails, notifications - **How to get**: [Resend Dashboard](https://resend.com/dashboard){rel=""nofollow""} ::callout{type="warning"} **Important** : This variable is now required if you want to use email authentication (OTP). Without it, users can only authenticate via OAuth providers. :: ::callout{type="info"} **Recommendation** : Use [Resend](https://resend.com){rel=""nofollow""} with [Vercel integration](https://vercel.com/integrations/resend){rel=""nofollow""} for automated configuration. :: ### `NUXT_PRIVATE_SENDER_EMAIL` - **Type**: String (Email) - **Required**: ⚠️ Required for email authentication (required if RESEND\_API\_KEY is set) - **Description**: Sender email address for OTP codes and notifications - **Format**: `noreply@yourdomain.com` - **Constraint**: Must be a valid email verified in Resend ## Administration variables ### `NUXT_PRIVATE_ADMIN_EMAILS` - **Type**: String (comma-separated list) - **Required**: 🔶 Optional - **Description**: List of emails with access to admin features - **Format**: `admin@app.com,admin2@app.com` - **Usage**: Access to statistics, user management ### `NUXT_PRIVATE_ALLOWED_ORIGINS` - **Type**: String (comma-separated list of URLs) - **Required**: 🔶 Optional - **Description**: Allowed origins for CORS requests - **Format**: `https://app.com,https://www.app.com` - **Usage**: Security, CSRF attack prevention ## Automatic validation Shelve uses an automatic environment variables validation system: - **Startup validation**: The application checks all required variables - **Format validation**: URLs, emails, minimum lengths are verified - **Consistency validation**: OAuth pairs are validated together - **Explicit error messages**: In case of error, logs indicate exactly what's wrong ## Configuration by environment ### Local development Create a `.env` file at the root of your project: ```bash # Copy from .env.example DATABASE_URL=postgresql://localhost:5432/shelve_dev NUXT_SESSION_PASSWORD=your-dev-session-password-32-chars-min NUXT_PRIVATE_ENCRYPTION_KEY=your-dev-encryption-key-32-chars-min ``` ### Production on Vercel Configure all variables in your Vercel project settings: 1. Vercel Project > Settings > Environment Variables 2. Add each variable with its value 3. Select appropriate environments (Production, Preview, Development) ## Troubleshooting ### Common errors **"Session password must be at least 32 characters long"** - Generate a longer key: `openssl rand -base64 48` **"Must be a valid PostgreSQL URL"** - Check the format: `postgresql://user:pass@host:port/db` - Test the connection to your database **"GitHub OAuth validation failed"** - Make sure you have defined CLIENT\_ID AND CLIENT\_SECRET together - Verify that the values are correct **"Resend API key must start with 're\_'"** - Use the Resend API key, not a test key - Verify that you copied the complete key ### Missing environment variables Vercel deployment logs will show: ```text 🔐 Auth configuration validated: GitHub OAuth: ✅/❌ Google OAuth: ✅/❌ Email Service: ✅/❌ ``` This allows you to quickly check which integrations are active. # Introduction Thank you for your interest in contributing to Shelve! We welcome contributions from the community and are excited to work with you. Please follow the guidelines below to ensure a smooth and effective contribution process. ## Contribution Guidelines ### Reporting Issues If you encounter any issues or bugs while using Shelve, please report them by opening an issue on our [GitHub Issues](https://github.com/HugoRCD/shelve/issues){rel=""nofollow""} page. Provide as much detail as possible, including steps to reproduce the issue, screenshots, and any relevant error messages. ### Suggesting Features We welcome feature suggestions and ideas to improve Shelve. To suggest a new feature, please open an issue on our [GitHub Issues](https://github.com/HugoRCD/shelve/issues){rel=""nofollow""} page and provide a clear description of the feature, its benefits, and any potential use cases. ::tip{to="https://shelve.cloud/docs/contributing/dev-setup"} If you want to contribute to Shelve, but don't know how to run the project locally, check out the [Dev Setup](https://shelve.cloud/docs/contributing/dev-setup) guide. :: # Dev Setup #### Monorepo Structure Shelve uses a monorepo structure to manage multiple packages and applications. The main packages are: - `apps/shelve`: The main Shelve application. - `apps/lp`: The landing page for Shelve. - `apps/vault`: The vault application. - `apps/base`: This is the base layer that contains the shared configuration lp and shelve (components, plugins, etc). - `packages/cli`: The CLI connected to the Shelve application (push, pull, etc). - `packages/crypto`: The encryption and decryption package. - `packages/types`: The shared types inside the monorepo. - `packages/utils`: The shared utilities inside the monorepo. The monorepo is managed utilizing [Turborepo](https://turbo.build){rel=""nofollow""}. With Turborepo, it is possible to execute commands across all packages or target specific packages. Consequently, you will primarily operate from the root of the repository and execute commands such as `pnpm run dev:app` to initiate the Shelve application. #### Necessary Environment Variables - `DATABASE_URL` (PostgreSQL) - `NUXT_OAUTH_GITHUB_CLIENT_ID` (or google) - `NUXT_OAUTH_GITHUB_CLIENT_SECRET` (or google) - `NUXT_PRIVATE_ENCRYPTION_KEY` To set up the development environment for Shelve, follow these steps: ::steps ### Clone the Repository: ```sh [terminal] git clone https://github.com/HugoRCD/shelve.git && cd shelve ``` ### Install Dependencies: ```sh [terminal] pnpm install ``` ### Copy the Example Environment File: ```sh [terminal] cp apps/shelve/.env.example apps/shelve/.env ``` ### Update Environment Variables: Edit the `apps/shelve/.env` file and update the necessary environment variables. ### Start the Development Server: ```sh [terminal] pnpm run dev:app ``` :: # Introducing Shelve's Blog: Building in Public Welcome to Shelve's official blog! 👋 As Shelve continues to grow and evolve, I wanted to create a space where I could share more detailed insights about the project, its development, and the future I envision for it. ### Why Start a Blog? Building in public has always been part of Shelve's DNA. While Twitter is great for quick updates, some topics deserve a deeper dive. This blog will be the place for: - Technical deep-dives into Shelve's architecture - Behind-the-scenes of new features - Tutorials and best practices - Project updates and roadmap discussions ### What's Coming The next few months are exciting for Shelve. With the recent migration to Nuxt Hub, we're unlocking new possibilities that will transform how developers manage their environments. Expect articles about: - How Shelve leverages AI for smarter environment management - Deep dives into our new file upload system - Best practices for team environment sharing - And much more! ### Join the Journey Shelve started with a simple goal: make environment management less painful for developers. Today, it's growing into something much bigger, and I'm excited to share this journey with you. Whether you're a long-time user or just discovering Shelve, I hope this blog will give you valuable insights into modern development workflows and tools. Stay tuned for more articles, and feel free to reach out on [Twitter](https://x.com/hugorcd){rel=""nofollow""} with topics you'd like to see covered! Happy coding! ✨ # Announcing 2.0 ## Shelve 2.0: A Giant Leap Forward For a long time, Shelve has been a passion project, a side endeavor born from my own needs as a developer. But today, I'm incredibly proud to announce that Shelve is taking a monumental leap forward. **Shelve 2.0 is here, and it marks its transformation from a side project to a truly polished and feature-rich tool.** This isn't just an incremental update. I've poured my heart and soul into rebuilding Shelve from the ground up, focusing on: - **Professional Polish:** A brand new visual identity, logo, and dedicated [brand guidelines](https://shelve.cloud/brand). Shelve now looks and feels more professional than ever. - **Enhanced User Experience:** Every interaction has been refined for simplicity and elegance, making complex tasks feel intuitive. - **Powerful Core Features, Radically Simplified:** Shelve 2.0 is packed with features focused on environment management, now more accessible and user-friendly than ever. ## A New Era, A New Identity ✨ One of the most visible changes in Shelve 2.0 is the complete visual overhaul. I've invested heavily in crafting a brand identity that reflects the maturity and ambition of Shelve: - **Brand New Logo:** A modern, clean logo that embodies Shelve's core values of simplicity and efficiency. - **Dedicated Brand Guidelines:** Ensuring a consistent and professional experience across all platforms and touchpoints. - **Revamped Landing Page & Documentation:** A completely redesigned website built with Nuxt UI Pro, showcasing Shelve's polished new look and feel. This visual transformation isn't just cosmetic. It signals my commitment to building a tool that developers can rely on and be proud to use. ## Power & Simplicity, Reimagined 🚀 Shelve 2.0 is not just about looks. I've also massively enhanced the core functionality and developer experience around environment management: ### Say Goodbye to .env Files with `shelve run` One of the flagship features of v2.0 is the **`shelve run` command**. Imagine running any npm script (`dev`, `build`, `start`, etc.) directly from your terminal, **without needing a `.env` file**. Shelve 2.0 introduces runtime secrets injection, allowing you to access your environment variables securely stored in Shelve, directly within your scripts. This radically simplifies your workflow and eliminates a major source of configuration headaches. ### Seamlessly Push Variables to GitHub Secrets Taking integrations a step further, Shelve 2.0 now allows you to **push environment variables directly to your GitHub repositories as GitHub Secrets.** This is a game-changer for streamlining your CI/CD pipelines and managing configurations across your projects. Forget the cumbersome manual process of managing secrets directly in GitHub - Shelve makes it effortless. This integration is the first step towards deeper connections with other developer tools and platforms, expanding Shelve's reach beyond just environment variable management. ### Introducing Vault as a standalone project for secrets sharing Vault, previously a feature within the Shelve landing page, is now a **completely standalone application, accessible at [vault.shelve.cloud](https://vault.shelve.cloud){rel=""nofollow""}.** This self-hostable application allows anyone to securely share encrypted environment variables with others, independent of the main Shelve application. Vault provides a basic yet powerful solution for secure secret sharing, easily deployable and usable by anyone. ### Enhanced Core Features & Developer Experience Beyond these highlights, v2.0 is packed with improvements across the board: - **Drizzle ORM Migration:** I've migrated from Prisma to Drizzle ORM, enhancing performance and flexibility. - **Citty CLI Framework:** A switch to Citty provides a more robust and extensible CLI experience. - **GitHub Secrets Sync:** Effortlessly synchronize your environment variables with GitHub Secrets for enhanced security and streamlined workflows. - **Monorepo Workflow Improvements:** Enhanced support for monorepos, making Shelve even more versatile for complex projects. - **Real-time Visitor Stats:** A new module to track website visitors in real-time. - **Vault UI Revamp:** A more intuitive and user-friendly interface for managing your secrets. - **Global Stats Dashboard:** Gain insights into your Shelve usage with a new global statistics dashboard. - **Improved Integrations View:** A cleaner and more informative view for managing your integrations. - **And much more:** From view transitions to enhanced search and sorting, v2.0 is filled with refinements and new features designed to elevate your development experience. ## Under the Hood 🛠️ While the visual and functional improvements are front and center, Shelve 2.0 also includes significant under-the-hood refactoring and enhancements: - **Server Upgrade:** Major server-side improvements for performance and stability. - **Codebase Refactoring:** Extensive codebase cleanup and refactoring for maintainability and scalability. - **Documentation Overhaul:** Comprehensive documentation updates and improvements, now integrated directly into the landing page for easier access. - **Testing & Bug Fixes:** Numerous bug fixes and stability improvements to ensure a rock-solid experience. ## Try Shelve 2.0 Today! 🚀 Shelve 2.0 is a massive step forward, and I'm incredibly excited for you to experience it. Whether you're a solo developer or part of a team, Shelve 2.0 is designed to streamline your environment management and empower you to build amazing things. - **Visit the Website:** [shelve.cloud](https://shelve.cloud) - **Explore the Documentation:** [shelve.cloud/docs/getting-started](https://shelve.cloud/docs/getting-started) - **Contribute on GitHub:** [github.com/HugoRCD/shelve](https://github.com/HugoRCD/shelve){rel=""nofollow""} 💚 Hugo # Crafting Clarity: Introducing the New Shelve Landing Page ## A New Foundation for Shelve Today, I'm excited to introduce a significant step forward for Shelve: a completely redesigned landing page experience at [shelve.cloud](https://shelve.cloud){rel=""nofollow""}. This project was driven by the desire to create an interface that truly reflects the core principles guiding Shelve itself – **simplicity, security, elegance, and a deep focus on Developer Experience (DX).** ## Aligning Perception with Ambition As Shelve continues to evolve, aiming to be a foundational tool in the developer workflow, it felt essential that its primary touchpoint – the landing page – accurately reflected this ambition and the level of quality I strive for in the product. The goals for this redesign were clear: - **Establish Clarity & Coherence:** Immediately convey Shelve's value in making secrets management effortless and secure. - **Build Trust:** Present a polished, professional identity that inspires confidence. - **Ensure Coherence:** Align the messaging, visuals, and overall feel with the product's philosophy. - **Elevate the Experience:** Apply meticulous attention to detail in design and interaction, making the page itself a pleasure to use. The result is a landing page and documentation section that feels more focused, refined, and fundamentally *Shelve*. It provides a clear, coherent foundation that accurately represents where the project is today and sets the stage for its future. ## The Craft: Elevating the Experience Achieving the level of polish and specific design vision I aimed for with this new landing page required not just meticulous planning but also leveraging tools that could accelerate the process without compromising quality or flexibility. While Shelve has always utilized the Nuxt ecosystem, this redesign was an opportunity to lean heavily into the capabilities offered by **Nuxt UI** and especially **Nuxt UI Pro**. The challenge often lies in balancing speed with originality. Building every intricate detail, managing responsiveness across devices, ensuring accessibility, and handling theming for light/dark modes from scratch is incredibly time-consuming. Component libraries like Nuxt UI Pro offer a significant advantage by providing **well-architected, production-ready building blocks** for many common (and complex) UI patterns. This instantly addresses a large portion of the foundational work. However, the true value unlocked for this project wasn't just using pre-built components, but the **depth of customization** they allow. The goal was never to adopt a generic template, but to craft a unique visual identity for Shelve. Nuxt UI Pro facilitates this by enabling extensive tailoring – modifying styles, overriding structures, and integrating custom interactions – all while retaining the benefits of the underlying framework (like type-safety and developer experience). This meant I could **focus creative energy on the unique aspects** – the specific animations using Motion One, the precise layout adjustments, the custom visual effects – rather than rebuilding standard interface elements. It significantly shortens the path to a sophisticated, bespoke result that truly feels aligned with the brand. This entire process underscores a philosophy: powerful tools should enable creativity and efficiency, not restrict them. In this spirit, **the complete codebase for the landing page and documentation is open-source**, available in the main [Shelve repository](https://github.com/HugoRCD/shelve){rel=""nofollow""}. It serves as a tangible example of how these libraries can be pushed and customized to build premium, distinct web experiences. Feel free to explore how components are adapted and complex layouts are achieved. It’s often in the finer points and layers beneath the surface where the most interesting details reside, rewarding careful exploration. [H1dd3n\_\_P4yl04d!]{.text-default} # Announcing Shelve 3 ## Shelve 3: built for teams, agents, and production **Shelve 3 is here.** If you have been using Shelve since [2.0](https://shelve.cloud/blog/announcing-2.0), you already know the pitch. Centralize your environment variables, inject them at runtime with `shelve run`, sync to GitHub Secrets, and keep your team aligned from the app or the CLI. Version 2 delivered on that promise with a polished UI, a capable CLI, and a workflow that made `.env` files optional on your laptop. Version 3 builds on that solid foundation. The theme is **more control, more visibility, and a CLI that matches how we work today**, whether you are in a team, in CI, or working alongside AI coding agents. Same product you already trust, with a deeper toolkit underneath. ## What's new at a glance ::callout{type="tip"} Already on Shelve 2.x? Your secrets, projects, and workflows carry over. Most of 3.0 is additive, with new capabilities on top of what you already use. :: | Area | Highlights | | ----------------- | -------------------------------------------------------------------- | | **CLI login** | Browser device flow, no more copying API tokens by hand | | **Tokens** | Scoped permissions, expiry, IP allowlists, usage tracking | | **Encryption** | Per-project envelope encryption on top of existing AES-256 | | **Observability** | Full audit log with a polished team UI | | **`shelve run`** | Offline cache, watch mode, `shelve://` templates, OS keychain | | **Agents & CI** | `shelve init`, `--json`, `shelve doctor`, sync policies, agent skill | | **Platform** | Variable groups, OTP auth, Docus docs, E2E test coverage | ## Before you upgrade ::callout{type="important"} **A few breaking changes in 3.0.** Plan a short maintenance window before upgrading production or CI. :: - **API tokens need to be re-issued after upgrading.** The token format and storage model changed. Plaintext is now shown only at creation, and tokens are stored as hashes. Create new tokens in the UI and update your CI secrets. - **The CLI now sends `Authorization: Bearer `.** Cookie-based auth still works for one release window with `Deprecation` and `Sunset` response headers. - **Upgrade the CLI:** `npm i -g @shelve/cli@latest` (or your package manager equivalent). See the [CLI upgrade guide](https://shelve.cloud/docs/cli/upgrade) and [API tokens docs](https://shelve.cloud/docs/core-features/tokens) for details. ## Sign in from the browser The biggest day-to-day CLI change: **`shelve login` no longer asks you to paste an API token.** Instead, Shelve uses the [RFC 8628 device authorization flow](https://datatracker.ietf.org/doc/html/rfc8628){rel=""nofollow""}, the same pattern as the GitHub CLI or Vercel. 1. Run `shelve login` in your terminal. 2. Your browser opens to Shelve (or the CLI prints a URL and a short code). 3. Sign in if needed, review the request, and click **Authorize CLI**. 4. Shelve creates a revocable API token named `CLI — ` (90-day expiry, read/write) and stores it in your OS keychain. No dashboard tab hunting. No copy-paste into the terminal. The token shows up in [your tokens list](https://app.shelve.cloud/user/tokens){rel=""nofollow""} like any other. Revoke it anytime. ```bash shelve login # opens browser (default) shelve login --no-browser # print URL + code only (SSH, remote machines) shelve login --with-token # paste a token manually (optional) ``` **CI and agents stay the same.** Set `SHELVE_TOKEN` or pass `--token`. No browser required. Scoped, expiring tokens from the dashboard remain the right choice for pipelines. See [login and logout docs](https://shelve.cloud/docs/cli/login-logout). ## More control over API tokens Shelve has always encrypted secrets at rest. With 3.0, **API tokens get the same level of care**, plus a set of features teams have been asking for: - **256 bits of entropy**, encoded in [Crockford base32](https://www.crockford.com/base32.html){rel=""nofollow""}. - **Hashed at rest.** Only `sha256(token)` and a display **prefix** (`she_…`) are stored. The full value is shown **exactly once** at creation. - **Granular scopes** to restrict a token to specific teams, projects, environments, and `read` / `write` permissions, enforced server-side. - **Optional expiry**, **CIDR allowlists**, and **`lastUsedAt` / `lastUsedIp`** on every request so you always know which token did what. The token UI at `/user/tokens` shows scopes, expiry, and last-used info at a glance. The creation flow uses a roomier modal with cascading multi-select pickers, inline CIDR validation, and a helpful nudge when a token has no restrictions applied. ### Standard Bearer authentication The CLI sends `Authorization: Bearer she_…`, the same pattern used by most APIs and CI systems. Cookie-based auth remains available during the transition, with clear deprecation headers. ### Envelope encryption, per project Shelve already sealed every variable with **AES-256-GCM** before it hit the database. Version 3 adds **envelope encryption**, an extra layer for finer-grained key management: ```text value ──seal──▶ ciphertext (key = DEK, per-project) DEK ──seal──▶ encryptedDek (key = KEK, platform-wide) ``` Each project gets its own **Data Encryption Key (DEK)**, sealed by the platform **Key Encryption Key (KEK)**. You get per-project key rotation without touching application code, and a smaller blast radius if a single project key is ever rotated or replaced. Existing projects upgrade seamlessly. Decryption tries the project DEK first, then transparently falls back to the previous scheme. No data migration required. Read the full scheme in the [encryption docs](https://shelve.cloud/docs/core-features/encryption). ### Audit logs One of the most requested features: a full **audit trail** for your team. Every sensitive action (token create/delete, variable read/write, environment and project mutations) is recorded with actor, action, resource, IP, user agent, and metadata. Team admins get an **Audit logs** tab in team settings with cursor pagination and action filters. The UI includes color-coded action badges, resource icons, parsed user-agent labels (e.g. `Shelve CLI 5.0.0`, `Chrome · macOS`), and a per-row metadata popover to inspect the full JSON payload. You can query the feed via API too. See [audit logs docs](https://shelve.cloud/docs/core-features/audit-logs). ## `shelve run` grows up `shelve run` was the headline feature of [2.0](https://shelve.cloud/blog/announcing-2.0). Run any script with secrets injected at runtime, no `.env` on disk. In Shelve 3 it became significantly more capable and reliable. ### Credentials in the OS keychain Whether you signed in via the browser flow or set a token for CI, credentials live in the **OS keychain** (macOS Keychain, Windows Credential Manager, libsecret), with a hardened XDG fallback at `~/.config/.shelve`. Legacy `~/.shelve` files migrate automatically on first read. ### Offline cache, watch mode, and secret references Working on a plane? API briefly down? After every successful fetch, `shelve run` keeps an **encrypted offline cache** (AES-256-GCM, keyed from your token via HKDF). Revoke the token and the cache becomes unreadable. New flags and modes: - **`--offline`**: use cache only - **`--no-cache`**: always fetch fresh - **`--cache-ttl 24h`**: control freshness - **`--watch`**: poll for variable changes and forward `SIGHUP` to the child (Vite/Nuxt/Next hot-reload) - **`--restart-on-change`**: respawn the process instead - **`--template .env.template`**: resolve `shelve://///` references and commit the template safely to Git Signal forwarding, watch mode, and cross-platform spawn were hardened across the 3.x CLI releases, including Windows `.cmd` shims, parent-death watchdogs, and cleaner exit-code propagation. ```bash shelve init shelve run -- pnpm dev shelve run --watch -- pnpm dev shelve run --template .env.template -- pnpm build ``` ### Built for the AI-agent era AI coding agents (Cursor, Claude Code, Codex, Aider, Continue, and more) are part of many workflows now. Shelve 3 meets that with practical guardrails: - **`shelve init`**: one command to add `.cursorignore`, `.aiderignore`, `.codeiumignore`, `.continueignore`, and a `# shelve-managed-block` in `.gitignore`. - **`shelve pull`**: detects agent environments and asks before writing plaintext to disk (`--yes` to skip). - **`shelve run`**: secrets stay in the child process memory, never on disk. If you use agents, `shelve run -- ` is the recommended path. ## Platform improvements Alongside the security and CLI work, the platform itself got a lot of love: - **Variable groups and descriptions** to organize variables in the UI. `pull` outputs section headers (`# ---- Group ----`) and inline description comments, and auto-generates a `.env.example`. - **OTP auth** and **bot-id protection** on the login surface. - **Documentation migrated to Docus** for clearer structure, better search, and a full CLI reference on shelve.cloud. - **Vercel self-hosting docs** and updated terms/privacy pages. - **Test infrastructure** with unit and E2E coverage across push/pull, tokens, auth, audit logs, offline cache, and secret references. - **Navigation and members table** refactors, single-team auto-redirect, Nuxt 4 and Nuxt UI upgrades. ## Sync policies and agent automation Shelve 3 also ships **team-grade sync controls** and **automation-first CLI flags**. ### Global flags and structured output Every command supports flags built for CI and agent shells: | Flag | Purpose | | ------------------- | -------------------------------------------------------------------- | | `--json` | Machine-readable success on stdout, structured errors on stderr | | `--quiet` / `-q` | Suppress spinners and intro/outro | | `--yes` / `-y` | Skip confirmation prompts | | `--non-interactive` | Fail fast instead of prompting (auto-enabled in CI and agent shells) | | `--debug` | Verbose logs without Authorization headers or secret values | **`shelve doctor`** validates your setup and reports health checks. See [Agents & automation](https://shelve.cloud/docs/cli/agents-automation) for JSON output shapes per command. ### Sync policies, diff, and protected environments **Sync policies** define what happens when local and remote diverge: - **`shelve diff`**: compare local `.env` with Shelve (no writes, safe with `--json`) - **`shelve sync`**: reconcile according to `sourceOfTruth` (`remote` vs `local`) - **`onPushConflict`**: `overwrite`, `skip`, `fail`, or `prompt` - **`pullMode`**: `replace` or `merge` (local-only keys kept) - **`protectedEnvironments`**: mark production (or any env) as push-protected on the server Server policies are the source of truth. If production is protected, the CLI respects that. See [sync policies docs](https://shelve.cloud/docs/cli/sync-policies). ### Agent skill and CI tooling Install the published agent skill so Cursor, Claude, and other agents know how to use Shelve: ```bash npx skills add https://shelve.cloud ``` The skill covers `shelve run`, `shelve init`, scoped tokens, sync policies, and the `--json` / `--non-interactive` flags. A **GitHub Action** is available for CI. Full CLI documentation and troubleshooting guides live on [shelve.cloud/docs/cli](https://shelve.cloud/docs/cli). ## What this means for you | Persona | What you get | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | **Solo developer** | `shelve login` once, then `shelve run -- pnpm dev`. Offline cache when you need it. Agent-safe by default. | | **Team admin** | Scoped, expiring tokens with IP allowlists. A full audit log. Protected environments. Per-project encryption keys. | | **CI / agent automation** | `SHELVE_TOKEN` + `--json` + `--non-interactive`. `shelve doctor --json` in your setup step. The agent skill for Cursor and friends. | Shelve 2.x made environment management a pleasure. Shelve 3 adds **depth**: browser login, observability, fine-grained access control, and a CLI that fits how teams and agents work today. ## Try Shelve 3 today - **Get started:** [app.shelve.cloud](https://app.shelve.cloud){rel=""nofollow""} - **Read the docs:** [shelve.cloud/docs/getting-started](https://shelve.cloud/docs/getting-started) - **Install the CLI:** `npm i -g @shelve/cli@latest` - **Contribute:** [github.com/HugoRCD/shelve](https://github.com/HugoRCD/shelve){rel=""nofollow""} ## Thank you This release spans dozens of pull requests and months of work. Thank you to everyone in the community who contributed code, opened issues, tried early builds, and shared feedback along the way. Open-source, built in public, shaped by the people who use it. ::read-more --- icon: i-simple-icons-github target: _blank to: https://github.com/HugoRCD/shelve/releases/tag/v3.2.0 --- Read the full release notes on GitHub. :: 💚 Hugo