@curless/agentbank-sdk 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +137 -16
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,14 +1,127 @@
1
1
  # @curless/agentbank-sdk
2
2
 
3
- Official SDK for **agentbank** — agent-commerce payments. Wraps the
4
- agentbank API: agent OAuth, agent/customer management, and the Pay
5
- spend-control plane.
3
+ Official buyer-side SDK for **agentbank** — agent-commerce payments.
4
+
5
+ Two things live here:
6
+
7
+ - **The buyer's wallet** — a person signs in, binds a card, sets their own
8
+ spend limits, and mints a one-off credential to pay a merchant with. This is
9
+ what most integrators come for.
10
+ - **The Pay spend-control plane** — agent OAuth, agent/customer management,
11
+ authorize/capture against an agent's budget.
6
12
 
7
13
  ```bash
8
14
  npm install @curless/agentbank-sdk
9
15
  ```
10
16
 
11
- ## Quick start
17
+ > Install this package, not `@curless/agentbank-core`. Core is the shared
18
+ > kernel (crypto, errors, HTTP) that this package is built on; it arrives as a
19
+ > transitive dependency and has nothing in it you should be calling.
20
+
21
+ ## The buyer's wallet
22
+
23
+ The wallet is **independent of any merchant**. It holds the buyer's card and
24
+ the buyer's limits, and it issues a credential; it never talks to a shop. The
25
+ credential is what travels.
26
+
27
+ ### Sign in
28
+
29
+ The buyer signs in **in their own browser**, not in your app — verifying an
30
+ email means clicking a link in an inbox, and tokenizing a real card means
31
+ Stripe.js on a page. Neither fits in a chat window or a CLI. So sign-in is the
32
+ RFC 8628 device flow: you get a URL, you show it, you wait.
33
+
34
+ ```ts
35
+ import { createBuyerSession } from '@curless/agentbank-sdk';
36
+
37
+ const wallet = createBuyerSession({ baseUrl: 'https://mcp.curless.ai' });
38
+
39
+ const login = await wallet.startDeviceLogin();
40
+ console.log(`Open ${login.verificationUriComplete} to sign in`);
41
+ const user = await login.wait(); // resolves when they finish in the browser
42
+ ```
43
+
44
+ `wait()` polls for you and tells `pending` / `slow_down` / `expired` / `denied`
45
+ apart — an expired sign-in is a different sentence from one still in progress.
46
+ The device code itself never leaves the returned object: it is the bearer
47
+ credential that collects the session, so a caller who only needs a URL never
48
+ sees it.
49
+
50
+ There is also `wallet.login(email, password)` for a deployment configured with
51
+ a single account.
52
+
53
+ ### Pay for something
54
+
55
+ ```ts
56
+ // The merchant priced this and opened a checkout; you have its id and total.
57
+ const credential = await wallet.payCredential({
58
+ amount: 192_000, // minor units — €1,920.00
59
+ currency: 'EUR',
60
+ merchantRef: merchantId, // checked against the buyer's own allowlist
61
+ });
62
+
63
+ // Hand credential.token to that merchant's checkout as the payment token.
64
+ // You never see a card number, and neither does the merchant.
65
+ ```
66
+
67
+ The credential is a Stripe Shared Payment Token: **one seller, one currency,
68
+ one maximum, fifteen minutes**. It cannot be replayed against a different
69
+ merchant or a larger sum.
70
+
71
+ The buyer's own limits are evaluated here, before Stripe is asked for anything.
72
+ A refusal is **the buyer's limit talking, not a payment failure** — worth saying
73
+ to them in those words:
74
+
75
+ ```ts
76
+ try {
77
+ await wallet.payCredential({ amount: 192_000, currency: 'EUR', merchantRef });
78
+ } catch (err) {
79
+ if (AgentbankError.is(err) && err.status === 403) {
80
+ // "this is over the daily limit you set" — not "the payment failed"
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### The rest of the wallet
86
+
87
+ ```ts
88
+ await wallet.me(); // who this is + spendPolicy + spentToday/Month
89
+ await wallet.setLimits({ dailyLimit: 50_000, merchantAllowlist: ['clubmed'] });
90
+ await wallet.balance();
91
+ await wallet.orders({ limit: 20 });
92
+
93
+ await wallet.cards(); // { cards, unavailable? } ← see below
94
+ await wallet.bindCard('pm_card_visa');
95
+ await wallet.unbindCard('pm_123');
96
+
97
+ await wallet.logout(); // revokes server-side, then forgets it
98
+ ```
99
+
100
+ `wallet.fetch<T>(path, init)` is the escape hatch for anything not wrapped
101
+ above — it carries the session and the same 401/403 handling. Reach for a
102
+ method first: the `/v1/buyer/*` paths are ours to change, and the methods are
103
+ the part we keep.
104
+
105
+ **`cards()` returns `unavailable` for a reason.** An empty `cards` with no
106
+ `unavailable` means the buyer has bound none. An empty `cards` *with* it means
107
+ we could not read them. Flatten the two and you tell someone their cards are
108
+ gone, and watch them bind another.
109
+
110
+ `setLimits` **replaces** the policy rather than merging it — an omitted field
111
+ clears that limit. Read `me()` first and spread if you mean to change one.
112
+
113
+ ### Session state
114
+
115
+ - Only a **401** ends the session. A **403** does not: hitting a limit you set
116
+ yourself must not sign you out, or you cannot reach the session you would
117
+ need to raise it.
118
+ - `wallet.current()` returns the signed-in user or `null`, no round-trip.
119
+ - Calls made while signed out throw with code `buyer_not_logged_in`; calls made
120
+ after expiry throw `buyer_session_expired`. They are deliberately different
121
+ strings, because "your session expired" rendered as "you have no orders" is
122
+ the same bug twice.
123
+
124
+ ## The Pay spend-control plane
12
125
 
13
126
  ### Admin (manage agents, fund, approve)
14
127
 
@@ -16,17 +129,15 @@ npm install @curless/agentbank-sdk
16
129
  import { Agentbank } from '@curless/agentbank-sdk';
17
130
 
18
131
  const ab = new Agentbank({
19
- baseUrl: 'https://api.agentbank.example',
132
+ baseUrl: 'https://mcp.curless.ai',
20
133
  apiKey: 'agb_admin_...', // an agentbank:admin / pay:admin key
21
134
  });
22
135
 
23
- // 1. create an agent + a spend policy
24
136
  const agent = await ab.agents.create({
25
137
  name: 'procurement-bot',
26
138
  spendPolicy: { perTransactionLimit: 50_00, dailyLimit: 500_00, approvalRequiredAbove: 100_00 },
27
139
  });
28
140
 
29
- // 2. fund it (after an org deposit), and issue it a client credential
30
141
  await ab.pay.deposit({ amount: 1000_00, currency: 'USD' });
31
142
  await ab.pay.fundAgent(agent.id, { amount: 500_00, currency: 'USD' });
32
143
  const cred = await ab.agents.issueCredential(agent.id); // cred.secret shown once
@@ -35,33 +146,43 @@ const cred = await ab.agents.issueCredential(agent.id); // cred.secret shown onc
35
146
  ### Agent (spend, with auto-managed token)
36
147
 
37
148
  ```ts
38
- // Exchanges the credential for short-lived tokens; auto-refreshes.
39
149
  const agentClient = Agentbank.withClientCredentials({
40
- baseUrl: 'https://api.agentbank.example',
41
- clientSecret: cred.secret, // the agb_… issued above
150
+ baseUrl: 'https://mcp.curless.ai',
151
+ clientSecret: cred.secret,
42
152
  });
43
153
 
44
154
  const auth = await agentClient.pay.authorize({
45
155
  agentId: agent.id,
46
156
  merchantRef: 'acme.example',
47
- amount: 12_00, // minor units
157
+ amount: 12_00,
48
158
  idempotencyKey: 'order-123',
49
159
  });
50
160
  // auth.status: 'authorized' | 'pending_approval' | 'denied'
51
- if (auth.status === 'authorized') {
52
- await agentClient.pay.capture(auth.id);
53
- }
161
+ if (auth.status === 'authorized') await agentClient.pay.capture(auth.id);
54
162
  ```
55
163
 
164
+ `SpendPolicy` is one type across both halves — an agent's budget and a buyer's
165
+ wallet limits are the same shape, evaluated by the same code on the server.
166
+
56
167
  ## Notes
57
168
 
58
- - **Amounts** are minor-unit integers (USD = cents).
169
+ - **Amounts** are minor-unit integers (USD = cents, EUR = cents).
170
+ - **Never send a card number.** Cards are bound by Stripe reference (`pm_…` /
171
+ `tok_…`); a PAN reaching a server is a compliance incident, and this API
172
+ refuses one rather than storing it.
59
173
  - **Errors** throw `AgentbankError` with `.status` + `.code` — including
60
174
  transport failures: `status === 0` with code `timeout` / `aborted` /
61
- `network_error` means no HTTP response happened. One catch type.
175
+ `network_error` means no HTTP response happened. One catch type. Use
176
+ `AgentbankError.is(err)`, not `instanceof`, so the guard survives two copies
177
+ of the package in one dependency tree.
62
178
  - **Timeouts**: every request has a 30s deadline by default. Tune per client
63
179
  (`new Agentbank({ ..., timeoutMs })`) or per request
64
180
  (`RequestOptions.timeoutMs`; `0` disables). `RequestOptions.signal` accepts
65
181
  an `AbortSignal` for caller-side cancellation.
66
182
  - ESM-only; Node ≥ 18 (uses global `fetch`).
67
183
  - Pass `fetch` in the constructor to inject a custom implementation (tests, proxies).
184
+
185
+ ## Prefer not to write code?
186
+
187
+ `npx -y @curless/agentbank-mcp` is this wallet as an MCP server — ten tools,
188
+ no configuration, the buyer signs in at runtime. Same session, same limits.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curless/agentbank-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Official buyer/agent SDK for agentbank — OAuth, agent + customer management, Wallets, and the Pay spend-control plane.",
5
5
  "license": "MIT",
6
6
  "type": "module",