diff --git a/.amazonq/rules/nevermined-router.md b/.amazonq/rules/nevermined-router.md new file mode 100644 index 00000000..039c2619 --- /dev/null +++ b/.amazonq/rules/nevermined-router.md @@ -0,0 +1,118 @@ +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/.clinerules/nevermined-router.md b/.clinerules/nevermined-router.md new file mode 100644 index 00000000..a1897952 --- /dev/null +++ b/.clinerules/nevermined-router.md @@ -0,0 +1,127 @@ +--- +paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" + - "**/*.py" +--- + +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/.cursor/rules/nevermined-router.mdc b/.cursor/rules/nevermined-router.mdc new file mode 100644 index 00000000..30b6992b --- /dev/null +++ b/.cursor/rules/nevermined-router.mdc @@ -0,0 +1,123 @@ +--- +description: Nevermined Router — pay any external x402 or MPP service from a capped Delegation +globs: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.py", "*.mjs"] +--- + +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/.github/workflows/publish-skill-clawhub.yml b/.github/workflows/publish-skill-clawhub.yml index 7dbbab8e..35a443f8 100644 --- a/.github/workflows/publish-skill-clawhub.yml +++ b/.github/workflows/publish-skill-clawhub.yml @@ -57,10 +57,6 @@ jobs: - path: skills/nevermined-router slug: nevermined-router name: Nevermined Router - # First publish of a new slug only — pins it to the frontmatter - # version instead of the CLI's 1.0.0 default. Delete this line once - # it has published once; auto-increment takes over from there. - version: "0.1.0" env: SKILL_PATH: ${{ matrix.path }} OWNER: nevermined-io diff --git a/.gitignore b/.gitignore index 52f27b26..4ce65a4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -# Cursor -/.cursor +# Cursor — local editor state, EXCEPT `.cursor/rules/`, which is published +# content: the install page tells users to curl it from `main`, so a rule file +# that silently fails to commit becomes a 404 (and, without `--fail`, writes +# "404: Not Found" into their rules directory). `nevermined-payments.mdc` only +# survived the blanket `/.cursor` because it was tracked before the rule existed. +/.cursor/* +!/.cursor/rules/ # Node node_modules diff --git a/.windsurf/rules/nevermined-router.md b/.windsurf/rules/nevermined-router.md new file mode 100644 index 00000000..039c2619 --- /dev/null +++ b/.windsurf/rules/nevermined-router.md @@ -0,0 +1,118 @@ +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/CLAUDE.md b/CLAUDE.md index c7ac76d0..a8a3dc45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,25 @@ This document provides comprehensive guidelines for AI agents to write and maint ### AI Coding Skill -This repository includes a **Nevermined AI Skill** at `skills/nevermined-payments/SKILL.md` with 7 reference files. This skill provides AI coding assistants (Claude Code, Cursor, Copilot, ChatGPT) with SDK patterns and integration knowledge. When updating SDK code examples, framework integrations, or x402 protocol documentation, also update the corresponding skill reference files in `skills/nevermined-payments/references/` to keep them in sync. IDE-specific files (`.cursorrules`, `.cursor/rules/`, `.github/copilot-instructions.md`) contain condensed versions of the skill and should be updated when core patterns change. +This repository publishes **two** Nevermined AI Skills, pointed in opposite directions: + +- `skills/nevermined-payments/` — **receiving** payments (SDK, middleware, plans, credits), 12 reference files. +- `skills/nevermined-router/` — **spending** at external x402 / MPP services through the Nevermined Router, 5 reference files. + +They give AI coding assistants (Claude Code, Cursor, Copilot, Codex, Windsurf, Cline, Amazon Q) SDK patterns and integration knowledge. When updating SDK code examples, framework integrations, or x402 protocol documentation, also update the matching `references/` files to keep them in sync. + +**IDE-specific files carry condensed versions of a skill and must be updated when core patterns change.** The full set — note there is **no `.cursorrules`** in this repo: + +| Path | Skills present | +| --- | --- | +| `.cursor/rules/.mdc` | payments, router | +| `.windsurf/rules/.md` | payments, router (**6,000-char limit per file**) | +| `.clinerules/.md` | payments, router | +| `.amazonq/rules/.md` | payments, router | +| `.github/copilot-instructions.md` | payments only — single file, no skill segment | +| `AGENTS.md` | payments only — single file, no skill segment | + +`.cursor/` is otherwise gitignored; `.gitignore` re-includes `.cursor/rules/` specifically, because those files are published — users `curl` them from `main`, so one that fails to commit becomes a 404 in their editor. Both `development-guide/build-using-nvm-skill.mdx` (the install page) and its "Supported Tools at a Glance" table must be updated when this set changes. --- diff --git a/development-guide/build-using-nvm-skill.mdx b/development-guide/build-using-nvm-skill.mdx index 89ea7cee..7abfc489 100644 --- a/development-guide/build-using-nvm-skill.mdx +++ b/development-guide/build-using-nvm-skill.mdx @@ -17,7 +17,11 @@ There are two, and which one you want depends on which side of the payment you'r | **`nevermined-payments`** | **receiving** money, or buying a Nevermined plan | SDK integration, middleware, plans, credits — the rest of this page | | **`nevermined-router`** | **spending** money at external services | Discovering services in the catalog, creating a spending Delegation, paying any x402 or MPP endpoint through the [Nevermined Router](/products/router/overview) | -Every install method below works for either — swap `nevermined-payments` for `nevermined-router` in the paths. Installing both is fine; they don't overlap. +Installing both is fine — they don't overlap. How you install the Router one depends on your tool: + +- **Claude Code, Cursor, Windsurf, Cline, Amazon Q** — swap `nevermined-payments` for `nevermined-router` in the paths below, or run the extra command shown in each tab. +- **Codex CLI** — its `AGENTS.md` route is a *single* file with no skill name in it, and that file carries the **payments** guidance only. Use the tab's **"install as a Codex skill"** alternative instead: `.agents/skills/` takes a skill directory, so `nevermined-router` works there. +- **GitHub Copilot** — `.github/copilot-instructions.md` is a single file with nothing to swap, and it carries the **payments** guidance only. Until that changes, use the [full skill on GitHub](https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router) or paste it in manually. ## What's Included @@ -46,9 +50,10 @@ Choose your AI coding tool below for step-by-step installation instructions. tmpdir="$(mktemp -d)" git clone --depth 1 --filter=blob:none --sparse https://github.com/nevermined-io/docs "$tmpdir" cd "$tmpdir" - git sparse-checkout set skills/nevermined-payments + git sparse-checkout set skills/nevermined-payments skills/nevermined-router cp -R skills/nevermined-payments ~/.claude/skills/ + cp -R skills/nevermined-router ~/.claude/skills/ # buying through the Router cd - rm -rf "$tmpdir" @@ -92,8 +97,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .cursor/rules - curl -o .cursor/rules/nevermined-payments.mdc \ + curl --fail -o .cursor/rules/nevermined-payments.mdc \ https://raw.githubusercontent.com/nevermined-io/docs/main/.cursor/rules/nevermined-payments.mdc + + # Buying from external x402 / MPP services through the Router + curl --fail -o .cursor/rules/nevermined-router.mdc \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.cursor/rules/nevermined-router.mdc ``` The `.cursor/rules/*.mdc` format supports glob-based activation — the Nevermined rule auto-activates when you work with `.ts`, `.js`, or `.py` files. @@ -106,8 +115,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .windsurf/rules - curl -o .windsurf/rules/nevermined-payments.md \ + curl --fail -o .windsurf/rules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.windsurf/rules/nevermined-payments.md + + # Buying from external x402 / MPP services through the Router + curl --fail -o .windsurf/rules/nevermined-router.md \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.windsurf/rules/nevermined-router.md ``` Windsurf automatically discovers rules from `.windsurf/rules/` and applies them based on their activation mode. @@ -126,7 +139,7 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .github - curl -o .github/copilot-instructions.md \ + curl --fail -o .github/copilot-instructions.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.github/copilot-instructions.md ``` @@ -151,7 +164,7 @@ Choose your AI coding tool below for step-by-step installation instructions. Codex CLI reads `AGENTS.md` files for project context. Add it to your project: ```bash - curl -o AGENTS.md \ + curl --fail -o AGENTS.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/AGENTS.md ``` @@ -162,6 +175,7 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .agents/skills cp -r skills/nevermined-payments .agents/skills/ + cp -r skills/nevermined-router .agents/skills/ # buying through the Router ``` Codex CLI detects skills from `.agents/skills/` and loads them when relevant. @@ -174,8 +188,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .clinerules - curl -o .clinerules/nevermined-payments.md \ + curl --fail -o .clinerules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.clinerules/nevermined-payments.md + + # Buying from external x402 / MPP services through the Router + curl --fail -o .clinerules/nevermined-router.md \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.clinerules/nevermined-router.md ``` Cline discovers all `.md` files in the `.clinerules/` directory and applies them based on the `paths` frontmatter. The Nevermined rule activates for `.ts`, `.js`, and `.py` files. @@ -190,8 +208,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .amazonq/rules - curl -o .amazonq/rules/nevermined-payments.md \ + curl --fail -o .amazonq/rules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.amazonq/rules/nevermined-payments.md + + # Buying from external x402 / MPP services through the Router + curl --fail -o .amazonq/rules/nevermined-router.md \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.amazonq/rules/nevermined-router.md ``` Amazon Q automatically uses rules from `.amazonq/rules/` as context. You can toggle rules on/off via the Rules button in the Amazon Q chat panel. @@ -212,15 +234,17 @@ Choose your AI coding tool below for step-by-step installation instructions. ## Supported Tools at a Glance -| Tool | Config File | Auto-Activates | -|---|---|---| -| **Claude Code** | `~/.claude/skills/nevermined-payments/SKILL.md` | Yes — when relevant | -| **Cursor** | `.cursor/rules/nevermined-payments.mdc` | Yes — on `.ts`, `.js`, `.py` files | -| **Windsurf** | `.windsurf/rules/nevermined-payments.md` | Yes — model decides | -| **GitHub Copilot** | `.github/copilot-instructions.md` | Yes — always | -| **Codex CLI** | `AGENTS.md` or `.agents/skills/nevermined-payments/` | Yes — always or when relevant | -| **Cline** | `.clinerules/nevermined-payments.md` | Yes — on `.ts`, `.js`, `.py` files | -| **Amazon Q** | `.amazonq/rules/nevermined-payments.md` | Yes — always | +`` is `nevermined-payments` or `nevermined-router` — install either or both. + +| Tool | Config File | Router available? | Auto-Activates | +|---|---|---|---| +| **Claude Code** | `~/.claude/skills//SKILL.md` | Yes | Yes — when relevant | +| **Cursor** | `.cursor/rules/.mdc` | Yes | Yes — on `.ts`, `.js`, `.py` files | +| **Windsurf** | `.windsurf/rules/.md` | Yes | Yes — model decides | +| **GitHub Copilot** | `.github/copilot-instructions.md` | **No** — single file, payments only | Yes — always | +| **Codex CLI** | `AGENTS.md` or `.agents/skills//` | Yes — via `.agents/skills/`; **not** via `AGENTS.md` (single file, payments only) | Yes — always or when relevant | +| **Cline** | `.clinerules/.md` | Yes | Yes — on `.ts`, `.js`, `.py` files | +| **Amazon Q** | `.amazonq/rules/.md` | Yes | Yes — always | ## Example Prompts diff --git a/skills/nevermined-router/SKILL.md b/skills/nevermined-router/SKILL.md index bf5b25a1..1d884d06 100644 --- a/skills/nevermined-router/SKILL.md +++ b/skills/nevermined-router/SKILL.md @@ -1,7 +1,7 @@ --- name: nevermined-router -version: "0.1.0" -lastUpdated: "2026-08-03" +version: "0.1.1" +lastUpdated: "2026-08-04" description: > Use when an AI agent needs to PAY an external service it does not have an account with — any x402 agent or MPP merchant — using the Nevermined Router. Covers @@ -28,7 +28,7 @@ metadata: # Nevermined Router — buy from any x402 or MPP service -> **Skill version**: 0.1.0 | **Last updated**: 2026-08-03 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +> **Skill version**: 0.1.1 | **Last updated**: 2026-08-04 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router > > **⚠️ Use the latest version.** If you have a cached copy, check its **Last updated** date against the canonical source and refresh if older. > @@ -48,6 +48,8 @@ It works because a growing set of services quote their price **on the wire**: yo **This skill cannot help you with conventional SaaS APIs.** Exa, Firecrawl, Tavily and similar are billed out of band — a monthly plan, a long-lived key. They never quote a price for one call, so there is nothing on the wire for the Router to pay and no address to pay it to. The Router isn't missing a feature; the transaction it performs does not exist for those services. If a service answers `401` or `403` rather than `402`, it wants **authentication**, not payment — stop, and tell the user it needs an account. +That is not a dead end, just a different rail. Nevermined can still buy from such a provider **out of band** — purchasing API credits up front instead of paying per call. Exa is the worked example: a $7 x402 card-delegation purchase provisions or tops up an Exa API key, fully agent-driven — https://nevermined.ai/docs/integrations/exa. That flow belongs to the `nevermined-payments` skill and the Payments SDK. What you cannot do is put those calls through `/router/route`. + ## The buy loop Six steps. Steps 1–3 happen once; 4–6 repeat per purchase. diff --git a/skills/nevermined-router/references/paying.md b/skills/nevermined-router/references/paying.md index 027ea5e7..4dceffb4 100644 --- a/skills/nevermined-router/references/paying.md +++ b/skills/nevermined-router/references/paying.md @@ -177,18 +177,24 @@ Call the merchant with no payment. It answers `402` with its requirements: Pass `target` **verbatim** from the 402. Do not normalise, reorder or re-encode it. +The response echoes the negotiated version, and `credential.name` follows from it — the request +above is v1, so this one comes back v1 with the v1 header: + ```json { "paymentId": "b1f9c2e4-…", "protocol": "x402", - "x402Version": 2, - "credential": { "transport": "header", "name": "PAYMENT-SIGNATURE", "value": "eyJ4NDAy…" }, + "x402Version": 1, + "credential": { "transport": "header", "name": "X-PAYMENT", "value": "eyJ4NDAy…" }, "settlement": { "recipient": "0x2096…", "amount": "1000", "asset": "USDC", "network": "base", "approxCents": "1" }, "status": "Issued" } ``` +Had you passed a v2 `target` (the default), the same call would return `"x402Version": 2` and +`"name": "PAYMENT-SIGNATURE"`. + ### 3 · Attach it and re-send Set an HTTP header named **`credential.name`** to **`credential.value`** on your original request