npm package: @suronai/sdk
Description: App SDK for Suron — await vault() to load secrets
Latest version: 0.1.36

# @suronai/sdk

Runtime SDK for [Suron](https://suronai.com) — a drop-in replacement for `dotenv` that gates every app boot behind a Telegram approval before secrets are decrypted into `process.env`.

## Install

```bash
npm install @suronai/sdk
```

Requires Node.js 18+. Run [`suron init`](https://www.npmjs.com/package/@suronai/cli) in your project first.

---

## Usage

Replace your `dotenv` bootstrap at the very top of your entry point, before any `process.env` access:

```js
// before
import { config } from 'dotenv'
config()

// after
import { config } from '@suronai/sdk'
await config()
```

That's it. When your app starts:

1. Suron sends a message to your Telegram bot with an **Approve / Deny** button
2. `await config()` blocks until you tap Approve
3. Secrets are decrypted into `process.env` — your app continues normally
4. If you tap Deny, or the timeout expires, the process exits with a clear message

`suron init` can patch this automatically — it detects `dotenv` in your entry point and offers to replace it interactively.

---

## Requirements

- `.suron.json` in the working directory — created by `suron init`
- Encrypted `.env` in the working directory — created by `suron init`
- No extra environment variables needed — the API URL is stored in `.suron.json`

---

## API

### `config(options?)`

Drop-in safe wrapper. Handles all known Suron errors with `console.error` + `process.exit(1)`. Only unexpected errors are re-thrown.

```js
import { config } from '@suronai/sdk'
await config()
```

### `vault(options?)`

Same as `config()` but throws on all errors, giving you full control over error handling:

```js
import { vault, SuronDeniedError, SuronTimeoutError, SuronConfigError, SuronAppNotFoundError } from '@suronai/sdk'

try {
  await vault()
} catch (err) {
  if (err instanceof SuronDeniedError)      { console.error('[suron] Boot denied.');        process.exit(1) }
  if (err instanceof SuronTimeoutError)     { console.error('[suron] Approval timed out.'); process.exit(1) }
  if (err instanceof SuronConfigError)      { console.error('[suron]', err.message);        process.exit(1) }
  if (err instanceof SuronAppNotFoundError) { console.error('[suron]', err.message);        process.exit(1) }
  throw err
}
```

### Options

Both `config()` and `vault()` accept the same options object:

```js
await config({
  configPath:   '/custom/path', // dir containing .suron.json — default: process.cwd()
  timeout:      300_000,        // ms to wait for Telegram approval — default: 5 minutes
  pollInterval: 3_000,          // ms between /status polls — default: 3s
})
```

---

## Errors

| Class | Thrown when |
|---|---|
| `SuronConfigError` | `.suron.json` missing, malformed, or `SURON_API_URL` not set |
| `SuronAppNotFoundError` | App not registered — run `suron init` |
| `SuronRateLimitError` | More than 20 boot requests in 1 hour |
| `SuronDeniedError` | You tapped Deny in Telegram |
| `SuronTimeoutError` | No approval received within the timeout |

`config()` exits the process for all errors except `SuronRateLimitError` and unknown errors, which are re-thrown. `vault()` always throws.

---

## How it works

```
await config()
  └─ reads .suron.json             # gets app_id and api_url
  └─ POST /request-access          # triggers Telegram approval message
  └─ GET  /status (polling)        # waits for Approve / Deny tap
  └─ POST /fetch-key               # retrieves private key (single-use token)
  └─ dotenvx.parse(.env, key)      # decrypts secrets into process.env
```

The private key never touches disk during runtime. It's received in memory, used to decrypt, and the reference is dropped immediately after.

---

## Security

- The SDK reads only `.suron.json` for the `app_id` — no access to `~/.suron-config`
- The `MASTER_KEY` lives exclusively inside the Convex deployment — the SDK never sees it
- `/fetch-key` is single-use — the token is consumed on first retrieval
- `DOTENV_PUBLIC_KEY` and `DOTENV_PRIVATE_KEY` are filtered out of `process.env` after decryption

---

## Related

- [`@suronai/cli`](https://www.npmjs.com/package/@suronai/cli) — the CLI for setup and key rotation