clavitor/docs/INTEGRATION-RESEARCH.md

12 KiB

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:

{
  "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:

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:

# 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:

clavitor read "clavitor://GitHub/Token"
# → ghp_xxxxxxxxxxxx (stdout, no newline)

Users can wire this into OpenClaw just like 1Password:

{
  "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:

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.

# .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.

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.

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

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