|
| 1 | +# AgentPin CLI Guide |
| 2 | + |
| 3 | +The AgentPin CLI (`agentpin`) provides commands for key generation, credential issuance, credential verification, and serving discovery endpoints. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Installation |
| 8 | + |
| 9 | +Build from the Rust workspace: |
| 10 | + |
| 11 | +```bash |
| 12 | +# Install the CLI binary |
| 13 | +cargo install --path crates/agentpin-cli |
| 14 | + |
| 15 | +# Or build the full workspace |
| 16 | +cargo build --workspace --release |
| 17 | +``` |
| 18 | + |
| 19 | +The binary is at `target/release/agentpin`. |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## Commands |
| 24 | + |
| 25 | +### `agentpin keygen` — Generate Key Pair |
| 26 | + |
| 27 | +Generate an ECDSA P-256 key pair for signing agent credentials. |
| 28 | + |
| 29 | +```bash |
| 30 | +agentpin keygen \ |
| 31 | + --domain example.com \ |
| 32 | + --kid example-2026-01 \ |
| 33 | + --output-dir ./keys |
| 34 | +``` |
| 35 | + |
| 36 | +**Options:** |
| 37 | + |
| 38 | +| Flag | Required | Description | |
| 39 | +|------|----------|-------------| |
| 40 | +| `--domain` | Yes | Domain this key is associated with | |
| 41 | +| `--kid` | Yes | Key identifier (used in JWT headers and discovery documents) | |
| 42 | +| `--output-dir` | Yes | Directory to write key files | |
| 43 | + |
| 44 | +**Output files:** |
| 45 | + |
| 46 | +``` |
| 47 | +keys/ |
| 48 | +├── example-2026-01.private.pem # ECDSA P-256 private key (keep secret!) |
| 49 | +├── example-2026-01.public.pem # Public key in PEM format |
| 50 | +└── example-2026-01.public.jwk.json # Public key in JWK format |
| 51 | +``` |
| 52 | + |
| 53 | +The JWK file is ready to embed in your discovery document's `public_keys` array. |
| 54 | + |
| 55 | +**Example JWK output:** |
| 56 | + |
| 57 | +```json |
| 58 | +{ |
| 59 | + "kid": "example-2026-01", |
| 60 | + "kty": "EC", |
| 61 | + "crv": "P-256", |
| 62 | + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", |
| 63 | + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", |
| 64 | + "use": "sig", |
| 65 | + "key_ops": ["verify"] |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +### `agentpin issue` — Issue a Credential |
| 72 | + |
| 73 | +Issue a signed JWT credential for an agent. |
| 74 | + |
| 75 | +```bash |
| 76 | +agentpin issue \ |
| 77 | + --private-key ./keys/example-2026-01.private.pem \ |
| 78 | + --kid example-2026-01 \ |
| 79 | + --issuer example.com \ |
| 80 | + --agent-id "urn:agentpin:example.com:scout" \ |
| 81 | + --capabilities "read:data,write:reports" \ |
| 82 | + --ttl 3600 |
| 83 | +``` |
| 84 | + |
| 85 | +**Options:** |
| 86 | + |
| 87 | +| Flag | Required | Default | Description | |
| 88 | +|------|----------|---------|-------------| |
| 89 | +| `--private-key` | Yes | — | Path to PEM-encoded private key | |
| 90 | +| `--kid` | Yes | — | Key identifier (must match a key in the discovery document) | |
| 91 | +| `--issuer` | Yes | — | Issuer domain (e.g., `example.com`) | |
| 92 | +| `--agent-id` | Yes | — | Agent identifier URN (e.g., `urn:agentpin:example.com:scout`) | |
| 93 | +| `--capabilities` | Yes | — | Comma-separated capability list | |
| 94 | +| `--audience` | No | — | Target audience domain | |
| 95 | +| `--ttl` | No | `3600` | Credential lifetime in seconds | |
| 96 | +| `--constraints` | No | — | JSON object of constraints | |
| 97 | + |
| 98 | +**Output:** |
| 99 | + |
| 100 | +The signed JWT is printed to stdout: |
| 101 | + |
| 102 | +``` |
| 103 | +eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImV4YW1wbGUtMjAyNi0wMSJ9.eyJpc3M... |
| 104 | +``` |
| 105 | + |
| 106 | +**Usage in scripts:** |
| 107 | + |
| 108 | +```bash |
| 109 | +# Store credential in a variable |
| 110 | +CREDENTIAL=$(agentpin issue \ |
| 111 | + --private-key ./keys/example-2026-01.private.pem \ |
| 112 | + --kid example-2026-01 \ |
| 113 | + --issuer example.com \ |
| 114 | + --agent-id "urn:agentpin:example.com:scout" \ |
| 115 | + --capabilities "read:data" \ |
| 116 | + --ttl 3600) |
| 117 | + |
| 118 | +# Pass to another agent via HTTP header |
| 119 | +curl -H "Authorization: Bearer $CREDENTIAL" https://api.verifier.com/endpoint |
| 120 | + |
| 121 | +# Or verify it immediately |
| 122 | +agentpin verify --credential "$CREDENTIAL" --discovery ./agent-identity.json |
| 123 | +``` |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +### `agentpin verify` — Verify a Credential |
| 128 | + |
| 129 | +Verify a signed JWT credential against a discovery document. |
| 130 | + |
| 131 | +**Offline verification (recommended):** |
| 132 | + |
| 133 | +```bash |
| 134 | +agentpin verify \ |
| 135 | + --credential <jwt> \ |
| 136 | + --discovery ./agent-identity.json \ |
| 137 | + --pin-store ./pins.json |
| 138 | +``` |
| 139 | + |
| 140 | +**Online verification (fetches discovery from issuer domain):** |
| 141 | + |
| 142 | +```bash |
| 143 | +agentpin verify --credential <jwt> |
| 144 | +``` |
| 145 | + |
| 146 | +**Options:** |
| 147 | + |
| 148 | +| Flag | Required | Description | |
| 149 | +|------|----------|-------------| |
| 150 | +| `--credential` | Yes | The JWT credential to verify | |
| 151 | +| `--discovery` | No | Path to local discovery document (offline mode) | |
| 152 | +| `--revocation` | No | Path to local revocation document | |
| 153 | +| `--pin-store` | No | Path to TOFU pin store file (created if missing) | |
| 154 | +| `--audience` | No | Expected audience domain | |
| 155 | +| `--bundle` | No | Path to trust bundle file | |
| 156 | + |
| 157 | +**Output (JSON):** |
| 158 | + |
| 159 | +```json |
| 160 | +{ |
| 161 | + "valid": true, |
| 162 | + "agent_id": "urn:agentpin:example.com:scout", |
| 163 | + "issuer": "example.com", |
| 164 | + "capabilities": ["read:data", "write:reports"], |
| 165 | + "key_pinning": "first_use", |
| 166 | + "delegation_chain_valid": true, |
| 167 | + "expires_at": "2026-02-15T13:00:00Z" |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +**Failure output:** |
| 172 | + |
| 173 | +```json |
| 174 | +{ |
| 175 | + "valid": false, |
| 176 | + "error_code": "expired", |
| 177 | + "error_message": "Credential expired at 2026-02-15T12:00:00Z" |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +**Using with trust bundles:** |
| 182 | + |
| 183 | +```bash |
| 184 | +agentpin verify \ |
| 185 | + --credential <jwt> \ |
| 186 | + --bundle ./trust-bundle.json \ |
| 187 | + --pin-store ./pins.json |
| 188 | +``` |
| 189 | + |
| 190 | +--- |
| 191 | + |
| 192 | +### `agentpin-server` — Serve Discovery Endpoints |
| 193 | + |
| 194 | +The `agentpin-server` binary serves `.well-known` discovery and revocation endpoints over HTTP. |
| 195 | + |
| 196 | +```bash |
| 197 | +agentpin-server \ |
| 198 | + --discovery ./agent-identity.json \ |
| 199 | + --revocation ./revocations.json \ |
| 200 | + --port 8080 |
| 201 | +``` |
| 202 | + |
| 203 | +**Options:** |
| 204 | + |
| 205 | +| Flag | Required | Default | Description | |
| 206 | +|------|----------|---------|-------------| |
| 207 | +| `--discovery` | Yes | — | Path to discovery document JSON | |
| 208 | +| `--revocation` | No | — | Path to revocation document JSON | |
| 209 | +| `--port` | No | `8080` | HTTP listening port | |
| 210 | +| `--host` | No | `0.0.0.0` | Bind address | |
| 211 | + |
| 212 | +**Endpoints served:** |
| 213 | + |
| 214 | +| Path | Cache-Control | Description | |
| 215 | +|------|---------------|-------------| |
| 216 | +| `GET /.well-known/agent-identity.json` | `max-age=3600` | Discovery document | |
| 217 | +| `GET /.well-known/agent-identity-revocations.json` | `max-age=300` | Revocation document | |
| 218 | +| `GET /health` | — | Health check (returns `200 OK`) | |
| 219 | + |
| 220 | +**Production deployment behind a reverse proxy:** |
| 221 | + |
| 222 | +```nginx |
| 223 | +# nginx configuration |
| 224 | +server { |
| 225 | + listen 443 ssl; |
| 226 | + server_name example.com; |
| 227 | +
|
| 228 | + location /.well-known/agent-identity.json { |
| 229 | + proxy_pass http://127.0.0.1:8080; |
| 230 | + proxy_set_header Host $host; |
| 231 | + add_header Cache-Control "public, max-age=3600"; |
| 232 | + add_header Content-Type "application/json"; |
| 233 | + } |
| 234 | +
|
| 235 | + location /.well-known/agent-identity-revocations.json { |
| 236 | + proxy_pass http://127.0.0.1:8080; |
| 237 | + proxy_set_header Host $host; |
| 238 | + add_header Cache-Control "public, max-age=300"; |
| 239 | + add_header Content-Type "application/json"; |
| 240 | + } |
| 241 | +} |
| 242 | +``` |
| 243 | + |
| 244 | +--- |
| 245 | + |
| 246 | +## Common Workflows |
| 247 | + |
| 248 | +### Generate Keys, Issue, and Verify |
| 249 | + |
| 250 | +```bash |
| 251 | +# 1. Generate keys |
| 252 | +agentpin keygen --domain example.com --kid key-01 --output-dir ./keys |
| 253 | + |
| 254 | +# 2. Issue a credential |
| 255 | +JWT=$(agentpin issue \ |
| 256 | + --private-key ./keys/key-01.private.pem \ |
| 257 | + --kid key-01 \ |
| 258 | + --issuer example.com \ |
| 259 | + --agent-id "urn:agentpin:example.com:agent" \ |
| 260 | + --capabilities "read:data" \ |
| 261 | + --ttl 3600) |
| 262 | + |
| 263 | +# 3. Verify offline with a discovery document |
| 264 | +agentpin verify \ |
| 265 | + --credential "$JWT" \ |
| 266 | + --discovery ./agent-identity.json \ |
| 267 | + --pin-store ./pins.json |
| 268 | +``` |
| 269 | + |
| 270 | +### Rotate Keys |
| 271 | + |
| 272 | +```bash |
| 273 | +# 1. Generate new key |
| 274 | +agentpin keygen --domain example.com --kid key-02 --output-dir ./keys |
| 275 | + |
| 276 | +# 2. Add new key to discovery document (keep old key temporarily) |
| 277 | +# Edit agent-identity.json to add key-02 to public_keys array |
| 278 | + |
| 279 | +# 3. Issue new credentials with the new key |
| 280 | +agentpin issue --private-key ./keys/key-02.private.pem --kid key-02 ... |
| 281 | + |
| 282 | +# 4. After transition period, revoke old key |
| 283 | +# Add key-01 to revocation document |
| 284 | + |
| 285 | +# 5. Remove old key from discovery document |
| 286 | +``` |
| 287 | + |
| 288 | +### Batch Verification |
| 289 | + |
| 290 | +```bash |
| 291 | +# Verify multiple credentials from a file |
| 292 | +while IFS= read -r jwt; do |
| 293 | + result=$(agentpin verify --credential "$jwt" --discovery ./discovery.json --pin-store ./pins.json) |
| 294 | + echo "$jwt: $(echo "$result" | jq -r '.valid')" |
| 295 | +done < credentials.txt |
| 296 | +``` |
0 commit comments