clavitor/docs/INTEGRATION-RESEARCH.md

293 lines
12 KiB
Markdown

# Clavitor CLI — Integration Research & Proposal
*C-009 | Revised 2026-03-24 — focus on how OpenClaw integrates with 1Password, and how we can do it better*
---
## 1. How OpenClaw Integrates with 1Password (the Real Answer)
OpenClaw has **two distinct integration points** with 1Password, each serving a different purpose.
### Integration Point A: SecretRef exec provider (gateway config)
OpenClaw has a built-in secrets system called **SecretRefs**. Every config field that accepts a credential can instead take a SecretRef object pointing to a source. One of those sources is `exec` — run any binary, capture stdout as the secret value.
The community-standard way to wire 1Password in:
```json
{
"secrets": {
"providers": {
"op-discord": {
"source": "exec",
"command": "/usr/bin/op",
"args": ["read", "--no-newline", "op://OpenClaw/Discord Bot token/credential"],
"passEnv": ["OP_SERVICE_ACCOUNT_TOKEN"],
"jsonOnly": false
}
}
},
"channels": {
"discord": {
"token": { "source": "exec", "provider": "op-discord", "id": "value" }
}
}
}
```
**How it works:**
1. User creates a 1Password service account (`ops_...` token), grants it read access to a vault
2. Sets `OP_SERVICE_ACCOUNT_TOKEN` in the environment
3. Defines one `exec` provider per secret, each calling `op read op://vault/item/field`
4. OpenClaw calls the binary at startup, captures stdout, resolves the secret into memory
5. The secret never touches disk beyond the service account token in the environment
**Known pain point (active GitHub issue #29183):** OpenClaw's SecretRef `id` field has a strict regex pattern that rejects spaces, so `op://My Vault/My Item/credential` can't be used as the `id` directly. The workaround is to bake the full `op://` path into the provider `args` and use `"id": "value"` as a placeholder. One provider definition per secret.
**Resolution approach (eager, not lazy):** Secrets are resolved once at startup into an in-memory snapshot. Provider outages don't hit live request paths. Startup fails fast if a required secret can't be resolved.
### Integration Point B: 1Password Skill (agent-level)
OpenClaw ships a bundled `1password` skill (`skills/1password/SKILL.md`). This lets the AI agent itself use 1Password — reading credentials on demand during a conversation.
**How the skill works:**
- Declares a dependency on the `op` binary
- Provides install instructions (Homebrew)
- Documents the authentication flow: desktop app integration (macOS/Linux/Windows) or service account for headless
- **Key architectural requirement**: All `op` commands must run inside a **dedicated tmux session** because OpenClaw's shell tool spawns a fresh TTY per command, which breaks `op`'s session persistence
The tmux pattern from the skill:
```bash
SOCKET="$SOCKET_DIR/openclaw-op.sock"
SESSION="op-auth-$(date +%Y%m%d-%H%M%S)"
tmux -S "$SOCKET" new -d -s "$SESSION" -n shell
tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- "op signin --account my.1password.com" Enter
tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- "op read op://vault/item/field" Enter
tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200
tmux -S "$SOCKET" kill-session -t "$SESSION"
```
Supported operations via skill: `op signin`, `op read`, `op run`, `op inject`, `op whoami`, `op account list`.
### Community Bitwarden skill (clawdhub)
There's also a community-published `bitwarden` skill (`jimihford/openclaw-bitwarden`) on clawdhub. Same tmux pattern, but for Bitwarden's `bw` CLI:
```bash
# Unlock and capture session key
tmux ... send-keys -- 'export BW_SESSION=$(bw unlock --raw)' Enter
tmux ... send-keys -- 'bw list items --search github' Enter
```
Includes a Vaultwarden (self-hosted Bitwarden) Docker Compose setup for local testing. Supports `bw get password`, `bw get totp`, `bw generate`, etc.
A second community skill (`asleep123/bitwarden`) uses `rbw` (Rust Bitwarden CLI) with a daemon-based session model — no tmux required since `rbw` maintains its own background agent.
---
## 2. What This Means for Clavitor
### The OpenClaw SecretRef integration is already ours to take
OpenClaw resolves secrets via `exec` providers. Any binary that returns a value on stdout works. **Clavitor CLI already outputs credentials on stdout.** We are immediately compatible with OpenClaw's SecretRef system — no changes needed from OpenClaw's side.
What we need to make this seamless:
```bash
clavitor read "clavitor://GitHub/Token"
# → ghp_xxxxxxxxxxxx (stdout, no newline)
```
Users can wire this into OpenClaw just like 1Password:
```json
{
"secrets": {
"providers": {
"clavitor-github": {
"source": "exec",
"command": "/usr/local/bin/clavitor",
"args": ["read", "--no-newline", "clavitor://GitHub/Token"],
"passEnv": ["CLAVITOR_TOKEN"],
"jsonOnly": false
}
}
}
}
```
**Advantage over 1Password here**: No spaces problem. Our `clavitor://entry/field` URI has a clean, regex-safe format. No per-secret workarounds needed.
### The skill integration gap
OpenClaw has a bundled 1Password skill. We don't have an equivalent Clavitor skill.
The 1Password skill's main complexity is session management (tmux required for TTY persistence). Clavitor's CLI doesn't have this problem — it's a stateless HTTP client. Every call goes to the local vault server, authenticated by a token. No session to maintain. **A Clavitor skill would be dramatically simpler than the 1Password skill.**
### The scoped token advantage (nobody else has this)
1Password's OpenClaw integration gives the AI agent access to the entire vault (limited only by the service account's vault access). There's no way to scope it to "only the credentials this specific agent needs."
Clavitor supports scoped tokens: a token tagged `["social", "twitter"]` can only see entries with those tags. In a multi-agent OpenClaw setup (which OpenClaw supports), each agent gets its own scoped Clavitor token. A compromised agent leaks only what it was scoped to.
This is the differentiator to lead with in any integration story.
---
## 3. Concrete Integration Proposals
### A. `clavitor read` — OpenClaw SecretRef exec provider (Priority: High, ~1 day)
**The ask**: `clavitor read [--no-newline] <clavitor://entry/field>`
This makes Clavitor a drop-in `exec` provider for OpenClaw (and any other tool using the same pattern — Doppler, HashiCorp Vault, etc.).
**CLI design**:
```bash
clavitor read "clavitor://GitHub/Token" # stdout + newline
clavitor read --no-newline "clavitor://GitHub/Token" # stdout, no newline (for env injection)
clavitor read --json "clavitor://GitHub/Token" # {"value": "...", "entry": "GitHub", "field": "Token"}
```
**Auth**: reads token from `CLAVITOR_TOKEN` env var, or `--token` flag, or `~/.config/clavitor/token`.
**Implementation** (C CLI, `clovis-cli`):
- Add `cmd_read()` in `main.c` (~100 lines)
- Parse `clavitor://` URI into entry query + field label
- Call `GET /api/search?q=<entry>` → pick best match → `GET /api/entries/:id/field/:label`
- Write to stdout, respect `--no-newline`
---
### B. `clavitor run` — Secret injection at process spawn (Priority: High, ~1-2 days)
**The ask**: `clavitor run [--env .env] -- <command>`
This is the `op run` / `doppler run` equivalent. Resolves `clavitor://` references in an env file, then exec's the command with secrets set as env vars.
```bash
# .env file contains: GITHUB_TOKEN=clavitor://GitHub/Token
clavitor run --env .env -- node server.js
clavitor run --set "GITHUB_TOKEN=clavitor://GitHub/Token" -- make deploy
```
**Why this matters for agentic platforms**: When an OpenClaw agent spawns a subprocess (coding agent, test runner, deploy script), `clavitor run` ensures it gets credentials without the agent needing to know them or pass them explicitly.
**Implementation**: Add `cmd_run()` in `main.c`. Parse env file, resolve `clavitor://` refs via HTTP, build `envp[]`, call `execvp()`. ~200 lines C, no new deps.
---
### C. OpenClaw Skill: `clavitor` (Priority: High, ~half day)
**The ask**: An OpenClaw skill that wraps Clavitor for agent-level credential access.
This is the equivalent of OpenClaw's bundled `1password` skill, but simpler because:
- No session management (stateless HTTP)
- No tmux required
- No desktop app dependency
- Works headless out of the box
**Skill structure**:
```
skills/clavitor/
SKILL.md
references/
setup.md # install CLI, get token from web UI, set CLAVITOR_TOKEN
cli-examples.md # clavitor read, list, totp, generate
```
**SKILL.md description**: "Access Clavitor vault credentials. Use when reading passwords, API keys, TOTP codes, or searching the vault. Requires CLAVITOR_TOKEN env var and local Clavitor server running."
**Key guardrails in the skill**:
- Never print secrets in chat responses
- Use `clavitor read` for single fields, `clavitor list` for discovery
- L2 fields return `[PROTECTED]` — tell the user, don't error
---
### D. Bitwarden-compatible export (Priority: Medium)
**The ask**: `clavitor export --format bitwarden`
Essential for migration trust. The Bitwarden JSON format is the de-facto interchange standard — every major password manager (1Password, LastPass, KeePass) can import it.
```bash
clavitor export --format bitwarden > vault-backup.json
clavitor export --format csv > vault-backup.csv
clavitor export --format json > vault-backup.clavitor.json # native, full fidelity
```
**L2 handling**: L2 fields export as `[PROTECTED]` in the notes. User is warned before export completes. This is correct — we never export L2 plaintext from the server side.
**Implementation**: Server endpoint `GET /api/export?format=bitwarden` + CLI `clavitor export` subcommand.
---
### E. `clavitor inject` — Config file template rendering (Priority: Medium)
**The ask**: Mirror `op inject`. Resolves `clavitor://` references in config templates.
```bash
echo "db_password: {{ clavitor://Postgres/Password }}" | clavitor inject
clavitor inject -i config.yml.tpl -o config.yml
```
Complements `clavitor run` for static config files (nginx, docker-compose, etc.) that can't use env vars.
---
### F. SSH Agent integration (Priority: Low)
**The ask**: `clavitor ssh-agent --add` — load `ssh_key` entries into `ssh-agent` on vault unlock.
Mirrors KeePassXC. Entries of type `ssh_key` get loaded via `SSH_AUTH_SOCK`. Removed on `clavitor ssh-agent --remove` or vault lock.
---
## 4. Where We Beat 1Password for Agentic Use
| | 1Password + OpenClaw | Clavitor + OpenClaw |
|---|---|---|
| Setup complexity | High: desktop app + service account + one provider per secret | Low: install binary + set CLAVITOR_TOKEN |
| Spaces-in-URI workaround | Required (active bug #29183) | Not needed (clean URI scheme) |
| Scoped per-agent tokens | ❌ vault-level only | ✅ per-tag, per-entry |
| Agent can access TOTP | Only via skill + tmux | Native MCP tool + CLI |
| Field-level AI visibility | ❌ all or nothing | ✅ L1/L2 per field |
| Self-hosted | ❌ requires 1Password subscription | ✅ |
| Skill session complexity | tmux required | Stateless, no tmux needed |
| Audit log per agent | ❌ | ✅ token label in every log entry |
---
## 5. Recommended Sprint Sequencing
```
Sprint 1 (CLI v0.2, ~3 days):
✅ clavitor read <clavitor://entry/field> ← unlocks OpenClaw SecretRef compat
✅ clavitor run --env .env -- <cmd> ← op run parity
✅ clavitor:// URI scheme canonicalized
Sprint 2 (Ecosystem, ~2 days):
✅ OpenClaw skill (SKILL.md + references/)
✅ clavitor inject (template rendering)
✅ Publish skill to clawhub.com
Sprint 3 (Export + Migration, ~2 days):
✅ clavitor export --format bitwarden|csv|json
✅ /api/export server endpoint
Later (v2):
- SSH agent integration
- D-Bus Secret Service (Linux)
- rbw-compatible API (for drop-in Bitwarden agent replacement)
```
---
## 6. The Pitch
> "OpenClaw users currently need 1Password ($36/yr), one provider definition per secret, and a tmux session just to keep credentials out of their config file. Clavitor does the same thing: install one binary, set one env var, done. And unlike 1Password, each of your ten agents gets its own scoped token — so one compromised agent doesn't hand over your entire vault."
---
*Researched and written by Research Agent | C-009 (revised)*
*Sources: docs.openclaw.ai/gateway/secrets, github.com/openclaw/openclaw skills/1password, github.com/openclaw/skills Bitwarden community skills, github.com/openclaw/openclaw issue #29183, prokopov.me/posts/securing-openclaw-with-1password*