npm package: @ravi-hq/ravi
Description: OpenClaw plugin for Ravi — identity provider for AI agents. Email channels + full agent toolbox.
Latest version: 0.7.11

# @ravi-hq/openclaw-plugin

[![npm version](https://img.shields.io/npm/v/@ravi-hq/openclaw-plugin)](https://www.npmjs.com/package/@ravi-hq/openclaw-plugin)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

OpenClaw plugin for [Ravi](https://ravi.id) -- identity provider for AI agents.
Gives your agent a real email address, phone number, password manager, and
encrypted secrets store through a single plugin.

## Features

- **2 messaging channels** -- real-time email via cursor-based polling (owner, trusted)
- **30 agent tools** -- manage identities, inboxes, passwords, secrets, contacts, and more
- **DM policy controls** -- allowlist or open policy per channel account

## Installation

```bash
openclaw plugins install @ravi-hq/ravi
```

## Quick Start

```bash
openclaw plugins install @ravi-hq/ravi
openclaw ravi onboard
```

`onboard` authenticates (device-code flow + E2E encryption PIN), selects an
identity, auto-configures OpenClaw, and sets up the `ravi-secrets` provider.
Credentials are stored in `~/.ravi/config.json` and `identityUuid` is set via
`openclaw config set`. No manual config editing needed.

The auth flow matches the Ravi Go CLI exactly:
- Verification URL displayed immediately (copy-paste friendly for headless VMs)
- First-time users: choose and confirm a 6-digit encryption PIN
- Returning users: enter PIN (3 attempts) to unlock existing keypair
- Multiple identities: interactive numbered selection prompt

Use `openclaw ravi login` to re-authenticate without changing identity or config.

## Configuration

Authentication is stored in `~/.ravi/config.json` (written by `openclaw ravi login` or `onboard`).
The `identityUuid` is set automatically by `openclaw ravi onboard` via `openclaw config set`.

Manual plugin config is only needed for multi-agent setups where different agents
use different identities, or to customize channel DM policies.

```yaml
plugins:
  ravi:
    # Ravi API base URL (default: https://ravi.id)
    apiUrl: "https://ravi.id"

    # Identity UUID -- set automatically by 'openclaw ravi onboard'.
    # Override here only for multi-agent setups.
    identityUuid: "identity-uuid-here"

    # Channel accounts (from 'openclaw ravi onboard')
    channels:
      ravi-email-owner:
        accounts:
          "identity-uuid-here":
            identityUuid: "identity-uuid-here"
            identityName: "My Agent"
            email: "agent@in.ravi.id"
            dm:
              policy: "allowlist"           # "allowlist" or "open"
              allowFrom:
                - "you@gmail.com"

```

## Channels

### Ravi Email

Real-time email channel powered by cursor-based polling from the Ravi backend.

- **Threading model:** Each email thread maps 1:1 to an OpenClaw session. A new
  `thread_id` creates a new session; subsequent messages in that thread resume it.
- **Session key format:** `agent:{agentId}:ravi-email-owner:{accountId}:thread:{threadId}`
- **DM policy:** Set `policy: "allowlist"` to restrict incoming email to specific
  sender addresses, or `"open"` to accept all senders.
- **Outbound:** Replies are sent via the Ravi API and threaded correctly in the
  original email conversation.

## Agent Tools

The plugin registers 30 tools that agents can invoke.

### Identity

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_identity_list` | List all identities for the authenticated user | -- |
| `ravi_identity_create` | Create a new identity (provisions email + phone + secrets) | `name` (string, required), `email` (string, optional) |
| `ravi_get_info` | Get the active identity's email and phone number | -- |

### Email Inbox

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_inbox_email` | List email threads, optionally unread only | `unread` (boolean) |
| `ravi_read_email` | Read all messages in an email thread | `thread_id` (string, required) |
| `ravi_email_compose` | Compose and send a new email | `to`, `subject`, `body` (all required) |
| `ravi_email_reply` | Reply to an existing email | `message_id` (number, required), `body` (required), `reply_all` |
| `ravi_email_forward` | Forward an email to a new recipient | `message_id` (number, required), `to`, `subject`, `body` (all required) |

### SMS

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_inbox_sms` | List SMS conversations, optionally unread only | `unread` (boolean) |
| `ravi_read_sms` | Read all messages in an SMS conversation | `conversation_id` (string, required) |
| `ravi_sms_send` | Send an SMS from the active identity's phone | `to_number` (E.164, required), `body` (required) |

### Passwords

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_passwords_list` | List all saved passwords (passwords hidden in list view) | -- |
| `ravi_passwords_get` | Get a specific password entry with decrypted password | `uuid` (string, required) |
| `ravi_passwords_create` | Create a new password (encrypted) | `domain` (required), `username`, `password`, `notes` |
| `ravi_passwords_update` | Update an existing password entry | `uuid` (required), `domain`, `username`, `password`, `notes` |
| `ravi_generate_password` | Generate a random password without storing it | `length`, `special`, `digits` |
| `ravi_passwords_delete` | Delete a password entry | `uuid` (string, required) |

### Secrets

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_secrets_list` | List all secret keys | -- |
| `ravi_secrets_get` | Get a secret by key (auto-decrypted) | `key` (string, required) |
| `ravi_secrets_set` | Store a secret (auto-encrypted) | `key` (string, required), `value` (string, required) |
| `ravi_secrets_delete` | Delete a secret by UUID | `uuid` (string, required) |

### Feedback

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_feedback` | Submit feedback about Ravi | `message` (required), `category` (bug/feature/positive/friction/other) |

### Contacts

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_contacts_list` | List all contacts for the active identity | -- |
| `ravi_contacts_search` | Fuzzy search contacts by name, nickname, or email | `query` (string, required) |
| `ravi_contacts_get` | Get a specific contact by UUID | `uuid` (string, required) |
| `ravi_contacts_create` | Create a new contact | `email`, `phone_number`, `display_name`, `nickname` |
| `ravi_contacts_update` | Update an existing contact | `uuid` (required), `email`, `phone_number`, `display_name`, `nickname` |
| `ravi_contacts_delete` | Delete a contact | `uuid` (string, required) |

### SSO

| Tool | Description | Parameters |
|------|-------------|------------|
| `ravi_sso_token` | Generate a short-lived SSO verification token (rvt_...) to prove identity to a third-party service | -- |

## CLI Commands

Run these via `openclaw ravi <command>`:

| Command | Description |
|---------|-------------|
| `login` | Authenticate via device-code flow and set up E2E encryption PIN |
| `onboard` | Full setup: auth + identity selection + OpenClaw config + secrets provider |
| `status` | Show auth status and list available identities |
| `create-agent-key <uuid> [label]` | Create an identity key for a sub-agent (requires management key) |

## Secrets Provider

The plugin ships a `ravi-secrets` binary that implements the OpenClaw exec
secrets protocol, letting you use the Ravi secrets store as a secrets provider.
The binary is installed automatically with the package.

### Configuration

Add the provider to your OpenClaw config:

```yaml
secrets:
  providers:
    ravi:
      source: exec
      command: ravi-secrets
```

Then reference secrets anywhere a secret is accepted:

```yaml
{ source: "exec", provider: "ravi", id: "my-api-key" }
```

### Requirements

- You must run `openclaw ravi onboard` (or `login`) first -- the binary reads
  credentials from `~/.ravi/config.json`.
- Secrets are decrypted automatically using the E2E keypair stored
  during login. If no keypair is found, raw (encrypted) values are returned.

## Architecture

```text
Agent (OpenClaw runtime)
   |
   +-- Channels (ravi-email-owner, ravi-email-trusted)
   |     '-- Polling --> GET /api/email-messages/?direction=incoming&created_after=...
   |           Events arrive in plaintext (server decrypts before dispatch)
   |
   +-- Tools (30 agent tools)
   |     '-- REST calls --> GET/POST/DELETE /api/...
   |           Identity-scoped (identity_uuid in token)
   |           Returns MCP-style content arrays
   |
   '-- Crypto layer (lazy-initialized)
         PIN + salt --> Argon2id --> NaCl SealedBox
         Decrypts historical data from REST ("e2e::<base64>" fields)
         Encrypts secrets/password writes before sending to server
```

Key points:

- **Polling responses are plaintext** -- the server decrypts before dispatch.
  No client-side decryption needed for real-time channel messages.
- **REST data may be encrypted** -- historical email content, passwords, and
  secrets can contain `"e2e::<base64>"` ciphertext that the plugin decrypts
  client-side using the PIN-derived keypair.
- **Tool responses** return MCP-style content arrays (e.g. `[{ type: "text", text: "..." }]`),
  letting the runtime render structured results natively.
- **Authentication** uses identity-scoped tokens with `identity_uuid` baked in.
  Identity is resolved from token claims.

## Development

### Prerequisites

- Node.js 18+
- npm

### Setup

```bash
npm install
```

### Build Targets

```bash
make build           # Local dev build (reads .env.local for API URL)
make build-prod      # Production build (always uses https://ravi.id)
make test            # Run all tests
make lint            # Type-check without emitting (tsc --noEmit)
```

Publishing to npm happens only via GitHub Actions -- there is no local
publish target.

### npm Scripts

```bash
npm run build        # Compile TypeScript to dist/
npm test             # Run tests (vitest, single run)
npm run test:watch   # Run tests in watch mode
npm run lint         # Type-check without emitting (tsc --noEmit)
```

### Project structure

```text
src/
  index.ts            # Plugin entry point and registration
  types.ts            # TypeScript interfaces for API responses and polling events
  client.ts           # RaviClient -- typed HTTP client for all REST endpoints
  polling.ts          # RaviPollingClient -- cursor-based polling for email events
  polling-pool.ts     # Polling connection pool (ref-counted, one client per identity)
  auth.ts             # Auth persistence (loadConfig, saveConfig, resolveApiKey)
  config.ts           # Build-time API URL configuration
  utils.ts            # Shared utilities (tryDecrypt, wrapInHtml)
  runtime.ts          # PluginRuntime singleton for channel gateway adapters
  attachments.ts      # Email attachment validation helpers
  cli.ts              # CLI commands (login, onboard, status, create-agent-key)
  bin/
    ravi-secrets.ts   # OpenClaw exec secrets provider binary
  channels/
    email.ts          # ravi-email-owner channel
    email-trusted.ts  # ravi-email-trusted channel
  tools/
    identity.ts       # Identity management tools
    inbox.ts          # Email and SMS inbox tools
    email-send.ts     # Email compose and reply tools
    sms-send.ts       # SMS send tool
    passwords.ts      # Password manager tools
    secrets.ts        # Secret management tools
    contacts.ts       # Contact management tools
    feedback.ts       # Feedback tool
  __tests__/          # Test files (*.test.ts)
```

## License

MIT