diff --git a/.github/AGENTS.md b/.github/AGENTS.md index 129f87d..a93ed0c 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -37,6 +37,26 @@ - Use workflow concurrency groups to prevent overlapping deploys for the same environment. - Run Wrangler through workspace tooling (`pnpm exec wrangler`) in CI so commands work without a global Wrangler install on GitHub runners. +## Deployment Rules (Landing) +- `deploy-landing-develop.yml` deploys landing docs/asset output from `develop` to the Pages `develop` branch. +- `deploy-landing.yml` deploys landing docs/asset output from `main` to the Pages `main` branch. +- Both landing deploy workflows must trigger on: + - `apps/landing/**` + - `apps/openclaw-skill/skill/**` + - `apps/landing/scripts/**` + - `.github/workflows/deploy-landing*.yml` +- Landing workflows must bootstrap Cloudflare Pages project `clawdentity-site` if missing before deploy. +- Landing workflows must assert generated artifacts exist before invoking `pages deploy`: + - `apps/landing/dist/skill.md` + - `apps/landing/dist/install.sh` + - `apps/landing/dist/install.ps1` + +## Release Rules (Rust) +- `publish-rust.yml` must publish six binary archives per release (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64/aarch64). +- Rust release assets must always include: + - `clawdentity--windows-aarch64.zip` + - installer scripts copied from `apps/landing/public/install.sh` and `apps/landing/public/install.ps1` + ## Release Rules (CLI) - `publish-cli.yml` is manual (`workflow_dispatch`) and must accept `release_type` (`patch`/`minor`/`major`) + `dist_tag` inputs. - Compute the next CLI version in CI from the currently published npm `clawdentity` version (fallback `0.0.0` if first publish), then bump `apps/cli/package.json` in the workflow. @@ -67,7 +87,7 @@ - `Workers Scripts:Edit` - `Workers Routes:Edit` (zone-level, custom domains) - `D1:Edit` - - add `Cloudflare Pages:Edit` only when Pages deploy workflow is introduced. + - `Cloudflare Pages:Edit` ## Migration Rollback Strategy (Develop) - Capture pre-deploy artifacts: diff --git a/.github/workflows/AGENTS.md b/.github/workflows/AGENTS.md index 50d129c..99085da 100644 --- a/.github/workflows/AGENTS.md +++ b/.github/workflows/AGENTS.md @@ -13,8 +13,10 @@ - `x86_64-apple-darwin` - `aarch64-apple-darwin` - `x86_64-pc-windows-msvc` + - `aarch64-pc-windows-msvc` - Use only supported runner labels; avoid deprecated/unsupported macOS labels (for example `macos-13` if unavailable in project settings). - Smoke-test binaries only on native runner/target pairs. +- Skip smoke execution for `aarch64-pc-windows-msvc` on `windows-latest` because the hosted runner is x64. - Do not execute cross-built `linux-aarch64` artifacts on `ubuntu-latest` x86 runners; this must be skipped (exec format mismatch). - When `x86_64-apple-darwin` is built on Apple Silicon runners, skip smoke execution unless a native Intel runner is configured. - Keep binary naming stable in packaged archives: @@ -26,10 +28,21 @@ - `clawdentity--macos-x86_64.tar.gz` - `clawdentity--macos-aarch64.tar.gz` - `clawdentity--windows-x86_64.zip` + - `clawdentity--windows-aarch64.zip` + - `install.sh` + - `install.ps1` - `clawdentity--checksums.txt` +- Installer script assets in Rust releases must be sourced from `apps/landing/public/install.sh` and `apps/landing/public/install.ps1`. - Always generate and publish SHA256 checksums. - Keep release uploads idempotent (`overwrite_files` / clobber-safe behavior) so reruns replace assets cleanly. +## Landing Deploy Rules +- `deploy-landing-develop.yml` and `deploy-landing.yml` must keep Cloudflare Pages bootstrap behavior before deploy. +- Both landing deploy workflows must assert these built artifacts before running `pages deploy`: + - `apps/landing/dist/skill.md` + - `apps/landing/dist/install.sh` + - `apps/landing/dist/install.ps1` + ## Rust Crate Publish Rules - Resolve next version from crates metadata using `cargo info` and bump both crate manifests consistently: - `crates/clawdentity-core/Cargo.toml` diff --git a/.github/workflows/deploy-landing-develop.yml b/.github/workflows/deploy-landing-develop.yml new file mode 100644 index 0000000..6e852a7 --- /dev/null +++ b/.github/workflows/deploy-landing-develop.yml @@ -0,0 +1,94 @@ +name: Deploy Landing Site (Develop) + +on: + push: + branches: [develop] + paths: + - "apps/landing/**" + - "apps/openclaw-skill/skill/**" + - "apps/landing/scripts/**" + - ".github/workflows/deploy-landing*.yml" + workflow_dispatch: + +concurrency: + group: deploy-landing-develop + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10.23.0 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install D2 CLI + run: curl -fsSL https://d2lang.com/install.sh | sh -s -- + + - run: pnpm install --frozen-lockfile + + - name: Verify Cloudflare secrets + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + set -euo pipefail + + if [ -z "${CLOUDFLARE_API_TOKEN:-}" ]; then + echo "Missing required secret: CLOUDFLARE_API_TOKEN" + exit 1 + fi + + if [ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]; then + echo "Missing required secret: CLOUDFLARE_ACCOUNT_ID" + exit 1 + fi + + - name: Bootstrap Cloudflare Pages project + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + set -euo pipefail + + PROJECT_NAME="clawdentity-site" + + PROJECTS_JSON="$(pnpm dlx wrangler@4 pages project list --json)" + if PROJECTS_JSON="${PROJECTS_JSON}" node - "${PROJECT_NAME}" <<'NODE' + const projectName = process.argv[2]; + const projects = JSON.parse(process.env.PROJECTS_JSON ?? "[]"); + const exists = Array.isArray(projects) && projects.some((project) => project?.name === projectName); + process.exit(exists ? 0 : 1); + NODE + then + echo "Cloudflare Pages project '${PROJECT_NAME}' already exists" + else + echo "Creating Cloudflare Pages project '${PROJECT_NAME}' with production branch 'main'" + pnpm dlx wrangler@4 pages project create "${PROJECT_NAME}" --production-branch main + fi + + - run: pnpm --filter @clawdentity/landing build + + - name: Assert landing artifacts exist + run: | + set -euo pipefail + test -f apps/landing/dist/skill.md + test -f apps/landing/dist/install.sh + test -f apps/landing/dist/install.ps1 + + - name: Deploy to Cloudflare Pages (develop) + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy apps/landing/dist --project-name=clawdentity-site --branch=develop diff --git a/.github/workflows/deploy-landing.yml b/.github/workflows/deploy-landing.yml new file mode 100644 index 0000000..6e1c501 --- /dev/null +++ b/.github/workflows/deploy-landing.yml @@ -0,0 +1,94 @@ +name: Deploy Landing Site + +on: + push: + branches: [main] + paths: + - "apps/landing/**" + - "apps/openclaw-skill/skill/**" + - "apps/landing/scripts/**" + - ".github/workflows/deploy-landing*.yml" + workflow_dispatch: + +concurrency: + group: deploy-landing-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10.23.0 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install D2 CLI + run: curl -fsSL https://d2lang.com/install.sh | sh -s -- + + - run: pnpm install --frozen-lockfile + + - name: Verify Cloudflare secrets + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + set -euo pipefail + + if [ -z "${CLOUDFLARE_API_TOKEN:-}" ]; then + echo "Missing required secret: CLOUDFLARE_API_TOKEN" + exit 1 + fi + + if [ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]; then + echo "Missing required secret: CLOUDFLARE_ACCOUNT_ID" + exit 1 + fi + + - name: Bootstrap Cloudflare Pages project + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + set -euo pipefail + + PROJECT_NAME="clawdentity-site" + + PROJECTS_JSON="$(pnpm dlx wrangler@4 pages project list --json)" + if PROJECTS_JSON="${PROJECTS_JSON}" node - "${PROJECT_NAME}" <<'NODE' + const projectName = process.argv[2]; + const projects = JSON.parse(process.env.PROJECTS_JSON ?? "[]"); + const exists = Array.isArray(projects) && projects.some((project) => project?.name === projectName); + process.exit(exists ? 0 : 1); + NODE + then + echo "Cloudflare Pages project '${PROJECT_NAME}' already exists" + else + echo "Creating Cloudflare Pages project '${PROJECT_NAME}' with production branch 'main'" + pnpm dlx wrangler@4 pages project create "${PROJECT_NAME}" --production-branch main + fi + + - run: pnpm --filter @clawdentity/landing build + + - name: Assert landing artifacts exist + run: | + set -euo pipefail + test -f apps/landing/dist/skill.md + test -f apps/landing/dist/install.sh + test -f apps/landing/dist/install.ps1 + + - name: Deploy to Cloudflare Pages (main) + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy apps/landing/dist --project-name=clawdentity-site --branch=main diff --git a/.github/workflows/publish-rust.yml b/.github/workflows/publish-rust.yml index 4fcbca8..5d29a1a 100644 --- a/.github/workflows/publish-rust.yml +++ b/.github/workflows/publish-rust.yml @@ -293,6 +293,12 @@ jobs: archive_ext: zip use_cross: "false" smoke_test: "true" + - os: windows-latest + platform: windows-aarch64 + target: aarch64-pc-windows-msvc + archive_ext: zip + use_cross: "false" + smoke_test: "false" steps: - name: Checkout release tag uses: actions/checkout@v4 @@ -395,6 +401,12 @@ jobs: permissions: contents: write steps: + - name: Checkout release tag + uses: actions/checkout@v4 + with: + ref: ${{ needs.publish.outputs.tag }} + fetch-depth: 0 + - name: Download packaged artifacts uses: actions/download-artifact@v4 with: @@ -402,6 +414,14 @@ jobs: path: dist merge-multiple: true + - name: Stage installer scripts + shell: bash + run: | + set -euo pipefail + mkdir -p dist + install -m 0755 apps/landing/public/install.sh dist/install.sh + install -m 0644 apps/landing/public/install.ps1 dist/install.ps1 + - name: Verify assets and generate checksums shell: bash run: | @@ -413,8 +433,8 @@ jobs: \( -name "clawdentity-${VERSION}-*.tar.gz" -o -name "clawdentity-${VERSION}-*.zip" \) \ | sort) - if [[ "${#ARCHIVES[@]}" -ne 5 ]]; then - echo "Expected 5 release archives, found ${#ARCHIVES[@]}" + if [[ "${#ARCHIVES[@]}" -ne 6 ]]; then + echo "Expected 6 release archives, found ${#ARCHIVES[@]}" ls -lah dist exit 1 fi @@ -432,7 +452,8 @@ jobs: Assets in this release: - Linux: x86_64 and aarch64 - macOS: x86_64 and aarch64 - - Windows: x86_64 (`clawdentity.exe`) + - Windows: x86_64 and aarch64 (`clawdentity.exe`) + - Installers: `install.sh` and `install.ps1` - SHA256 checksums: `clawdentity--checksums.txt` Windows install example (PowerShell + `irm`): @@ -475,4 +496,7 @@ jobs: dist/clawdentity-${{ needs.publish.outputs.next_version }}-macos-x86_64.tar.gz dist/clawdentity-${{ needs.publish.outputs.next_version }}-macos-aarch64.tar.gz dist/clawdentity-${{ needs.publish.outputs.next_version }}-windows-x86_64.zip + dist/clawdentity-${{ needs.publish.outputs.next_version }}-windows-aarch64.zip dist/clawdentity-${{ needs.publish.outputs.next_version }}-checksums.txt + dist/install.sh + dist/install.ps1 diff --git a/.gitignore b/.gitignore index 07805a5..7672d49 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ nx.bat .dev.vars .pnpm-store/ target/ +.astro/ # Working docs (plans, reviews, research — keep local) **/PLAN.md diff --git a/AGENTS.md b/AGENTS.md index 6f3af76..aff680a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ Run from `crates/`: ### Apps (deployable services) - `apps/registry` - Cloudflare Worker HTTP API for humans, agents, invites, API keys, and revocation data. - `apps/proxy` - Cloudflare Worker relay/proxy that verifies Clawdentity auth headers and enforces trust policy. -- `apps/cli` - TypeScript CLI package (`clawdentity`) for onboarding, identity ops, provider setup, and skill install. +- `apps/cli` - TypeScript CLI compatibility package (`clawdentity`); Rust CLI is the canonical operator surface. - `apps/openclaw-skill` - OpenClaw skill package and relay transform artifacts used by CLI install flow. ### Packages (shared libraries) @@ -49,7 +49,7 @@ Run from `crates/`: ### Rust workspace crates - `crates/clawdentity-core` - Core Rust library for identity, registry clients, connector/runtime, providers, pairing, and persistence. -- `crates/clawdentity-cli` - Rust CLI binary and command surface replacing the legacy TypeScript CLI over time. +- `crates/clawdentity-cli` - Rust CLI binary and command surface for current operator workflows. ### Rust local test services - `crates/tests/local/mock-registry` - Local mock registry used for integration and harness-style flows. @@ -59,11 +59,11 @@ Run from `crates/`: ### TypeScript CLI (`apps/cli`) - Build/package: `pnpm -F clawdentity build` -- Common ops: `clawdentity config init`, `clawdentity invite redeem `, `clawdentity agent create `, `clawdentity openclaw setup `, `clawdentity skill install`, `clawdentity connector start ` +- Treat command docs here as compatibility guidance, not canonical runtime surface. ### Rust CLI (`crates/clawdentity-cli`) - Help: `cargo run -p clawdentity-cli -- --help` -- Common ops: `cargo run -p clawdentity-cli -- init`, `whoami`, `agent create `, `invite redeem --display-name `, `connector start `, `provider doctor --for openclaw` +- Common ops: `cargo run -p clawdentity-cli -- init`, `register`, `whoami`, `agent create `, `agent auth revoke `, `provider setup --for --agent-name `, `provider doctor --for `, `connector start ` ## 5) Deeper Docs Use `docs/` as system of record: @@ -79,6 +79,7 @@ Use `docs/` as system of record: - Favor actionable errors and stable machine-readable outputs. - Run relevant TypeScript and Rust checks before commit (`pnpm build` and `cargo check` are baseline gates). - Keep docs synchronized with implementation changes, especially when changing CLI flows or skill behavior. +- Keep user onboarding docs prompt-first (`/skill.md` canonical); treat command-by-command and Rust toolchain flows as advanced fallback guidance only. ## 7) Release Automation - Keep Rust release automation in `.github/workflows/publish-rust.yml` as the single canonical path for version bump + crates.io publish + tag creation + binary release. @@ -92,6 +93,7 @@ Use `docs/` as system of record: - `clawdentity--macos-x86_64.tar.gz` - `clawdentity--macos-aarch64.tar.gz` - `clawdentity--windows-x86_64.zip` + - `clawdentity--windows-aarch64.zip` - `clawdentity--checksums.txt` - Binary naming contract for release artifacts: - Unix binary is `clawdentity` diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c60718f..99f7456 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -191,22 +191,17 @@ Operators exchange peer metadata out-of-band (alias, DID, proxy URL). No relay i ``` Alice's Operator Bob's Operator │ │ - │ Shares metadata out-of-band ─────────►│ - │ alias, DID, proxy URL │ + │ POST /pair/start (proxy API) │ + │ receives clwpair1_... ticket │ + │────────────────────────────────────────►│ │ │ - │ │ clawdentity openclaw setup - │ │ bob --peer-alias alice - │ │ --peer-did did:cdi::agent:... - │ │ --peer-proxy-url https://alice-proxy/hooks/agent + │ │ POST /pair/confirm (proxy API) + │ │ ticket + responder metadata │ │ - │ │ Stores peer in peers.json: - │ │ { "alice": { - │ │ "did": "did:cdi::agent:...", - │ │ "proxyUrl": "https://..." - │ │ }} + │ │ Persists trusted peer metadata: + │ │ alias + DID + proxy URL │ │ - │ │ Installs relay transform - │ │ Configures OpenClaw hooks + │ │ Relay transform reads paired peers ``` **Security:** Setup uses only public peer metadata (DID + proxy URL + alias). No keys, tokens, or secrets are exchanged. Alice and Bob must complete proxy pairing (`/pair/start` + `/pair/confirm`) before either side can send messages. @@ -300,7 +295,7 @@ Bob's OpenClaw relay-to-peer.ts Alice's Proxy Alice's Ope ### Sender Side Operator (Owner/Admin) -- Action: `clawdentity agent revoke ` +- Action: registry API revoke (`DELETE /v1/agents/:id`) - Scope: **global** (registry-level identity revocation) - Effect: every receiving proxy rejects that revoked token once CRL refreshes. - Use when: key compromise, decommissioning, or ownership/admin suspension events. @@ -369,18 +364,16 @@ Bob's OpenClaw relay-to-peer.ts Alice's Proxy Alice's Ope ### 4) Operator Lifecycle Tooling (CLI) -- Handled by: `apps/cli` -- `clawdentity agent create` for local keypair + registry registration. -- `clawdentity agent inspect` and `clawdentity verify` for offline token checks. -- `clawdentity agent revoke` for kill switch workflows. -- `clawdentity api-key create` to mint a new PAT (token shown once). -- `clawdentity api-key list` to view PAT metadata (`id`, `name`, `status`, `createdAt`, `lastUsedAt`). -- `clawdentity api-key revoke ` to invalidate a PAT without rotating unrelated keys. -- `clawdentity share` for contact-card exchange (DID, verify URL, endpoint). -- `clawdentity connector start ` to run local relay connector runtime. -- `clawdentity connector service install ` to configure connector autostart after reboot/login (`launchd` on macOS, `systemd --user` on Linux). -- `clawdentity connector service uninstall ` to remove connector autostart service. -- `clawdentity skill install` to install/update OpenClaw relay skill artifacts under `~/.openclaw`. +- Handled by: `crates/clawdentity-cli`, `crates/clawdentity-core` +- `clawdentity init` + `clawdentity register` for local identity bootstrap and registry enrollment. +- `clawdentity agent create ` for local keypair + agent registration. +- `clawdentity agent inspect ` for local identity/auth state inspection. +- `clawdentity agent auth refresh ` / `clawdentity agent auth revoke ` for per-agent auth lifecycle. +- `clawdentity api-key create|list|revoke` for PAT lifecycle. +- `clawdentity install --platform ` for provider artifact install/bootstrap. +- `clawdentity provider setup --for --agent-name ` for runtime/hook setup. +- `clawdentity provider doctor --for ` and `provider relay-test --for --peer ` for readiness and relay diagnostics. +- `clawdentity connector start ` and `connector service install|uninstall ` for runtime operations. #### Connector Local OpenClaw Resilience @@ -421,7 +414,8 @@ Bob's OpenClaw relay-to-peer.ts Alice's Proxy Alice's Ope Expected operator flow starts from the CLI command: ```bash -clawdentity skill install +clawdentity install --platform openclaw +clawdentity provider setup --for openclaw --agent-name ``` Installer logic prepares OpenClaw runtime artifacts automatically: diff --git a/README-draft.md b/README-draft.md deleted file mode 100644 index 439fdd8..0000000 --- a/README-draft.md +++ /dev/null @@ -1,325 +0,0 @@ -# Clawdentity - -**Your AI agent talks to other AI agents. Clawdentity makes sure they know who they're talking to.** - -Think of it like this: your agent runs on your laptop. It's private. But it needs to collaborate with agents owned by other people — send messages, request data, delegate tasks. How does the other agent know yours is legit? How do you know theirs is? - -That's what Clawdentity solves. Every agent gets a verified identity. Every message is signed. Every connection is authorized. Your agent stays private, but it can talk to the world. - ---- - -## How it works (30-second version) - -``` -You run your AI agent on your machine (private, never exposed to internet) - │ - ▼ -Clawdentity gives it an identity (like a passport) - │ - ▼ -When your agent talks to another agent: - ✍️ It signs every message with its private key - 🛂 The other side's proxy checks the passport - ✅ If legit → message delivered - ❌ If not → rejected - │ - ▼ -Your agent stays private. The proxy is the only thing exposed. -The other agent never touches your machine directly. -``` - -**That's it.** Identity. Signing. Verification. Your agent stays safe behind a wall, but can talk to anyone it trusts. - ---- - -## Real-world analogy - -| Concept | Real world | Clawdentity | -|---------|-----------|-------------| -| Your agent | You, at home | Runs on your laptop, never exposed | -| Identity | Passport | AIT — a signed token proving who your agent is | -| Signing messages | Your signature on a letter | Every request is cryptographically signed | -| Proxy | Bouncer at the door | Checks identity before letting messages through | -| Registry | Passport office | Issues and verifies agent identities | -| Revocation | Canceling a passport | One command kills a compromised agent everywhere | - ---- - -## Why not just use API keys? - -Most agent frameworks use shared API keys or webhook tokens. Here's why that breaks: - -| Problem | Shared API Key | Clawdentity | -|---------|---------------|-------------| -| Someone leaks the key | **Everyone** is compromised | Only that one agent is affected | -| Who sent this request? | No idea — all callers look the same | Cryptographic proof of exactly which agent | -| Kill one bad agent | Rotate the key, break everything | Revoke one agent, others keep working | -| Replay attack | No protection | Every request has a unique nonce + timestamp | -| Your agent's machine exposed? | Yes, if using webhooks directly | No — proxy is the only public endpoint | - ---- - -## Getting started - -### 1. Install - -```bash -npm install -g clawdentity -``` - -### 2. Set up your identity - -```bash -# First time: get an invite from an admin -clawdentity invite redeem - -# Create your agent -clawdentity agent create my-agent -``` - -Your agent now has a cryptographic identity. Private key stays on your machine. Nobody else ever sees it. - -### 3. Connect with another agent - -Someone shares a connection link with you: - -```bash -clawdentity openclaw setup my-agent --invite-code -``` - -Done. Your agents can now talk to each other. - -### 4. Send a message - -From your agent's AI session, just say: - -``` -Send "Hello from my agent!" to peer alice -``` - -The message is signed, verified, and delivered. Alice's human can see it in their chat. - ---- - -## The flow (visual) - -### Connecting two agents - -``` - Ravi (owns Kai) Sarah (owns Scout) - │ │ - │ "Hey Sarah, let's connect │ - │ our agents" │ - │ │ - │ Shares connection link ────────►│ - │ │ - │ │ Clicks link - │ │ Agents pair automatically - │ │ - │◄──── Both agents now trust ─────►│ - │ each other │ -``` - -### Sending messages - -``` - Kai (Ravi's agent) Scout (Sarah's agent) - │ │ - │ Signs message with │ - │ private key │ - │ │ - │ ──── signed message ──────► Proxy checks: - │ ✅ Valid identity? - │ ✅ Not revoked? - │ ✅ Trusted pair? - │ ✅ Not a replay? - │ │ - │ Delivered to Scout - │ │ - │ Sarah sees in her chat: - │ "🤖 Kai (Ravi): Hello!" -``` - -### If something goes wrong - -``` - Ravi notices Kai is compromised - │ - │ clawdentity agent revoke kai - │ - ▼ - Registry adds Kai to revocation list - │ - ▼ - Every proxy stops accepting Kai's messages - within minutes. No other agents affected. - │ - ▼ - Sarah's Scout is safe. Mike's DataBot is safe. - Only Kai is cut off. -``` - ---- - -## Groups — Multi-agent collaboration - -Create a group. Share the invite. Everyone's agents can talk. Every human sees the conversation. - -``` - 📢 AI Research Squad - - 🤖 Kai (Ravi): "Found a new paper on tool-use benchmarks" - 🤖 Scout (Sarah): "Does it cover multi-step reasoning?" - 🤖 DataBot (Mike): "I have the dataset, sharing now" - - ───── Ravi whispers to Kai ───── - 💬 "Ask about evaluation metrics too" - ───────────────────────────────── - - 🤖 Kai (Ravi): "What evaluation metrics does it use?" -``` - -**Humans can see everything. Humans can nudge their own agent. Humans can't control other agents.** - ---- - -## Agent Services — Let your agent do things for others - -Your agent can publish services that other agents can discover and use: - -``` - Kai publishes: - 📄 summarize-paper — Give me an arxiv URL, I'll summarize it - 🔍 search-papers — Search for papers on any topic - - Scout discovers Kai's services and calls: - "Hey Kai, summarize arxiv.org/abs/2401.12345" - - Kai does the work → returns result → signs a receipt - Scout verifies the receipt → uses the result - Sarah (Scout's owner) sees the whole interaction -``` - -Not just messaging — actual agent-to-agent work, with cryptographic proof of what happened. - ---- - -## What stays private - -| What | Where | Who sees it | -|------|-------|-------------| -| Your agent | Your machine | Only you | -| Private key | Your machine | Only your agent | -| OpenClaw webhook token | Your machine | Only your proxy | -| Messages | Between proxies | Sender + receiver only | -| Agent identity (DID, name) | Public | Anyone who connects | - -**Your machine is never exposed to the internet.** The proxy (runs on Cloudflare) is the only public-facing piece. It checks identities and forwards verified messages to your private agent. - ---- - -## Architecture - -``` -┌─────────────────────────────────────────────────┐ -│ REGISTRY │ -│ (Passport Office — Cloudflare Worker) │ -│ │ -│ • Issues agent identities │ -│ • Publishes revocation lists │ -│ • Verifies ownership │ -└──────────┬──────────────────────┬────────────────┘ - │ │ - issues identity issues identity - │ │ -┌──────────▼──────────┐ ┌────────▼───────────────┐ -│ YOUR AGENT │ │ THEIR AGENT │ -│ (your laptop) │ │ (their machine) │ -│ │ │ │ -│ Private key 🔐 │ │ Private key 🔐 │ -│ Never exposed │ │ Never exposed │ -└──────────┬───────────┘ └────────┬───────────────┘ - │ │ - signs requests signs requests - │ │ -┌──────────▼──────────┐ ┌────────▼───────────────┐ -│ THEIR PROXY │ │ YOUR PROXY │ -│ (Cloudflare) │ │ (Cloudflare) │ -│ │ │ │ -│ Checks identity │ │ Checks identity │ -│ Blocks bad actors │ │ Blocks bad actors │ -│ Forwards to agent │ │ Forwards to agent │ -└──────────────────────┘ └────────────────────────┘ -``` - ---- - -## Kill switch - -Agent compromised? One command: - -```bash -clawdentity agent revoke my-agent -``` - -Revoked everywhere. Instantly. No other agents affected. No shared keys to rotate. - ---- - -## Built with - -- **Registry + Proxy**: Cloudflare Workers (globally distributed, fast) -- **Identity**: Ed25519 cryptographic signatures (same as SSH keys) -- **Tokens**: JWT with EdDSA signing -- **Framework**: OpenClaw (first supported framework, more coming) - ---- - -## Project structure - -``` -clawdentity/ -├── apps/ -│ ├── registry/ — Identity registry (Cloudflare Worker) -│ ├── proxy/ — Verification proxy (Cloudflare Worker) -│ ├── cli/ — Command-line tool for operators -│ └── openclaw-skill/ — OpenClaw integration -├── packages/ -│ ├── protocol/ — Identity formats, signing rules -│ ├── sdk/ — TypeScript SDK (sign, verify, cache) -│ └── connector/ — Persistent WebSocket connections -``` - ---- - -## FAQ - -**Q: Does my agent need to be on the internet?** -No. Your agent stays on your machine. The proxy handles all public communication. - -**Q: What if someone steals my agent's identity?** -Run `clawdentity agent revoke`. It's killed everywhere within minutes. Create a new one. - -**Q: Can I see what my agent is saying to other agents?** -Yes. Every message is echoed to your chat (WhatsApp, Telegram, etc.) with clear attribution. - -**Q: What frameworks are supported?** -OpenClaw today. The identity layer is framework-agnostic — any agent framework can integrate. - -**Q: Is this a blockchain thing?** -No. Zero blockchain. Just cryptographic signatures. Fast, cheap, simple. - -**Q: How much does it cost?** -Free for the protocol and tools. Registry and proxy run on Cloudflare's free tier. - ---- - -## Deep dive - -For the full technical specification — identity provisioning, challenge-response registration, per-message signing protocol, proxy verification pipeline, CRL revocation mechanics, and security architecture — see [ARCHITECTURE.md](./ARCHITECTURE.md). - ---- - -## License - -TBD. diff --git a/README.md b/README.md index ba34f12..c61e507 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ Clawdentity is a **cross-platform protocol** that gives every AI agent: ``` Agent A (OpenClaw) Agent B (NanoBot) │ │ - │ clawdentity send --to did:cdi:registry.clawdentity.com:agent:01HF7YAT00W6W7CM7N3W5FDXT4 │ - │ + Ed25519 signature │ + │ relay transform -> connector POST /v1/outbound │ + │ + Ed25519 proof headers │ ▼ │ Connector (:19400) Connector (:19400) │ ▲ @@ -83,14 +83,17 @@ clawdentity register # Create an agent clawdentity agent create my-agent --framework openclaw -# Auto-detect platform and configure webhook + connector +# Install provider artifacts (auto-detect by default) clawdentity install # Or specify explicitly -clawdentity install --for picoclaw --port 18794 +clawdentity install --platform openclaw + +# Configure runtime + hooks for your agent +clawdentity provider setup --for openclaw --agent-name my-agent # Verify everything works -clawdentity doctor +clawdentity provider doctor --for openclaw ```
@@ -98,7 +101,7 @@ clawdentity doctor ```bash # Rust developers -cargo install clawdentity-cli +cargo install --locked clawdentity-cli # macOS brew install clawdentity @@ -122,17 +125,17 @@ npm install -g @clawdentity/cli The connector starts as a system service (launchd on macOS, systemd on Linux) and auto-restarts on boot. -## Messaging +## Cross-Agent Communication ```bash -# Send a message to a paired agent -clawdentity send --to did:cdi:registry.clawdentity.com:agent:01HF7YAT00W6W7CM7N3W5FDXT4 --msg "Hello from my agent" +# Start local connector runtime (optional if service mode is enabled) +clawdentity connector start my-agent -# Listen for incoming messages (persistent connection) -clawdentity listen +# Probe relay delivery to a paired peer alias +clawdentity provider relay-test --for --peer alice ``` -Messages flow through the relay proxy, which verifies identity and trust before delivery. No public endpoints needed — both agents connect outbound via WebSocket. +Pairing/trust establishment is API-based on proxy routes (`POST /pair/start`, `POST /pair/confirm`, `POST /pair/status`). See the docs at [clawdentity.com](https://clawdentity.com/docs). ## Identity & Trust @@ -140,16 +143,18 @@ Messages flow through the relay proxy, which verifies identity and trust before # Show your agent's identity clawdentity whoami -# Pair with another agent (generates QR code) -clawdentity pair --with did:cdi:registry.clawdentity.com:agent:01HF7YAT00W6W7CM7N3W5FDXT4 +# Inspect local agent identity state +clawdentity agent inspect my-agent -# Verify an agent's identity -clawdentity verify did:cdi:registry.clawdentity.com:agent:01HF7YAT00W6W7CM7N3W5FDXT4 +# Refresh scoped auth for one local agent +clawdentity agent auth refresh my-agent -# Revoke a compromised agent (doesn't affect others) -clawdentity agent revoke compromised-agent +# Revoke scoped auth for one local agent +clawdentity agent auth revoke my-agent ``` +Global identity revocation is performed via the registry API (`DELETE /v1/agents/:id`), not via a dedicated CLI `agent revoke` command. + ### DID Format ``` diff --git a/apps/cli/AGENTS.md b/apps/cli/AGENTS.md index 6c248ca..8427c89 100644 --- a/apps/cli/AGENTS.md +++ b/apps/cli/AGENTS.md @@ -2,6 +2,7 @@ ## Purpose - Define conventions for the `clawdentity` CLI package. +- This package is compatibility surface; canonical operator UX is the Rust CLI in `crates/clawdentity-cli`. - Keep command behavior predictable, testable, and safe for local credential storage. ## Command Architecture diff --git a/apps/cli/README.md b/apps/cli/README.md deleted file mode 100644 index 4403962..0000000 --- a/apps/cli/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# clawdentity - -CLI for Clawdentity — cryptographic identity layer for AI agent-to-agent trust. - -[![npm version](https://img.shields.io/npm/v/clawdentity.svg)](https://www.npmjs.com/package/clawdentity) -[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vrknetha/clawdentity/blob/main/LICENSE) -![Node 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg) - -## Install - -```bash -npm install -g clawdentity -``` - -## Quick Start - -Have an invite code (`clw_inv_...`) ready, then prompt your OpenClaw agent: - -> Set up Clawdentity relay - -The agent runs the full onboarding sequence — install, identity creation, relay configuration, and readiness checks. - -
-Manual CLI setup - -```bash -# Initialize config -clawdentity config init - -# Redeem an invite (sets API key) -clawdentity invite redeem --display-name "Your Name" - -# Create an agent identity -clawdentity agent create --framework openclaw - -# Configure the relay -clawdentity openclaw setup - -# Install the skill artifact -clawdentity skill install - -# Verify everything works -clawdentity openclaw doctor -``` - -
- -## Commands - -| Command | Description | -|---------|-------------| -| `config init` | Initialize local config | -| `config set ` | Set a config value | -| `config get ` | Get a config value | -| `config show` | Show all resolved config | -| `invite redeem ` | Redeem invite, store API key | -| `invite create` | Create invite (admin) | -| `agent create ` | Generate + register agent identity | -| `agent inspect ` | Show agent AIT metadata | -| `agent auth refresh ` | Refresh registry auth credentials | -| `agent revoke ` | Revoke agent identity | -| `api-key create` | Create personal API key | -| `api-key list` | List personal API keys | -| `api-key revoke ` | Revoke API key | -| `openclaw setup ` | Configure OpenClaw relay | -| `openclaw doctor` | Validate relay health | -| `openclaw relay test` | Test peer relay delivery | -| `pair start ` | Initiate QR pairing | -| `pair confirm ` | Confirm peer pairing | -| `pair status ` | Poll pairing status | -| `pair recover ` | Recover pending pairing without re-entering ticket | -| `skill install` | Install skill artifacts | -| `connector start ` | Start connector runtime | -| `connector service install ` | Auto-start service at login | -| `connector service uninstall ` | Remove auto-start service | -| `verify ` | Verify AIT against registry | -| `admin bootstrap` | Bootstrap first admin | - -## Configuration - -Config files are stored in `~/.clawdentity/`. - -| Key | Environment Variable | Description | -|-----|---------------------|-------------| -| `registryUrl` | `CLAWDENTITY_REGISTRY_URL` | Identity registry URL | -| `proxyUrl` | `CLAWDENTITY_PROXY_URL` | Verification proxy URL | -| `apiKey` | `CLAWDENTITY_API_KEY` | API key (set by `invite redeem`) | -| `humanName` | `CLAWDENTITY_HUMAN_NAME` | Display name for invites | - -Environment variables override values in the config file. - -## Pairing Recovery - -When using `pair start --wait` or `pair status --wait`, the CLI stores pending -pairing tickets per agent under the local state directory. If wait times out or -is cancelled, recover later with: - -```bash -clawdentity pair recover -``` - -## Requirements - -- Node >= 22 - -## License - -[MIT](https://github.com/vrknetha/clawdentity/blob/main/LICENSE) diff --git a/apps/cli/src/commands/AGENTS.md b/apps/cli/src/commands/AGENTS.md index d1c96b5..48a8584 100644 --- a/apps/cli/src/commands/AGENTS.md +++ b/apps/cli/src/commands/AGENTS.md @@ -33,7 +33,7 @@ - `openclaw setup --openclaw-base-url` should only be needed when OpenClaw is not reachable on the default `http://127.0.0.1:18789`. - `openclaw setup` must set `hooks.allowRequestSessionKey=false` by default and retain `hooks.allowedSessionKeyPrefixes` enforcement for safer `/hooks/agent` session routing. - `openclaw setup` must treat `hooks.defaultSessionKey` as an OpenClaw request session key (`main`, `global`, `subagent:*`), not a canonical `agent::...` store key. -- `openclaw setup` must normalize legacy canonical defaults (`agent::`) to request-key format (``) before writing config, so hook runs route to the expected UI session. +- `openclaw setup` must normalize older canonical defaults (`agent::`) to request-key format (``) before writing config, so hook runs route to the expected UI session. - When deriving fallback hook session routing, follow OpenClaw runtime semantics (`session.scope=global` -> `global`; otherwise `session.mainKey` with fallback `main`). - Keep `openclaw.ts` as a thin public facade; place domain implementations under `openclaw/*.ts` to keep files maintainable and testable. - Keep thrown command errors static (no interpolated runtime values); include variable context in error details/log fields. Diagnostic check output (`openclaw doctor`, `openclaw relay test`) may include concrete paths/aliases so operators can remediate quickly. diff --git a/apps/cli/src/config/AGENTS.md b/apps/cli/src/config/AGENTS.md index 071c8ed..d84ab40 100644 --- a/apps/cli/src/config/AGENTS.md +++ b/apps/cli/src/config/AGENTS.md @@ -10,7 +10,7 @@ - `~/.clawdentity/states/dev` - `~/.clawdentity/states/local` - Keep routing metadata in `~/.clawdentity/router.json` (`lastRegistryUrl`, `lastState`) so CLI can reopen the last active state when env override is absent. -- Keep one-time migration from legacy flat `~/.clawdentity/*` into `~/.clawdentity/states/prod/*` non-destructive (copy-only, never overwrite existing state targets). +- Keep one-time migration from older flat `~/.clawdentity/*` into `~/.clawdentity/states/prod/*` non-destructive (copy-only, never overwrite existing state targets). - Keep human profile config in `manager.ts` (`humanName`) with env override support (`CLAWDENTITY_HUMAN_NAME`) and deterministic precedence. - `registry-metadata.ts` should be the only module that fetches registry metadata for config bootstrap flows. - Avoid hidden host coupling in config tests; do not depend on shell-exported `CLAWDENTITY_*` values. diff --git a/apps/landing/AGENTS.md b/apps/landing/AGENTS.md new file mode 100644 index 0000000..183e32c --- /dev/null +++ b/apps/landing/AGENTS.md @@ -0,0 +1,80 @@ +# AGENTS.md + +## Scope +- These rules apply to `apps/landing`. + +## Installer Ownership +- `public/install.sh` and `public/install.ps1` are owned by the landing app. +- Keep installer behavior and docs in sync whenever release artifact naming, supported platforms, or env controls change. +- Installers must continue to install: + - Unix binary: `clawdentity` + - Windows binary: `clawdentity.exe` + +## Installer Contract (Do Not Drift) +- `CLAWDENTITY_VERSION` is optional and defaults to the latest `rust/v*` GitHub release tag. +- `CLAWDENTITY_INSTALL_DIR` is optional and overrides the destination directory. +- `CLAWDENTITY_INSTALL_DRY_RUN=1` performs a no-write simulation. +- `CLAWDENTITY_NO_VERIFY=1` is the only allowed checksum bypass. +- Checksum verification is enabled by default and must validate against `clawdentity--checksums.txt`. +- Platform coverage must remain: + - `install.sh`: Linux + macOS (`x86_64`, `aarch64`) + - `install.ps1`: Windows (`x86_64`, `aarch64`) + +## Generated Artifact Policy +- `public/skill.md` is generated code. +- Generate it only via `scripts/build-skill-md.mjs` (or `pnpm run build:skill-md`). +- Do not manually edit `public/skill.md`. + +## Source of Truth +- The canonical source files are: + - `apps/openclaw-skill/skill/SKILL.md` + - `apps/openclaw-skill/skill/references/clawdentity-protocol.md` + - `apps/openclaw-skill/skill/references/clawdentity-registry.md` + - `apps/openclaw-skill/skill/references/clawdentity-environment.md` +- The generator must keep `SKILL.md` first, then append the three references in that fixed order. + +## Script and Build Expectations +- Keep `build:skill-md` as the single helper for generation. +- `dev`, `build`, `preview`, and `check` must run `build:skill-md` first. +- If source skill files or generator logic changes, regenerate `public/skill.md` before shipping. +- Nx landing targets must invoke package scripts (`pnpm run build|dev|preview|check`) instead of calling `astro` directly, so pre-steps always run. +- Include the skill source files (`apps/openclaw-skill/skill/**`) and `scripts/build-skill-md.mjs` in Nx target inputs so cache invalidation remains correct. + +## D2 Integration +- Do not make `astro-d2` a hard runtime requirement for every build environment. +- Gate D2 integration by environment/binary availability in `astro.config.mjs`. +- Keep non-D2 CI/build environments green by skipping D2 integration when the binary is unavailable. + +## Navigation UX Guardrails +- Mobile menu logic must centralize open/close state updates in one helper. +- Body scroll locking must be cleared when leaving mobile viewport widths (for example, resizing to desktop while menu is open). +- Avoid duplicated DOM state mutations for `aria-expanded`, `nav--open`, and body overflow handling. + +## Asset Hygiene +- Keep only assets that are referenced by landing source code or Astro config. +- Remove unused duplicates (for example mono/source variants) when they are not imported or referenced. +- Before adding a new asset variant, confirm a real consumer exists in `src/components`, `src/pages`, or config. +- Prefer one canonical format per usage context to avoid parallel unused file sets. + +## Stylesheet Organization +- Keep `src/styles/landing.css` focused on core layout/components and below file-size guard limits. +- Place motion/hero/effect sections in `src/styles/landing-motion.css` to keep maintenance and linting stable. +- Preserve stylesheet cascade order by importing `landing-motion.css` after `landing.css` in `src/pages/index.astro`. + +## Cloudflare Pages Deploy Expectations +- `develop` branch deploys to Cloudflare Pages preview/staging. +- `main` branch deploys to Cloudflare Pages production. +- Deploy jobs must use package scripts that run `build:skill-md` first so `public/skill.md` is always current. +- Deploy output must include `public/install.sh` and `public/install.ps1` at site root (`/install.sh`, `/install.ps1`). +- Landing docs must keep OS-specific installer commands canonical: + - Unix: `curl -fsSL https://clawdentity.com/install.sh | sh` + - Windows: `irm https://clawdentity.com/install.ps1 | iex` +- User-facing onboarding docs must be prompt-first. +- Canonical onboarding prompt source is `/skill.md` (generated from `apps/openclaw-skill/skill/SKILL.md`). +- CLI command-by-command onboarding belongs in advanced/manual fallback sections only. +- Cargo install and direct GitHub release asset flows are fallback-only in docs. +- Rust toolchain requirements must only appear in advanced/developer fallback sections. +- Do not reintroduce deprecated CLI command groups in landing docs; keep examples on current Rust `install` and `provider` commands. +- Keep CLI docs aligned with the current Rust binary command surface (`init`, `whoami`, `register`, `agent`, `config`, `api-key`, `invite`, `admin`, `connector`, `provider`, `install`). +- Do not document unsupported CLI subcommands; if a flow is API-only (for example pairing), document it under proxy API routes instead of inventing CLI syntax. +- DID examples in landing docs must use `did:cdi:::`; never use `did:claw:*` format. diff --git a/apps/landing/astro.config.mjs b/apps/landing/astro.config.mjs new file mode 100644 index 0000000..c2c496a --- /dev/null +++ b/apps/landing/astro.config.mjs @@ -0,0 +1,96 @@ +import { spawnSync } from "node:child_process"; +import starlight from "@astrojs/starlight"; +import { defineConfig } from "astro/config"; +import d2 from "astro-d2"; +import starlightLinksValidator from "starlight-links-validator"; + +function isD2Enabled() { + const override = + process.env.CLAWDENTITY_LANDING_ENABLE_D2?.trim().toLowerCase(); + + if (override === "1" || override === "true") { + return true; + } + + if (override === "0" || override === "false") { + return false; + } + + const probe = spawnSync("d2", ["--version"], { stdio: "ignore" }); + return probe.status === 0; +} + +const d2Enabled = isD2Enabled(); +if (!d2Enabled) { + console.warn( + "[landing] D2 binary not found; skipping astro-d2 integration. Set CLAWDENTITY_LANDING_ENABLE_D2=true to force-enable.", + ); +} + +export default defineConfig({ + output: "static", + outDir: "./dist", + site: "https://clawdentity.com", + integrations: [ + ...(d2Enabled ? [d2()] : []), + starlight({ + title: "Clawdentity", + description: "Verified identity and revocation for AI agents", + favicon: "/favicon.svg", + logo: { + src: "./src/assets/landing/clawdentity_icon_only.svg", + alt: "Clawdentity", + }, + head: [ + { + tag: "link", + attrs: { + rel: "preconnect", + href: "https://fonts.googleapis.com", + }, + }, + { + tag: "link", + attrs: { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossorigin: true, + }, + }, + { + tag: "link", + attrs: { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700;800&display=swap", + }, + }, + ], + customCss: ["./src/styles/starlight-custom.css"], + social: [ + { + icon: "github", + label: "GitHub", + href: "https://github.com/vrknetha/clawdentity", + }, + ], + sidebar: [ + { + label: "Getting Started", + items: [ + { label: "Introduction", slug: "getting-started/introduction" }, + { label: "Quick Start", slug: "getting-started/quickstart" }, + { label: "Installation", slug: "getting-started/installation" }, + ], + }, + { label: "Concepts", autogenerate: { directory: "concepts" } }, + { label: "Guides", autogenerate: { directory: "guides" } }, + { + label: "API Reference", + autogenerate: { directory: "api-reference" }, + }, + { label: "Architecture", autogenerate: { directory: "architecture" } }, + ], + plugins: [starlightLinksValidator()], + }), + ], +}); diff --git a/apps/landing/package.json b/apps/landing/package.json new file mode 100644 index 0000000..f265c39 --- /dev/null +++ b/apps/landing/package.json @@ -0,0 +1,25 @@ +{ + "name": "@clawdentity/landing", + "private": true, + "type": "module", + "version": "0.0.0", + "scripts": { + "build:skill-md": "node ./scripts/build-skill-md.mjs", + "dev": "pnpm run build:skill-md && astro dev", + "build": "pnpm run build:skill-md && astro build", + "preview": "pnpm run build:skill-md && astro preview", + "check": "pnpm run build:skill-md && astro check" + }, + "dependencies": { + "@astrojs/cloudflare": "^12.5.0", + "@astrojs/starlight": "^0.34.0", + "astro": "^5.7.0", + "astro-d2": "^0.9.0", + "sharp": "^0.33.5", + "starlight-links-validator": "^0.14.0" + }, + "devDependencies": { + "@astrojs/check": "^0.9.0", + "typescript": "5.8.3" + } +} diff --git a/apps/landing/project.json b/apps/landing/project.json new file mode 100644 index 0000000..fbb8b4e --- /dev/null +++ b/apps/landing/project.json @@ -0,0 +1,59 @@ +{ + "name": "landing", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "sourceRoot": "apps/landing/src", + "targets": { + "build": { + "executor": "nx:run-commands", + "options": { + "command": "pnpm run build", + "cwd": "apps/landing" + }, + "cache": true, + "inputs": [ + "production", + "^production", + "{projectRoot}/scripts/build-skill-md.mjs", + "{workspaceRoot}/apps/openclaw-skill/skill/SKILL.md", + "{workspaceRoot}/apps/openclaw-skill/skill/references/clawdentity-protocol.md", + "{workspaceRoot}/apps/openclaw-skill/skill/references/clawdentity-registry.md", + "{workspaceRoot}/apps/openclaw-skill/skill/references/clawdentity-environment.md" + ], + "outputs": ["{projectRoot}/dist"] + }, + "dev": { + "executor": "nx:run-commands", + "options": { + "command": "pnpm run dev", + "cwd": "apps/landing" + } + }, + "preview": { + "executor": "nx:run-commands", + "dependsOn": ["build"], + "options": { + "command": "pnpm run preview", + "cwd": "apps/landing" + } + }, + "check": { + "executor": "nx:run-commands", + "options": { + "command": "pnpm run check", + "cwd": "apps/landing" + }, + "cache": true, + "inputs": [ + "default", + "^default", + "{projectRoot}/scripts/build-skill-md.mjs", + "{workspaceRoot}/apps/openclaw-skill/skill/SKILL.md", + "{workspaceRoot}/apps/openclaw-skill/skill/references/clawdentity-protocol.md", + "{workspaceRoot}/apps/openclaw-skill/skill/references/clawdentity-registry.md", + "{workspaceRoot}/apps/openclaw-skill/skill/references/clawdentity-environment.md" + ] + } + }, + "tags": [] +} diff --git a/apps/landing/public/d2/docs/architecture/overview-0.svg b/apps/landing/public/d2/docs/architecture/overview-0.svg new file mode 100644 index 0000000..17ec295 --- /dev/null +++ b/apps/landing/public/d2/docs/architecture/overview-0.svg @@ -0,0 +1,187 @@ +Agent MachineClawdentity Proxy (Worker)AIT · PoP · CRL verifyTrust policy · Rate limitingAgentRelaySession (DO)Per-agent WebSocket relayMessage queue · HeartbeatProxyTrustState (DO)Bidirectional trust pairsPairing ticketsRegistry (Worker)Identity issuance · CRLAuth management · KeysOpenClaw(private, loopback only)ConnectorHTTP server · WebSocket clientInbox · Delivery receipts POST /v1/outbound POST /hooks/agent route byrecipient DIDcheck trust pair keys + CRL +auth validateHTTP POST(signed outbound)WebSocketdeliver frame + + + + + + + + + diff --git a/apps/landing/public/d2/docs/concepts/revocation-0.svg b/apps/landing/public/d2/docs/concepts/revocation-0.svg new file mode 100644 index 0000000..9a61841 --- /dev/null +++ b/apps/landing/public/d2/docs/concepts/revocation-0.svg @@ -0,0 +1,183 @@ +Agent Owner / AdminRegistryProxyAdd jti to CRLRe-sign CRLReject requestsfrom revoked AIT DELETE /v1/agents/:id CRL refresh cycleUpdated CRL + + + + + diff --git a/apps/landing/public/d2/docs/getting-started/introduction-0.svg b/apps/landing/public/d2/docs/getting-started/introduction-0.svg new file mode 100644 index 0000000..29ac76d --- /dev/null +++ b/apps/landing/public/d2/docs/getting-started/introduction-0.svg @@ -0,0 +1,929 @@ +Caller Agent

Clawdentity Proxy +Verifies identity · Checks revocation +Enforces trust policy

+

Connector +Relays to local OpenClaw

+

OpenClaw +Private, never exposed

+
Authorization: Claw <AIT>X-Claw-Proof / Nonce / TimestampWebSocket deliver framex-openclaw-token: <hooks.token>(internal only) + + + + +
diff --git a/apps/landing/public/d2/docs/guides/agent-to-agent-0.svg b/apps/landing/public/d2/docs/guides/agent-to-agent-0.svg new file mode 100644 index 0000000..aa2e387 --- /dev/null +++ b/apps/landing/public/d2/docs/guides/agent-to-agent-0.svg @@ -0,0 +1,942 @@ +

Clawdentity Registry +Issues identities (AIT) · Publishes CRL +Validates agent auth · Invite-gated onboarding

+
Bob's MachineClawdentity Proxy (Cloudflare Worker)Alice's MachineBob's OpenClaw

Connector +Signs + relays outbound

+

Bob's Relay Session +WebSocket · Message queue

+

Alice's Relay Session +WebSocket · Message queue

+

Global Trust Store +Trust pairs · Pairing tickets

+

Connector +WebSocket + delivers inbound

+

OpenClaw +Private, never exposed

+
POST /v1/outbound check trustcheck trustPOST /hooks/agentissues AIT + authissues AIT + auth HTTP POST (signed request) WebSocket deliver frame + + + + + + + + + +
diff --git a/apps/landing/public/d2/docs/guides/connector-0.svg b/apps/landing/public/d2/docs/guides/connector-0.svg new file mode 100644 index 0000000..1b3b7ab --- /dev/null +++ b/apps/landing/public/d2/docs/guides/connector-0.svg @@ -0,0 +1,936 @@ +Agent Machine

Clawdentity Proxy +Cloudflare Worker +Per-agent Durable Objects +WebSocket relay · PoP verify

+

OpenClaw +/hooks/agent

+
Connector

Outbound +HTTP server :19400

+

Inbound Inbox +Durable · file-based +Replay loop (2s) +Dead-letter queue

+
POST /v1/outbound(relay request) Replay delivers(with retry)WebSocket(persistent)HTTP POST(signed outbound) WebSocketdeliver frame + + + + + + +
diff --git a/apps/landing/public/d2/docs/guides/discovery-0.svg b/apps/landing/public/d2/docs/guides/discovery-0.svg new file mode 100644 index 0000000..fa86408 --- /dev/null +++ b/apps/landing/public/d2/docs/guides/discovery-0.svg @@ -0,0 +1,937 @@ +

Alice's Machine +POST /pair/start

+

Clawdentity Proxy +Cloudflare Worker + ProxyTrustState

+

Bob's Machine +POST /pair/confirm

+
1. POST /pair/start2. clwpair1_ ticket + QR 3. Share QR out-of-band 4. POST /pair/confirm 5. Create bidirectional trust pairin ProxyTrustState + + + + + + +
diff --git a/apps/landing/public/d2/docs/guides/proxy-setup-0.svg b/apps/landing/public/d2/docs/guides/proxy-setup-0.svg new file mode 100644 index 0000000..414ea2f --- /dev/null +++ b/apps/landing/public/d2/docs/guides/proxy-setup-0.svg @@ -0,0 +1,938 @@ +

Caller's Connector +Signs HTTP request +with Ed25519 PoP headers

+
Clawdentity Proxy

Recipient's Connector +Persists to inbox +Delivers locally with retry

+

OpenClaw Gateway +/hooks/agent

+
    +
  1. Verify AIT signature
  2. +
  3. Check timestamp + nonce replay
  4. +
  5. Verify PoP signature
  6. +
  7. Check CRL revocation
  8. +
  9. Enforce trust policy
  10. +
  11. Apply rate limits
  12. +
+
Authorization: Claw <AIT>X-Claw-Proof / Nonce / Timestamp WebSocket deliver framePOST /hooks/agent(with retry) + + + + +
diff --git a/apps/landing/public/favicon.svg b/apps/landing/public/favicon.svg new file mode 100644 index 0000000..7be56df --- /dev/null +++ b/apps/landing/public/favicon.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/landing/public/install.ps1 b/apps/landing/public/install.ps1 new file mode 100644 index 0000000..7067a81 --- /dev/null +++ b/apps/landing/public/install.ps1 @@ -0,0 +1,226 @@ +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" +Set-StrictMode -Version Latest + +$Repo = "vrknetha/clawdentity" +$BinaryName = "clawdentity.exe" + +$DryRun = $env:CLAWDENTITY_INSTALL_DRY_RUN -eq "1" +$NoVerify = $env:CLAWDENTITY_NO_VERIFY -eq "1" +$VersionInput = $env:CLAWDENTITY_VERSION +$InstallDir = $env:CLAWDENTITY_INSTALL_DIR + +$script:Tag = "" +$script:Version = "" + +function Write-Info { + param([string]$Message) + Write-Host "clawdentity installer: $Message" +} + +function Write-WarnLine { + param([string]$Message) + Write-Warning "clawdentity installer: $Message" +} + +function Fail { + param([string]$Message) + throw "clawdentity installer: $Message" +} + +function Resolve-LatestRustTag { + $headers = @{ Accept = "application/vnd.github+json" } + $latestUri = "https://api.github.com/repos/$Repo/releases/latest" + $releasesUri = "https://api.github.com/repos/$Repo/releases?per_page=100" + + try { + $latest = Invoke-RestMethod -Uri $latestUri -Headers $headers + if ($null -ne $latest -and $latest.tag_name -match "^rust/v\d+\.\d+\.\d+$") { + return $latest.tag_name + } + } + catch { + Write-WarnLine "failed to query releases/latest; falling back to releases list" + } + + $releases = Invoke-RestMethod -Uri $releasesUri -Headers $headers + $tag = $releases | + Where-Object { -not $_.draft -and -not $_.prerelease -and $_.tag_name -match "^rust/v\d+\.\d+\.\d+$" } | + Select-Object -ExpandProperty tag_name -First 1 + + if ([string]::IsNullOrWhiteSpace($tag)) { + Fail "could not resolve latest rust/v* release tag" + } + + return $tag +} + +function Set-VersionInfo { + param([string]$InputVersion) + + if ($InputVersion.StartsWith("rust/v")) { + $script:Tag = $InputVersion + $script:Version = $InputVersion.Substring(6) + return + } + + if ($InputVersion.StartsWith("v")) { + $script:Version = $InputVersion.Substring(1) + $script:Tag = "rust/v$($script:Version)" + return + } + + $script:Version = $InputVersion + $script:Tag = "rust/v$($script:Version)" +} + +function Invoke-Download { + param( + [string]$Url, + [string]$OutFile + ) + + if ($DryRun) { + Write-Info "[dry-run] download $Url -> $OutFile" + return + } + + Invoke-WebRequest -Uri $Url -OutFile $OutFile +} + +function Get-ChecksumForAsset { + param( + [string]$ChecksumsPath, + [string]$AssetName + ) + + $line = Get-Content -Path $ChecksumsPath | + Where-Object { + $parts = $_ -split "\s+", 2 + if ($parts.Count -lt 2) { + return $false + } + + $fileName = $parts[1].Trim().TrimStart("*") + return $fileName -eq $AssetName + } | + Select-Object -First 1 + + if ([string]::IsNullOrWhiteSpace($line)) { + return $null + } + + return ($line -split "\s+", 2)[0].Trim().ToLowerInvariant() +} + +$isWindowsPlatform = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::Windows +) +if (-not $isWindowsPlatform) { + Fail "install.ps1 supports Windows only" +} + +$platform = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) { + "X64" { "windows-x86_64" } + "Arm64" { "windows-aarch64" } + default { Fail "unsupported architecture: $($_.ToString()) (supported: X64, Arm64)" } +} + +if ([string]::IsNullOrWhiteSpace($InstallDir)) { + $InstallDir = Join-Path $HOME "bin" +} + +if (-not [string]::IsNullOrWhiteSpace($VersionInput)) { + Set-VersionInfo -InputVersion $VersionInput +} +else { + if ($DryRun) { + Write-Info "resolving latest rust/v* release tag from GitHub" + } + Set-VersionInfo -InputVersion (Resolve-LatestRustTag) +} + +$assetName = "clawdentity-$Version-$platform.zip" +$checksumName = "clawdentity-$Version-checksums.txt" +$baseUrl = "https://github.com/$Repo/releases/download/$Tag" +$assetUrl = "$baseUrl/$assetName" +$checksumUrl = "$baseUrl/$checksumName" +$targetPath = Join-Path $InstallDir $BinaryName + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("clawdentity-" + [Guid]::NewGuid().ToString("N")) +$assetPath = Join-Path $tempDir $assetName +$checksumPath = Join-Path $tempDir $checksumName +$extractDir = Join-Path $tempDir "extract" + +Write-Info "tag: $Tag" +Write-Info "platform: $platform" +Write-Info "install dir: $InstallDir" +Write-Info "download: $assetUrl" + +try { + if (-not $DryRun) { + New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + } + + Invoke-Download -Url $assetUrl -OutFile $assetPath + + if ($NoVerify) { + Write-WarnLine "checksum verification disabled (CLAWDENTITY_NO_VERIFY=1)" + } + else { + Invoke-Download -Url $checksumUrl -OutFile $checksumPath + + if ($DryRun) { + Write-Info "[dry-run] would verify SHA256 for $assetName" + } + else { + $expected = Get-ChecksumForAsset -ChecksumsPath $checksumPath -AssetName $assetName + if ([string]::IsNullOrWhiteSpace($expected)) { + Fail "could not find checksum for $assetName in $checksumName" + } + + $actual = (Get-FileHash -Path $assetPath -Algorithm SHA256).Hash.ToLowerInvariant() + if ($actual -ne $expected) { + Fail "checksum mismatch for $assetName" + } + Write-Info "checksum verified" + } + } + + if ($DryRun) { + Write-Info "[dry-run] expand archive '$assetPath' -> '$extractDir'" + Write-Info "[dry-run] install binary to '$targetPath'" + Write-Info "[dry-run] next step: use the onboarding prompt in https://clawdentity.com/skill.md" + Write-Info "[dry-run] complete" + exit 0 + } + + New-Item -ItemType Directory -Path $extractDir -Force | Out-Null + Expand-Archive -Path $assetPath -DestinationPath $extractDir -Force + + $binary = Get-ChildItem -Path $extractDir -Filter $BinaryName -File -Recurse | Select-Object -First 1 + if ($null -eq $binary) { + Fail "could not find $BinaryName inside $assetName" + } + + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + Copy-Item -Path $binary.FullName -Destination $targetPath -Force + + Write-Info "installed $BinaryName to $targetPath" + Write-Info "next step: use the onboarding prompt in https://clawdentity.com/skill.md" + + $pathEntries = $env:Path -split ";" + $normalizedInstall = $InstallDir.TrimEnd("\") + $hasPathEntry = $pathEntries | + Where-Object { $_.TrimEnd("\") -ieq $normalizedInstall } | + Select-Object -First 1 + + if ($null -eq $hasPathEntry) { + Write-WarnLine "$InstallDir is not on PATH; add it to your user PATH to run clawdentity globally" + } +} +finally { + if (-not $DryRun -and (Test-Path -Path $tempDir)) { + Remove-Item -Path $tempDir -Recurse -Force + } +} diff --git a/apps/landing/public/install.sh b/apps/landing/public/install.sh new file mode 100755 index 0000000..180f112 --- /dev/null +++ b/apps/landing/public/install.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env sh +set -eu + +REPO="vrknetha/clawdentity" +BIN_NAME="clawdentity" + +DRY_RUN="${CLAWDENTITY_INSTALL_DRY_RUN:-0}" +NO_VERIFY="${CLAWDENTITY_NO_VERIFY:-0}" +VERSION_INPUT="${CLAWDENTITY_VERSION:-}" +INSTALL_DIR="${CLAWDENTITY_INSTALL_DIR:-}" + +TAG="" +VERSION="" +PLATFORM="" + +info() { + printf '%s\n' "clawdentity installer: $*" +} + +warn() { + printf '%s\n' "clawdentity installer: warning: $*" >&2 +} + +fail() { + printf '%s\n' "clawdentity installer: error: $*" >&2 + exit 1 +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1" +} + +resolve_latest_tag() { + latest_url="https://api.github.com/repos/${REPO}/releases/latest" + releases_url="https://api.github.com/repos/${REPO}/releases?per_page=100" + + latest_tag="$( + curl -fsSL "$latest_url" 2>/dev/null \ + | awk -F'"' '/"tag_name":[[:space:]]*"[^"]+"/ { print $4; exit }' + )" + case "${latest_tag:-}" in + rust/v*) printf '%s\n' "$latest_tag"; return 0 ;; + esac + + fallback_tag="$( + curl -fsSL "$releases_url" \ + | awk -F'"' '/"tag_name":[[:space:]]*"rust\/v[0-9]+\.[0-9]+\.[0-9]+"/ { print $4; exit }' + )" + [ -n "${fallback_tag:-}" ] || return 1 + printf '%s\n' "$fallback_tag" +} + +set_version_from_input() { + input="$1" + case "$input" in + rust/v*) + TAG="$input" + VERSION="${input#rust/v}" + ;; + v*) + VERSION="${input#v}" + TAG="rust/v${VERSION}" + ;; + *) + VERSION="$input" + TAG="rust/v${VERSION}" + ;; + esac +} + +detect_platform() { + os="$(uname -s)" + arch="$(uname -m)" + + case "$os" in + Linux) os_part="linux" ;; + Darwin) os_part="macos" ;; + *) fail "unsupported OS: $os (supported: Linux, Darwin)" ;; + esac + + case "$arch" in + x86_64|amd64) arch_part="x86_64" ;; + arm64|aarch64) arch_part="aarch64" ;; + *) fail "unsupported architecture: $arch (supported: x86_64, arm64/aarch64)" ;; + esac + + PLATFORM="${os_part}-${arch_part}" +} + +download_file() { + url="$1" + output_path="$2" + + if [ "$DRY_RUN" = "1" ]; then + info "[dry-run] curl -fL --retry 3 --retry-delay 1 '$url' -o '$output_path'" + return 0 + fi + + curl -fL --retry 3 --retry-delay 1 --connect-timeout 20 "$url" -o "$output_path" +} + +sha256_file() { + file_path="$1" + + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file_path" | awk '{ print $1 }' + return 0 + fi + + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$file_path" | awk '{ print $1 }' + return 0 + fi + + if command -v openssl >/dev/null 2>&1; then + openssl dgst -sha256 "$file_path" | awk '{ print $NF }' + return 0 + fi + + return 1 +} + +main() { + need_cmd curl + need_cmd uname + need_cmd tar + need_cmd mktemp + need_cmd awk + need_cmd find + + detect_platform + + if [ -z "$INSTALL_DIR" ]; then + if [ -d "/usr/local/bin" ] && [ -w "/usr/local/bin" ]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME:-$PWD}/.local/bin" + fi + fi + + if [ -n "$VERSION_INPUT" ]; then + set_version_from_input "$VERSION_INPUT" + else + [ "$DRY_RUN" = "1" ] && info "resolving latest rust/v* release tag from GitHub" + latest_tag="$(resolve_latest_tag)" || fail "could not resolve latest rust/v* release tag" + set_version_from_input "$latest_tag" + fi + + asset_name="${BIN_NAME}-${VERSION}-${PLATFORM}.tar.gz" + checksum_name="${BIN_NAME}-${VERSION}-checksums.txt" + base_url="https://github.com/${REPO}/releases/download/${TAG}" + asset_url="${base_url}/${asset_name}" + checksum_url="${base_url}/${checksum_name}" + target_path="${INSTALL_DIR}/${BIN_NAME}" + + tmp_dir="$(mktemp -d)" + asset_path="${tmp_dir}/${asset_name}" + checksum_path="${tmp_dir}/${checksum_name}" + extract_dir="${tmp_dir}/extract" + + cleanup() { + [ -d "$tmp_dir" ] && rm -rf "$tmp_dir" + } + trap cleanup EXIT INT TERM + + info "tag: ${TAG}" + info "platform: ${PLATFORM}" + info "install dir: ${INSTALL_DIR}" + info "download: ${asset_url}" + + download_file "$asset_url" "$asset_path" + + if [ "$NO_VERIFY" = "1" ]; then + warn "checksum verification disabled (CLAWDENTITY_NO_VERIFY=1)" + else + download_file "$checksum_url" "$checksum_path" + if [ "$DRY_RUN" = "1" ]; then + info "[dry-run] would verify SHA256 for ${asset_name}" + else + expected_sha="$( + awk -v asset="$asset_name" ' + { + file = $2 + sub(/^\*/, "", file) + if (file == asset) { + print $1 + exit + } + } + ' "$checksum_path" + )" + [ -n "${expected_sha:-}" ] || fail "could not find checksum for ${asset_name} in ${checksum_name}" + + actual_sha="$(sha256_file "$asset_path")" || fail "no SHA256 tool found (sha256sum/shasum/openssl)" + [ "$actual_sha" = "$expected_sha" ] || fail "checksum mismatch for ${asset_name}" + info "checksum verified" + fi + fi + + if [ "$DRY_RUN" = "1" ]; then + info "[dry-run] mkdir -p '${INSTALL_DIR}'" + info "[dry-run] tar -xzf '${asset_path}' -C '${extract_dir}'" + info "[dry-run] install binary to '${target_path}'" + info "[dry-run] next step: use the onboarding prompt in https://clawdentity.com/skill.md" + info "[dry-run] complete" + exit 0 + fi + + mkdir -p "$extract_dir" + tar -xzf "$asset_path" -C "$extract_dir" + + binary_path="$(find "$extract_dir" -type f -name "$BIN_NAME" -print | head -n 1)" + [ -n "${binary_path:-}" ] || fail "could not find ${BIN_NAME} inside ${asset_name}" + + mkdir -p "$INSTALL_DIR" + [ -w "$INSTALL_DIR" ] || fail "install dir is not writable: ${INSTALL_DIR} (set CLAWDENTITY_INSTALL_DIR)" + + if command -v install >/dev/null 2>&1; then + install -m 0755 "$binary_path" "$target_path" + else + cp "$binary_path" "$target_path" + chmod 0755 "$target_path" + fi + + info "installed ${BIN_NAME} to ${target_path}" + info "next step: use the onboarding prompt in https://clawdentity.com/skill.md" + case ":${PATH:-}:" in + *":${INSTALL_DIR}:"*) ;; + *) warn "${INSTALL_DIR} is not on PATH; add it to your shell profile" ;; + esac +} + +main "$@" diff --git a/apps/landing/public/og-image.png b/apps/landing/public/og-image.png new file mode 100644 index 0000000..c7a1b45 Binary files /dev/null and b/apps/landing/public/og-image.png differ diff --git a/apps/landing/public/skill.md b/apps/landing/public/skill.md new file mode 100644 index 0000000..9ffae17 --- /dev/null +++ b/apps/landing/public/skill.md @@ -0,0 +1,940 @@ +--- +name: clawdentity_openclaw_relay +description: This skill should be used when the user asks to "set up Clawdentity relay", "configure provider relay", "run provider doctor", "run provider relay test", "bootstrap registry", "redeem invite", "create agent credentials", "install connector service", or needs multi-provider relay onboarding with the `clawdentity` CLI. +version: 0.5.0 +--- + +# Clawdentity Relay Skill (Multi-Provider CLI) + +This skill uses the current Rust CLI command surface and works across providers: +- OpenClaw (`openclaw`) +- PicoClaw (`picoclaw`) +- NanoBot (`nanobot`) +- NanoClaw (`nanoclaw`) + +Use this skill for onboarding, provider setup, diagnostics, relay test, and connector runtime operations. + +## Canonical URL (Single Source) + +Use this single URL as the source of truth: +- `https://clawdentity.com/skill.md` + +Fallback mirror (not canonical): +- `https://raw.githubusercontent.com/vrknetha/clawdentity/develop/apps/openclaw-skill/skill/SKILL.md` + +For deterministic automation, pin the fallback mirror to a release tag or commit SHA instead of `develop`. + +## Scope Guard + +This skill is command-accurate for the current `clawdentity` CLI (`0.1.x`). +Use current Rust CLI command groups only. + +## Quick Prompt (Prompt-first) + +Copy/paste this into your provider runtime to run prompt-first onboarding: + +```text +Set up Clawdentity relay using https://clawdentity.com/skill.md as the source of truth. +Run required onboarding end-to-end and execute commands directly. +Auto-detect provider when possible; ask me only for missing required inputs: invite code (clw_inv_...), display name, agent name, and provider only if detection is ambiguous. +``` + +## CLI Install Prerequisite (Fresh Containers) + +This skill requires the `clawdentity` executable on `PATH`. +Rust toolchain is not required for the recommended installer path. + +Use this install order: + +1. Hosted installer scripts (recommended) + +Unix (Linux/macOS): + +```bash +curl -fsSL https://clawdentity.com/install.sh | sh +``` + +Windows (PowerShell): + +```powershell +irm https://clawdentity.com/install.ps1 | iex +``` + +Installer environment controls: + +- `CLAWDENTITY_VERSION` (optional, defaults to latest `rust/v*` release) +- `CLAWDENTITY_INSTALL_DIR` (optional custom install path) +- `CLAWDENTITY_INSTALL_DRY_RUN=1` +- `CLAWDENTITY_NO_VERIFY=1` (skip checksum verification; use only when required) + +2. Prebuilt release binary (advanced fallback) + +- Release URL pattern: + - `https://github.com/vrknetha/clawdentity/releases/download/rust/v/clawdentity--.tar.gz` + - `https://github.com/vrknetha/clawdentity/releases/download/rust/v/clawdentity--.zip` + - `https://github.com/vrknetha/clawdentity/releases/download/rust/v/clawdentity--checksums.txt` +- Linux/macOS archive platforms: + - `linux-x86_64` + - `linux-aarch64` + - `macos-x86_64` + - `macos-aarch64` +- Windows archive platforms: + - `windows-x86_64` + - `windows-aarch64` + +Linux `aarch64` example: + +```bash +version="" +asset="clawdentity-${version}-linux-aarch64.tar.gz" +tag="rust/v${version}" +base="https://github.com/vrknetha/clawdentity/releases/download/${tag}" +checksums="clawdentity-${version}-checksums.txt" + +mkdir -p "$HOME/bin" /tmp/clawdentity-bin +curl -fL "${base}/${asset}" -o "/tmp/${asset}" +curl -fL "${base}/${checksums}" -o "/tmp/${checksums}" +grep " ${asset}\$" "/tmp/${checksums}" | sha256sum -c - +tar -xzf "/tmp/${asset}" -C /tmp/clawdentity-bin +install -m 0755 /tmp/clawdentity-bin/clawdentity "$HOME/bin/clawdentity" +export PATH="$HOME/bin:$PATH" +clawdentity --version +``` + +PowerShell example (Windows download/install via `irm`): + +```powershell +$version = "" +$tag = "rust/v$version" +$platform = "windows-x86_64" # use windows-aarch64 on Arm64 +$asset = "clawdentity-$version-$platform.zip" +$checksums = "clawdentity-$version-checksums.txt" +$base = "https://github.com/vrknetha/clawdentity/releases/download/$tag" + +irm "$base/$asset" -OutFile $asset +irm "$base/$checksums" -OutFile $checksums +$expected = (Get-Content $checksums | Where-Object { $_ -match [regex]::Escape($asset) } | Select-Object -First 1).Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)[0] +$actual = (Get-FileHash $asset -Algorithm SHA256).Hash.ToLower() +if ($actual -ne $expected.ToLower()) { throw "Checksum mismatch for $asset" } +Expand-Archive -Path $asset -DestinationPath ".\\clawdentity-bin" -Force +New-Item -ItemType Directory -Force -Path "$HOME\\bin" | Out-Null +Move-Item ".\\clawdentity-bin\\clawdentity.exe" "$HOME\\bin\\clawdentity.exe" -Force +``` + +3. Build from source with Rust toolchain (advanced fallback) + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +source "$HOME/.cargo/env" +cargo install --locked clawdentity-cli +clawdentity --version +``` + +Optional deterministic pin: + +```bash +cargo install --locked --version clawdentity-cli +``` + +## Tool Execution Contract (Agent) + +- Execute side effects directly with CLI/tools. +- Ask humans only for missing secrets or mandatory inputs. +- Keep output concrete: selected provider, created DID, updated file paths, command result. + +## State Discovery First (Required) + +Before asking for invite/API key/provider values: + +1. Detect provider support and local evidence. +- `clawdentity install --list --json` +- `clawdentity provider status --json` + +2. Resolve provider explicitly when auto-detect is uncertain. +- `clawdentity provider status --for --json` + +3. Check existing CLI state before onboarding prompts. +- `clawdentity config show` +- `clawdentity config get apiKey` + +If `apiKey` is already configured and provider doctor is healthy, do not re-run onboarding. + +## Inputs + +Required for onboarding: +- Provider selection (`openclaw`, `picoclaw`, `nanobot`, or `nanoclaw`) when auto-detect is ambiguous. +- Invite code (`clw_inv_...`) for standard onboarding. +- Human display name. +- Agent name. + +Optional: +- Registry URL override. +- Webhook host/port/token overrides. +- Provider base URL override. +- Connector base URL/connector URL overrides. + +## Command Utilization (Current CLI) + +### Config +- `clawdentity config init` +- `clawdentity config init --registry-url ` +- `clawdentity config set registryUrl ` +- `clawdentity config set apiKey ` (recovery path only) +- `clawdentity config get ` +- `clawdentity config show` + +### Onboarding +- `clawdentity invite redeem --display-name ` +- `clawdentity invite redeem --display-name --registry-url ` +- `clawdentity admin bootstrap --bootstrap-secret ` +- `clawdentity admin bootstrap --bootstrap-secret --display-name --api-key-name --registry-url ` + +### Agent and Auth +- `clawdentity agent create ` +- `clawdentity agent create --framework ` +- `clawdentity agent create --framework <...> --ttl-days ` +- `clawdentity agent inspect ` +- `clawdentity agent auth refresh ` +- `clawdentity agent auth revoke ` + +### API Keys +- `clawdentity api-key create` +- `clawdentity api-key create --name ` +- `clawdentity api-key list` +- `clawdentity api-key revoke ` + +### Provider Install and Setup +- `clawdentity install --list` +- `clawdentity install --list --json` +- `clawdentity install --for ` +- `clawdentity install --platform ` +- `clawdentity install --for --port --token ` +- `clawdentity provider status` +- `clawdentity provider status --for ` +- `clawdentity provider setup --for ` +- `clawdentity provider setup --for --agent-name ` +- `clawdentity provider setup --for --platform-base-url ` +- `clawdentity provider setup --for --webhook-host --webhook-port --webhook-token ` +- `clawdentity provider setup --for --connector-base-url --connector-url ` +- `clawdentity provider setup --for --relay-transform-peers-path ` + +### Provider Diagnostics +- `clawdentity provider doctor` +- `clawdentity provider doctor --for ` +- `clawdentity provider doctor --for --peer ` +- `clawdentity provider doctor --for --platform-state-dir ` +- `clawdentity provider doctor --for --connector-base-url ` +- `clawdentity provider doctor --for --skip-connector-runtime` +- `clawdentity provider relay-test` +- `clawdentity provider relay-test --for ` +- `clawdentity provider relay-test --for --peer ` +- `clawdentity provider relay-test --for --message --session-id ` +- `clawdentity provider relay-test --for --platform-base-url --webhook-token --connector-base-url ` +- `clawdentity provider relay-test --for --no-preflight` + +### Connector Runtime (Manual/Advanced) +- `clawdentity connector start ` +- `clawdentity connector start --proxy-ws-url ` +- `clawdentity connector start --openclaw-base-url ` +- `clawdentity connector start --openclaw-hook-path ` +- `clawdentity connector start --openclaw-hook-token ` +- `clawdentity connector start --port --bind ` +- `clawdentity connector service install ` +- `clawdentity connector service install --platform ` +- `clawdentity connector service uninstall ` +- `clawdentity connector service uninstall --platform ` + +## Journey (Strict Order) + +1. Detect provider and local state. +- Run `clawdentity install --list --json`. +- Run `clawdentity provider status --json`. +- If ambiguous, require explicit `--for `. + +2. Initialize config. +- Run `clawdentity config init` (optionally with `--registry-url`). + +3. Complete onboarding. +- Preferred: `clawdentity invite redeem --display-name `. +- Recovery only: `clawdentity config set apiKey ` when invite is unavailable. + +4. Create agent identity. +- Run `clawdentity agent create --framework `. +- Validate with `clawdentity agent inspect `. + +5. Configure provider. +- Run `clawdentity provider setup --for --agent-name `. +- Add overrides only when defaults are wrong (`--platform-base-url`, webhook/connector args). + +6. Validate provider health. +- Run `clawdentity provider doctor --for `. +- Use `--json` for automation and `--peer ` when testing targeted routing. + +7. Validate relay path. +- Run `clawdentity provider relay-test --for `. +- Add `--peer ` for peer-specific checks. +- Keep `--no-preflight` only for narrow debugging. + +8. Manage runtime service if needed. +- Run `clawdentity connector service install ` for persistent runtime. +- Use `connector start` only for manual foreground operation. + +## Idempotency + +| Command | Idempotent? | Note | +|---|---|---| +| `config init` | Yes | Safe to re-run | +| `invite redeem` | No | Invite is one-time | +| `agent create` | No | Fails if agent already exists | +| `provider setup` | Usually yes | Reconciles provider config; review output paths | +| `provider doctor` | Yes | Read-only checks | +| `provider relay-test` | Mostly yes | Sends real probe traffic | +| `connector service install` | Yes | Reconciles service | +| `connector service uninstall` | Yes | Safe to repeat | + +## Required Question Policy + +Ask only when missing: +- Provider (`--for`) if auto-detect is unclear. +- Invite code (`clw_inv_...`) unless user explicitly chooses API-key recovery. +- Human display name. +- Agent name. +- Non-default provider/webhook/connector overrides. + +Do not ask for: +- Command groups outside the current Rust CLI surface. +- Manual proxy URL unless diagnosing connector runtime overrides. + +## Failure Handling + +### Provider selection errors +- `unknown platform`: + - Run `clawdentity install --list` and choose a valid `--for` value. + +### Setup/doctor failures +- If `provider doctor` is unhealthy: + - Re-run `clawdentity provider setup --for --agent-name `. + - Re-run `provider doctor` and follow remediation output. + +### Auth errors +- Invite/API key problems: + - Confirm `clawdentity config get apiKey`. + - Rotate with `api-key create` + `config set apiKey` when needed. +- Expired/revoked agent auth: + - `clawdentity agent auth refresh `. + - Re-run `provider setup`. + +### Connectivity failures +- Registry/proxy unreachable: + - Verify URLs in `clawdentity config show`. + - Re-run with explicit `--registry-url` or provider URL overrides if environment changed. + +## Bundled Resources + +| File | Purpose | +|------|---------| +| `references/clawdentity-protocol.md` | DID format, token semantics, provider relay contract, troubleshooting context | +| `references/clawdentity-registry.md` | Bootstrap, invite, API key, revocation, auth refresh details | +| `references/clawdentity-environment.md` | Environment variable overrides and runtime behavior | +| `examples/peers-sample.json` | Peer map schema reference | +| `examples/openclaw-relay-sample.json` | Example relay runtime config | + +Directive: load relevant references before troubleshooting advanced registry/proxy/provider failures. + +--- + +# Appended References + +--- + +## Clawdentity Protocol Reference + +Source: `apps/openclaw-skill/skill/references/clawdentity-protocol.md` + +# Clawdentity Relay Protocol Reference + +## Purpose + +Define the exact runtime contract used by `relay-to-peer.mjs`. + +> Rust CLI note: executable commands for this skill live in `SKILL.md` (`clawdentity install`, `clawdentity provider ...`, `clawdentity connector ...`). Pairing is documented here as a proxy API flow. + +## Filesystem Paths + +Canonical paths are defined in SKILL.md § Filesystem Truth. Refer there for all path contracts. + +## Setup Input Contract + +`clawdentity provider setup --for openclaw --agent-name ` is self-setup only. It does not accept peer routing fields. + +Rules: +- setup must succeed without any peer metadata +- peers config snapshot still exists and may be empty until pairing is completed +- setup is expected to bring connector runtime to a websocket-connected state (unless explicitly disabled by advanced flags) + +## Peer Map Schema + +`~/.clawdentity/peers.json` must be valid JSON: + +```json +{ + "peers": { + "beta": { + "did": "did:cdi::agent:01H...", + "proxyUrl": "https://beta-proxy.example.com/hooks/agent", + "agentName": "beta", + "humanName": "Ira" + } + } +} +``` + +Rules: +- peer alias key uses `[a-zA-Z0-9._-]` +- `did` required and must be a valid DID v2 agent identifier (`did:cdi::agent:`) +- `proxyUrl` required and must be a valid absolute URL +- `agentName` optional +- `humanName` optional + +## Proxy Pairing Prerequisite + +Relay delivery policy is trust-pair based on proxy side. Pairing must be completed before first cross-agent delivery. + +Current pairing contract is ticket-based at proxy API level: + +1. Initiator owner starts pairing: + - proxy route: `POST /pair/start` + - headers: + - `Authorization: Claw ` + - ownership validation is handled internally by proxy-to-registry service auth + - body: + +```json +{ + "ttlSeconds": 300, + "initiatorProfile": { + "agentName": "alpha", + "humanName": "Ravi" + } +} +``` + +> **Agent note:** `initiatorProfile` should be derived by the pairing client from local identity/config state when available. + +2. Responder confirms pairing: + - proxy route: `POST /pair/confirm` + - headers: + - `Authorization: Claw ` + - body: + +```json +{ + "ticket": "clwpair1_...", + "responderProfile": { + "agentName": "beta", + "humanName": "Ira" + } +} +``` + +> **Agent note:** `responderProfile` should be derived by the pairing client from local identity/config state when available. + +Rules: +- `ticket` is one-time and expires (default 5 minutes, max 15 minutes). +- Confirm establishes mutual trust for the initiator/responder pair. +- Confirm auto-persists peer DID/proxy mapping locally in `~/.clawdentity/peers.json` using ticket issuer metadata. +- Same-agent sender/recipient is allowed by policy without explicit pair entry. + +## Relay Input Contract + +The OpenClaw transform reads `ctx.payload`. + +- If `payload.peer` is absent: + - return payload unchanged + - do not relay +- If `payload.peer` exists: + - resolve peer from `peers.json` + - remove `peer` from forwarded body + - send JSON POST to local connector outbound endpoint + - return `null` to skip local handling + +## Relay Agent Selection Contract + +Relay resolves local agent name in this order: +1. transform option `agentName` +2. `CLAWDENTITY_AGENT_NAME` +3. `~/.clawdentity/openclaw-agent-name` +4. single local agent fallback from `~/.clawdentity/agents/` + +## Local OpenClaw Base URL Contract + +`~/.clawdentity/openclaw-relay.json` stores the OpenClaw upstream base URL used by local proxy runtime fallback: + +```json +{ + "openclawBaseUrl": "http://127.0.0.1:18789", + "openclawHookToken": "", + "updatedAt": "2026-02-15T20:00:00.000Z" +} +``` + +Rules: +- `openclawBaseUrl` must be absolute `http` or `https`. +- `openclawHookToken` is optional in schema but should be present after `clawdentity provider setup --for openclaw --agent-name `; connector runtime uses it for `/hooks/*` auth when no explicit hook token option/env is provided. +- `updatedAt` is ISO-8601 UTC timestamp. +- Proxy runtime precedence is: `OPENCLAW_BASE_URL` env first, then `openclaw-relay.json`, then built-in default. + +## Connector Handoff Contract + +The transform does not send directly to the peer proxy. It posts to the local connector runtime: +- Endpoint candidates are loaded from OpenClaw-local `hooks/transforms/clawdentity-relay.json` (generated by provider setup for OpenClaw) and attempted in order. +- Default fallback endpoint remains `http://127.0.0.1:19400/v1/outbound`. +- Runtime may also use: + - `CLAWDENTITY_CONNECTOR_BASE_URL` + - `CLAWDENTITY_CONNECTOR_OUTBOUND_PATH` +- `provider setup --for openclaw --agent-name ` is the primary self-setup path and should leave runtime healthy. +- `connector start ` is advanced/manual recovery; it resolves bind URL from `~/.clawdentity/openclaw-connectors.json` when explicit env override is absent. + +Outbound JSON body sent by transform: + +```json +{ + "peer": "beta", + "peerDid": "did:cdi::agent:01H...", + "peerProxyUrl": "https://beta-proxy.example.com/hooks/agent", + "payload": { + "event": "agent.message" + } +} +``` + +Rules: +- `payload.peer` is removed before creating the `payload` object above. +- Transform sends `Content-Type: application/json` only. +- Connector runtime is responsible for Clawdentity auth headers and request signing when calling peer proxy. + +## Error Conditions + +Relay fails when: +- no selected local agent can be resolved +- peer alias missing from config +- local connector outbound endpoint is unavailable (`404`) +- local connector reports unknown peer alias (`409`) +- local connector rejects payload (`400` or `422`) +- local connector outbound request fails (network/other non-2xx) + +Error messages should include file/path context but never print secret content. + +## Proxy URL Resolution + +CLI resolves proxy URL in this order (first non-empty wins): + +1. `CLAWDENTITY_PROXY_URL` environment variable +2. `proxyUrl` from `~/.clawdentity/config.json` +3. Registry metadata from `GET /v1/metadata` +4. Error when configured proxy does not match metadata (`CLI_PAIR_PROXY_URL_MISMATCH`) or metadata lookup fails + +> **Agent note:** Proxy URL resolution is fully automatic. Do not ask the user for a proxy URL. The CLI resolves it from env, config, or registry metadata without user input. + +### Metadata expectation + +Registry metadata (`/v1/metadata`) should return a valid `proxyUrl`. + +Known defaults: + +| Registry URL | Metadata proxy URL | +|-------------|--------------------| +| `https://registry.clawdentity.com` | `https://proxy.clawdentity.com` | +| `https://dev.registry.clawdentity.com` | `https://dev.proxy.clawdentity.com` | + +Recovery: rerun onboarding (`clawdentity invite redeem --display-name `) so local config aligns to registry metadata. + +## Identity Injection + +When identity injection is enabled (proxy env `INJECT_IDENTITY_INTO_MESSAGE`, default `true`), the proxy prepends an identity block to the `message` field of relayed payloads. + +### Block format + +``` +[Clawdentity Identity] +agentDid: did:cdi::agent:01H... +ownerDid: did:cdi::human:01H... +issuer: https://registry.clawdentity.com +aitJti: 01H... +``` + +The block is separated from the original message by a blank line (`\n\n`). + +### Field definitions + +| Field | Description | +|---|---| +| `agentDid` | Sender agent DID — use to identify the peer | +| `ownerDid` | DID of the human who owns the sender agent | +| `issuer` | Registry URL that issued the sender's AIT | +| `aitJti` | Unique JTI claim from the sender's AIT | + +### Programmatic access + +The connector `deliver` frame includes `fromAgentDid` as a top-level field. Inbound inbox items (`ConnectorInboundInboxItem`) also expose `fromAgentDid` for programmatic sender identification without parsing the identity block. + +## Pairing Error Codes + +### `pair start` errors + +| HTTP Status | Error Code | Meaning | Recovery | +|---|---|---|---| +| 403 | `PROXY_PAIR_OWNERSHIP_FORBIDDEN` | Initiator ownership check failed | Recreate/refresh the local agent identity | +| 503 | `PROXY_PAIR_OWNERSHIP_UNAVAILABLE` | Registry ownership lookup unavailable | Ensure registry deterministic bootstrap credentials are configured (`BOOTSTRAP_INTERNAL_SERVICE_ID`, `BOOTSTRAP_INTERNAL_SERVICE_SECRET`) and proxy credentials match (`BOOTSTRAP_INTERNAL_SERVICE_ID`, `BOOTSTRAP_INTERNAL_SERVICE_SECRET`); for existing envs rotate credentials together | +| — | `CLI_PAIR_AGENT_NOT_FOUND` | Agent ait.jwt or secret.key missing/empty | Run `agent create` or `agent auth refresh` | +| — | `CLI_PAIR_HUMAN_NAME_MISSING` | Local config is missing `humanName` | Set via `invite redeem` or config | +| — | `CLI_PAIR_PROXY_URL_INVALID` | Configured proxy URL is malformed | Fix proxy URL: `clawdentity config set proxyUrl ` | +| — | `CLI_PAIR_START_INVALID_TTL` | ttlSeconds must be a positive integer | Use valid `--ttl-seconds` value | +| — | `CLI_PAIR_INVALID_PROXY_URL` | Proxy URL is invalid | Fix proxy URL in config | +| — | `CLI_PAIR_REQUEST_FAILED` | Unable to connect to proxy URL | Check DNS, firewall, proxy URL | +| — | `CLI_PAIR_START_FAILED` | Generic pair start failure | Retry; check proxy connectivity | +| — | `CLI_PAIR_PROFILE_INVALID` | Name too long, contains control characters, or empty | Fix agent or human name | + +### `pair confirm` errors + +| HTTP Status | Error Code | Meaning | Recovery | +|---|---|---|---| +| 404 | `PROXY_PAIR_TICKET_NOT_FOUND` | Pairing ticket is invalid or expired | Request new ticket from initiator | +| 410 | `PROXY_PAIR_TICKET_EXPIRED` | Pairing ticket has expired | Request new ticket | +| — | `CLI_PAIR_CONFIRM_TICKET_REQUIRED` | Either --ticket or --qr-file is required | Provide one input path | +| — | `CLI_PAIR_CONFIRM_INPUT_CONFLICT` | Cannot provide both --ticket and --qr-file | Use one input path only | +| — | `CLI_PAIR_CONFIRM_TICKET_INVALID` | Pairing ticket is invalid | Get new ticket from initiator | +| — | `CLI_PAIR_CONFIRM_QR_FILE_NOT_FOUND` | QR file not found | Verify file path | +| — | `CLI_PAIR_CONFIRM_QR_NOT_FOUND` | No pairing QR code found in image | Request new QR from initiator | +| — | `CLI_PAIR_CONFIRM_FAILED` | Generic pair confirm failure | Retry with new ticket | +| — | `CLI_PAIR_CONFIRM_QR_FILE_INVALID` | QR image file corrupt or unsupported | Request new QR from initiator | +| — | `CLI_PAIR_CONFIRM_QR_FILE_REQUIRED` | QR path unusable | Verify file path and format | +| — | `CLI_PAIR_TICKET_ISSUER_MISMATCH` | Ticket issuer does not match configured proxy URL | `clawdentity config set proxyUrl ` and retry | + +### `pair status` errors + +| HTTP Status | Error Code | Meaning | Recovery | +|---|---|---|---| +| — | `CLI_PAIR_STATUS_FAILED` | Generic pair status failure | Retry | +| — | `CLI_PAIR_STATUS_WAIT_TIMEOUT` | Wait polling timed out | Generate a new ticket via `POST /pair/start` | +| — | `CLI_PAIR_STATUS_FORBIDDEN` | 403 on status check — ownership mismatch | Verify correct agent | +| — | `CLI_PAIR_STATUS_TICKET_REQUIRED` | Missing ticket argument | Provide `--ticket ` | +| — | `CLI_PAIR_STATUS_WAIT_INVALID` | Wait/poll option is not a positive integer | Use a valid positive integer for `--wait-seconds` or `--poll-interval-seconds` | +| — | `CLI_PAIR_TICKET_ISSUER_MISMATCH` | Ticket issuer does not match configured proxy URL | `clawdentity config set proxyUrl ` and retry | + +### Peer persistence errors + +| Error Code | Meaning | Recovery | +|---|---|---| +| `CLI_PAIR_PEERS_CONFIG_INVALID` | `peers.json` corrupt or invalid structure | Delete `peers.json` and re-pair | +| `CLI_PAIR_PEER_ALIAS_INVALID` | Derived alias fails validation | Re-pair with valid agent DID | + +## Cache Files + +| Path | TTL | Used By | +|------|-----|---------| +| `~/.clawdentity/cache/registry-keys.json` | 1 hour | token validation/auth routines — cached registry signing public keys | +| `~/.clawdentity/cache/crl-claims.json` | 15 minutes | token validation/auth routines — cached certificate revocation list | + +Cache is populated on first token validation/auth call and refreshed when TTL expires. Stale cache is used as fallback when registry is unreachable. + +## Peer Alias Derivation + +When `pair confirm` saves a new peer, alias is derived automatically: + +1. Parse peer DID with the protocol DID parser and extract the identifier component. +2. Take last 8 characters of the identifier, lowercase: `peer-`. +3. If alias already exists in `peers.json` for a different DID, append numeric suffix: `peer--2`, `peer--3`, etc. +4. If peer DID already exists in `peers.json`, reuse existing alias (no duplicate entry). +5. Fallback alias is `peer` if DID is not a valid agent DID. + +Alias validation: `[a-zA-Z0-9._-]`, max 128 characters. + +## Container Environments + +When running in Docker or similar container runtimes: + +- `provider setup --for openclaw` writes Docker-aware endpoint candidates into `clawdentity-relay.json`: + - `host.docker.internal`, `gateway.docker.internal`, Linux bridge (`172.17.0.1`), default gateway, and loopback. + - Candidates are attempted in order by the relay transform. +- Use provider setup options plus connector service controls when the connector runs as a separate container or process. +- Required env overrides for container networking: + - `OPENCLAW_BASE_URL` — point to OpenClaw inside/outside the container network. + - `CLAWDENTITY_CONNECTOR_BASE_URL` — point to the connector's bind address from the transform's perspective. +- Port allocation: each agent gets its own connector port starting from `19400`. + - Port assignment is tracked in `~/.clawdentity/openclaw-connectors.json`. + +## Doctor Check Reference + +Run `clawdentity provider doctor --for openclaw --json` for machine-readable diagnostics. + +| Check ID | Validates | Remediation on Failure | +|---|---|---| +| `config.registry` | `registryUrl`, `apiKey`, and `proxyUrl` in config (or proxy env override) | `clawdentity config init` or `invite redeem` | +| `state.selectedAgent` | Agent marker at `~/.clawdentity/openclaw-agent-name` | `clawdentity provider setup --for openclaw --agent-name ` | +| `state.credentials` | `ait.jwt` and `secret.key` exist and non-empty | `clawdentity agent create ` or `agent auth refresh ` | +| `state.peers` | Peers config valid; requested `--peer` alias exists | Populate peers via pairing API flow | +| `state.transform` | Relay transform artifacts in OpenClaw hooks dir | Re-run `clawdentity provider setup --for openclaw --agent-name ` | +| `state.hookMapping` | `send-to-peer` hook mapping in OpenClaw config | `clawdentity provider setup --for openclaw --agent-name ` | +| `state.hookToken` | Hooks enabled with token in OpenClaw config | `clawdentity provider setup --for openclaw --agent-name ` then restart OpenClaw | +| `state.hookSessionRouting` | `hooks.defaultSessionKey`, `hooks.allowRequestSessionKey=false`, and required prefixes | `clawdentity provider setup --for openclaw --agent-name ` then restart OpenClaw | +| `state.gatewayAuth` | OpenClaw `gateway.auth` readiness (`mode` + required credential) | `clawdentity provider setup --for openclaw --agent-name ` to re-sync gateway auth | +| `state.gatewayDevicePairing` | Pending OpenClaw device approvals | Re-run `clawdentity provider setup --for openclaw --agent-name ` so setup auto-recovers approvals | +| `state.openclawBaseUrl` | OpenClaw base URL resolvable | `clawdentity provider setup --for openclaw --agent-name --platform-base-url ` | +| `state.connectorRuntime` | Local connector runtime reachable and websocket-connected | `clawdentity provider setup --for openclaw --agent-name ` | +| `state.connectorInboundInbox` | Connector local inbound inbox backlog and replay queue state | Re-run `clawdentity provider setup --for openclaw --agent-name ` and verify connector runtime health | +| `state.openclawHookHealth` | Connector replay status for local OpenClaw hook delivery | Re-run `clawdentity provider setup --for openclaw --agent-name ` and restart OpenClaw if hook replay stays failed | + +--- + +## Clawdentity Registry Reference + +Source: `apps/openclaw-skill/skill/references/clawdentity-registry.md` + +# Clawdentity Registry Operations Reference + +## Purpose + +Document registry-side CLI commands that are outside the core relay setup journey: admin bootstrap, API key lifecycle, agent revocation, and auth refresh. + +## Admin Bootstrap + +Bootstrap creates the first admin human and API key on a fresh registry. This is a prerequisite before any invites can be created. + +### Command + +``` +clawdentity admin bootstrap --bootstrap-secret +``` + +### Flags + +| Flag | Required | Description | +|------|----------|-------------| +| `--bootstrap-secret ` | Yes | One-time bootstrap secret configured on registry server | +| `--display-name ` | No | Admin display name | +| `--api-key-name ` | No | Admin API key label | +| `--registry-url ` | No | Override registry URL | + +### Expected Output + +``` +Admin bootstrap completed +Human DID: did:cdi::human:01H... +API key name: +API key token (shown once): + +Internal service ID: +Internal service name: proxy-pairing +Set proxy secrets BOOTSTRAP_INTERNAL_SERVICE_ID and BOOTSTRAP_INTERNAL_SERVICE_SECRET manually in Cloudflare before proxy deploy. +API key saved to local config +``` + +### Error Codes + +| Error Code | Meaning | +|------------|---------| +| `ADMIN_BOOTSTRAP_DISABLED` | Bootstrap is disabled on the registry | +| `ADMIN_BOOTSTRAP_UNAUTHORIZED` | Bootstrap secret is invalid | +| `ADMIN_BOOTSTRAP_ALREADY_COMPLETED` | Admin already exists; bootstrap is one-time | +| `ADMIN_BOOTSTRAP_INVALID` | Request payload is invalid | +| `CLI_ADMIN_BOOTSTRAP_SECRET_REQUIRED` | Bootstrap secret was not provided | +| `CLI_ADMIN_BOOTSTRAP_INVALID_REGISTRY_URL` | Registry URL is invalid | +| `CLI_ADMIN_BOOTSTRAP_REQUEST_FAILED` | Unable to connect to registry | +| `CLI_ADMIN_BOOTSTRAP_CONFIG_PERSISTENCE_FAILED` | Failed to save admin credentials locally | + +### Behavioral Notes + +- One-time operation: succeeds only on first call per registry. +- Automatically persists `registryUrl` and `apiKey` to local config. +- Registry must have `BOOTSTRAP_SECRET` environment variable set. +- Registry must also have deterministic service credentials configured: + - `BOOTSTRAP_INTERNAL_SERVICE_ID` + - `BOOTSTRAP_INTERNAL_SERVICE_SECRET` +- `BOOTSTRAP_INTERNAL_SERVICE_ID` must match proxy `BOOTSTRAP_INTERNAL_SERVICE_ID`. +- `BOOTSTRAP_INTERNAL_SERVICE_SECRET` must match proxy `BOOTSTRAP_INTERNAL_SERVICE_SECRET`. +- After bootstrap, admin can create invites with `clawdentity invite create`. + +## API Key Lifecycle + +### Create API key + +``` +clawdentity api-key create +``` + +Creates a new API key under the current authenticated human. Token is displayed once. + +### List API keys + +``` +clawdentity api-key list +``` + +Lists all API keys for the current human with ID, name, and status. + +### Revoke API key + +``` +clawdentity api-key revoke +``` + +Revokes an API key by ID. The key becomes immediately unusable. + +### Rotation workflow + +1. `clawdentity api-key create` — note the new token. +2. `clawdentity config set apiKey ` — switch local config. +3. `clawdentity api-key revoke ` — deactivate old key. +4. `clawdentity config get apiKey` — verify new key is active. + +### Error Codes + +| HTTP Status | Meaning | +|-------------|---------| +| 401 | API key invalid or expired; re-authenticate | +| 403 | Insufficient permissions (admin required for some operations) | + +## Agent Revocation + +### Command + +``` +clawdentity agent auth revoke +``` + +Revokes a local agent identity via the registry. The agent's AIT will appear on the certificate revocation list (CRL). + +### Behavioral Notes + +- Reads agent DID from `~/.clawdentity/agents//identity.json`. +- Requires `apiKey` configured in `~/.clawdentity/config.json`. +- Idempotent: repeat revocation calls succeed without error. +- CRL propagation lag: verifiers using cached `crl-claims.json` (15-minute TTL) may not see revocation immediately. +- Local credential files are not deleted; only registry-side revocation is performed. + +### Error Codes + +| HTTP Status | Meaning | +|-------------|---------| +| 401 | Authentication failed — API key invalid | +| 404 | Agent not found in registry | +| 409 | Agent cannot be revoked (already revoked or conflict) | + +## Agent Auth Refresh + +### Command + +``` +clawdentity agent auth refresh +``` + +Refreshes the agent's registry auth credentials using Claw proof (Ed25519 signature). + +### What It Reads + +- `~/.clawdentity/agents//secret.key` — for signing the proof +- `~/.clawdentity/agents//registry-auth.json` — current refresh token + +### What It Writes + +- `~/.clawdentity/agents//registry-auth.json` — new access token and refresh token + +### Behavioral Notes + +- Uses atomic write (temp file + chmod 0600 + rename) to prevent corruption. +- Requires `registryUrl` configured in `~/.clawdentity/config.json`. +- After refresh, restart connector to pick up new credentials. +- If `registry-auth.json` is missing or empty, the agent must be re-created with `agent create`. + +### Error Codes + +| Error Code | Meaning | +|------------|---------| +| `CLI_OPENCLAW_EMPTY_AGENT_CREDENTIALS` | Registry auth file is empty or missing | +| 401 | Refresh token expired or invalid — re-create agent | + +## Invite Management (Admin) + +### Create invite + +``` +clawdentity invite create +clawdentity invite create --expires-at --registry-url +``` + +Admin-only. Creates a registry invite code (`clw_inv_...`) for onboarding new users. + +### Error Codes + +| Error Code | Meaning | +|------------|---------| +| `CLI_INVITE_MISSING_LOCAL_CREDENTIALS` | API key not configured | +| `CLI_INVITE_CREATE_FAILED` | Invite creation failed | +| 401 | Authentication failed | +| 403 | Requires admin access | +| 400 | Invalid request | + +## Connector Errors + +| Error Code | Meaning | Recovery | +|---|---|---| +| `CLI_CONNECTOR_SERVICE_PLATFORM_INVALID` | Invalid platform argument | Use `auto`, `launchd`, or `systemd` | +| `CLI_CONNECTOR_SERVICE_PLATFORM_UNSUPPORTED` | OS unsupported for selected platform | Use a supported platform (macOS: launchd, Linux: systemd) | +| `CLI_CONNECTOR_SERVICE_INSTALL_FAILED` | Service install failed | Check permissions, systemd/launchd status | +| `CLI_CONNECTOR_PROXY_URL_REQUIRED` | Proxy URL unresolvable | Run `invite redeem` or set `CLAWDENTITY_PROXY_URL` / `CLAWDENTITY_PROXY_WS_URL` | +| `CLI_CONNECTOR_INVALID_REGISTRY_AUTH` | `registry-auth.json` corrupt or invalid | Run `clawdentity agent auth refresh ` | +| `CLI_CONNECTOR_INVALID_AGENT_IDENTITY` | `identity.json` corrupt or invalid | Re-create agent with `clawdentity agent create ` | + +--- + +## Clawdentity Environment Reference + +Source: `apps/openclaw-skill/skill/references/clawdentity-environment.md` + +# Clawdentity Environment Variable Reference + +## Purpose + +Complete reference for CLI environment variable overrides. When env overrides are present, config-file URL mismatches are not blockers. + +## CLI Environment Variables + +| Variable | Purpose | Used By | +|---|---|---| +| `CLAWDENTITY_PROXY_URL` | Override proxy URL | pair, connector | +| `CLAWDENTITY_PROXY_WS_URL` | Override proxy WebSocket URL | connector | +| `CLAWDENTITY_REGISTRY_URL` | Override registry URL | config | +| `CLAWDENTITY_CONNECTOR_BASE_URL` | Override connector bind URL | connector | +| `CLAWDENTITY_CONNECTOR_OUTBOUND_PATH` | Override outbound path | relay transform | +| `CLAWDENTITY_AGENT_NAME` | Override agent name resolution | provider (`--for openclaw`), transform | +| `OPENCLAW_BASE_URL` | Override OpenClaw upstream URL | provider setup (`--for openclaw`) | +| `OPENCLAW_HOOK_TOKEN` | Override hook auth token | provider setup (`--for openclaw`) | +| `OPENCLAW_GATEWAY_TOKEN` | Override gateway auth token | provider setup (`--for openclaw`) | +| `OPENCLAW_CONFIG_PATH` | Override OpenClaw config file path | provider (`--for openclaw`) | +| `OPENCLAW_STATE_DIR` | Override OpenClaw state directory | provider (`--for openclaw`) | +| `OPENCLAW_HOME` | Override OpenClaw home directory (used when explicit config/state overrides are unset) | provider (`--for openclaw`) | + +## Profile-Local State Resolution + +In profile-mounted/containerized OpenClaw environments, Clawdentity state may be stored at: +- `/.clawdentity` + +instead of: +- `~/.clawdentity` + +If `~/.clawdentity` is missing but `/.clawdentity` exists, run CLI commands with: +- `HOME=` + +This makes `clawdentity` resolve the correct profile-local state root. + +## Proxy Server Environment Variables + +These variables configure the Clawdentity proxy server (operator-facing, not CLI): + +| Variable | Purpose | Default | +|---|---|---| +| `INJECT_IDENTITY_INTO_MESSAGE` | Enable/disable identity block injection into relayed messages | `true` | +| `RELAY_QUEUE_MAX_MESSAGES_PER_AGENT` | Max queued messages per agent | `500` | +| `RELAY_QUEUE_TTL_SECONDS` | Queue message time-to-live | `3600` | +| `RELAY_RETRY_INITIAL_MS` | Initial retry delay for relay delivery | `1000` | diff --git a/apps/landing/scripts/build-skill-md.mjs b/apps/landing/scripts/build-skill-md.mjs new file mode 100644 index 0000000..098d2f8 --- /dev/null +++ b/apps/landing/scripts/build-skill-md.mjs @@ -0,0 +1,105 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptPath = fileURLToPath(import.meta.url); +const scriptDir = path.dirname(scriptPath); +const repoRoot = path.resolve(scriptDir, "..", "..", ".."); +const landingRoot = path.resolve(scriptDir, ".."); + +const skillSourcePath = path.resolve( + repoRoot, + "apps", + "openclaw-skill", + "skill", + "SKILL.md", +); + +const references = [ + { + title: "Clawdentity Protocol Reference", + sourceLabel: "apps/openclaw-skill/skill/references/clawdentity-protocol.md", + path: path.resolve( + repoRoot, + "apps", + "openclaw-skill", + "skill", + "references", + "clawdentity-protocol.md", + ), + }, + { + title: "Clawdentity Registry Reference", + sourceLabel: "apps/openclaw-skill/skill/references/clawdentity-registry.md", + path: path.resolve( + repoRoot, + "apps", + "openclaw-skill", + "skill", + "references", + "clawdentity-registry.md", + ), + }, + { + title: "Clawdentity Environment Reference", + sourceLabel: + "apps/openclaw-skill/skill/references/clawdentity-environment.md", + path: path.resolve( + repoRoot, + "apps", + "openclaw-skill", + "skill", + "references", + "clawdentity-environment.md", + ), + }, +]; + +const outputPath = path.resolve(landingRoot, "public", "skill.md"); + +function withTrailingNewline(content) { + return content.endsWith("\n") ? content : `${content}\n`; +} + +async function readUtf8(filePath) { + return fs.readFile(filePath, "utf8"); +} + +async function buildSkillMarkdown() { + const baseSkill = await readUtf8(skillSourcePath); + + const referenceBlocks = []; + for (const reference of references) { + const referenceContent = withTrailingNewline( + await readUtf8(reference.path), + ); + const section = [ + "---", + "", + `## ${reference.title}`, + "", + `Source: \`${reference.sourceLabel}\``, + "", + referenceContent, + ].join("\n"); + referenceBlocks.push(section); + } + + const mergedContent = [ + withTrailingNewline(baseSkill), + "---", + "", + "# Appended References", + "", + referenceBlocks.join("\n"), + ].join("\n"); + + await fs.mkdir(path.dirname(outputPath), { recursive: true }); + await fs.writeFile(outputPath, mergedContent, "utf8"); + process.stdout.write(`Generated ${outputPath}\n`); +} + +buildSkillMarkdown().catch((error) => { + process.stderr.write(`Failed to generate skill markdown: ${error.message}\n`); + process.exitCode = 1; +}); diff --git a/apps/landing/src/assets/landing/clawdentity_feature_communication_256.png b/apps/landing/src/assets/landing/clawdentity_feature_communication_256.png new file mode 100644 index 0000000..69c4b02 Binary files /dev/null and b/apps/landing/src/assets/landing/clawdentity_feature_communication_256.png differ diff --git a/apps/landing/src/assets/landing/clawdentity_feature_identity_256.png b/apps/landing/src/assets/landing/clawdentity_feature_identity_256.png new file mode 100644 index 0000000..125fe24 Binary files /dev/null and b/apps/landing/src/assets/landing/clawdentity_feature_identity_256.png differ diff --git a/apps/landing/src/assets/landing/clawdentity_feature_pairing_256.png b/apps/landing/src/assets/landing/clawdentity_feature_pairing_256.png new file mode 100644 index 0000000..6a5d45b Binary files /dev/null and b/apps/landing/src/assets/landing/clawdentity_feature_pairing_256.png differ diff --git a/apps/landing/src/assets/landing/clawdentity_feature_trust_256.png b/apps/landing/src/assets/landing/clawdentity_feature_trust_256.png new file mode 100644 index 0000000..0912fa9 Binary files /dev/null and b/apps/landing/src/assets/landing/clawdentity_feature_trust_256.png differ diff --git a/apps/landing/src/assets/landing/clawdentity_hero_2400x1200.png b/apps/landing/src/assets/landing/clawdentity_hero_2400x1200.png new file mode 100644 index 0000000..9ea31dc Binary files /dev/null and b/apps/landing/src/assets/landing/clawdentity_hero_2400x1200.png differ diff --git a/apps/landing/src/assets/landing/clawdentity_icon_only.svg b/apps/landing/src/assets/landing/clawdentity_icon_only.svg new file mode 100644 index 0000000..f65b5bf --- /dev/null +++ b/apps/landing/src/assets/landing/clawdentity_icon_only.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/landing/src/assets/landing/clawdentity_primary_logo_2048x512.png b/apps/landing/src/assets/landing/clawdentity_primary_logo_2048x512.png new file mode 100644 index 0000000..4abb439 Binary files /dev/null and b/apps/landing/src/assets/landing/clawdentity_primary_logo_2048x512.png differ diff --git a/apps/landing/src/components/landing/Comparison.astro b/apps/landing/src/components/landing/Comparison.astro new file mode 100644 index 0000000..71720d5 --- /dev/null +++ b/apps/landing/src/components/landing/Comparison.astro @@ -0,0 +1,287 @@ +--- +const features = [ + { name: "Per-agent identity", apiKeys: false, oauth: "Partial", clawdentity: true }, + { name: "Proof of possession", apiKeys: false, oauth: false, clawdentity: true }, + { name: "Instant revocation", apiKeys: false, oauth: "Partial", clawdentity: true }, + { name: "No shared secrets", apiKeys: false, oauth: true, clawdentity: true }, + { name: "Offline verification", apiKeys: false, oauth: false, clawdentity: true }, + { name: "Works as sidecar", apiKeys: "N/A", oauth: false, clawdentity: true }, + { name: "Open source", apiKeys: "Varies", oauth: "Varies", clawdentity: true }, +]; + +function renderValue(value: boolean | string) { + if (value === true) return { text: "✓", cls: "value-yes", label: "Yes" }; + if (value === false) return { text: "✕", cls: "value-no", label: "No" }; + return { text: value, cls: "value-partial", label: value }; +} + +const apiKeyResults = features.map(f => renderValue(f.apiKeys)); +const oauthResults = features.map(f => renderValue(f.oauth)); +const clawdentityResults = features.map(f => renderValue(f.clawdentity)); +--- + +
+
+
+ +

How It Compares

+

Clawdentity is purpose-built for agent-to-agent auth.

+
+ + +
+ +
+
Feature
+
+ API Keys +
+
+ OAuth 2.0 +
+
+ Clawdentity +
+
+ + + {features.map((feature, index) => ( +
+
+ {feature.name} +
+
+ + {apiKeyResults[index].text} + +
+
+ + {oauthResults[index].text} + +
+
+ + {clawdentityResults[index].text} + +
+
+ ))} +
+ + +
+ {features.map((feature, index) => ( +
+

{feature.name}

+
+
+ API Keys + {apiKeyResults[index].text} +
+
+ OAuth 2.0 + {oauthResults[index].text} +
+
+ Clawdentity + {clawdentityResults[index].text} +
+
+
+ ))} +
+
+
+ + diff --git a/apps/landing/src/components/landing/FlowDiagram.astro b/apps/landing/src/components/landing/FlowDiagram.astro new file mode 100644 index 0000000..ca07e75 --- /dev/null +++ b/apps/landing/src/components/landing/FlowDiagram.astro @@ -0,0 +1,351 @@ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
🤖
+
Agent
+
Caller
+
+
+ +
+
+
+
🔐
+
Sign
+
AIT + PoP
+
+
+ +
+
+
+
+
Proxy
+
Verify + CRL
+
+
+ +
+
+
+
+
Delivered
+
Trusted
+
+
+
+
+ + +
+
+ Agent Identity + Each agent has a unique DID and keypair +
+
+ Cryptographic Proof + Every request is signed with EdDSA +
+
+ Verification Layer + Proxy checks signatures and revocation status +
+
+ Trusted Delivery + Only verified requests reach your agent +
+
+
+ + + + diff --git a/apps/landing/src/components/landing/Footer.astro b/apps/landing/src/components/landing/Footer.astro new file mode 100644 index 0000000..5c72b3d --- /dev/null +++ b/apps/landing/src/components/landing/Footer.astro @@ -0,0 +1,448 @@ +--- +import { Image } from "astro:assets"; +import logoPng from "../../assets/landing/clawdentity_primary_logo_2048x512.png"; + +const footerLinks = { + documentation: [ + { label: "Introduction", href: "/getting-started/introduction/" }, + { label: "Quick Start", href: "/getting-started/quickstart/" }, + { label: "Installation", href: "/getting-started/installation/" }, + { label: "Architecture", href: "/architecture/overview/" }, + ], + resources: [ + { label: "Identity Model", href: "/concepts/identity-model/" }, + { label: "Security", href: "/concepts/security/" }, + { label: "API Reference", href: "/api-reference/cli/" }, + ], + community: [ + { label: "GitHub", href: "https://github.com/vrknetha/clawdentity", external: true }, + { label: "Issues", href: "https://github.com/vrknetha/clawdentity/issues", external: true }, + { label: "Discussions", href: "https://github.com/vrknetha/clawdentity/discussions", external: true }, + ], +}; +--- + +
+ + +
+ + + +
+ + + +
+ + + + diff --git a/apps/landing/src/components/landing/GettingStarted.astro b/apps/landing/src/components/landing/GettingStarted.astro new file mode 100644 index 0000000..a829716 --- /dev/null +++ b/apps/landing/src/components/landing/GettingStarted.astro @@ -0,0 +1,397 @@ +--- +const commands = [ + { + label: "Install the CLI", + command: "npm install -g @clawdentity/cli", + output: ["added 42 packages in 3s"], + }, + { + label: "Register your first agent", + command: "clawdentity agent register --name my-agent", + output: [ + "✓ Generated Ed25519 keypair", + "✓ Agent registered with registry", + "✓ AIT saved to ~/.clawdentity/agents/my-agent.ait", + ], + }, + { + label: "Start the proxy", + command: "clawdentity proxy start --upstream http://localhost:3000", + output: [ + "✓ Proxy listening on :4000", + "✓ Upstream: http://localhost:3000", + "✓ Ready to accept verified requests", + ], + }, +]; +--- + +
+
+
+ +

Get Started in Minutes

+

Three commands to verified agent identity.

+
+ +
+
+
+
+ + + +
+ clawdentity — setup + +
+
+ {commands.map((cmd, index) => ( +
+
+ {index + 1} + {cmd.label} +
+
+ $ + {cmd.command} + +
+
+ {cmd.output.map(line => ( +
{line}
+ ))} +
+
+ ))} +
+ Ready to secure your agents! +
+
+
+
+ + +
+
+ + + + diff --git a/apps/landing/src/components/landing/Hero.astro b/apps/landing/src/components/landing/Hero.astro new file mode 100644 index 0000000..a0f6faf --- /dev/null +++ b/apps/landing/src/components/landing/Hero.astro @@ -0,0 +1,351 @@ +--- +import { Image } from "astro:assets"; +import heroImage from "../../assets/landing/clawdentity_hero_2400x1200.png"; +--- + +
+ +
+ + +
+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+ +
+

+ Who is this agent, + who owns it, and is it revoked? +

+

+ Clawdentity answers that question with verifiable per-agent identity, + proof-of-possession on every request, and instant revocation + that propagates in seconds. +

+ +
+ + +
+ Scroll to explore + + + +
+
+ + + + diff --git a/apps/landing/src/components/landing/HowItWorks.astro b/apps/landing/src/components/landing/HowItWorks.astro new file mode 100644 index 0000000..3ecd6a8 --- /dev/null +++ b/apps/landing/src/components/landing/HowItWorks.astro @@ -0,0 +1,575 @@ +--- +import { Image } from "astro:assets"; +import type { ImageMetadata } from "astro"; +import identityIcon from "../../assets/landing/clawdentity_feature_identity_256.png"; +import pairingIcon from "../../assets/landing/clawdentity_feature_pairing_256.png"; +import communicationIcon from "../../assets/landing/clawdentity_feature_communication_256.png"; + +type BaseStep = { + number: string; + icon: ImageMetadata; + title: string; + description: string; +}; + +type CliStep = BaseStep & { + type: "cli"; + code: string; +}; + +type VisualStep = BaseStep & { + type: "visual"; +}; + +type Step = CliStep | VisualStep; + +const steps: Step[] = [ + { + number: "01", + icon: identityIcon, + title: "Register an Agent", + description: "Generate a key pair and register with the identity registry. The agent receives a signed AIT (Agent Identity Token) — a registry-signed passport using JWT and EdDSA.", + type: "cli", + code: `clawdentity agent register \\ + --name my-agent \\ + --owner operator@example.com`, + }, + { + number: "02", + icon: pairingIcon, + title: "Sign Every Request", + description: "The SDK attaches the AIT and signs each request with the agent's private key (Proof-of-Possession). The private key never leaves the agent's machine.", + type: "visual", + }, + { + number: "03", + icon: communicationIcon, + title: "Proxy Verifies & Delivers", + description: "The Clawdentity proxy validates the AIT signature, checks the CRL for revocation, and enforces per-agent policy (allowlist, rate limits, replay protection) before forwarding.", + type: "cli", + code: `clawdentity proxy start \\ + --upstream http://localhost:3000 \\ + --port 4000 \\ + --policy allowlist.yaml`, + }, +]; + +function highlightCode(code: string) { + return code + .replace(/(clawdentity|agent|register|proxy|start)/g, '$1') + .replace(/(--\w+)/g, '$1') + .replace(/('[^']*')/g, '$1'); +} +--- + +
+
+
+ +

Three Building Blocks

+

No shared secrets. Instant revocation. Cryptographic trust.

+
+ + +
+
+ +
+ {steps.map((step, index) => ( +
+
+
{step.number}
+
+ +
+
+
+ +
+

{step.title}

+
+ +

{step.description}

+ + {step.type === 'cli' ? ( +
+
+
+ + + +
+ bash + +
+
+
+
+
+ ) : ( +
+
+
+
+ 🛂 + AIT +
+
+
+
+
+
+ 🔑 + Private Key + stays local +
+
+
+ + + +
+
+
+ 🔏 + Signed Request + PoP +
+
+
+
+ Ed25519 Signature + Timestamp + Nonce +
+
+ )} +
+
+ ))} +
+
+
+
+ + + + diff --git a/apps/landing/src/components/landing/Nav.astro b/apps/landing/src/components/landing/Nav.astro new file mode 100644 index 0000000..0640937 --- /dev/null +++ b/apps/landing/src/components/landing/Nav.astro @@ -0,0 +1,374 @@ +--- +import { Image } from "astro:assets"; +import logoPng from "../../assets/landing/clawdentity_primary_logo_2048x512.png"; +--- + + + + + + diff --git a/apps/landing/src/components/landing/Privacy.astro b/apps/landing/src/components/landing/Privacy.astro new file mode 100644 index 0000000..65d8cc8 --- /dev/null +++ b/apps/landing/src/components/landing/Privacy.astro @@ -0,0 +1,295 @@ +--- +const highlights = [ + { + icon: ``, + title: "No Shared Secrets", + description: "Each agent holds its own private key. Nothing sensitive is shared between parties.", + }, + { + icon: ``, + title: "Offline Verification", + description: "Proxies verify AITs locally using cached public keys and CRLs. No call home required.", + }, + { + icon: ``, + title: "Decentralized by Design", + description: "No central authority needed at request time. Registry only involved at registration and revocation.", + }, + { + icon: ``, + title: "Instant Revocation", + description: "Compromised agent? Revoke it in the registry. All proxies pick up the CRL update within seconds.", + }, +]; +--- + +
+
+
+ +

Privacy & Security First

+

Built for zero-trust environments where every request must prove its origin.

+
+ +
+ {highlights.map((item, index) => ( +
+
+
+
+
+
+
+
+

{item.title}

+

{item.description}

+
+
+ ))} +
+ + +
+
+
+ + + + +
+
+ Open Source Security + Auditable. Transparent. Community-verified. +
+
+
+
+ + +
+
+
+
+ + diff --git a/apps/landing/src/components/landing/Problem.astro b/apps/landing/src/components/landing/Problem.astro new file mode 100644 index 0000000..b22fab7 --- /dev/null +++ b/apps/landing/src/components/landing/Problem.astro @@ -0,0 +1,215 @@ +--- +import { Image } from "astro:assets"; +import identityIcon from "../../assets/landing/clawdentity_feature_identity_256.png"; +import communicationIcon from "../../assets/landing/clawdentity_feature_communication_256.png"; +import trustIcon from "../../assets/landing/clawdentity_feature_trust_256.png"; + +const problems = [ + { + icon: identityIcon, + title: "No Per-Agent Identity", + description: + "Receivers cannot prove which exact agent sent a request or who owns it. Any caller with the shared token looks the same to the gateway.", + }, + { + icon: communicationIcon, + title: "Shared-Secret Blast Radius", + description: + "If one token leaks, any caller can impersonate a trusted agent until rotation. One leak exposes every integration.", + }, + { + icon: trustIcon, + title: "Weak Revocation Model", + description: + "Disabling one compromised agent means rotating shared credentials across all integrations. No way to revoke a single identity.", + }, +]; +--- + +
+
+
+ +

The Problem with Shared Tokens

+

+ Shared gateway tokens work for transport, but not for identity-aware agent systems. +

+
+ +
+ {problems.map((problem, index) => ( +
+
+
+
+
+ +
+
+
+

{problem.title}

+

{problem.description}

+
+ ))} +
+
+
+ + diff --git a/apps/landing/src/components/landing/TheFix.astro b/apps/landing/src/components/landing/TheFix.astro new file mode 100644 index 0000000..55ef92f --- /dev/null +++ b/apps/landing/src/components/landing/TheFix.astro @@ -0,0 +1,188 @@ +--- +import FlowDiagram from "./FlowDiagram.astro"; +--- + +
+
+
+ +

+ The Fix: Clawdentity +

+

+ Each agent gets a unique DID and signed passport. Every request proves + exactly which agent sent it. Revocation is instant. +

+
+ +
+ +
+ +
+
+
+ AIT +
+
+ Agent Identity Token + A registry-signed passport (JWT / EdDSA) that uniquely identifies each agent +
+
+
+
+ PoP +
+
+ Proof-of-Possession + Every request signed with the agent's private key — never shared +
+
+
+
+ CRL +
+
+ Certificate Revocation List + Signed feed that proxies cache and refresh for instant revocation +
+
+
+
+
+ + diff --git a/apps/landing/src/components/landing/UseCases.astro b/apps/landing/src/components/landing/UseCases.astro new file mode 100644 index 0000000..0b3b35c --- /dev/null +++ b/apps/landing/src/components/landing/UseCases.astro @@ -0,0 +1,303 @@ +--- +import { Image } from "astro:assets"; +import identityIcon from "../../assets/landing/clawdentity_feature_identity_256.png"; +import communicationIcon from "../../assets/landing/clawdentity_feature_communication_256.png"; +import pairingIcon from "../../assets/landing/clawdentity_feature_pairing_256.png"; +import trustIcon from "../../assets/landing/clawdentity_feature_trust_256.png"; + +const useCases = [ + { + icon: communicationIcon, + title: "Multi-Agent Orchestration", + description: "Verify which agent is calling before executing sensitive workflows. Enforce per-caller policy by agent DID, with rate limits per agent.", + tags: ["Orchestration", "Security"], + featured: true, + }, + { + icon: identityIcon, + title: "Webhook Authentication", + description: "Replace shared webhook tokens with per-agent identities. Drop the Clawdentity proxy in front of OpenClaw or any webhook endpoint.", + tags: ["Integration"], + featured: false, + }, + { + icon: pairingIcon, + title: "Agent-to-Agent Trust", + description: "Two agents verify each other's identity before exchanging data. Mutual authentication with timestamp, nonce, and signature on every request.", + tags: ["Trust"], + featured: false, + }, + { + icon: trustIcon, + title: "Compliance & Audit", + description: "Every request is signed and traceable to a specific agent and owner. Body hash + PoP signature means any modification is detectable.", + tags: ["Compliance", "Audit"], + featured: false, + }, +]; +--- + +
+
+
+ +

Anywhere agents need to prove who they are

+

Secure agent-to-agent communication across any platform or framework.

+
+ +
+ {useCases.map((uc, index) => ( +
+
+
+
+
+ +
+
+ {uc.tags.map(tag => ( + {tag} + ))} +
+
+

{uc.title}

+

{uc.description}

+
+
+
+ ))} +
+
+
+ + diff --git a/apps/landing/src/content.config.ts b/apps/landing/src/content.config.ts new file mode 100644 index 0000000..a4eec59 --- /dev/null +++ b/apps/landing/src/content.config.ts @@ -0,0 +1,6 @@ +import { defineCollection } from "astro:content"; +import { docsSchema } from "@astrojs/starlight/schema"; + +export const collections = { + docs: defineCollection({ schema: docsSchema() }), +}; diff --git a/apps/landing/src/content/docs/api-reference/cli.mdx b/apps/landing/src/content/docs/api-reference/cli.mdx new file mode 100644 index 0000000..71766e7 --- /dev/null +++ b/apps/landing/src/content/docs/api-reference/cli.mdx @@ -0,0 +1,318 @@ +--- +title: CLI Reference +description: Complete command reference for the Clawdentity CLI. +--- + +The Clawdentity CLI (`clawdentity`) is the primary operator tool for managing agent identities, keys, and provider integrations. + +## Admin commands + +### `admin bootstrap` + +Bootstraps the registry by creating the first admin account and PAT. + +```bash +clawdentity admin bootstrap \ + --registry-url \ + --bootstrap-secret +``` + +The returned API key is printed once and persisted to local config. If config persistence fails, the token is still printed for manual recovery. + +## Config commands + +### `config init` + +Initialize local config file at `~/.clawdentity/config.json`. + +```bash +clawdentity config init [--registry-url ] +``` + +Options: +- `--registry-url ` — override registry URL + +Creates the file with default values if it does not already exist. Auto-fetches registry metadata via `GET /v1/metadata` to resolve `proxyUrl`. + +### `config set` + +Set a config value. + +```bash +clawdentity config set +``` + +Valid keys: `registryUrl`, `proxyUrl`, `apiKey`, `humanName`. + +### `config get` + +Get a resolved config value. + +```bash +clawdentity config get +``` + +Prints `(not set)` when the key has no value. + +### `config show` + +Show all resolved config values. + +```bash +clawdentity config show +``` + +Outputs JSON with `apiKey` masked as `********`. + +## Invite commands + +### `invite create` + +Creates a registry onboarding invite code. Requires admin authentication. + +```bash +clawdentity invite create [--expires-at ] [--registry-url ] +``` + +Options: +- `--expires-at ` — optional invite expiry (ISO-8601) +- `--registry-url ` — override registry URL + +Returns a single-use, time-limited invite code (`clw_inv_`). + +### `invite redeem` + +Redeems an invite code to create an account and receive an API key. + +```bash +clawdentity invite redeem [--display-name ] [--registry-url ] +``` + +Options: +- `--display-name ` — human display name for onboarding +- `--registry-url ` — override registry URL + +The plaintext PAT is printed before config persistence so operators can recover from local write failures. + +## Agent commands + +### `agent create` + +Creates a new agent identity using a two-step challenge-response handshake. + +```bash +clawdentity agent create [--framework ] [--ttl-days ] +``` + +Options: +- `--framework ` — agent framework label (registry defaults to `openclaw`) +- `--ttl-days ` — agent token TTL in days (registry default when omitted) + +Generates an Ed25519 keypair locally, proves key ownership to the registry, and stores all credentials under `~/.clawdentity/agents//`. + +### `agent inspect` + +Displays agent identity details and token metadata. + +```bash +clawdentity agent inspect +``` + +### `agent auth revoke` + +Revokes an agent's active auth session tokens. Use this when you need to invalidate connector/session auth for an agent without deleting the agent identity. + +```bash +clawdentity agent auth revoke +``` + +### `agent auth refresh` + +Refreshes agent auth tokens using Claw + PoP headers. + +```bash +clawdentity agent auth refresh +``` + +Rewrites `registry-auth.json` atomically on success. + +## API key commands + +### `api-key create` + +Creates a new PAT. The token is printed once and not persisted automatically. + +```bash +clawdentity api-key create --name +``` + +### `api-key list` + +Lists PAT metadata (never exposes token values). + +```bash +clawdentity api-key list +``` + +### `api-key revoke` + +Revokes a specific PAT by ULID. + +```bash +clawdentity api-key revoke +``` + +## Install command + +### `install` + +Detects or selects a provider platform and installs provider artifacts and defaults. + +```bash +clawdentity install [--for ] [--port ] [--token ] [--json] +``` + +Options: +- `--for ` — explicit platform (`openclaw`, `picoclaw`, `nanobot`, `nanoclaw`) +- `--port ` — webhook port override +- `--token ` — webhook auth token override +- `--json` — machine-readable JSON output + +List providers: + +```bash +clawdentity install --list [--json] +``` + +## Provider commands + +### `provider status` + +Reports auto-detected provider status or explicit provider diagnostics. + +```bash +clawdentity provider status [--for ] [--json] +``` + +### `provider setup` + +Configures provider runtime integration (paths, webhook settings, connector options). + +```bash +clawdentity provider setup \ + --for \ + [--agent-name ] \ + [--platform-base-url ] \ + [--webhook-host ] \ + [--webhook-port ] \ + [--webhook-token ] \ + [--connector-base-url ] \ + [--connector-url ] \ + [--relay-transform-peers-path ] \ + [--json] +``` + +### `provider doctor` + +Runs provider health checks for config, runtime wiring, and connector readiness. + +```bash +clawdentity provider doctor \ + [--for ] \ + [--peer ] \ + [--platform-state-dir ] \ + [--connector-base-url ] \ + [--skip-connector-runtime] \ + [--json] +``` + +### `provider relay-test` + +Sends a relay probe to a peer alias using provider runtime settings. + +```bash +clawdentity provider relay-test \ + [--for ] \ + [--peer ] \ + [--platform-state-dir ] \ + [--platform-base-url ] \ + [--webhook-token ] \ + [--connector-base-url ] \ + [--message ] \ + [--session-id ] \ + [--no-preflight] \ + [--json] +``` + +## Pairing and cross-agent trust + +Proxy trust pairing is supported through proxy API routes (`POST /pair/start`, `POST /pair/confirm`, `POST /pair/status`). + +The current Rust CLI surface in this branch does not expose `pair` subcommands yet. Use the proxy API/SDK flow documented in: +- [/api-reference/proxy/](/api-reference/proxy/) +- [/guides/discovery/](/guides/discovery/) + +## Connector commands + +### `connector start` + +Starts the local relay connector runtime. + +```bash +clawdentity connector start \ + [--proxy-ws-url ] \ + [--openclaw-base-url ] \ + [--openclaw-hook-path ] \ + [--openclaw-hook-token ] +``` + +Options: +- `--proxy-ws-url ` — proxy websocket URL (or `CLAWDENTITY_PROXY_WS_URL` env) +- `--openclaw-base-url ` — OpenClaw base URL (default `OPENCLAW_BASE_URL` env or `http://127.0.0.1:18789`) +- `--openclaw-hook-path ` — OpenClaw hooks path (default `OPENCLAW_HOOK_PATH` env or `/hooks/agent`) +- `--openclaw-hook-token ` — OpenClaw hooks token (default `OPENCLAW_HOOK_TOKEN` env) + +### `connector service install` + +Installs autostart service using OS-native tooling (launchd on macOS, systemd on Linux). + +```bash +clawdentity connector service install \ + [--platform ] \ + [--proxy-ws-url ] \ + [--openclaw-base-url ] \ + [--openclaw-hook-path ] \ + [--openclaw-hook-token ] +``` + +Options: +- `--platform ` — service platform: `auto`, `launchd`, or `systemd` (default `auto`) +- `--proxy-ws-url ` — proxy websocket URL (or `CLAWDENTITY_PROXY_WS_URL` env) +- `--openclaw-base-url ` — OpenClaw base URL override for connector runtime +- `--openclaw-hook-path ` — OpenClaw hooks path override for connector runtime +- `--openclaw-hook-token ` — OpenClaw hooks token override for connector runtime + +### `connector service uninstall` + +Removes the autostart service. Safe to re-run. + +```bash +clawdentity connector service uninstall [--platform ] +``` + +Options: +- `--platform ` — service platform: `auto`, `launchd`, or `systemd` (default `auto`) + +## Command idempotency + +| Command | Idempotent? | Note | +|---------|-------------|------| +| `config init` | Yes | Safe to re-run | +| `invite redeem` | **No** | One-time; invite consumed on success | +| `agent create` | No | Fails if agent directory exists | +| `agent auth revoke` | Yes | Safe to re-run; token state remains revoked | +| `install --for ` | Yes | Reports platform install and verify status | +| `provider setup --for ` | Usually yes | Primary reconciliation re-entry point | +| `provider doctor --for ` | Yes | Read-only health checks | +| `provider relay-test --for ` | Mostly yes | Sends real probe traffic | +| `connector service install` | Yes | Idempotent | diff --git a/apps/landing/src/content/docs/api-reference/protocol.mdx b/apps/landing/src/content/docs/api-reference/protocol.mdx new file mode 100644 index 0000000..bc1d114 --- /dev/null +++ b/apps/landing/src/content/docs/api-reference/protocol.mdx @@ -0,0 +1,200 @@ +--- +title: Protocol Reference +description: DID format, signing rules, and protocol constants used by Clawdentity. +--- + +## DID format + +Clawdentity uses a custom DID method with ULID-based identifiers: + +| Type | Format | Example | +|------|--------|---------| +| Agent | `did:cdi::agent:` | `did:cdi:registry.clawdentity.com:agent:01HXK5M2V3N7P8Q9R0S1T2U3V4` | +| Human | `did:cdi::human:` | `did:cdi:registry.clawdentity.com:human:01HXK5M2V3N7P8Q9R0S1T2U3V5` | + +## Cryptographic primitives + +| Primitive | Algorithm | Usage | +|-----------|-----------|-------| +| AIT signing | EdDSA (Ed25519) | Registry signs AITs and CRLs | +| PoP signing | Ed25519 | Agent signs each request | +| Body hashing | SHA-256 | Body integrity in PoP | +| Nonce generation | Random bytes | Replay protection | + +## AIT (Agent Identity Token) + +JWT with `alg=EdDSA`, `typ=AIT`. + +### Required claims + +| Claim | Type | Description | +|-------|------|-------------| +| `iss` | string | Registry URL | +| `sub` | string | Agent DID | +| `owner` / `ownerDid` | string | Human DID | +| `cnf` | object | Confirmation key (`cnf.jwk.x` = public key) | +| `iat` | number | Issued at (Unix seconds) | +| `nbf` | number | Not before (Unix seconds) | +| `exp` | number | Expiry (Unix seconds) | +| `jti` | string | Token ID (for revocation tracking) | +| `name` | string | Agent name (strict validation) | +| `framework` | string | Agent framework identifier | + +### Constraints + +- One active AIT per agent DID +- Reissue/rotate automatically revokes the previous `jti` +- Expiry window: 1–90 days +- `name`: must match `/^[A-Za-z0-9._ -]{1,64}$/` (max 64 chars) +- `framework`: max 32 characters, no control characters +- `description`: optional, max 280 characters, no control characters +- `jti`: must be a valid ULID +- `cnf.jwk.x`: must decode to a 32-byte Ed25519 public key + +## CRL (Certificate Revocation List) + +Signed JWT with `typ=CRL`. + +- Contains list of revoked `jti` values with metadata +- Default cache/refresh interval: 300 seconds +- Staleness policy: configurable as `fail-open` or `fail-closed` + +### Revocation entry fields + +| Field | Type | Description | +|-------|------|-------------| +| `jti` | string | Revoked token ID (must be a valid ULID) | +| `agentDid` | string | Agent DID being revoked | +| `reason` | string? | Optional reason, max 280 characters | +| `revokedAt` | number | Unix timestamp of revocation | + +## Agent registration proof + +During agent registration, the agent signs a canonical proof message to bind its identity to the registration challenge. + +### Proof template + +Version: `clawdentity.register.v1` + +``` +clawdentity.register.v1 +challengeId:{challengeId} +nonce:{nonce} +ownerDid:{ownerDid} +publicKey:{publicKey} +name:{name} +framework:{framework} +ttlDays:{ttlDays} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `challengeId` | string | yes | Challenge ID from the registry | +| `nonce` | string | yes | Unique nonce for the proof | +| `ownerDid` | string | yes | Human DID of the agent owner | +| `publicKey` | string | yes | Agent's Ed25519 public key | +| `name` | string | yes | Agent name | +| `framework` | string | no | Framework identifier (empty string if omitted) | +| `ttlDays` | number | no | Requested TTL in days (empty string if omitted) | + +Optional fields are serialized as empty strings when not provided (e.g. `framework:`). + +## PoP request signing + +### Required headers + +| Header | Value | +|--------|-------| +| `Authorization` | `Claw ` | +| `X-Claw-Timestamp` | Unix seconds | +| `X-Claw-Nonce` | Base64url random bytes | +| `X-Claw-Body-SHA256` | Base64url SHA-256 of raw body | +| `X-Claw-Proof` | Base64url Ed25519 signature | + +### Canonical string format + +The proof signature is computed over a newline-joined canonical string: + +``` +CLAW-PROOF-V1 +METHOD +pathWithQuery +timestamp +nonce +bodyHash +``` + +Each field is a raw value with no labels. `METHOD` is uppercased (e.g. `POST`), `pathWithQuery` includes the full path and query string, `timestamp` is the Unix-seconds value from `X-Claw-Timestamp`, `nonce` is the value from `X-Claw-Nonce`, and `bodyHash` is the Base64url SHA-256 from `X-Claw-Body-SHA256`. + +### Verification rules + +| Rule | Default | +|------|---------| +| Max timestamp skew | 300 seconds | +| Nonce replay cache TTL | 5 minutes | +| Proof key source | `cnf.jwk.x` from AIT | + +## Protocol endpoints + +Constants exported from `@clawdentity/protocol`: + +| Constant | Path | +|----------|------| +| `ADMIN_BOOTSTRAP_PATH` | `/v1/admin/bootstrap` | +| `ADMIN_INTERNAL_SERVICES_PATH` | `/v1/admin/internal-services` | +| `AGENT_REGISTRATION_CHALLENGE_PATH` | `/v1/agents/challenge` | +| `AGENT_AUTH_REFRESH_PATH` | `/v1/agents/auth/refresh` | +| `AGENT_AUTH_VALIDATE_PATH` | `/v1/agents/auth/validate` | +| `INVITES_PATH` | `/v1/invites` | +| `INVITES_REDEEM_PATH` | `/v1/invites/redeem` | +| `ME_API_KEYS_PATH` | `/v1/me/api-keys` | +| `REGISTRY_METADATA_PATH` | `/v1/metadata` | +| `INTERNAL_IDENTITY_AGENT_OWNERSHIP_PATH` | `/internal/v1/identity/agent-ownership` | +| `RELAY_CONNECT_PATH` | `/v1/relay/connect` | +| `RELAY_DELIVERY_RECEIPTS_PATH` | `/v1/relay/delivery-receipts` | + +## Headers + +| Constant | Header | +|----------|--------| +| `RELAY_RECIPIENT_AGENT_DID_HEADER` | `x-claw-recipient-agent-did` | +| `RELAY_CONVERSATION_ID_HEADER` | `x-claw-conversation-id` | +| `RELAY_DELIVERY_RECEIPT_URL_HEADER` | `x-claw-delivery-receipt-url` | +| `REQUEST_ID_HEADER` | `x-request-id` | + +## Connector frame protocol + +The connector uses a WebSocket-based framing protocol for real-time agent communication. + +### Frame base schema + +Every frame includes these base fields: + +| Field | Type | Description | +|-------|------|-------------| +| `v` | number | Frame version (must be `1`) | +| `id` | string | Unique frame ID (ULID) | +| `ts` | string | ISO-8601 timestamp | +| `type` | string | Frame type discriminator | + +### Frame types + +| Type | Direction | Additional Fields | Description | +|------|-----------|-------------------|-------------| +| `heartbeat` | client/server | -- | Keep-alive ping | +| `heartbeat_ack` | server/client | `ackId` | Acknowledges a heartbeat | +| `deliver` | server -> client | `fromAgentDid`, `toAgentDid`, `payload`, `contentType?`, `conversationId?`, `replyTo?` | Delivers a message to an agent | +| `deliver_ack` | client -> server | `ackId`, `accepted`, `reason?` | Acknowledges delivery | +| `enqueue` | client -> server | `toAgentDid`, `payload`, `conversationId?`, `replyTo?` | Enqueues a message for another agent | +| `enqueue_ack` | server -> client | `ackId`, `accepted`, `reason?` | Acknowledges enqueue | + +### Frame constants + +| Constant | Value | Description | +|----------|-------|-------------| +| `CONNECTOR_FRAME_VERSION` | `1` | Current frame protocol version | +| `DEFAULT_HEARTBEAT_INTERVAL_MS` | `30000` (30s) | Heartbeat send interval | +| `DEFAULT_RECONNECT_MIN_DELAY_MS` | `1000` (1s) | Minimum reconnect backoff | +| `DEFAULT_RECONNECT_MAX_DELAY_MS` | `30000` (30s) | Maximum reconnect backoff | +| `DEFAULT_RECONNECT_BACKOFF_FACTOR` | `2` | Exponential backoff multiplier | +| `DEFAULT_RECONNECT_JITTER_RATIO` | `0.2` | Jitter ratio for reconnect delay | diff --git a/apps/landing/src/content/docs/api-reference/proxy.mdx b/apps/landing/src/content/docs/api-reference/proxy.mdx new file mode 100644 index 0000000..c5e2318 --- /dev/null +++ b/apps/landing/src/content/docs/api-reference/proxy.mdx @@ -0,0 +1,278 @@ +--- +title: Proxy API +description: HTTP API reference for the Clawdentity verification proxy. +--- + +The proxy is a Cloudflare Worker that verifies Clawdentity identity headers and forwards authenticated requests to OpenClaw. It also supports WebSocket relay connections for real-time agent-to-agent message delivery. + +## Rate limits + +The proxy enforces per-agent rate limiting. The default is **60 requests/minute per agent** with a 60-second sliding window. Configure via `AGENT_RATE_LIMIT_REQUESTS_PER_MINUTE` and `AGENT_RATE_LIMIT_WINDOW_MS` environment variables. + +## Endpoints + +### Health check + +```http +GET /health +``` + +Returns proxy status, version, and environment. This endpoint bypasses authentication. + +### Agent hook (relay delivery) + +```http +POST /hooks/agent +``` + +The primary proxy route. Accepts requests from authenticated agents and delivers them to the recipient agent's relay session via Durable Object. + +**Required headers:** + +| Header | Value | +|--------|-------| +| `Authorization` | `Claw ` | +| `X-Claw-Timestamp` | Unix seconds | +| `X-Claw-Nonce` | Base64url random value | +| `X-Claw-Body-SHA256` | Base64url SHA-256 of the raw body | +| `X-Claw-Proof` | Ed25519 signature over the canonical string | +| `X-Claw-Agent-Access` | Agent session access token | +| `X-Claw-Recipient-Agent-Did` | DID of the target agent (e.g., `did:cdi:registry.clawdentity.com:agent:01HXYZ...`) | +| `Content-Type` | Must be `application/json` | + +#### Verification pipeline + +1. Verify AIT signature against registry EdDSA keys +2. Check AIT expiry +3. Verify timestamp skew (max +/-300 seconds) +4. Verify PoP signature against AIT `cnf` public key +5. Reject nonce replay (per-agent, 5-minute cache) +6. Check CRL for revoked `jti` +7. Enforce trust policy +8. Validate agent access token via registry +9. Apply per-agent rate limits + +**Identity injection:** + +When `INJECT_IDENTITY_INTO_MESSAGE=true` (default), the proxy prepends an identity block to the `message` field of the JSON payload before delivery. The block is formatted as: + +``` +[Clawdentity Identity] +agentDid: did:cdi:registry.clawdentity.com:agent:01HXYZ... +ownerDid: did:cdi:registry.clawdentity.com:human:01HABC... +issuer: https://api.clawdentity.com +aitJti: 01HJKL... +``` + +Sanitization rules: +- Control characters (ASCII 0--31 and 127) are stripped from all fields +- Consecutive whitespace is collapsed to a single space +- Fields are truncated to maximum lengths: `agentDid` (160), `ownerDid` (160), `issuer` (200), `aitJti` (64) +- Empty fields after sanitization are replaced with `"unknown"` + +Identity injection only applies when the payload is a JSON object containing a `message` field of type `string`. + +**Response (202):** + +```json +{ + "accepted": true, + "delivered": true, + "connectedSockets": 1 +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `accepted` | `boolean` | Always `true` on success | +| `delivered` | `boolean` | Whether the message was delivered to a connected connector | +| `connectedSockets` | `number` | Number of WebSocket connections for the recipient agent | + +### Relay connect (WebSocket) + +```http +GET /v1/relay/connect +``` + +Upgrades to a WebSocket connection for real-time relay message delivery. Used by the connector runtime to maintain a persistent link between the local agent and the proxy. + +Requires the same Claw authentication headers as `/hooks/agent` (including `Authorization`, `X-Claw-Agent-Access`, and all PoP headers). Must include the `Upgrade: websocket` header. + +**Session model:** Each agent DID maps to a Durable Object (`AgentRelaySession`). When a connector connects via WebSocket, the Durable Object accepts the socket and schedules heartbeat alarms at 30-second intervals. Incoming delivery requests from `/hooks/agent` are forwarded to the Durable Object, which sends a `deliver` frame over the WebSocket and waits for a `deliver_ack` response from the connector. + +### Pairing: Start (`POST /pair/start`) + +Initiator starts a pairing session. Requires `Authorization: Claw ` with PoP headers. Ownership is verified internally via proxy-to-registry service auth. + +**Request body:** + +```json +{ + "ttlSeconds": 300, + "initiatorProfile": { + "agentName": "alpha", + "humanName": "Ravi" + } +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `ttlSeconds` | number | No | Ticket TTL (default 300, max 900) | +| `initiatorProfile.agentName` | string | Yes | Initiator agent name | +| `initiatorProfile.humanName` | string | Yes | Initiator human name | + +**Response (200):** Returns pairing ticket (`clwpair1_...`) and metadata. + +### Pairing: Confirm (`POST /pair/confirm`) + +Responder confirms pairing with a ticket. Creates a bidirectional trust pair. Requires `Authorization: Claw ` with PoP headers. + +**Request body:** + +```json +{ + "ticket": "clwpair1_...", + "responderProfile": { + "agentName": "beta", + "humanName": "Ira" + } +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `ticket` | string | Yes | Pairing ticket from initiator | +| `responderProfile.agentName` | string | Yes | Responder agent name | +| `responderProfile.humanName` | string | Yes | Responder human name | + +### Pairing: Status (`POST /pair/status`) + +Check pairing ticket status. Returns `pending` or `confirmed`. Only participants can check. + +### Delivery receipts + +#### `POST /v1/relay/delivery-receipts` + +Connector sends a receipt after processing a relayed message. Requires Claw authentication headers. + +**Request body:** + +```json +{ + "requestId": "", + "recipientAgentDid": "did:cdi::agent:", + "status": "processed_by_openclaw", + "timestamp": "2025-01-15T10:30:00.000Z" +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `requestId` | string | Yes | The `x-request-id` from the original relay request | +| `recipientAgentDid` | string | Yes | DID of the agent that received the message | +| `status` | string | Yes | `processed_by_openclaw` or `dead_lettered` | +| `timestamp` | string | Yes | ISO-8601 timestamp of when the message was processed | + +**Response (201):** Receipt stored. + +#### `GET /v1/relay/delivery-receipts` + +Query receipt status by request ID and recipient. Requires Claw authentication headers. + +**Query parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `requestId` | string | Yes | The original relay request ID | +| `recipientAgentDid` | string | Yes | The recipient agent DID | + +**Response (200):** + +```json +{ + "receipts": [ + { + "requestId": "", + "recipientAgentDid": "did:cdi::agent:", + "status": "processed_by_openclaw", + "receivedAt": "2025-01-15T10:30:00.000Z" + } + ] +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `receipts` | array | List of matching delivery receipts | +| `receipts[].requestId` | string | Original relay request ID | +| `receipts[].recipientAgentDid` | string | Recipient agent DID | +| `receipts[].status` | string | `processed_by_openclaw` or `dead_lettered` | +| `receipts[].receivedAt` | string | ISO-8601 timestamp when the receipt was recorded | + +## Error responses + +All error responses follow a consistent envelope format: + +```json +{ + "error": { + "code": "PROXY_AUTH_INVALID_AIT", + "message": "Human-readable error description" + } +} +``` + +Each response includes an `x-request-id` header for tracing. + +### Authentication errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `PROXY_AUTH_MISSING_TOKEN` | 401 | Authorization header is missing | +| `PROXY_AUTH_INVALID_SCHEME` | 401 | Authorization is not in `Claw ` format | +| `PROXY_AUTH_INVALID_AIT` | 401 | AIT signature is invalid or token is malformed | +| `PROXY_AUTH_INVALID_TIMESTAMP` | 401 | X-Claw-Timestamp is missing or not a valid unix seconds integer | +| `PROXY_AUTH_TIMESTAMP_SKEW` | 401 | Request timestamp exceeds allowed +/-300 second skew | +| `PROXY_AUTH_INVALID_PROOF` | 401 | PoP signature verification failed | +| `PROXY_AUTH_INVALID_NONCE` | 401 | Nonce validation failed | +| `PROXY_AUTH_REPLAY` | 401 | Nonce has been seen before (replay detected) | +| `PROXY_AUTH_REVOKED` | 401 | Agent AIT has been revoked via CRL | +| `PROXY_AUTH_FORBIDDEN` | 403 | Agent DID is not in a confirmed trust pair | +| `PROXY_AGENT_ACCESS_REQUIRED` | 401 | X-Claw-Agent-Access header is missing | +| `PROXY_AGENT_ACCESS_INVALID` | 401 | Agent session access token is expired or invalid | + +### Relay delivery errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `PROXY_HOOK_UNSUPPORTED_MEDIA_TYPE` | 415 | Content-Type is not `application/json` | +| `PROXY_HOOK_INVALID_JSON` | 400 | Request body is not valid JSON | +| `PROXY_HOOK_RECIPIENT_REQUIRED` | 400 | `X-Claw-Recipient-Agent-Did` header is missing | +| `PROXY_HOOK_RECIPIENT_INVALID` | 400 | Recipient header is not a valid agent DID | +| `PROXY_RELAY_UNAVAILABLE` | 503 | Relay session Durable Object namespace is not available | +| `PROXY_RELAY_DELIVERY_FAILED` | 502 | Delivery to the relay session failed (internal error or timeout) | +| `PROXY_RELAY_CONNECTOR_OFFLINE` | 502 | No connector is currently connected for the target agent | + +### WebSocket relay errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `PROXY_RELAY_UPGRADE_REQUIRED` | 426 | Request did not include WebSocket upgrade header | +| `PROXY_RELAY_AUTH_CONTEXT_MISSING` | 500 | Internal error: auth context was not set | + +### Infrastructure errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `PROXY_AUTH_DEPENDENCY_UNAVAILABLE` | 503 | Registry keys, CRL, or agent auth validation is unreachable | +| `PROXY_RATE_LIMIT_EXCEEDED` | 429 | Per-agent rate limit exceeded (default: 60/minute) | + +### Pairing errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `PROXY_PAIR_OWNERSHIP_FORBIDDEN` | 403 | Initiator ownership check failed | +| `PROXY_PAIR_OWNERSHIP_UNAVAILABLE` | 503 | Registry ownership lookup unavailable | +| `PROXY_PAIR_TICKET_NOT_FOUND` | 404 | Pairing ticket is invalid or expired | +| `PROXY_PAIR_TICKET_EXPIRED` | 410 | Pairing ticket has expired | diff --git a/apps/landing/src/content/docs/api-reference/registry.mdx b/apps/landing/src/content/docs/api-reference/registry.mdx new file mode 100644 index 0000000..f80bee0 --- /dev/null +++ b/apps/landing/src/content/docs/api-reference/registry.mdx @@ -0,0 +1,465 @@ +--- +title: Registry API +description: HTTP API reference for the Clawdentity identity registry. +--- + +The registry is a Cloudflare Worker that issues agent identities, manages revocation, and handles onboarding. All routes use JSON request/response bodies. + +## Public endpoints (no auth required) + +### Health check + +```http +GET /health +``` + +Returns registry status, version, and environment. + +### Registry signing keys + +```http +GET /.well-known/claw-keys.json +``` + +Returns the registry's Ed25519 public keys used to verify AIT and CRL signatures. Clients should cache these keys for offline verification. + +### Certificate Revocation List + +```http +GET /v1/crl +``` + +Returns a signed JWT (`typ=CRL`) containing the list of revoked AIT `jti` values. Clients should cache this and refresh at the configured interval (default: 300 seconds). Rate-limited to 30 requests/minute per IP. + +### Resolve agent + +```http +GET /v1/resolve/:id +``` + +Resolves an agent by ULID and returns public metadata. Rate-limited to 10 requests/minute per IP. + +**Response schema:** + +| Field | Type | Description | +|-------|------|-------------| +| `did` | `string` | Agent DID | +| `name` | `string` | Agent display name | +| `framework` | `string` | Agent framework (defaults to `"openclaw"`) | +| `status` | `string` | `"active"` or `"revoked"` | +| `ownerDid` | `string` | Owner human DID | + +### Registry metadata + +```http +GET /v1/metadata +``` + +Returns registry metadata including service URLs and environment information. + +**Response:** + +| Field | Type | Description | +|-------|------|-------------| +| `registryUrl` | string | Registry base URL | +| `proxyUrl` | string | Proxy base URL | +| `environment` | string | Current environment (`local`, `dev`, `production`) | +| `version` | string | Registry version | + +### Redeem invite + +```http +POST /v1/invites/redeem +``` + +Redeems a registry onboarding invite code. Creates a human account and returns an API key. No authentication required — the invite code itself is the credential. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `code` | `string` | Yes | Invite code (max 128 characters) | +| `displayName` | `string` | No | Human display name (max 64 characters, defaults to `"User"`) | +| `apiKeyName` | `string` | No | Name for the created API key (max 64 characters, defaults to `"invite"`) | + +**Response (201):** + +```json +{ + "human": { + "id": "...", + "did": "did:cdi::human:...", + "displayName": "User", + "role": "user", + "status": "active" + }, + "apiKey": { + "id": "...", + "name": "invite", + "token": "clw_pat_..." + } +} +``` + +### Agent auth refresh + +```http +POST /v1/agents/auth/refresh +``` + +Refreshes agent auth tokens. Requires `Authorization: Claw ` with PoP headers. Rate-limited to 20 requests/minute per IP. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `refreshToken` | `string` | Yes | Current refresh token | + +**Response schema:** + +| Field | Type | Description | +|-------|------|-------------| +| `agentAuth.tokenType` | `string` | Always `"Bearer"` | +| `agentAuth.accessToken` | `string` | New access token | +| `agentAuth.accessExpiresAt` | `string` | Access token expiry (ISO-8601) | +| `agentAuth.refreshToken` | `string` | New refresh token (rotated) | +| `agentAuth.refreshExpiresAt` | `string` | Refresh token expiry (ISO-8601) | + +Token defaults: access tokens expire after 15 minutes, refresh tokens expire after 30 days. + +### Agent auth validate + +```http +POST /v1/agents/auth/validate +``` + +Validates an agent's access token. Used by the proxy to verify agent session tokens. Rate-limited to 120 requests/minute per IP. + +**Required headers:** + +| Header | Description | +|--------|-------------| +| `X-Claw-Agent-Access` | Agent session access token | + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `agentDid` | `string` | Yes | Agent DID to validate | +| `aitJti` | `string` | Yes | AIT `jti` claim to match | + +**Response:** `204 No Content` on success. + +## Authenticated endpoints (PAT required) + +All endpoints below require `Authorization: Bearer ` header. + +### Current user + +```http +GET /v1/me +``` + +Returns the authenticated human's profile. + +### Invite management + +```http +POST /v1/invites +``` + +Creates a new onboarding invite code with optional expiry. Admin only. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `expiresAt` | `string \| null` | No | ISO-8601 expiry datetime (must be in the future, or `null` for no expiry) | + +### API key lifecycle + +```http +POST /v1/me/api-keys +``` + +Creates a new PAT. The plaintext token is returned once in the response and cannot be retrieved later. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | `string` | No | Key name (max 64 characters, defaults to `"api-key"`) | + +```http +GET /v1/me/api-keys +``` + +Lists PAT metadata (`id`, `name`, `status`, `createdAt`, `lastUsedAt`). Never returns token values. + +```http +DELETE /v1/me/api-keys/:id +``` + +Revokes a specific PAT. Unrelated active PATs continue to work. Returns `204 No Content`. + +### Agent management + +#### List agents + +```http +GET /v1/agents +``` + +Lists all agents owned by the authenticated human. + +**Query parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `status` | `string` | No | Filter by `"active"` or `"revoked"` | +| `framework` | `string` | No | Filter by framework name (max 32 characters) | +| `limit` | `integer` | No | Page size, 1--100 (default: 20) | +| `cursor` | `string` | No | ULID cursor from previous page's `pagination.nextCursor` | + +**Response schema:** + +```json +{ + "agents": [ + { + "id": "...", + "did": "did:cdi::agent:...", + "name": "my-agent", + "status": "active", + "expires": "2025-04-01T00:00:00.000Z" + } + ], + "pagination": { + "limit": 20, + "nextCursor": "01HXYZ..." // null when no more pages + } +} +``` + +Pagination uses descending ULID order. Pass `nextCursor` as the `cursor` parameter to fetch the next page. + +#### Create registration challenge + +```http +POST /v1/agents/challenge +``` + +Initiates agent registration. Accepts a public key and returns a `challengeId`, `nonce`, and `ownerDid`. The challenge expires after 5 minutes. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `publicKey` | `string` | Yes | Base64url-encoded Ed25519 public key (must decode to exactly 32 bytes) | + +**Response (201):** + +| Field | Type | Description | +|-------|------|-------------| +| `challengeId` | `string` | ULID identifying this challenge | +| `nonce` | `string` | Base64url-encoded 24-byte random nonce | +| `ownerDid` | `string` | Owner DID to include in the proof message | +| `expiresAt` | `string` | ISO-8601 challenge expiry (5 minutes from creation) | +| `algorithm` | `string` | Always `"Ed25519"` | +| `messageTemplate` | `string` | Template for the canonical proof message | + +#### Register agent + +```http +POST /v1/agents +``` + +Completes agent registration. Requires the challenge signature proving ownership of the public key. Returns the agent record, signed AIT, and initial auth tokens. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | `string` | Yes | Agent name (validated via `validateAgentName`) | +| `publicKey` | `string` | Yes | Base64url-encoded Ed25519 public key (32 bytes decoded) | +| `challengeId` | `string` | Yes | ULID from the challenge response | +| `challengeSignature` | `string` | Yes | Base64url-encoded Ed25519 signature (64 bytes decoded) over the canonical proof message | +| `framework` | `string` | No | Agent framework (max 32 characters, defaults to `"openclaw"`) | +| `ttlDays` | `integer` | No | AIT time-to-live in days, 1--90 (defaults to 30) | + +**Response (201):** + +```json +{ + "agent": { + "id": "...", + "did": "did:cdi::agent:...", + "ownerDid": "did:cdi::human:...", + "name": "my-agent", + "framework": "openclaw", + "publicKey": "...", + "currentJti": "...", + "ttlDays": 30, + "status": "active", + "expiresAt": "...", + "createdAt": "...", + "updatedAt": "..." + }, + "ait": "", + "agentAuth": { + "tokenType": "Bearer", + "accessToken": "...", + "accessExpiresAt": "...", + "refreshToken": "...", + "refreshExpiresAt": "..." + } +} +``` + +#### Reissue agent AIT + +```http +POST /v1/agents/:id/reissue +``` + +Reissues an agent's AIT. The previous AIT `jti` is automatically added to the CRL with reason `"reissued"`. Only active agents can be reissued; revoked agents return a `409` error. + +**Response:** + +```json +{ + "agent": { ... }, + "ait": "" +} +``` + +#### Revoke agent auth session + +```http +DELETE /v1/agents/:id/auth/revoke +``` + +Revokes an agent's auth session without deleting the agent. Returns `204 No Content`. + +#### Delete agent + +```http +DELETE /v1/agents/:id +``` + +Deletes an agent identity, revokes its AIT, and revokes its auth session. Returns `204 No Content`. + +## Admin endpoints + +### Bootstrap + +```http +POST /v1/admin/bootstrap +``` + +Creates the first admin account and PAT. Requires `x-bootstrap-secret` header. Can only be called once. + +### Internal services + +```http +POST /v1/admin/internal-services +``` + +Manages internal service authentication between proxy and registry. Admin only. + +## Internal endpoints + +### Agent ownership verification + +```http +POST /internal/v1/identity/agent-ownership +``` + +Verifies that an agent is owned by the authenticated caller. Used internally by the proxy during pairing to validate initiator ownership. + +## Error codes + +All error responses follow a consistent envelope format: + +```json +{ + "error": { + "code": "ERROR_CODE", + "message": "Human-readable description" + } +} +``` + +In development environments, errors may include a `details` object with `fieldErrors` and `formErrors` for validation failures. + +### Invite errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `INVITE_CREATE_INVALID` | 400 | Invite create payload failed validation | +| `INVITE_CREATE_FORBIDDEN` | 403 | Caller does not have admin role | +| `INVITE_REDEEM_INVALID` | 400 | Invite redeem payload failed validation | +| `INVITE_REDEEM_CODE_INVALID` | 400 | Invite code does not exist | +| `INVITE_REDEEM_EXPIRED` | 400 | Invite code has expired | +| `INVITE_REDEEM_ALREADY_USED` | 409 | Invite code has already been redeemed | + +### Agent registration errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `AGENT_REGISTRATION_CHALLENGE_INVALID` | 400 | Challenge request payload failed validation | +| `AGENT_REGISTRATION_CHALLENGE_NOT_FOUND` | 400 | Challenge ID not found for this owner | +| `AGENT_REGISTRATION_CHALLENGE_EXPIRED` | 400 | Challenge has expired (5-minute TTL) | +| `AGENT_REGISTRATION_CHALLENGE_REPLAYED` | 400 | Challenge has already been used | +| `AGENT_REGISTRATION_INVALID` | 400 | Registration payload failed validation | +| `AGENT_REGISTRATION_PROOF_MISMATCH` | 400 | Public key in registration does not match the challenge | +| `AGENT_REGISTRATION_PROOF_INVALID` | 400 | Ed25519 signature verification failed | + +### Agent lifecycle errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `AGENT_NOT_FOUND` | 404 | Agent does not exist or is not owned by caller | +| `AGENT_REVOKE_INVALID_PATH` | 400 | Agent ID in URL path is not a valid ULID | +| `AGENT_REVOKE_INVALID_STATE` | 409 | Agent cannot be revoked in its current state | +| `AGENT_REISSUE_INVALID_STATE` | 409 | Agent cannot be reissued (e.g., already revoked) | +| `AGENT_RESOLVE_INVALID_PATH` | 400 | Resolve ID in URL path is not a valid ULID | +| `AGENT_LIST_INVALID_QUERY` | 400 | Agent list query parameters failed validation | + +### Agent auth errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `AGENT_AUTH_VALIDATE_INVALID` | 400 | Validate payload is missing or malformed | +| `AGENT_AUTH_VALIDATE_UNAUTHORIZED` | 401 | Access token is invalid or does not match | +| `AGENT_AUTH_VALIDATE_EXPIRED` | 401 | Access token has expired | +| `AGENT_AUTH_REFRESH_INVALID` | 400/401 | Refresh payload is invalid or token does not match | +| `AGENT_AUTH_REFRESH_UNAUTHORIZED` | 401 | Refresh request is unauthorized | +| `AGENT_AUTH_REFRESH_REVOKED` | 401 | Refresh token session has been revoked | +| `AGENT_AUTH_REFRESH_EXPIRED` | 401 | Refresh token has expired | +| `AGENT_AUTH_REFRESH_CONFLICT` | 409 | Session state changed during refresh (retry) | + +### API key errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `API_KEY_CREATE_INVALID` | 400 | API key create payload failed validation | +| `API_KEY_REVOKE_INVALID_PATH` | 400 | API key ID in URL path is not a valid ULID | +| `API_KEY_NOT_FOUND` | 404 | API key does not exist or is not owned by caller | + +### Admin errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `ADMIN_BOOTSTRAP_DISABLED` | 503 | Bootstrap secret is not configured | +| `ADMIN_BOOTSTRAP_UNAUTHORIZED` | 401 | Bootstrap secret is missing or incorrect | +| `ADMIN_BOOTSTRAP_INVALID` | 400 | Bootstrap payload failed validation | +| `ADMIN_BOOTSTRAP_ALREADY_COMPLETED` | 409 | An admin account already exists | + +### General errors + +| Code | HTTP | Meaning | +|------|------|---------| +| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests for this endpoint | +| `CRL_NOT_FOUND` | 404 | No revocations exist yet (CRL snapshot unavailable) | +| `CRL_BUILD_FAILED` | 500 | Internal CRL generation error | diff --git a/apps/landing/src/content/docs/api-reference/sdk.mdx b/apps/landing/src/content/docs/api-reference/sdk.mdx new file mode 100644 index 0000000..c37de3a --- /dev/null +++ b/apps/landing/src/content/docs/api-reference/sdk.mdx @@ -0,0 +1,196 @@ +--- +title: SDK Reference +description: TypeScript SDK for signing, verifying, and managing Clawdentity identities. +--- + +The `@clawdentity/sdk` package provides the core TypeScript SDK for working with Clawdentity identities, signatures, and revocation. + +## Installation + +```bash +pnpm add @clawdentity/sdk +``` + +## Core modules + +### Signing and verification + +The SDK handles AIT verification and PoP request signing: + +- **AIT verification** — verify registry-signed JWTs offline using cached public keys +- **PoP signing** — generate `X-Claw-*` headers for outbound requests +- **PoP verification** — validate inbound PoP signatures against AIT `cnf` keys +- **CRL verification** — verify signed CRL JWTs + +### CRL cache + +Manages local caching and refresh of the Certificate Revocation List: + +- Configurable refresh interval (default: `DEFAULT_CRL_REFRESH_INTERVAL_MS = 300000`, 5 min) +- Maximum age before considered stale (default: `DEFAULT_CRL_MAX_AGE_MS = 900000`, 15 min) +- Staleness policy via `staleBehavior`: + - `fail-open` (default) — when stale, returns warnings but allows `isRevoked()` to return `false` (treats as not revoked) + - `fail-closed` — when stale, throws `AppError` with code `CRL_CACHE_STALE` (status 503) + +```typescript +import { createCrlCache } from "@clawdentity/sdk"; + +const cache = createCrlCache({ + fetchLatest: () => fetchCrlFromRegistry(), + staleBehavior: "fail-closed", +}); + +await cache.refreshIfStale(); +const revoked = await cache.isRevoked(jti); +``` + +### Nonce cache + +Tracks nonces for replay protection: + +- Default TTL: 5 minutes (`DEFAULT_NONCE_TTL_MS = 300000`) +- Per-agent nonce tracking +- Automatic expiry purge on each `tryAcceptNonce` call + +### Agent auth client + +Manages agent authentication tokens: + +- Token refresh using Claw + PoP headers +- Automatic refresh decisions based on expiry metadata +- Single-flight deduplication for concurrent refresh attempts + +#### Functions + +**`refreshAgentAuthWithClawProof(input)`** — Sends a PoP-signed refresh request to the registry and returns a new `AgentAuthBundle`. + +```typescript +const bundle = await refreshAgentAuthWithClawProof({ + registryUrl: "https://registry.example.com", + ait: "", + secretKey: agentSecretKey, + refreshToken: currentBundle.refreshToken, +}); +``` + +**`executeWithAgentAuthRefreshRetry(input)`** — Executes an operation with automatic auth refresh on 401 errors. Uses single-flight deduplication for the refresh call. + +```typescript +const result = await executeWithAgentAuthRefreshRetry({ + key: "my-agent", + getAuth: () => loadBundle(), + refreshAuth: (auth) => refreshAgentAuthWithClawProof({ ... }), + persistAuth: (auth) => saveBundle(auth), + perform: (auth) => callApi(auth), +}); +``` + +**`isRetryableAuthExpiryError(error)`** — Returns `true` if the error is an `AppError` with status 401. + +#### Error codes + +| Code | Status | Description | +|------|--------|-------------| +| `AGENT_AUTH_REFRESH_INVALID` | 400 | Refresh request is invalid | +| `AGENT_AUTH_REFRESH_UNAUTHORIZED` | 401 | Credentials invalid, revoked, or expired | +| `AGENT_AUTH_REFRESH_CONFLICT` | 409 | Refresh conflict, retry request | +| `AGENT_AUTH_REFRESH_SERVER_ERROR` | 503 | Registry server error | +| `AGENT_AUTH_REFRESH_FAILED` | varies | Generic refresh failure | +| `AGENT_AUTH_REFRESH_NETWORK` | 503 | Network connectivity failure | +| `AGENT_AUTH_REFRESH_INVALID_RESPONSE` | 502 | Registry returned invalid payload | + +## Key types + +### `AppError` + +Structured error class with stable `code` values for deterministic error handling. Constructor takes a single options object. + +```typescript +import { AppError } from "@clawdentity/sdk"; + +throw new AppError({ + code: "AGENT_NOT_FOUND", + message: "Agent does not exist", + status: 404, +}); +``` + +| Field | Type | Description | +|-------|------|-------------| +| `code` | string | Stable error code for programmatic handling | +| `message` | string | Human-readable error message | +| `status` | number | HTTP status code | +| `details` | `Record?` | Optional structured details | +| `expose` | boolean | Whether to expose details in responses (defaults to `true` for status < 500) | + +### `Logger` + +Structured logging interface used across all Clawdentity services. + +```typescript +import { createLogger } from "@clawdentity/sdk"; + +const logger = createLogger({ service: "my-service" }); +``` + +### `NonceCache` + +Interface for nonce replay detection: + +```typescript +import { createNonceCache } from "@clawdentity/sdk"; + +const cache = createNonceCache({ ttlMs: 5 * 60 * 1000 }); +``` + +Options: + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `ttlMs` | number | `300000` (5 min) | Time-to-live for each nonce entry | +| `clock` | `() => number` | `Date.now` | Custom clock function for testing | + +Methods: + +| Method | Returns | Description | +|--------|---------|-------------| +| `tryAcceptNonce({ agentDid, nonce })` | `NonceCacheResult` | Accept or reject a nonce (returns `{ accepted, seenAt, expiresAt }`) | +| `purgeExpired()` | `void` | Remove all expired nonce entries | + +## HTTP signature headers + +Constants exported from `@clawdentity/sdk/http/constants`: + +| Constant | Value | +|----------|-------| +| `X_CLAW_TIMESTAMP` | `X-Claw-Timestamp` | +| `X_CLAW_NONCE` | `X-Claw-Nonce` | +| `X_CLAW_BODY_SHA256` | `X-Claw-Body-SHA256` | +| `X_CLAW_PROOF` | `X-Claw-Proof` | + +## Datetime utilities + +Helper functions from `@clawdentity/sdk/datetime`: + +| Function | Signature | Description | +|----------|-----------|-------------| +| `nowIso()` | `() => string` | Returns the current time as an ISO-8601 string | +| `addSeconds(value, seconds)` | `(Date \| string \| number, number) => string` | Adds seconds to a datetime value and returns ISO-8601 | +| `isExpired(expiresAt, reference?)` | `(Date \| string \| number, Date \| string \| number?) => boolean` | Returns `true` if `expiresAt <= reference` (defaults to `Date.now()`) | + +## Middleware (Hono) + +The SDK provides Hono middleware for building services: + +- `createRequestContextMiddleware()` — adds `x-request-id` tracking +- `createRequestLoggingMiddleware(logger)` — structured request logging +- `createHonoErrorHandler(logger)` — consistent error response formatting + +## Dependencies + +The SDK builds on: + +- `@noble/ed25519` — Ed25519 signature operations +- `jose` — JWT handling (sign, verify, decode) +- `hono` — HTTP framework middleware +- `zod` — Runtime schema validation diff --git a/apps/landing/src/content/docs/architecture/overview.mdx b/apps/landing/src/content/docs/architecture/overview.mdx new file mode 100644 index 0000000..b2b7d2f --- /dev/null +++ b/apps/landing/src/content/docs/architecture/overview.mdx @@ -0,0 +1,154 @@ +--- +title: Architecture Overview +description: Repository layout, deployment model, and system architecture of Clawdentity. +--- + +## Repository layout + +Clawdentity is an Nx monorepo with pnpm workspaces, deployed entirely on Cloudflare: + +``` +clawdentity/ +├── apps/ +│ ├── registry/ — Identity registry (Cloudflare Worker) +│ │ Issues AITs, serves CRL + public keys +│ │ Worker config: wrangler.jsonc +│ ├── proxy/ — Verification proxy (Cloudflare Worker) +│ │ Verifies headers, forwards to OpenClaw +│ │ Worker config: wrangler.jsonc +│ ├── cli/ — Operator CLI +│ │ Agent create/revoke, invite, api-key, config +│ ├── openclaw-skill/ — OpenClaw skill integration +│ │ Relay transform for agent-to-agent messaging +│ └── landing/ — Documentation site (Astro + Starlight) +├── packages/ +│ ├── protocol/ — Canonical types + signing rules +│ │ AIT claims, DID format, HTTP signing, endpoints +│ ├── sdk/ — TypeScript SDK +│ │ Sign/verify, CRL cache, auth client, crypto +│ └── connector/ — Connector library +│ WebSocket relay, inbound inbox with durable persistence, +│ delivery receipts, dead-letter queue +└── Configuration + ├── nx.json — Monorepo task orchestration + ├── pnpm-workspace.yaml + └── tsconfig.base.json +``` + +## System architecture + +```d2 +direction: down + +agent-machine: Agent Machine { + style.fill: "#e8f8e8" + + openclaw: "OpenClaw\n(private, loopback only)" { + style.fill: "#f8e8f8" + style.font-size: 14 + } + + connector: "Connector\nHTTP server · WebSocket client\nInbox · Delivery receipts" { + style.fill: "#fff4e8" + style.font-size: 14 + } + + openclaw -> connector: "POST /v1/outbound" { + style.stroke: "#1976d2" + } + connector -> openclaw: "POST /hooks/agent" { + style.stroke: "#388e3c" + } +} + +proxy: "Clawdentity Proxy (Worker)\nAIT · PoP · CRL verify\nTrust policy · Rate limiting" { + style.fill: "#e8f4f8" + style.font-size: 14 +} + +relay-do: "AgentRelaySession (DO)\nPer-agent WebSocket relay\nMessage queue · Heartbeat" { + style.fill: "#dce8f0" + style.font-size: 14 +} + +trust-do: "ProxyTrustState (DO)\nBidirectional trust pairs\nPairing tickets" { + style.fill: "#f0e8d8" + style.font-size: 14 +} + +registry: "Registry (Worker)\nIdentity issuance · CRL\nAuth management · Keys" { + style.fill: "#e8f0fe" + style.font-size: 14 +} + +proxy -> relay-do: "route by\nrecipient DID" { + style.stroke: "#666" +} +proxy -> trust-do: "check trust pair" { + style.stroke: "#666" +} +proxy -> registry: "keys + CRL +\nauth validate" { + style.stroke-dash: 3 +} + +agent-machine.connector -> proxy: "HTTP POST\n(signed outbound)" { + style.stroke: "#1976d2" + style.stroke-dash: 3 +} +relay-do -> agent-machine.connector: "WebSocket\ndeliver frame" { + style.stroke: "#388e3c" +} +``` + +## Technology stack + +| Component | Technology | +|-----------|-----------| +| Runtime | Cloudflare Workers | +| Database | Cloudflare D1 (SQLite) | +| ORM | Drizzle ORM | +| HTTP framework | Hono | +| Cryptography | Ed25519 (`@noble/ed25519`), JWT (`jose`) | +| Validation | Zod | +| Build | tsup | +| Monorepo | Nx + pnpm workspaces | +| Testing | Vitest | +| Linting | Biome | + +## Deployment model + +- **Registry and Proxy** deploy as Cloudflare Workers via `wrangler` +- **CLI** is distributed as an npm package (`clawdentity`) +- **OpenClaw skill** assets are bundled with the CLI package +- **Self-setup** via `clawdentity install --for openclaw` + `clawdentity provider setup --for openclaw --agent-name ` provisions runtime artifacts, wires hooks, and runs readiness checks +- **Node.js proxy** can run as a standalone Node.js server via `apps/proxy/src/node-server.ts` for local environments +- **D1 database** stores agent identities, invites, API keys, and auth sessions +- **Migrations** are managed with Drizzle Kit and applied before each deployment + +### Environments + +| Environment | Purpose | +|-------------|---------| +| `local` | Local Wrangler development only | +| `dev` | Cloud deployment for testing | +| `production` | Cloud deployment for live traffic | + +## CI/CD pipeline + +The CI pipeline runs on GitHub Actions: + +1. Checkout with full history (for Nx affected) +2. Install dependencies (`pnpm install --frozen-lockfile`) +3. Lint, typecheck, test, and build (via `nx affected`) +4. Deploy to Cloudflare Workers (on push to develop/main) +5. Health check deployed endpoints +6. Capture rollback artifacts on failure + +## Contributing + +This repo is delivered through small GitHub issues with a deployment-first gate: + +1. Pick an active issue and confirm dependencies/blockers +2. Implement in a feature branch with tests +3. Run validation (`pnpm affected:test:local`) +4. Open a PR and post implementation evidence on the issue diff --git a/apps/landing/src/content/docs/concepts/identity-model.mdx b/apps/landing/src/content/docs/concepts/identity-model.mdx new file mode 100644 index 0000000..cecce11 --- /dev/null +++ b/apps/landing/src/content/docs/concepts/identity-model.mdx @@ -0,0 +1,66 @@ +--- +title: Identity Model +description: How Clawdentity identifies agents using AITs, DIDs, and Proof-of-Possession. +--- + +## Agent Identity Token (AIT) + +An AIT is a registry-signed JWT that acts as an agent's passport. It is issued when an agent is created and ties together the agent's identity, ownership, and cryptographic key. + +| Claim | Purpose | +|-------|---------| +| `sub` | Agent DID (`did:cdi::agent:`) — unique identity | +| `ownerDid` | Human DID — who owns this agent | +| `cnf.jwk.x` | Agent's public key — for verifying PoP signatures | +| `jti` | Token ID — for revocation tracking | +| `iss` | Registry URL — who vouches for this identity | +| `exp` | Expiry — credential lifetime (1–90 days) | + +AITs use `alg=EdDSA` with Ed25519 keys. The registry signs each AIT with its own signing key, which is published at `/.well-known/claw-keys.json` for offline verification. + +### One active AIT per agent + +Each agent DID has exactly one active AIT at a time. Reissuing or rotating an AIT automatically revokes the previous `jti`. + +## DID format + +Clawdentity uses a custom DID method: + +- **Agent DID:** `did:cdi::agent:` +- **Human DID:** `did:cdi::human:` + +ULIDs provide time-ordered, globally unique identifiers without coordination. + +## Proof-of-Possession (PoP) + +Every request is signed with the agent's private key. This proves the sender actually holds the key bound to the AIT — not just a copy of the token. + +PoP headers on each request: + +| Header | Value | +|--------|-------| +| `Authorization` | `Claw ` | +| `X-Claw-Timestamp` | Unix seconds | +| `X-Claw-Nonce` | Base64url random value | +| `X-Claw-Body-SHA256` | Base64url SHA-256 of the raw body | +| `X-Claw-Proof` | Ed25519 signature over the canonical string | + +The canonical string binds method, path, timestamp, nonce, and body hash together. Any change to these values invalidates the proof signature. + +### Replay protection + +- **Timestamp skew:** requests older than 300 seconds (default) are rejected +- **Nonce cache:** each nonce is tracked per-agent for 5 minutes to prevent reuse + +## Key management + +- The agent's Ed25519 private key is generated locally and **never leaves the machine** +- The registry only receives the public key during challenge-response registration +- Keys are stored at `~/.clawdentity/agents//secret.key` with `0600` permissions +- The registry signing key is server-side only and signs AITs and CRLs + +## What gets shared + +- **In-band on each request:** AIT + PoP proof headers +- **Publicly available:** registry signing public keys + CRL (signed, cacheable) +- **Never shared:** the agent's private key or identity folder diff --git a/apps/landing/src/content/docs/concepts/revocation.mdx b/apps/landing/src/content/docs/concepts/revocation.mdx new file mode 100644 index 0000000..f8e8e21 --- /dev/null +++ b/apps/landing/src/content/docs/concepts/revocation.mdx @@ -0,0 +1,95 @@ +--- +title: Revocation Model +description: How Clawdentity handles agent revocation through signed CRLs. +--- + +## Certificate Revocation List (CRL) + +The CRL is a signed JWT (`typ=CRL`) published by the registry. It contains a list of revoked AIT `jti` values with metadata. + +### How it works + +1. An agent owner or admin revokes an agent at the registry +2. The registry adds the AIT's `jti` to the CRL and re-signs it +3. Proxies cache the CRL and refresh it periodically (default: 300 seconds) +4. On the next refresh, the revoked agent's requests start being rejected + +### CRL revocation entry schema + +Each entry in the `revocations` array contains: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `jti` | string (ULID) | Yes | Unique identifier for the revocation entry | +| `agentDid` | string | Yes | DID of the revoked agent | +| `revokedAt` | number | Yes | Unix timestamp (seconds) when revocation occurred | +| `reason` | string | No | Human-readable reason for revocation (max 280 characters) | + +The `reason` field is optional and intended for audit trails and operator diagnostics. When provided, it is included in the signed CRL payload and visible to any proxy that fetches the list. + +### CRL properties + +- **Signed:** CRL is a JWS signed by the registry's Ed25519 key — cannot be forged +- **Cached:** clients cache the CRL locally and refresh at a configurable interval +- **Staleness policy:** configurable as `fail-open` (allow if CRL is stale) or `fail-closed` (reject if CRL is stale) + +## Revocation flow + +```d2 +direction: right + +owner: Agent Owner / Admin +registry: Registry { + style.fill: "#f0f4ff" +} +proxy: Proxy { + style.fill: "#e8f4f8" +} + +owner -> registry: "DELETE /v1/agents/:id" { + style.stroke: "#d32f2f" +} +registry.note: "Add jti to CRL\nRe-sign CRL" +proxy -> registry: "CRL refresh cycle" { + style.stroke-dash: 3 +} +registry -> proxy: "Updated CRL" { + style.stroke-dash: 3 +} +proxy.result: "Reject requests\nfrom revoked AIT" { + style.fill: "#ffebee" + style.font-color: "#d32f2f" +} +``` + +## Operator controls + +Revocation operates at two levels with distinct authority: + +### Global revocation (sender side) + +- **Action:** registry API `DELETE /v1/agents/:id` (or equivalent admin tooling) +- **Scope:** registry-level identity revocation +- **Effect:** every receiving proxy rejects the revoked token once the CRL refreshes +- **Use when:** key compromise, decommissioning, or ownership suspension + +### Local blocking (receiver side) + +- **Action:** remove a trust pair from the proxy trust store +- **Scope:** that specific gateway/proxy only +- **Effect:** caller is blocked on this gateway immediately, but remains valid elsewhere +- **Use when:** policy mismatch, abuse from a specific caller, temporary trust removal + +### Key distinction + +- **Global revoke** = sender owner/admin authority at the registry +- **Local block** = receiver operator authority at their own gateway +- An opposite-side operator cannot globally revoke someone else's agent identity; they can only deny locally + +## Incident response pattern + +1. Receiver blocks caller locally for immediate containment +2. Sender owner/admin performs registry revoke for ecosystem-wide invalidation +3. Proxies return: + - `401` for invalid, expired, or revoked identity + - `403` for valid identity that is not in a confirmed trust pair diff --git a/apps/landing/src/content/docs/concepts/security.mdx b/apps/landing/src/content/docs/concepts/security.mdx new file mode 100644 index 0000000..35c6078 --- /dev/null +++ b/apps/landing/src/content/docs/concepts/security.mdx @@ -0,0 +1,55 @@ +--- +title: Security Architecture +description: Trust boundaries, threats addressed, and security guarantees of Clawdentity. +--- + +## Trust boundaries and sensitive assets + +| Asset | Classification | Location | +|-------|---------------|----------| +| Agent private key | Secret | Local only — never leaves agent machine | +| Registry signing key | Secret | Server-side only — signs AIT and CRL | +| OpenClaw `hooks.token` | Secret | Gateway host/proxy only | +| AIT + PoP headers | Transmitted | Safe to share in-band per request | +| Registry public keys | Public | `/.well-known/claw-keys.json` | +| CRL | Public | Signed, cacheable revocation list | + +## Threats addressed + +### Transport-level + +- **Do not expose OpenClaw webhooks directly to the public internet.** Follow OpenClaw guidance (loopback, tailnet, trusted reverse proxy). +- Clawdentity adds identity verification on top of transport security. + +### Request integrity + +PoP signatures bind method, path, timestamp, nonce, and body hash. This means: + +- **Tampering** — any change to method, path, body, timestamp, or nonce invalidates the proof +- **Replay** — nonce cache + timestamp skew rejection prevents reuse +- **Impersonation** — without the private key, an attacker cannot produce a valid proof + +### Identity and authorization + +- **Unauthorized callers** — AIT verification + trust policy enforcement at the proxy +- **Compromised identities** — CRL-based revocation checks reject revoked agents +- **Abuse containment** — per-agent rate limits at the proxy boundary + +## Security guarantees + +- Caller identity can be cryptographically verified +- Caller ownership is traceable via token claims +- Revocation can be enforced without rotating shared OpenClaw token + +## Known limits + +- If the endpoint holding the agent private key is compromised, the attacker can sign as that agent until revocation +- If CRL refresh is delayed, enforcement follows the configured staleness policy (`fail-open` or `fail-closed`) + +## Safe defaults and operator guidance + +- Treat any identity fields (agent name/description) as untrusted input — never allow prompt injection via identity metadata +- Keep OpenClaw behind trusted network boundaries; expose only proxy entry points +- Rotate PATs and audit trust pairs regularly +- Store PATs in secure local config only; create responses return the token once and it cannot be retrieved later +- Rotation baseline: keep one primary key + one standby key, rotate at least every 90 days, revoke stale keys immediately after rollout diff --git a/apps/landing/src/content/docs/getting-started/installation.mdx b/apps/landing/src/content/docs/getting-started/installation.mdx new file mode 100644 index 0000000..904892e --- /dev/null +++ b/apps/landing/src/content/docs/getting-started/installation.mdx @@ -0,0 +1,127 @@ +--- +title: Installation +description: Install the Clawdentity CLI using platform installers, with source/release fallback options. +--- + +import { Steps, Tabs, TabItem } from "@astrojs/starlight/components"; + +## Install the Clawdentity CLI (recommended) + + + + +```bash +curl -fsSL https://clawdentity.com/install.sh | sh +``` + + + + +```powershell +irm https://clawdentity.com/install.ps1 | iex +``` + + + + +The installer scripts verify release checksums by default and install the CLI binary (`clawdentity` / `clawdentity.exe`). +Rust toolchain is not required for this recommended path; installer scripts download prebuilt release binaries. + +### Installer environment variables + +| Variable | Description | +|--------|-------------| +| `CLAWDENTITY_VERSION` | Optional version override (`0.1.2`, `v0.1.2`, or `rust/v0.1.2`). Defaults to latest `rust/v*` release. | +| `CLAWDENTITY_INSTALL_DIR` | Optional install target directory. | +| `CLAWDENTITY_INSTALL_DRY_RUN=1` | Print planned actions without installing. | +| `CLAWDENTITY_NO_VERIFY=1` | Skip checksum verification (not recommended). | + +### Example overrides + +```bash +CLAWDENTITY_VERSION=0.1.2 CLAWDENTITY_INSTALL_DIR="$HOME/.local/bin" \ + curl -fsSL https://clawdentity.com/install.sh | sh +``` + +```powershell +$env:CLAWDENTITY_VERSION = "0.1.2" +$env:CLAWDENTITY_INSTALL_DRY_RUN = "1" +irm https://clawdentity.com/install.ps1 | iex +``` + +## Advanced fallback: build from source (requires Rust toolchain) + +Use this when you prefer building from source with Rust tooling: + +```bash +cargo install --locked clawdentity-cli +``` + +Optional deterministic pin: + +```bash +cargo install --locked --version clawdentity-cli +``` + +## Fallback: direct release assets + +If you cannot use the hosted installer scripts, download release assets directly: + +| Artifact | Location | +|----------|----------| +| Linux/macOS archive | `https://github.com/vrknetha/clawdentity/releases/download/rust/v/clawdentity--.tar.gz` | +| Windows archive | `https://github.com/vrknetha/clawdentity/releases/download/rust/v/clawdentity--.zip` | +| Checksums file | `https://github.com/vrknetha/clawdentity/releases/download/rust/v/clawdentity--checksums.txt` | + +Supported platforms: + +- `linux-x86_64` +- `linux-aarch64` +- `macos-x86_64` +- `macos-aarch64` +- `windows-x86_64` +- `windows-aarch64` + +Always verify checksums before installing when downloading assets manually. + +## Prompt-first next step (OpenClaw-first) + +After installing the CLI, use the canonical onboarding prompt from [`/skill.md`](/skill.md). + +## Development setup + +For contributing to Clawdentity itself: + + + +1. **Clone the repository** + + ```bash + git clone https://github.com/vrknetha/clawdentity.git + cd clawdentity + ``` + +2. **Install dependencies** + + ```bash + pnpm install + ``` + +3. **Build all packages** + + ```bash + pnpm build + ``` + +4. **Run tests** + + ```bash + pnpm test + ``` + + + +## Next steps + +- [Quick Start](/getting-started/quickstart/) — end-to-end walkthrough +- [Architecture Overview](/architecture/overview/) — understand the repo layout diff --git a/apps/landing/src/content/docs/getting-started/introduction.mdx b/apps/landing/src/content/docs/getting-started/introduction.mdx new file mode 100644 index 0000000..010a014 --- /dev/null +++ b/apps/landing/src/content/docs/getting-started/introduction.mdx @@ -0,0 +1,93 @@ +--- +title: Introduction +description: Verified identity and revocation for AI agents across OpenClaw, PicoClaw, NanoBot, and NanoClaw. +--- + +Clawdentity solves one question for cross-provider agent-to-agent communication: + +> **"Who is this agent, who owns it, and is it revoked?"** + +It does this with three building blocks: + +- **AIT (Agent Identity Token)** — a registry-signed passport (JWT / EdDSA) +- **PoP (Proof-of-Possession)** — every request is signed with the agent's private key +- **CRL (Revocation List)** — a signed revocation feed clients cache and refresh + +## How Clawdentity relates to provider runtimes + +**OpenClaw** is an agent runtime — it runs your AI agent locally and exposes a webhook interface for incoming messages. Agents talk to each other by calling each other's webhooks. + +**Clawdentity** is an identity layer that sits in front of runtime webhooks. It answers "who sent this request?" before your runtime ever sees it. + +OpenClaw authenticates webhook calls with a single shared token (`hooks.token`). That works for transport, but every caller looks the same — there is no way to tell agents apart, trace who sent what, or revoke one caller without breaking all of them. + +The OpenClaw path is shown below as the reference flow; the same trust model applies to supported providers. + +Clawdentity fixes this by running as a **proxy** in front of OpenClaw: + +```d2 +direction: down + +caller: Caller Agent { + shape: rectangle +} + +proxy: Clawdentity Proxy { + shape: rectangle + style.fill: "#e8f4f8" + label: |md + **Clawdentity Proxy** + Verifies identity · Checks revocation + Enforces trust policy + | +} + +connector: Local Connector { + shape: rectangle + style.fill: "#fff4e8" + label: |md + **Connector** + Relays to local OpenClaw + | +} + +openclaw: OpenClaw { + shape: rectangle + label: |md + **OpenClaw** + Private, never exposed + | +} + +caller -> proxy: "Authorization: Claw \nX-Claw-Proof / Nonce / Timestamp" +proxy -> connector: "WebSocket deliver frame" +connector -> openclaw: "x-openclaw-token: \n(internal only)" +``` + +The shared `hooks.token` stays **private** on the local machine — only the connector uses it to forward verified requests to OpenClaw. Callers never see it. + +## What Clawdentity adds + +- Verifiable per-agent identity (AIT + PoP) +- Fast revocation propagation (signed CRL + cache refresh) +- QR-code pairing — one scan to approve trust between two agents +- Proxy-side policy enforcement (trust policy + rate limits + replay protection) + +## Why shared tokens fall short + +| Property | Shared Webhook Token | Clawdentity | +|----------|---------------------|-------------| +| **Identity** | All callers look the same | Each agent has a unique DID and signed passport | +| **Accountability** | Cannot trace who sent what | Every request proves exactly which agent sent it | +| **Blast radius** | One leak exposes everything | One compromised key only affects that agent | +| **Revocation** | Rotate shared token = break all integrations | Revoke one agent instantly via CRL, others unaffected | +| **Replay protection** | None | Timestamp + nonce + signature on every request | +| **Tamper detection** | None | Body hash + PoP signature = any modification is detectable | +| **Per-caller policy** | Not possible | Trust pairs via QR pairing, per-agent rate limits | +| **Key exposure** | Token must be shared with every caller | Private key never leaves the agent's machine | + +## Next steps + +- [Quick Start](/getting-started/quickstart/) — get your first verified agent call working +- [Installation](/getting-started/installation/) — install the CLI via `install.sh` / `install.ps1` +- [Identity Model](/concepts/identity-model/) — understand AITs, DIDs, and PoP diff --git a/apps/landing/src/content/docs/getting-started/quickstart.mdx b/apps/landing/src/content/docs/getting-started/quickstart.mdx new file mode 100644 index 0000000..b328c06 --- /dev/null +++ b/apps/landing/src/content/docs/getting-started/quickstart.mdx @@ -0,0 +1,184 @@ +--- +title: Quick Start +description: Get your agent set up with Clawdentity relay in minutes. +--- + +import { Steps, Tabs, TabItem } from "@astrojs/starlight/components"; + +This guide gets you from zero to a working Clawdentity relay for supported providers (`openclaw`, `picoclaw`, `nanobot`, `nanoclaw`). + +## Prerequisites + +- A running supported provider runtime (OpenClaw, PicoClaw, NanoBot, or NanoClaw) +- A registry onboarding invite code (`clw_inv_...`) from your operator +- OpenClaw prompt access for agent-assisted onboarding + +## Prompt-first setup (OpenClaw-first, recommended) + +1. Install the CLI: + + + + +```bash +curl -fsSL https://clawdentity.com/install.sh | sh +``` + + + + +```powershell +irm https://clawdentity.com/install.ps1 | iex +``` + + + + +2. Open OpenClaw and paste the canonical onboarding prompt from [`/skill.md`](/skill.md): + +```text +Set up Clawdentity relay for this OpenClaw environment using https://clawdentity.com/skill.md as the source of truth. +Run required onboarding end-to-end and execute commands directly. +Ask me only for missing required inputs: invite code (clw_inv_...), display name, and agent name. +``` + +3. Let the skill-driven flow complete onboarding. It will execute: + - `config init` + - `invite redeem` + - `agent create` + - `install --platform openclaw` + - `provider setup --for openclaw --agent-name ` + +4. Verify readiness: + +```bash +clawdentity provider doctor --for openclaw +clawdentity provider relay-test --for openclaw +``` + +Rust toolchain is not required for this recommended path. + +## Cross-agent trust setup + +Cross-agent trust is implemented at the proxy API layer via pairing endpoints: + + + +1. **Start pairing** — initiator calls `POST /pair/start` and receives a `clwpair1_...` ticket. + +2. **Share the ticket/QR** out-of-band with the peer operator. + +3. **Confirm pairing** — responder calls `POST /pair/confirm` with the ticket. + +4. **Check completion** — call `POST /pair/status` until `confirmed`. + + + +See the full pairing contract in [/api-reference/proxy/](/api-reference/proxy/) and discovery behavior in [/guides/discovery/](/guides/discovery/). + +Verify relay readiness: + +```bash +clawdentity provider doctor --for --peer +clawdentity provider relay-test --for --peer +``` + +
+Advanced / Manual CLI flow + +```bash +# Install the CLI (Unix) +curl -fsSL https://clawdentity.com/install.sh | sh + +# Initialize config (auto-fetches registry metadata for proxyUrl) +clawdentity config init + +# Redeem an invite (sets API key) +clawdentity invite redeem --display-name "Your Name" + +# Create an agent identity +clawdentity agent create --framework + +# Install provider artifacts +clawdentity install --platform + +# Configure the relay (provisions connector, wires hooks, runs readiness checks) +clawdentity provider setup --for --agent-name + +# Verify everything works +clawdentity provider doctor --for +``` + +
+ +## What gets created locally + +After setup, your local state includes these files (example shown for the `openclaw` provider): + +``` +~/.clawdentity/ +├── config.json # CLI config (registryUrl, proxyUrl, apiKey, humanName) +├── clawdentity.sqlite3 # Local state DB (peers, relay queues, receipts cache) +├── openclaw-agent-name # Selected agent marker +├── openclaw-relay.json # Relay runtime config +└── agents// + ├── secret.key # Ed25519 private key (0600 permissions) + ├── public.key # Ed25519 public key + ├── ait.jwt # Signed Agent Identity Token + ├── identity.json # Agent metadata (DID, owner, keys, expiry) + └── registry-auth.json # Auth tokens for registry API +``` + +### identity.json + +The agent's local identity record, created by `agent create`: + +```json +{ + "id": "", + "did": "did:cdi::agent:", + "ownerDid": "did:cdi::human:", + "name": "", + "framework": "", + "publicKey": "", + "currentJti": "", + "ttlDays": 30, + "status": "active", + "expiresAt": "", + "createdAt": "" +} +``` + +### Files created per step + +| Step | Command | Files created | +|------|---------|---------------| +| 1 | `config init` | `config.json` | +| 2 | `invite redeem` | Updates `config.json` with `apiKey` and `humanName` | +| 3 | `agent create` | `agents//` directory: `secret.key`, `public.key`, `ait.jwt`, `identity.json`, `registry-auth.json` | +| 4 | `install --platform ` | Provider config hooks + runtime defaults | +| 5 | `provider setup --for ` | `openclaw-agent-name`, `openclaw-relay.json` (OpenClaw), connector assignment metadata | + +:::caution +Never share the `secret.key` file or the `~/.clawdentity/agents/` directory. The private key should never leave the agent's machine. +::: + +## Verification + +Check your agent's identity: + +```bash +clawdentity agent inspect +``` + +Check provider detection state: + +```bash +clawdentity provider status --for +``` + +## Next steps + +- [Installation](/getting-started/installation/) — detailed install options +- [Agent-to-Agent Guide](/guides/agent-to-agent/) — full message flow walkthrough +- [Operator Controls](/guides/operator-controls/) — manage trust and revocation diff --git a/apps/landing/src/content/docs/guides/agent-to-agent.mdx b/apps/landing/src/content/docs/guides/agent-to-agent.mdx new file mode 100644 index 0000000..471efa6 --- /dev/null +++ b/apps/landing/src/content/docs/guides/agent-to-agent.mdx @@ -0,0 +1,243 @@ +--- +title: Agent-to-Agent Communication +description: Complete walkthrough of two OpenClaw agents exchanging their first verified message. +--- + +import { Steps } from "@astrojs/starlight/components"; + +This guide walks through every step from zero to two OpenClaw agents exchanging their first message. Each step adds a security guarantee that the shared-token model cannot provide. + +## Architecture overview + +```d2 +direction: down + +registry: Clawdentity Registry { + style.fill: "#f0f4ff" + label: |md + **Clawdentity Registry** + Issues identities (AIT) · Publishes CRL + Validates agent auth · Invite-gated onboarding + | +} + +bob-machine: Bob's Machine { + style.fill: "#fff4e8" + + bob-openclaw: Bob's OpenClaw { + style.fill: "#f8e8f8" + } + + bob-connector: Bob's Connector { + style.fill: "#ffe8cc" + label: |md + **Connector** + Signs + relays outbound + | + } + + bob-openclaw -> bob-connector: "POST /v1/outbound" +} + +proxy: Clawdentity Proxy (Cloudflare Worker) { + style.fill: "#e8f4f8" + + bob-relay: Bob's AgentRelaySession (DO) { + style.fill: "#dce8f0" + label: |md + **Bob's Relay Session** + WebSocket · Message queue + | + } + + alice-relay: Alice's AgentRelaySession (DO) { + style.fill: "#dce8f0" + label: |md + **Alice's Relay Session** + WebSocket · Message queue + | + } + + trust-state: ProxyTrustState (DO) { + style.fill: "#f0e8d8" + label: |md + **Global Trust Store** + Trust pairs · Pairing tickets + | + } + + bob-relay -> trust-state: check trust { + style.stroke-dash: 3 + style.stroke: "#999" + } + alice-relay -> trust-state: check trust { + style.stroke-dash: 3 + style.stroke: "#999" + } +} + +alice-machine: Alice's Machine { + style.fill: "#e8f8e8" + + alice-connector: Alice's Connector { + style.fill: "#ffe8cc" + label: |md + **Connector** + WebSocket + delivers inbound + | + } + + alice-openclaw: Alice's OpenClaw { + style.fill: "#f8e8f8" + label: |md + **OpenClaw** + Private, never exposed + | + } + + alice-connector -> alice-openclaw: "POST /hooks/agent" +} + +registry -> bob-machine: issues AIT + auth +registry -> alice-machine: issues AIT + auth +bob-machine.bob-connector -> proxy.alice-relay: "HTTP POST (signed request)" { + style.stroke-dash: 3 +} +proxy.alice-relay -> alice-machine.alice-connector: "WebSocket deliver frame" { + style.stroke: "#388e3c" +} +``` + +## Step 1: Human onboarding (invite-gated) + +An admin creates an invite code. A new operator redeems it to get API access. + + + +1. Admin generates an invite: + ```bash + clawdentity invite create + ``` + Returns `clw_inv_` with optional expiry. + +2. Admin shares the invite code out-of-band (email, chat, etc.) + +3. New operator redeems the invite: + ```bash + clawdentity invite redeem --display-name "Your Name" + ``` + Creates a human account and issues an API key (shown once). + + + +:::note +Invite codes are single-use and time-limited. One agent per invite prevents bulk abuse. +::: + +## Step 2: Agent identity creation (challenge-response) + +The operator creates an agent identity. The private key **never leaves the machine**. + + + +1. CLI generates an Ed25519 keypair locally (`secret.key` stays local) + +2. CLI sends the public key to the registry: `POST /v1/agents/challenge` + - Registry generates a 24-byte nonce + - Returns `challengeId`, `nonce`, and `ownerDid` + +3. CLI signs the canonical proof with the private key (proves ownership) + +4. CLI sends the signed challenge: `POST /v1/agents` + - Registry verifies the signature + - Creates the agent record + - Issues AIT (JWT, EdDSA) and auth tokens + +5. Credentials are stored locally: + ``` + ~/.clawdentity/agents// + ├── secret.key # private, 0600 permissions + ├── public.key + ├── ait.jwt # signed passport + ├── identity.json + └── registry-auth.json + ``` + + + +:::tip +The 5-minute challenge window prevents delayed replay. Each challenge is single-use. +::: + +## Step 3: Peer discovery (QR pairing) + +Alice and Bob establish trust via proxy pairing APIs. No secrets are exchanged. + + + +1. Alice calls `POST /pair/start` to create a `clwpair1_` pairing ticket (and optional QR payload). + +2. Alice shares the QR/ticket out-of-band (email, chat, airdrop) + +3. Bob confirms pairing with `POST /pair/confirm`. + A bidirectional trust pair is created in the proxy and peer routing metadata is persisted locally. + + + +## Step 4: First message (Bob to Alice) + +Bob's OpenClaw triggers the relay through the connector. Every request is cryptographically signed. + + + +1. Bob's OpenClaw fires a hook: `{ peer: "alice", message: "Hi!" }` + +2. The relay transform (`relay-to-peer.mjs`): + - Looks up "alice" in `peers.json` to get the DID and proxy URL + - Removes the `peer` field from the payload + - POSTs `{ payload, peer, peerDid, peerProxyUrl }` to Bob's connector at `http://127.0.0.1:19400/v1/outbound` + +3. Bob's connector signs the HTTP request with PoP headers: + - `Authorization: Claw ` + - `X-Claw-Agent-Access: ` + - `X-Claw-Timestamp`, `X-Claw-Nonce`, `X-Claw-Body-SHA256`, `X-Claw-Proof` + - `X-Claw-Recipient-Agent-Did: ` + - `x-claw-conversation-id` (when present) + + :::tip + The `X-Claw-Agent-Access` token is a short-lived session token issued during agent registration (stored in `registry-auth.json`). It is automatically refreshed by the connector using the refresh token. The proxy validates it via `POST /v1/agents/auth/validate` on the registry. Unlike the AIT (which proves identity), the access token proves the agent has an active authenticated session. + ::: + +4. The proxy runs the **[verification pipeline](/api-reference/proxy/#verification-pipeline)**: + 1. Verify AIT signature (registry EdDSA keys) + 2. Check AIT expiry + 3. Verify timestamp skew (max +/-300 seconds) + 4. Verify PoP signature (Ed25519 from AIT `cnf` key) + 5. Reject nonce replay (per-agent, 5-minute cache) + 6. Check CRL revocation (signed list from registry) + 7. Enforce trust policy (is Bob in a confirmed trust pair?) + 8. Validate agent access token via registry + 9. Apply per-agent rate limits + +5. All checks pass — proxy relays a `deliver` frame over WebSocket to Alice's connector + +6. Alice's connector POSTs the payload to Alice's local OpenClaw at `http://127.0.0.1:18789/hooks/agent` + +7. Alice's OpenClaw receives the message and returns `202` + +8. Alice's connector sends a delivery receipt (`processed_by_openclaw`) back to Bob's proxy + + + +## Verification failures + +| Check | Error Code | HTTP | Meaning | +|-------|-----------|------|---------| +| AIT signature | `PROXY_AUTH_INVALID_AIT` | 401 | Token is forged or tampered | +| Timestamp skew | `PROXY_AUTH_TIMESTAMP_SKEW` | 401 | Request is too old or clock is wrong | +| PoP signature | `PROXY_AUTH_INVALID_PROOF` | 401 | Sender doesn't hold the private key | +| Nonce replay | `PROXY_AUTH_REPLAY` | 401 | Same request was sent twice | +| CRL revocation | `PROXY_AUTH_REVOKED` | 401 | Agent identity has been revoked | +| Trust policy | `PROXY_AUTH_FORBIDDEN` | 403 | Agent is valid but not in a confirmed trust pair | +| Agent access token | `PROXY_AGENT_ACCESS_INVALID` | 401 | Session token expired or revoked | +| Rate limit | `PROXY_RATE_LIMIT_EXCEEDED` | 429 | Too many requests from this agent | diff --git a/apps/landing/src/content/docs/guides/connector.mdx b/apps/landing/src/content/docs/guides/connector.mdx new file mode 100644 index 0000000..8ec52f8 --- /dev/null +++ b/apps/landing/src/content/docs/guides/connector.mdx @@ -0,0 +1,323 @@ +--- +title: Connector Runtime +description: Running and managing the local relay connector for agent message delivery. +--- + +## Overview + +The connector runtime is a local process that bridges your agent with the Clawdentity proxy. It handles two directions of traffic: + +- **Inbound:** maintains a persistent WebSocket to the proxy Durable Object, persists incoming messages to a durable inbox, and delivers them to your local OpenClaw instance with retry and dead-letter support +- **Outbound:** runs an HTTP server that accepts relay requests from OpenClaw, signs them with the agent's credentials, and forwards them to the recipient's proxy + +```d2 +direction: right + +agent: Agent Machine { + style.fill: "#e8f8e8" + + openclaw: OpenClaw { + style.fill: "#f8e8f8" + label: |md + **OpenClaw** + /hooks/agent + | + } + + connector: Connector { + style.fill: "#fff4e8" + + outbound: { + style.fill: "#fff8ee" + label: |md + **Outbound** + HTTP server :19400 + | + } + + inbox: { + style.fill: "#e8f0e8" + label: |md + **Inbound Inbox** + Durable · file-based + Replay loop (2s) + Dead-letter queue + | + } + } + + openclaw -> connector.outbound: "POST /v1/outbound\n(relay request)" { + style.stroke: "#1976d2" + } + connector.inbox -> openclaw: "Replay delivers\n(with retry)" { + style.stroke: "#388e3c" + } +} + +proxy: Clawdentity Proxy { + style.fill: "#e8f4f8" + label: |md + **Clawdentity Proxy** + Cloudflare Worker + Per-agent Durable Objects + WebSocket relay · PoP verify + | +} + +agent.connector -> proxy: "WebSocket\n(persistent)" { + style.stroke: "#388e3c" +} +agent.connector -> proxy: "HTTP POST\n(signed outbound)" { + style.stroke: "#1976d2" + style.stroke-dash: 3 +} +proxy -> agent.connector.inbox: "WebSocket\ndeliver frame" { + style.stroke: "#2e7d32" +} +``` + +## Starting the connector + +```bash +clawdentity connector start +``` + +The connector: + +1. Validates local agent state (`identity.json`, `ait.jwt`, `secret.key`, `registry-auth.json`) +2. Checks if the access token expires within 30 seconds and refreshes it if needed +3. Opens a WebSocket to the proxy (`/v1/relay/connect`) with signed upgrade headers +4. Starts the outbound HTTP server on `http://127.0.0.1:19400` +5. Prints the resolved outbound endpoint and proxy WebSocket URL +6. Remains running as a long-lived process for bidirectional message relay + +:::caution +All required agent files must be present before starting. The connector fails early with deterministic errors if any are missing. +::: + +## Inbound delivery + +Messages arrive through a durable inbox model that ensures reliable delivery: + +1. Proxy sends a `DeliverFrame` over WebSocket → connector persists to inbound inbox → sends `DeliverAckFrame` immediately +2. Replay loop (every 2s) processes due messages → delivers to local OpenClaw hook endpoint +3. Success → mark delivered, send delivery receipt (`processed_by_openclaw`) +4. Retryable failure → exponential backoff retry (initial 1s, max 60s, factor 2x) +5. Non-retryable failure after 5 attempts → move to dead-letter queue, send receipt (`dead_lettered`) + +The optional `x-openclaw-token` header is included when configured, authenticating the connector to OpenClaw. + +## Inbound inbox + +The connector persists inbound messages to a durable inbox on disk before processing, ensuring no messages are lost if the process restarts. + +### Storage layout + +``` +~/.clawdentity/agents//inbound-inbox/ +├── index.json # Atomic index (version 2) with pending + dead-letter items +├── events.jsonl # Append-only event log +└── index.lock # File lock for concurrent writer safety +``` + +### Capacity limits + +| Limit | Value | +|-------|-------| +| Max messages | 100,000 | +| Max bytes | 2 GB | + +### Event types + +| Event | Description | +|-------|-------------| +| `inbound_persisted` | Message persisted to inbox | +| `replay_succeeded` | Message delivered to OpenClaw | +| `replay_failed` | Delivery attempt failed | +| `dead_letter_moved` | Message moved to dead-letter queue | +| `dead_letter_replayed` | Dead-letter item replayed | +| `dead_letter_purged` | Dead-letter item purged | + +### Event log rotation + +| Parameter | Value | +|-----------|-------| +| Max file size | 10 MB | +| Max files | 5 | + +## Dead-letter queue + +Messages that fail delivery after the maximum retry attempts are moved to the dead-letter queue. The connector exposes endpoints for managing dead-lettered items. + +### `GET /v1/inbound/dead-letter` + +Lists all dead-lettered items. + +### `POST /v1/inbound/dead-letter/replay` + +Replays dead-lettered items. Optional `requestIds` filter to replay specific items. + +### `POST /v1/inbound/dead-letter/purge` + +Purges dead-lettered items. Optional `requestIds` filter to purge specific items. + +## Delivery receipts + +When a `replyTo` URL is present in the inbound message, the connector sends an HTTP POST delivery receipt after processing. + +| Status | Meaning | +|--------|---------| +| `processed_by_openclaw` | Message successfully delivered to OpenClaw | +| `dead_lettered` | Message moved to dead-letter queue after max retries | + +Receipt targets are validated against trusted peers before sending. + +## Status endpoint + +```http +GET /v1/status +``` + +Returns connector health information: + +- WebSocket connection state +- Inbound inbox stats (pending count, dead-letter count, replayer status) +- Outbound queue stats +- Metrics (heartbeat RTT, delivery latency) + +## Outbound relay + +When OpenClaw triggers an outbound message: + +1. OpenClaw hook transform POSTs to the connector at `http://127.0.0.1:19400/v1/outbound` +2. Request body: `{ payload, peer, peerDid, peerProxyUrl, conversationId?, replyTo? }` +3. Connector signs the HTTP request with the agent's Ed25519 key (PoP headers) +4. Connector adds `x-claw-conversation-id` and `x-claw-delivery-receipt-url` headers when present +5. Connector forwards the signed request to the recipient's proxy URL +6. On success, returns `202 Accepted` with `{ accepted: true, peer: "" }` + +The outbound relay body is limited to 1 MB. On `401` responses, the connector refreshes the access token atomically (temp file + rename to `registry-auth.json`) and retries once. + +### Conversation tracking + +When present in the outbound request, `conversationId` and `replyTo` propagate through the full relay path: + +1. Added as HTTP headers by the connector (`x-claw-conversation-id`, `x-claw-delivery-receipt-url`) +2. Passed through the proxy [verification pipeline](/api-reference/proxy/#verification-pipeline) unchanged +3. Included in the `deliver` frame sent to the recipient's connector over WebSocket +4. Forwarded to the recipient's OpenClaw hook endpoint + +The `replyTo` URL is used by the recipient's connector to send [delivery receipts](#delivery-receipts) back to the sender's proxy after processing the message. + +## Frame protocol + +All WebSocket messages use JSON frames with version `v: 1`. Every frame includes `v`, `type`, `id` (ULID), and `ts` (ISO-8601). + +| Frame | Direction | Fields | Purpose | +|-------|-----------|--------|---------| +| `heartbeat` | Both | `v`, `type`, `id`, `ts` | Keepalive ping (default: every 30s) | +| `heartbeat_ack` | Both | `v`, `type`, `id`, `ts`, `ackId` | Keepalive response | +| `deliver` | Proxy to Connector | `v`, `type`, `id`, `ts`, `fromAgentDid`, `toAgentDid`, `payload`, `contentType?`, `conversationId?`, `replyTo?` | Inbound message delivery | +| `deliver_ack` | Connector to Proxy | `v`, `type`, `id`, `ts`, `ackId`, `accepted`, `reason?` | Delivery acknowledgment | +| `enqueue` | Connector to Proxy | `v`, `type`, `id`, `ts`, `toAgentDid`, `payload`, `conversationId?`, `replyTo?` | Outbound message via WebSocket | +| `enqueue_ack` | Proxy to Connector | `v`, `type`, `id`, `ts`, `ackId`, `accepted`, `reason?` | Enqueue acknowledgment | + +## Reconnection + +The connector automatically reconnects when the WebSocket connection drops. Reconnection uses exponential backoff with jitter: + +| Parameter | Default | +|-----------|---------| +| Minimum delay | 1 second | +| Maximum delay | 30 seconds | +| Backoff factor | 2x | +| Jitter | +/- 20% | + +On successful connection, the reconnect counter resets to zero. Queued outbound frames are flushed immediately after reconnection. + +## Delivery retry + +When local OpenClaw delivery fails, the connector retries with exponential backoff: + +| Parameter | Default | +|-----------|---------| +| Max attempts | 4 | +| Initial delay | 300 ms | +| Max delay | 2,000 ms | +| Backoff factor | 2x | +| Total retry budget | 14 seconds | +| Per-request timeout | 10 seconds | + +Retryable conditions: + +- HTTP status `5xx` (server error) +- HTTP status `404` (endpoint not ready) +- HTTP status `429` (rate limited) +- Network errors and timeouts + +Non-retryable responses (e.g. `400`, `403`) fail immediately. + +## Auth refresh + +The connector manages its own access token lifecycle: + +- **Startup check:** if the access token expires within 30 seconds, the connector refreshes it before connecting +- **401 auto-retry:** outbound relay requests that receive a `401` are retried once after refreshing the access token +- **Atomic persistence:** refreshed tokens are written to a temporary file and atomically renamed to `registry-auth.json`, preventing partial writes + +## Configuration + +### Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `CLAWDENTITY_PROXY_WS_URL` | — | Proxy WebSocket URL (required if not passed via CLI) | +| `CLAWDENTITY_CONNECTOR_BASE_URL` | `http://127.0.0.1:19400` | Outbound HTTP server bind address | +| `CLAWDENTITY_CONNECTOR_OUTBOUND_PATH` | `/v1/outbound` | Outbound HTTP server path | +| `OPENCLAW_BASE_URL` | `http://127.0.0.1:18789` | Local OpenClaw base URL | +| `OPENCLAW_HOOK_PATH` | `/hooks/agent` | OpenClaw hook delivery path | +| `OPENCLAW_HOOK_TOKEN` | — | Optional OpenClaw hook auth token | + +### Connector constants + +| Parameter | Default | Description | +|-----------|---------|-------------| +| Replay interval | 2,000 ms | Inbound replay loop interval | +| Replay batch size | 25 | Max messages per replay cycle | +| Retry initial delay | 1,000 ms | Exponential backoff initial | +| Retry max delay | 60,000 ms | Exponential backoff cap | +| Retry backoff factor | 2 | Backoff multiplier | +| Dead-letter max attempts | 5 | Non-retryable failures before dead-letter | +| Inbox max messages | 100,000 | Capacity limit | +| Inbox max bytes | 2 GB | Capacity limit | +| Events max bytes | 10 MB | Per-file log rotation threshold | +| Events max files | 5 | Max rotated log files | +| OpenClaw deliver timeout | 10,000 ms | Per-request timeout | +| OpenClaw deliver max attempts | 4 | Direct delivery retries | +| OpenClaw deliver initial delay | 300 ms | Direct delivery retry backoff | +| Heartbeat interval | 30,000 ms | WebSocket keepalive | +| Heartbeat ack timeout | 60,000 ms | Socket close on missed ack | + +## Autostart service + +Install the connector as an OS service so it starts automatically after reboot or login: + +```bash +# Install autostart +clawdentity connector service install + +# Remove autostart +clawdentity connector service uninstall +``` + +| Platform | Service manager | +|----------|----------------| +| macOS | `launchctl` (LaunchAgent) | +| Linux | `systemd --user` | + +Service names and paths are generated deterministically from the agent name for predictable support and debugging. + +### Properties + +- `service install` generates deterministic user-service files and wires autostart using OS-native tooling +- `service uninstall` is safe to re-run (ignores already-stopped service errors and still removes the service file) diff --git a/apps/landing/src/content/docs/guides/discovery.mdx b/apps/landing/src/content/docs/guides/discovery.mdx new file mode 100644 index 0000000..4885446 --- /dev/null +++ b/apps/landing/src/content/docs/guides/discovery.mdx @@ -0,0 +1,157 @@ +--- +title: Peer Discovery +description: How agents find and establish trust with each other via QR-code pairing. +--- + +## Discovery methods + +Clawdentity supports several ways for agents to find each other: + +### 1. QR-code pairing (primary) + +The primary trust establishment method. One side calls the pairing API to generate a ticket (and optional QR), shares it out-of-band, and the peer confirms. + +#### Flow + +```d2 +direction: down + +alice: Alice's Machine { + style.fill: "#e8f8e8" + label: |md + **Alice's Machine** + `POST /pair/start` + | +} + +proxy: Clawdentity Proxy { + style.fill: "#e8f4f8" + label: |md + **Clawdentity Proxy** + Cloudflare Worker + ProxyTrustState + | +} + +bob: Bob's Machine { + style.fill: "#e8e8f8" + label: |md + **Bob's Machine** + `POST /pair/confirm` + | +} + +alice -> proxy: "1. POST /pair/start" { + style.stroke: "#1976d2" +} +proxy -> alice: "2. clwpair1_ ticket + QR" { + style.stroke: "#1976d2" + style.stroke-dash: 3 +} +alice -> bob: "3. Share QR out-of-band" { + style.stroke: "#9e9e9e" + style.stroke-dash: 5 +} +bob -> proxy: "4. POST /pair/confirm" { + style.stroke: "#e65100" +} +proxy -> proxy: "5. Create bidirectional trust pair\nin ProxyTrustState" { + style.stroke: "#388e3c" + target-arrowhead.shape: diamond +} +``` + +1. Alice starts pairing via `POST /pair/start` — receives a pairing ticket (`clwpair1_...`) and optional QR payload. +2. Alice shares the ticket/QR to Bob (email, chat, airdrop) +3. Bob confirms via `POST /pair/confirm` +4. Local peer routing state is updated with an auto-derived alias +5. A bidirectional trust pair is created in the proxy trust store — both directions are now allowed + +#### Pairing ticket format + +- Prefix: `clwpair1_` +- Encoding: base64url JSON payload +- Payload fields (v2): `iss`, `kid`, `nonce`, `exp`, `pkid`, `sig` +- Default TTL: 300s (5 min), max: 900s (15 min) +- QR cleanup: stale images auto-cleaned from `~/.clawdentity/pairing/` after 900s + +#### Peer alias derivation + +When pairing confirm persists a new peer, the alias is derived automatically: + +1. Parse the peer DID to extract the ULID component +2. Take the last 8 characters of the ULID, lowercase: `peer-` +3. If the alias already exists for a different DID, append a numeric suffix: `peer--2`, `peer--3`, etc. +4. If the peer DID already exists, reuse the existing alias (no duplicate) +5. Fallback alias is `peer` if the DID is not a valid agent DID + +Alias validation: `[a-zA-Z0-9._-]`, max 128 characters. + +#### Peer routing snapshot format + +The relay transform consumes a JSON peer map snapshot (commonly `~/.clawdentity/peers.json`): + +```json +{ + "peers": { + "": { + "did": "did:cdi::agent:", + "proxyUrl": "https://proxy.clawdentity.com", + "agentName": "beta", + "humanName": "Ira" + } + } +} +``` + +| Field | Description | +|-------|-------------| +| `did` | The peer's agent DID | +| `proxyUrl` | The peer's proxy URL for outbound routing | +| `agentName` | The peer's agent name | +| `humanName` | The peer's human operator name | + +The [relay transform](/guides/openclaw-skill/#relay-payload-processing) uses this snapshot to resolve a peer alias to a DID and proxy URL for outbound message routing. + +:::note +`peers.json` (or equivalent snapshot path) is a **local routing view** only. The authoritative trust enforcement happens in [ProxyTrustState](#local-vs-proxy-trust) at the proxy. +::: + +### 2. Out-of-band contact card + +A human shares a contact card containing the agent's DID and proxy endpoint URL. No keys or tokens are exchanged. + +### 3. Registry resolution + +The callee publishes an endpoint in the registry. Callers resolve it via: + +```http +GET /v1/resolve/:id +``` + +Returns the agent's public metadata including any published gateway hint. + +## Local vs. proxy trust + +Two data stores track peer relationships, each with a different role: + +### Local peer state (client-side) + +Local peer records are persisted in client state (for example `~/.clawdentity/clawdentity.sqlite3`) and may be projected to a JSON snapshot (`peers.json`) for transform/runtime consumption. This local state is **not enforced** — you can have a local peer entry without a corresponding trust pair, but the proxy will reject messages with `PROXY_AUTH_FORBIDDEN` (403). + +### ProxyTrustState (proxy-side) + +A Durable Object that stores bidirectional trust pairs. Created during [QR pairing](#1-qr-code-pairing-primary). The proxy checks this on every inbound relay request at step 7 of the [verification pipeline](/api-reference/proxy/#verification-pipeline). This is the **authoritative** source for trust enforcement. + +### Relationship + +Local peer state and ProxyTrustState can diverge if: + +- local peer state is edited manually without pairing +- The proxy trust state is modified via admin tools +- A trust pair is revoked at the proxy but the local entry remains + +When they diverge, the proxy trust state wins — outbound messages to a peer that exists only in local state (without a proxy trust pair) will be rejected. + +## Key principle + +Identity is presented **per request**, not per connection. No one shares keys or credential files between agents. Each request carries its own proof of identity through PoP headers. diff --git a/apps/landing/src/content/docs/guides/openclaw-skill.mdx b/apps/landing/src/content/docs/guides/openclaw-skill.mdx new file mode 100644 index 0000000..69e1b38 --- /dev/null +++ b/apps/landing/src/content/docs/guides/openclaw-skill.mdx @@ -0,0 +1,193 @@ +--- +title: OpenClaw Skill Install +description: Installing and configuring the Clawdentity skill for OpenClaw agents. +--- + +import { Steps, Tabs, TabItem } from "@astrojs/starlight/components"; + +## Canonical skill artifact + +Use [`/skill.md`](/skill.md) as the canonical single-file skill artifact. + +Fallback mirror (not canonical): `https://raw.githubusercontent.com/vrknetha/clawdentity/develop/apps/openclaw-skill/skill/SKILL.md` + +## Install the CLI (recommended) + + + + +```bash +curl -fsSL https://clawdentity.com/install.sh | sh +``` + + + + +```powershell +irm https://clawdentity.com/install.ps1 | iex +``` + + + + +Installer scripts support: + +- `CLAWDENTITY_VERSION` +- `CLAWDENTITY_INSTALL_DIR` +- `CLAWDENTITY_INSTALL_DRY_RUN=1` +- `CLAWDENTITY_NO_VERIFY=1` + +Fallback options: + +- `cargo install --locked clawdentity-cli` (advanced fallback; requires Rust toolchain) +- Direct release assets from `https://github.com/vrknetha/clawdentity/releases` + +Rust toolchain is not required for the recommended installer path. + +## Prompt-first onboarding (OpenClaw-first) + +Paste the canonical prompt from [`/skill.md`](/skill.md) into OpenClaw: + +```text +Set up Clawdentity relay for this OpenClaw environment using https://clawdentity.com/skill.md as the source of truth. +Run required onboarding end-to-end and execute commands directly. +Ask me only for missing required inputs: invite code (clw_inv_...), display name, and agent name. +``` + +Manual fallback command (advanced): + +```bash +clawdentity install --platform openclaw +``` + +The published skill artifact is consolidated into `/skill.md`. For local runtime, install writes these artifacts automatically: + +| Artifact | Location | +|----------|----------| +| Skill definition | `~/.openclaw/skills/clawdentity-openclaw-relay/SKILL.md` | +| Reference docs | `~/.openclaw/skills/clawdentity-openclaw-relay/references/*` | +| Relay transform | `~/.openclaw/skills/clawdentity-openclaw-relay/relay-to-peer.mjs` | +| Hook transform | `~/.openclaw/hooks/transforms/relay-to-peer.mjs` | + +### Properties + +- **Idempotent** — re-running install is safe; each artifact reports `installed`, `updated`, or `unchanged` +- **Self-contained** — the CLI package ships bundled skill assets so clean installs don't depend on sibling workspace packages +- **Deterministic** — missing source artifacts fail with actionable errors + +## Setup flow + +After installing the skill, set up the agent for peer communication: + + + +1. **Redeem an invite** (if not already onboarded): + ```bash + clawdentity invite redeem --display-name "Your Name" + ``` + +2. **Create an agent identity**: + ```bash + clawdentity agent create my-agent --framework openclaw + ``` + +3. **Set up OpenClaw integration**: + ```bash + clawdentity provider setup --for openclaw --agent-name my-agent + ``` + This provisions the connector runtime, wires hooks, stabilizes gateway auth, and runs readiness checks. + +4. **Run diagnostics** to verify everything is healthy: + ```bash + clawdentity provider doctor --for openclaw + ``` + +5. **Validate relay end-to-end**: + ```bash + clawdentity provider relay-test --for openclaw + ``` + + + +## Peer aliases + +Each peer is identified by an alias in `~/.clawdentity/peers.json`. Alias rules: + +- **Pattern:** `[a-zA-Z0-9._-]` (letters, numbers, dot, underscore, hyphen) +- **Max length:** 128 characters +- Aliases are validated on both read and write + +Example `peers.json`: + +```json +{ + "peers": { + "alice": { + "did": "did:cdi::agent:...", + "proxyUrl": "https://proxy.example.com/hooks/agent", + "agentName": "alice", + "humanName": "Alice" + } + } +} +``` + +## Relay connector integration + +The relay transform forwards outbound messages through the local connector instead of calling peer proxies directly. This allows the connector to handle signing and auth refresh. + +Default connector endpoint: `http://127.0.0.1:19400/v1/outbound` + +Override with environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `CLAWDENTITY_CONNECTOR_BASE_URL` | `http://127.0.0.1:19400` | Connector HTTP server address | +| `CLAWDENTITY_CONNECTOR_OUTBOUND_PATH` | `/v1/outbound` | Connector outbound path | + +### Relay payload processing + +When the hook transform receives a payload with a `peer` field: + +1. Looks up the alias in `peers.json` to resolve `did` and `proxyUrl` +2. Removes the `peer` field from the payload +3. Sends `{ payload, peer, peerDid, peerProxyUrl }` to the connector +4. Returns `null` to signal that OpenClaw should not process the payload further + +If the payload has no `peer` field, the transform passes it through unchanged. + +## Registry auth file lock + +Concurrent auth refresh operations (e.g. multiple connector restarts) are serialized with a file lock on `registry-auth.json.lock`: + +| Parameter | Value | +|-----------|-------| +| Retry delay | 50 ms | +| Max attempts | 200 (~10 seconds timeout) | +| Stale lock cleanup | 30 seconds | + +The lock uses exclusive file creation (`O_CREAT | O_EXCL`). Stale locks older than 30 seconds are automatically removed. + +### Relay runtime config + +After `provider setup --for openclaw`, the relay runtime config is stored at `~/.clawdentity/openclaw-relay.json`: + +```json +{ + "openclawBaseUrl": "http://127.0.0.1:18789", + "openclawHookToken": "", + "updatedAt": "2025-01-01T00:00:00.000Z" +} +``` + +## Provider doctor + +The `provider doctor --for openclaw` command verifies that all components are healthy before relay work runs: + +- CLI tooling and config (PAT, registry URL) +- Skill assets presence +- OpenClaw state and hooks configuration +- Local agent identity validity +- Peer map and relay configuration + +It provides a concise healthy/unhealthy status summary and actionable errors when any step fails. diff --git a/apps/landing/src/content/docs/guides/operator-controls.mdx b/apps/landing/src/content/docs/guides/operator-controls.mdx new file mode 100644 index 0000000..995134f --- /dev/null +++ b/apps/landing/src/content/docs/guides/operator-controls.mdx @@ -0,0 +1,140 @@ +--- +title: Operator Controls +description: Managing trust, revocation, and access control as a Clawdentity operator. +--- + +## CLI operator commands + +### Configuration + +```bash +# Initialize local config file +clawdentity config init + +# Set a config value (valid keys: registryUrl, proxyUrl, apiKey, humanName) +clawdentity config set + +# Get a resolved config value +clawdentity config get + +# Show all resolved config values +clawdentity config show +``` + +### Agent lifecycle + +```bash +# Create a new agent identity (local keypair + registry registration) +clawdentity agent create + +# Inspect an agent's identity and token details +clawdentity agent inspect + +# Revoke an agent auth session (access/refresh token invalidation) +clawdentity agent auth revoke + +# Refresh agent auth tokens +clawdentity agent auth refresh +``` + +### API key management + +```bash +# Create a new PAT (token shown once) +clawdentity api-key create --name + +# List PAT metadata (id, name, status, createdAt, lastUsedAt) +clawdentity api-key list + +# Revoke a specific PAT +clawdentity api-key revoke +``` + +:::tip +Rotation baseline: keep one primary key + one standby key, rotate at least every 90 days, and revoke stale keys immediately after rollout. +::: + +### Invite management + +```bash +# Create a registry onboarding invite (admin only) +clawdentity invite create + +# Redeem an invite to create an account +clawdentity invite redeem +``` + +### Connector management + +```bash +# Start the local relay connector runtime +clawdentity connector start + +# Install autostart service (launchd on macOS, systemd on Linux) +clawdentity connector service install + +# Remove autostart service +clawdentity connector service uninstall +``` + +### Diagnostics + +```bash +# Validate provider relay setup and runtime checks +clawdentity provider doctor --for [--peer ] [--json] + +# Send a relay probe to a configured peer alias +clawdentity provider relay-test --for --peer [--json] +``` + +### Pairing + +```bash +# Start pairing (returns clwpair1_ ticket + metadata) +POST /pair/start + +# Confirm pairing with ticket from initiator +POST /pair/confirm + +# Check pairing status +POST /pair/status +``` + +### Skill management + +```bash +# Install provider artifacts for selected platform +clawdentity install --for +``` + +## Trust model + +### Sender side (agent owner/admin) + +- **Global revocation** via registry API (`DELETE /v1/agents/:id`) +- Scope: registry-level — every receiving proxy rejects the revoked token once CRL refreshes +- Use when: key compromise, decommissioning, or ownership suspension + +### Receiver side (gateway owner) + +- **Local blocking** by removing a trust pair from the proxy trust store +- Scope: that specific gateway/proxy only +- Use when: policy mismatch, abuse from a specific caller, temporary trust removal + +### Incident response + +1. Receiver blocks caller locally for immediate containment +2. Sender owner/admin performs registry revoke for ecosystem-wide invalidation +3. Proxies return `401` for revoked identity, `403` for valid but not in a confirmed trust pair + +## Command idempotency + +| Command | Idempotent? | Note | +|---------|-------------|------| +| `config init` | Yes | Safe to re-run | +| `invite redeem` | **No** | One-time; invite consumed on success | +| `agent create` | No | Fails if agent directory exists | +| `agent auth revoke` | Yes | Safe to repeat; session remains revoked | +| `provider setup --for ` | Usually yes | Primary reconciliation re-entry point | +| `install --for ` | Yes | Reports: installed/updated/unchanged | +| `connector service install` | Yes | Idempotent | diff --git a/apps/landing/src/content/docs/guides/proxy-setup.mdx b/apps/landing/src/content/docs/guides/proxy-setup.mdx new file mode 100644 index 0000000..6dd7e31 --- /dev/null +++ b/apps/landing/src/content/docs/guides/proxy-setup.mdx @@ -0,0 +1,193 @@ +--- +title: Proxy Setup +description: Deploy and configure the Clawdentity verification proxy. +--- + +The Clawdentity Proxy is a Cloudflare Worker that sits in front of OpenClaw. It verifies identity, checks revocation, enforces trust policy, and only forwards authenticated requests. + +## How the proxy works + +```d2 +direction: down + +caller: Caller's Connector { + style.fill: "#fff4e8" + label: |md + **Caller's Connector** + Signs HTTP request + with Ed25519 PoP headers + | +} + +proxy: Clawdentity Proxy { + style.fill: "#e8f4f8" + pipeline: |md + 1. Verify AIT signature + 2. Check timestamp + nonce replay + 3. Verify PoP signature + 4. Check CRL revocation + 5. Enforce trust policy + 6. Apply rate limits + | +} + +recipient: Recipient's Connector { + style.fill: "#fff4e8" + label: |md + **Recipient's Connector** + Persists to inbox + Delivers locally with retry + | +} + +openclaw: OpenClaw Gateway { + style.fill: "#f8e8f8" + label: |md + **OpenClaw Gateway** + /hooks/agent + | +} + +caller -> proxy: "Authorization: Claw \nX-Claw-Proof / Nonce / Timestamp" { + style.stroke: "#1976d2" +} +proxy -> recipient: "WebSocket deliver frame" { + style.stroke: "#388e3c" +} +recipient -> openclaw: "POST /hooks/agent\n(with retry)" { + style.stroke: "#388e3c" +} +``` + +## Deployment + +### Local development + +```bash +# Local env (ENVIRONMENT=local) +pnpm dev:proxy + +# Development env (ENVIRONMENT=dev) +pnpm dev:proxy:development + +# Fresh deploy-like env +pnpm dev:proxy:fresh +``` + +### Production deployment + +```bash +pnpm -F @clawdentity/proxy run deploy:production +``` + +## Configuration + +The proxy is configured through Cloudflare Worker environment variables in `apps/proxy/wrangler.jsonc`. + +### Identity injection + +By default, `INJECT_IDENTITY_INTO_MESSAGE=true` prepends a sanitized identity block into `/hooks/agent` payload messages: + +- `agentDid` — the caller's agent DID +- `ownerDid` — the caller's owner DID +- `issuer` — the registry URL +- `aitJti` — the AIT token ID + +Set `INJECT_IDENTITY_INTO_MESSAGE=false` to keep payloads unchanged. + +## Verification pipeline + +The proxy runs these checks in order on every request (see the [Proxy API reference](/api-reference/proxy/#verification-pipeline) for the authoritative list): + +1. **AIT signature** — validates against registry EdDSA keys from `/.well-known/claw-keys.json` +2. **AIT expiry** — rejects expired tokens +3. **Timestamp skew** — rejects requests older than 300 seconds (default) +4. **PoP signature** — verifies Ed25519 signature against the public key in the AIT `cnf` claim +5. **Nonce replay** — tracks nonces per-agent for 5 minutes +6. **CRL revocation** — checks the cached CRL for revoked `jti` values +7. **Trust policy** — verifies the caller's agent DID is in a confirmed trust pair +8. **Agent access token** — validates the session token via `POST /v1/agents/auth/validate` on the registry +9. **Rate limiting** — per-agent rate limits at the proxy boundary + +## Trust store + +Trust pairs established via QR pairing replace simple allowlists. The proxy uses a Durable Object-based trust store for production and development environments. + +### Backends + +| Backend | Environment | Details | +|---------|-------------|---------| +| Durable Object (`global-trust`) | `dev`, `production` | Persistent, distributed trust state | +| In-memory | `local` | Ephemeral, for local development only | + +### `ProxyTrustStore` interface + +| Method | Purpose | +|--------|---------| +| `isAgentKnown(agentDid)` | Check if an agent has any trust pair | +| `isPairAllowed(senderDid, recipientDid)` | Check if a specific pair is trusted | +| `upsertPair(pair)` | Create or update a trust pair | +| Pairing ticket operations | Create, confirm, and query pairing tickets | + +Trust pairs are **bidirectional** — once pairing is confirmed, both sides can send to each other. Same-agent delivery (sender DID equals recipient DID) is allowed without an explicit trust pair. + +## Pairing routes + +The proxy exposes pairing endpoints for establishing trust between agents. + +### `POST /pair/start` + +Initiator starts a pairing session. Requires agent ownership verification via internal proxy-to-registry service auth. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `ttlSeconds` | number | No | Ticket TTL in seconds (default 300, max 900) | +| `initiatorProfile.agentName` | string | Yes | Initiator's agent name | +| `initiatorProfile.humanName` | string | Yes | Initiator's human display name | + +**Response (200):** Returns a `clwpair1_` pairing ticket and ticket metadata. + +### `POST /pair/confirm` + +Responder confirms pairing with a ticket. Creates a bidirectional trust pair. + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `ticket` | string | Yes | Pairing ticket (`clwpair1_...`) | +| `responderProfile.agentName` | string | Yes | Responder's agent name | +| `responderProfile.humanName` | string | Yes | Responder's human display name | + +### `POST /pair/status` + +Check ticket status. Returns `pending` or `confirmed`. Only participants can check: initiator for pending tickets, either side for confirmed tickets. + +## Delivery receipts + +The proxy supports delivery receipt tracking for relay messages. + +### `POST /v1/relay/delivery-receipts` + +Connector sends a receipt after processing a relayed message. + +### `GET /v1/relay/delivery-receipts` + +Query receipt status. + +**Query parameters:** +- `requestId` — the original relay request ID +- `recipientAgentDid` — the recipient agent DID + +## Node.js deployment + +For `local` environment only, the proxy can run as a standalone Node.js server via `apps/proxy/src/node-server.ts`. + +- Trust store: **in-memory only** (no Durable Objects in Node.js) +- Suitable for local development and testing + +## Relay connector + +The proxy also supports WebSocket-based relay connections at `/v1/relay/connect` for real-time message delivery between agents through the connector runtime. diff --git a/apps/landing/src/layouts/LandingLayout.astro b/apps/landing/src/layouts/LandingLayout.astro new file mode 100644 index 0000000..a682df4 --- /dev/null +++ b/apps/landing/src/layouts/LandingLayout.astro @@ -0,0 +1,136 @@ +--- +interface Props { + title?: string; + description?: string; +} + +const { + title = "Clawdentity — Verified Identity for AI Agents", + description = "Cryptographic identity, proof-of-possession, and instant revocation for AI agent-to-agent communication. Open source, privacy-first.", +} = Astro.props; + +const canonicalUrl = new URL(Astro.url.pathname, Astro.site); +--- + + + + + + + + + {title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/landing/src/pages/index.astro b/apps/landing/src/pages/index.astro new file mode 100644 index 0000000..27d2fb7 --- /dev/null +++ b/apps/landing/src/pages/index.astro @@ -0,0 +1,70 @@ +--- +import LandingLayout from "../layouts/LandingLayout.astro"; +import "../styles/landing.css"; +import "../styles/landing-motion.css"; + +import Nav from "../components/landing/Nav.astro"; +import Hero from "../components/landing/Hero.astro"; +import Problem from "../components/landing/Problem.astro"; +import TheFix from "../components/landing/TheFix.astro"; +import Comparison from "../components/landing/Comparison.astro"; +import HowItWorks from "../components/landing/HowItWorks.astro"; +import Privacy from "../components/landing/Privacy.astro"; +import UseCases from "../components/landing/UseCases.astro"; +import GettingStarted from "../components/landing/GettingStarted.astro"; +import Footer from "../components/landing/Footer.astro"; +--- + + +