# Run

> Inject secrets into your application at runtime without writing a .env file.

`shelve run` spawns your command with every variable from your Shelve project already present in its environment. No `.env` file is written, no secret ever touches disk.

```bash [terminal]
shelve run -- <command> [args]
```

The double-dash is conventional — anything after `--` is passed verbatim to your command. For simple npm-style scripts, `shelve run dev` works too: it uses [`ni`](https://github.com/antfu-collective/ni) to detect your package manager and run the script.

## How it works

1. Shelve reads your token (from the OS keychain or the XDG config file), resolves your team / project / environment, and fetches the variables for that env.
2. The variables are spliced into a fresh environment map and merged onto your process's environment.
3. Your command is spawned via `node:child_process.spawn`, in its own process group, so Shelve can forward `SIGINT` / `SIGTERM` cleanly.
4. The exit code of your command becomes the exit code of `shelve run` — seamless in CI.

## Examples

```bash [terminal]
# Run your dev server with production-like secrets
shelve run --env=preview -- pnpm dev

# Short form when the command is a package.json script
shelve run build

# Explicit command + flags
shelve run -- node scripts/migrate.ts --dry-run
```

## Options

<field-group>
<field name="env" type="string">

Environment to use (for example `development`, `preview`, `production`). Overrides `defaultEnv` from `shelve.json`.

</field>

<field name="watch" type="boolean">

Restart the child process whenever variables change upstream. Combine with a dev server that already reloads on source edits.

</field>

<field name="offline" type="'auto' | 'never' | 'only'" default="'auto'">

How to use the encrypted offline cache. `auto` (default) refreshes variables from Shelve and falls back to the cache if the network is down. `never` disables the cache entirely. `only` refuses to make any network call.

</field>

<field name="cache-ttl" type="duration" default="15m">

How long a cached payload is considered fresh. Accepts `500ms`, `30s`, `15m`, `2h`, `7d`, or a raw number in milliseconds.

</field>
</field-group>

## Secret references (`.env.template`)

If a `.env.template` file is present in the current directory, Shelve uses it as a **layout** for the injected environment. Each line is either a literal value or a `shelve://` reference that resolves against the current environment:

```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, optionally renaming it on the left-hand side.
- `shelve://<env>/KEY` explicitly targets another environment — useful for mirroring production values into a preview command.

References that cannot be resolved are reported in a grouped diagnostic and the command aborts before spawning the child, so you never run a process with partially-hydrated secrets.

## Encrypted offline cache

After every successful fetch, Shelve writes the payload to `~/.shelve/cache/<hash>.cache`, sealed with a key derived from your current token via HKDF. The cache is scoped per team + project + environment; switching environments uses a different key.

- Loss of the token invalidates the cache automatically (the derived key no longer matches).
- Tampering with the cache file is detected via AES-GCM authentication tags — a modified file fails to decrypt.
- Files are created with `0600` permissions; the directory with `0700`.

Use `--offline=only` on planes, in restricted CI environments, or anywhere outbound egress is blocked.

## Watch mode

```bash [terminal]
shelve run --watch -- node server.js
```

With `--watch`, Shelve opens a server-sent events stream and listens for `variable.updated` / `variable.deleted` events on your project. When a change lands the child process is sent `SIGTERM`, waits for it to exit (or is killed after a grace period), and is respawned with the new environment. This is the closest thing to a managed "hot reload for secrets".

## AI-agent safety

When `shelve run` is invoked from a shell whose environment looks like an AI coding agent (Cursor, Claude Code, Aider, Continue, Codex, …), it prints a reminder that secrets will only exist in the spawned process's memory and will never be visible to the agent itself. Unlike `shelve pull`, `run` never prompts — running with an agent attached is safe by design.

## Exit behaviour

<table>
<thead>
  <tr>
    <th>
      Signal / event
    </th>
    
    <th>
      Behaviour
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Child exits normally
    </td>
    
    <td>
      <code>
        shelve run
      </code>
      
       exits with the same code
    </td>
  </tr>
  
  <tr>
    <td>
      Child crashes
    </td>
    
    <td>
      Shelve propagates the exit code
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Ctrl-C
      </code>
      
       (<code>
        SIGINT
      </code>
      
      )
    </td>
    
    <td>
      Forwarded to the entire process group; child terminates cleanly
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SIGTERM
      </code>
    </td>
    
    <td>
      Forwarded to the process group, grace period 5s, then <code>
        SIGKILL
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Variable change (watch mode)
    </td>
    
    <td>
      Child is respawned with the new env
    </td>
  </tr>
</tbody>
</table>
