Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
CL_AGENT=runtime.commandlayer.eth
CL_KEY_ID=vC4WbcNoq2znSCiQ
CL_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..."
CL_VERIFIER_URL=https://www.commandlayer.org/api/verify
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ Wrap your agent. Emit a signed receipt. Verify through CommandLayer.
npm install @commandlayer/agent-sdk
```

> Temporary caveat (as of May 9, 2026): package availability on npm may vary by registry policy/account permissions.
> If `npm install @commandlayer/agent-sdk` fails in your environment, use local development install:

```bash
npm install
npm run build
```

## Quickstart

```ts
Expand All @@ -38,7 +30,7 @@ const local = validateTrustReceipt(receipt); // schema only
if (!local.ok) throw new Error(local.errors.join("; "));

const remote = await cl.verify(receipt); // cryptographic verification
console.log({ output, receipt, remote });
process.stdout.write(JSON.stringify({ output, receipt, remote }) + "\n");
```

## Wrap your agent
Expand Down Expand Up @@ -73,10 +65,10 @@ import {
} from "@commandlayer/agent-sdk";

const requestResult = validateTrustRequest(requestPayload);
if (!requestResult.ok) console.error(requestResult.errors);
if (!requestResult.ok) process.stderr.write(requestResult.errors.join("\n") + "\n");

const receiptResult = validateTrustReceipt(receiptPayload);
if (!receiptResult.ok) console.error(receiptResult.errors);
if (!receiptResult.ok) process.stderr.write(receiptResult.errors.join("\n") + "\n");

assertValidTrustRequest(requestPayload);
assertValidTrustReceipt(receiptPayload);
Expand Down
12 changes: 6 additions & 6 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All examples are dependency-free and intentionally use mocked execution (no real
- `CL_PRIVATE_KEY_PEM`
- `CL_KEY_ID`
- `CL_AGENT` (defaults to `runtime.commandlayer.eth`)
- `CL_VERIFIER_URL` (optional, defaults to `https://www.commandlayer.org/api/verify`)

The demo signer/key id match the public VerifyAgent demo. For your own agent, replace these with your ENS signer and key id.

Expand Down Expand Up @@ -78,12 +79,11 @@ After building:
"completed_at": "2026-04-29T14:22:00.012Z"
},
"proof": {
"canonicalization": "json.sorted_keys.v1",
"hash": "...",
"signature_alg": "ed25519",
"signature": "...",
"key_id": "vC4WbcNoq2znSCiQ",
"signer": "runtime.commandlayer.eth"
"canonical": "json.sorted_keys.v1",
"alg": "ed25519",
"signature": "<base64-encoded-ed25519-signature>",
"kid": "vC4WbcNoq2znSCiQ",
"signer_id": "runtime.commandlayer.eth"
}
}
```
Expand Down
7 changes: 4 additions & 3 deletions examples/agent-to-agent-verify.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "dotenv/config";
import { CommandLayer } from "../src/index.js";

const privateKeyPem = process.env.CL_PRIVATE_KEY_PEM;
Expand All @@ -15,8 +16,8 @@ const result = await cl.wrap("agent.execute", async () => ({
executed_by: agent,
}));

console.log("output", result.output);
console.log("receipt", JSON.stringify(result.receipt, null, 2));
process.stdout.write(`output: ${JSON.stringify(result.output)}\n`);
process.stdout.write(`receipt: ${JSON.stringify(result.receipt, null, 2)}\n`);

const verification = await cl.verify(result.receipt);
console.log("verification_status", verification);
process.stdout.write(`verification_status: ${JSON.stringify(verification)}\n`);
8 changes: 4 additions & 4 deletions examples/basic-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ const cl = new CommandLayer({
agent: process.env.CL_AGENT ?? "runtime.commandlayer.eth",
keyId: process.env.CL_KEY_ID ?? "vC4WbcNoq2znSCiQ",
privateKeyPem: process.env.CL_PRIVATE_KEY_PEM,
verifierUrl: "https://www.commandlayer.org/api/verify",
verifierUrl: process.env.CL_VERIFIER_URL ?? "https://www.commandlayer.org/api/verify",
});

const result = await cl.wrap("summarize", async () => fakeSummarizeAgent("hello world"));

console.log("output", result.output);
console.log("receipt", JSON.stringify(result.receipt, null, 2));
process.stdout.write(`output: ${JSON.stringify(result.output)}\n`);
process.stdout.write(`receipt: ${JSON.stringify(result.receipt, null, 2)}\n`);

const verified = await cl.verify(result.receipt);
console.log("verified", verified);
process.stdout.write(`verified: ${JSON.stringify(verified)}\n`);
9 changes: 5 additions & 4 deletions examples/existing-agent-integration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "dotenv/config";
import { CommandLayer } from "../src/index.js";

const privateKeyPem = process.env.CL_PRIVATE_KEY_PEM;
Expand All @@ -24,12 +25,12 @@ const input = {
context: "release build green",
};

console.log("Already have an agent? Wrap the action, don't rewrite the agent.");
process.stdout.write("Already have an agent? Wrap the action, don't rewrite the agent.\n");

const result = await cl.wrap("agent.execute", async () => existingAgent.run(input));

const verification = await cl.verify(result.receipt);

console.log("output", result.output);
console.log("receipt", JSON.stringify(result.receipt, null, 2));
console.log("verification_status", verification);
process.stdout.write(`output: ${JSON.stringify(result.output)}\n`);
process.stdout.write(`receipt: ${JSON.stringify(result.receipt, null, 2)}\n`);
process.stdout.write(`verification_status: ${JSON.stringify(verification)}\n`);
30 changes: 13 additions & 17 deletions examples/full-demo.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "dotenv/config";
import { CommandLayer } from "../src/index.js";

const privateKeyPem = process.env.CL_PRIVATE_KEY_PEM;
Expand All @@ -15,20 +16,15 @@ const result = await cl.wrap("summarize", async () => {
return { summary: "hello world" };
});

console.log("Agent output");
console.log(JSON.stringify(result.output, null, 2));
console.log("");

console.log("Signed receipt");
console.log(JSON.stringify(result.receipt, null, 2));
console.log("receipt.signer:", result.receipt.signer);
console.log("receipt.verb:", result.receipt.verb);
console.log(
"receipt.proof.hash:",
result.receipt.proof.hash,
);
console.log("receipt.proof.key_id:", result.receipt.proof.key_id);
console.log("");
process.stdout.write("Agent output\n");
process.stdout.write(`${JSON.stringify(result.output, null, 2)}\n\n`);

process.stdout.write("Signed receipt\n");
process.stdout.write(`${JSON.stringify(result.receipt, null, 2)}\n`);
process.stdout.write(`receipt.signer: ${result.receipt.signer}\n`);
process.stdout.write(`receipt.verb: ${result.receipt.verb}\n`);
process.stdout.write(`receipt.proof.kid: ${result.receipt.proof.kid}\n`);
process.stdout.write(`receipt.proof.signer_id: ${result.receipt.proof.signer_id}\n\n`);

const statusOf = (value: unknown): string => {
if (!value || typeof value !== "object") {
Expand All @@ -41,7 +37,7 @@ const statusOf = (value: unknown): string => {

const verified = await cl.verify(result.receipt);
const verifiedStatus = statusOf(verified);
console.log(`Original receipt verification: ${verifiedStatus === "VERIFIED" ? "VERIFIED" : verifiedStatus}`);
process.stdout.write(`Original receipt verification: ${verifiedStatus === "VERIFIED" ? "VERIFIED" : verifiedStatus}\n`);

const tamperedReceipt = structuredClone(result.receipt);

Expand All @@ -53,6 +49,6 @@ if (!tamperedReceipt.output || typeof tamperedReceipt.output !== "object" || Arr

const tampered = await cl.verify(tamperedReceipt);
const tamperedStatus = statusOf(tampered);
console.log(`Tampered receipt verification: ${tamperedStatus === "INVALID" ? "INVALID" : tamperedStatus}`);
process.stdout.write(`Tampered receipt verification: ${tamperedStatus === "INVALID" ? "INVALID" : tamperedStatus}\n`);

console.log("\nAgents dont make claims — they produce proof.");
process.stdout.write("\nAgents don't make claims — they produce proof.\n");
9 changes: 5 additions & 4 deletions examples/openai-tool-wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "dotenv/config";
import { CommandLayer } from "../src/index.js";

const privateKeyPem = process.env.CL_PRIVATE_KEY_PEM;
Expand All @@ -20,9 +21,9 @@ const result = await cl.wrap("tool.get_weather", async () => ({
forecast: "sunny",
}));

console.log("tool_call", toolCall);
console.log("output", result.output);
console.log("receipt", JSON.stringify(result.receipt, null, 2));
process.stdout.write(`tool_call: ${JSON.stringify(toolCall)}\n`);
process.stdout.write(`output: ${JSON.stringify(result.output)}\n`);
process.stdout.write(`receipt: ${JSON.stringify(result.receipt, null, 2)}\n`);

const verification = await cl.verify(result.receipt);
console.log("verification_status", verification);
process.stdout.write(`verification_status: ${JSON.stringify(verification)}\n`);
7 changes: 4 additions & 3 deletions examples/workflow-job-runner.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "dotenv/config";
import { CommandLayer } from "../src/index.js";

const privateKeyPem = process.env.CL_PRIVATE_KEY_PEM;
Expand All @@ -14,8 +15,8 @@ const result = await cl.wrap("workflow.run", async () => ({
steps_completed: ["lead_received", "send_followup", "update_crm"],
}));

console.log("output", result.output);
console.log("receipt", JSON.stringify(result.receipt, null, 2));
process.stdout.write(`output: ${JSON.stringify(result.output)}\n`);
process.stdout.write(`receipt: ${JSON.stringify(result.receipt, null, 2)}\n`);

const verification = await cl.verify(result.receipt);
console.log("verification_status", verification);
process.stdout.write(`verification_status: ${JSON.stringify(verification)}\n`);
12 changes: 9 additions & 3 deletions examples/wrapped-agent-demo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import "dotenv/config";
import { CommandLayer } from "../src/index.js";

if (!process.env.CL_PRIVATE_KEY_PEM) {
console.error("Missing CL_PRIVATE_KEY_PEM. Copy .env.example to .env and add a PKCS8 Ed25519 private key.");
process.exit(1);
}

const cl = new CommandLayer({
agent: process.env.CL_AGENT ?? "runtime.commandlayer.eth",
privateKeyPem: process.env.CL_PRIVATE_KEY_PEM,
Expand All @@ -10,8 +16,8 @@ const result = await cl.wrap("summarize", async () => {
return "hello world";
});

console.log(result.output);
console.log(result.receipt);
process.stdout.write(`${JSON.stringify(result.output)}\n`);
process.stdout.write(`${JSON.stringify(result.receipt, null, 2)}\n`);

const verified = await cl.verify(result.receipt);
console.log(verified);
process.stdout.write(`${JSON.stringify(verified)}\n`);
74 changes: 69 additions & 5 deletions python-sdk/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,74 @@
# CommandLayer Python SDK

This directory is a placeholder for the Python SDK.
Signs and verifies CommandLayer agent action receipts using Ed25519.

The Python SDK is not yet implemented. Until it ships, use the REST API directly:
## Install

- **Sign**: `POST https://runtime.commandlayer.org/sign`
- **Verify**: `POST https://runtime.commandlayer.org/verify`
```bash
pip install commandlayer-agent-sdk[crypto]
```

See the [CommandLayer Protocol](https://commandlayer.org/protocol) for the full receipt format specification.
The `[crypto]` extra installs the `cryptography` package required for Ed25519 signing.

## Quickstart

```python
import os
from commandlayer import CommandLayer

cl = CommandLayer(
signer=os.environ["CL_AGENT"],
key_id=os.environ["CL_KEY_ID"],
private_key_pem=os.environ["CL_PRIVATE_KEY_PEM"],
)

result = cl.wrap("verify", lambda: {"approved": True}, input={"challenge": "abc"})
print(result["output"])
print(result["receipt"])

verification = cl.verify(result["receipt"])
print(verification)
```

## Environment variables

| Variable | Description |
|----------|-------------|
| `CL_AGENT` | ENS name of the signing agent |
| `CL_KEY_ID` | Key identifier |
| `CL_PRIVATE_KEY_PEM` | PKCS8-encoded Ed25519 private key |
| `CL_VERIFIER_URL` | Override the verifier endpoint (optional) |

## API

### `CommandLayer(*, signer, key_id, private_key_pem, [canonicalization], [verifier_url])`

Constructor. `agent` is accepted as an alias for `signer`.

### `cl.wrap(verb, fn, *, input=None) -> dict`

Executes `fn()`, records execution metadata, signs the receipt with Ed25519, and returns `{"output": ..., "receipt": ...}`.

If `fn()` raises, the error is recorded in `receipt.execution.error` and `status` is set to `"error"`.

### `cl.verify(receipt) -> dict`

POSTs the receipt to the configured verifier URL and returns the parsed JSON response.

## Receipt proof schema

```json
{
"proof": {
"alg": "ed25519",
"canonical": "json.sorted_keys.v1",
"kid": "<key-id>",
"signature": "<base64>",
"signer_id": "<ens-name>"
}
}
```

## License

MIT
Loading
Loading