@web42/w42 0.1.20 → 0.1.22
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.
- package/README.md +167 -67
- package/dist/commands/auth.js +2 -1
- package/dist/commands/cart.d.ts +2 -0
- package/dist/commands/cart.js +219 -0
- package/dist/commands/intent.d.ts +2 -0
- package/dist/commands/intent.js +105 -0
- package/dist/commands/register.js +13 -20
- package/dist/commands/search.js +4 -2
- package/dist/commands/send.js +38 -12
- package/dist/commands/serve.js +1 -0
- package/dist/index.js +4 -2
- package/dist/utils/api.d.ts +7 -0
- package/dist/utils/api.js +24 -2
- package/dist/utils/config.d.ts +12 -0
- package/dist/utils/config.js +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/commands/pay.d.ts +0 -2
- package/dist/commands/pay.js +0 -260
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @web42/cli
|
|
2
2
|
|
|
3
|
-
CLI for the Web42 Agent Network — authenticate, publish, discover,
|
|
3
|
+
CLI for the Web42 Agent Network — authenticate, publish, discover, interact with A2A agents, and manage AP2 payments.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -11,20 +11,22 @@ npm install -g @web42/cli
|
|
|
11
11
|
## Authentication
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
w42 auth login # Sign in via GitHub OAuth
|
|
15
|
+
w42 auth logout # Sign out
|
|
16
|
+
w42 auth whoami # Show current user
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
---
|
|
20
|
+
|
|
19
21
|
## Commands
|
|
20
22
|
|
|
21
|
-
### `
|
|
23
|
+
### `w42 search <query>`
|
|
22
24
|
|
|
23
25
|
Search the network for agents.
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
w42 search "data analysis"
|
|
29
|
+
w42 search "pizza ordering" --limit 20
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
| Option | Description |
|
|
@@ -33,13 +35,14 @@ web42 search "image processing" --limit 20
|
|
|
33
35
|
|
|
34
36
|
---
|
|
35
37
|
|
|
36
|
-
### `
|
|
38
|
+
### `w42 send <agent> <message>`
|
|
37
39
|
|
|
38
40
|
Send a message to an A2A agent. `<agent>` can be a slug (`@user/agent`) or a direct URL (`http://localhost:3001`).
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
w42 send @alice/summarizer "Summarize this document"
|
|
44
|
+
w42 send http://localhost:3001 "Hello"
|
|
45
|
+
w42 send @alice/pizza "Margherita please" --pay tx_a1b2c3d4
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
| Option | Description |
|
|
@@ -47,134 +50,231 @@ web42 send http://localhost:3001 "Hello"
|
|
|
47
50
|
| `--new` | Start a new conversation (clears saved context) |
|
|
48
51
|
| `--context <id>` | Use a specific context ID |
|
|
49
52
|
| `--task-id <id>` | Reply to a specific task (e.g. one in `input-required` state) |
|
|
50
|
-
| `--pay <
|
|
53
|
+
| `--pay <tx_id>` | Attach a `PaymentMandate` from a local transaction (use a tx ID from `w42 cart list`) |
|
|
54
|
+
|
|
55
|
+
When an agent sends a `CartMandate`, the CLI auto-saves it as a local transaction (`tx_xxx`) and prints the next step. If you have a matching intent locally cached, it prints the one-liner checkout command directly.
|
|
51
56
|
|
|
52
57
|
---
|
|
53
58
|
|
|
54
|
-
### `
|
|
59
|
+
### `w42 wallet`
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
View your Web42 Wallet balance.
|
|
57
62
|
|
|
58
63
|
```bash
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
w42 wallet
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### `w42 wallet topup <amount>`
|
|
68
|
+
|
|
69
|
+
Add funds to your wallet. Amount is in dollars.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
w42 wallet topup 50.00
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### `w42 cart`
|
|
78
|
+
|
|
79
|
+
Manage AP2 cart payments. Carts are auto-saved as local transactions (`tx_xxx`) when received from an agent via `w42 send`.
|
|
80
|
+
|
|
81
|
+
#### `w42 cart list`
|
|
82
|
+
|
|
83
|
+
List all local payment transactions.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
w42 cart list
|
|
87
|
+
w42 cart list --status cart_received
|
|
61
88
|
```
|
|
62
89
|
|
|
63
90
|
| Option | Description |
|
|
64
91
|
|---|---|
|
|
65
|
-
| `--
|
|
66
|
-
| `--url <url>` | Public URL for registration and AgentCard (e.g. from ngrok) |
|
|
67
|
-
| `--openclaw-port <port>` | OpenClaw gateway port (default: 18789) |
|
|
68
|
-
| `--openclaw-token <token>` | OpenClaw gateway auth token (or `OPENCLAW_GATEWAY_TOKEN`) |
|
|
69
|
-
| `--openclaw-agent <id>` | OpenClaw agent ID to target (default: `main`) |
|
|
70
|
-
| `--client-id <id>` | Developer app client ID (or `W42_CLIENT_ID`) |
|
|
71
|
-
| `--client-secret <secret>` | Developer app client secret (or `W42_CLIENT_SECRET`) |
|
|
72
|
-
| `--visibility <vis>` | Marketplace visibility: `public` or `private` |
|
|
73
|
-
| `--verbose` | Enable verbose request/response logging |
|
|
92
|
+
| `--status <status>` | Filter by status: `cart_received`, `session_created`, `approved`, `sent` |
|
|
74
93
|
|
|
75
|
-
|
|
94
|
+
#### `w42 cart sign <tx_id>`
|
|
76
95
|
|
|
77
|
-
|
|
96
|
+
Create a payment session for human approval. Opens a browser signing URL.
|
|
78
97
|
|
|
79
|
-
|
|
98
|
+
```bash
|
|
99
|
+
w42 cart sign tx_a1b2c3d4
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- Validates that the cart supports `WEB42_WALLET` before proceeding.
|
|
103
|
+
- Stores `sessionCode` and `signingUrl` on the transaction.
|
|
104
|
+
- Returns `{ tx, signing_url }` — present the URL to the user.
|
|
105
|
+
|
|
106
|
+
#### `w42 cart poll <tx_id>`
|
|
107
|
+
|
|
108
|
+
Check the status of a payment session. When completed, stores the full `PaymentMandate` on the transaction.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
w42 cart poll tx_a1b2c3d4
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### `w42 cart checkout <tx_id> --intent <nick>`
|
|
115
|
+
|
|
116
|
+
Execute a payment against a matching intent — no human approval needed.
|
|
80
117
|
|
|
81
118
|
```bash
|
|
82
|
-
|
|
83
|
-
web42 register https://my-agent.example.com --visibility private --tags "nlp,summarization"
|
|
119
|
+
w42 cart checkout tx_a1b2c3d4 --intent starbucks-daily
|
|
84
120
|
```
|
|
85
121
|
|
|
86
122
|
| Option | Description |
|
|
87
123
|
|---|---|
|
|
88
|
-
| `--
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
| `--intent <nick>` | Intent nick to use (required) |
|
|
125
|
+
|
|
126
|
+
- Validates that the cart supports `WEB42_WALLET` before proceeding.
|
|
127
|
+
- Returns structured error codes when the intent is invalid (see below).
|
|
128
|
+
- Stale intents (exhausted, expired, not found) are automatically evicted from the local cache.
|
|
129
|
+
|
|
130
|
+
**Intent error codes:**
|
|
131
|
+
|
|
132
|
+
| Code | Meaning |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `INTENT_NOT_FOUND` | Intent doesn't exist on the platform |
|
|
135
|
+
| `INTENT_INACTIVE` | Intent is exhausted or revoked |
|
|
136
|
+
| `INTENT_EXPIRED` | Intent has passed its expiry date |
|
|
137
|
+
| `INTENT_BUDGET_EXHAUSTED` | Lifetime budget fully consumed |
|
|
138
|
+
| `INTENT_PERIOD_BUDGET_EXHAUSTED` | Daily/weekly/monthly budget hit |
|
|
139
|
+
| `INTENT_AMOUNT_EXCEEDED` | Cart total exceeds the per-transaction cap |
|
|
140
|
+
| `INTENT_AGENT_NOT_AUTHORIZED` | Merchant not covered by this intent |
|
|
141
|
+
| `INTENT_CURRENCY_MISMATCH` | Cart currency doesn't match the intent |
|
|
93
142
|
|
|
94
143
|
---
|
|
95
144
|
|
|
96
|
-
### `
|
|
145
|
+
### `w42 intent`
|
|
97
146
|
|
|
98
|
-
|
|
147
|
+
Manage payment intents. Intents are pre-authorizations that let agents spend on your behalf without per-purchase approval.
|
|
99
148
|
|
|
100
|
-
#### `
|
|
149
|
+
#### `w42 intent propose`
|
|
101
150
|
|
|
102
|
-
Generate an intent creation URL for the user to authorize in the browser.
|
|
151
|
+
Generate an intent creation URL for the user to authorize in the browser (attaches a card for automatic charging).
|
|
103
152
|
|
|
104
153
|
```bash
|
|
105
|
-
|
|
154
|
+
w42 intent propose \
|
|
155
|
+
--nick starbucks-daily \
|
|
156
|
+
--agents @x~starbucks \
|
|
157
|
+
--max-amount 5.00 \
|
|
158
|
+
--prompt-playback "Spend up to $5/day at Starbucks" \
|
|
159
|
+
--recurring daily
|
|
106
160
|
```
|
|
107
161
|
|
|
108
162
|
| Option | Description |
|
|
109
163
|
|---|---|
|
|
110
|
-
| `--nick <nick>` | Short identifier
|
|
164
|
+
| `--nick <nick>` | Short identifier, 3–50 chars, lowercase alphanumeric + hyphens (required) |
|
|
111
165
|
| `--agents <slugs>` | Comma-separated merchant agent slugs (required) |
|
|
112
166
|
| `--max-amount <dollars>` | Max amount per transaction (required) |
|
|
113
167
|
| `--prompt-playback <text>` | Human-readable description (required) |
|
|
114
168
|
| `--currency <code>` | Currency code (default: `USD`) |
|
|
115
169
|
| `--recurring <type>` | `once`, `daily`, `weekly`, or `monthly` (default: `once`) |
|
|
116
|
-
| `--budget <dollars>` | Lifetime budget |
|
|
170
|
+
| `--budget <dollars>` | Lifetime budget cap |
|
|
117
171
|
| `--expires <date>` | Expiry date (ISO 8601) |
|
|
118
172
|
|
|
119
|
-
#### `
|
|
173
|
+
#### `w42 intent get <nick>`
|
|
120
174
|
|
|
121
|
-
Fetch an intent by nick. Caches locally if active.
|
|
175
|
+
Fetch an intent by nick. Caches it locally if active.
|
|
122
176
|
|
|
123
|
-
|
|
177
|
+
```bash
|
|
178
|
+
w42 intent get starbucks-daily
|
|
179
|
+
```
|
|
124
180
|
|
|
125
|
-
|
|
181
|
+
#### `w42 intent list`
|
|
126
182
|
|
|
127
|
-
|
|
183
|
+
List all your payment intents and sync the local cache.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
w42 intent list
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### `w42 intent revoke <nick>`
|
|
128
190
|
|
|
129
191
|
Revoke an active intent.
|
|
130
192
|
|
|
131
|
-
|
|
193
|
+
```bash
|
|
194
|
+
w42 intent revoke starbucks-daily
|
|
195
|
+
```
|
|
132
196
|
|
|
133
|
-
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### `w42 serve`
|
|
200
|
+
|
|
201
|
+
Start a local A2A server for your agent, bridging to an OpenClaw gateway.
|
|
134
202
|
|
|
135
203
|
```bash
|
|
136
|
-
|
|
204
|
+
w42 serve
|
|
205
|
+
w42 serve --port 3001 --url https://my-agent.ngrok.io --verbose
|
|
137
206
|
```
|
|
138
207
|
|
|
139
208
|
| Option | Description |
|
|
140
209
|
|---|---|
|
|
141
|
-
| `--
|
|
142
|
-
| `--
|
|
143
|
-
| `--
|
|
210
|
+
| `--port <port>` | Port to listen on (default: 4000) |
|
|
211
|
+
| `--url <url>` | Public URL for registration and AgentCard (e.g. from ngrok) |
|
|
212
|
+
| `--openclaw-port <port>` | OpenClaw gateway port (default: 18789) |
|
|
213
|
+
| `--openclaw-token <token>` | OpenClaw gateway auth token (or `OPENCLAW_GATEWAY_TOKEN`) |
|
|
214
|
+
| `--openclaw-agent <id>` | OpenClaw agent ID to target (default: `main`) |
|
|
215
|
+
| `--client-id <id>` | Developer app client ID (or `W42_CLIENT_ID`) |
|
|
216
|
+
| `--client-secret <secret>` | Developer app client secret (or `W42_CLIENT_SECRET`) |
|
|
217
|
+
| `--visibility <vis>` | Marketplace visibility: `public` or `private` |
|
|
218
|
+
| `--verbose` | Enable verbose request/response logging |
|
|
219
|
+
|
|
220
|
+
Requires an `agent-card.json` in the current directory with at least a `name` field.
|
|
144
221
|
|
|
145
|
-
|
|
222
|
+
---
|
|
146
223
|
|
|
147
|
-
|
|
224
|
+
### `w42 register <url>`
|
|
225
|
+
|
|
226
|
+
Register an agent with the Web42 Network. The URL must serve `/.well-known/agent-card.json`.
|
|
148
227
|
|
|
149
228
|
```bash
|
|
150
|
-
|
|
229
|
+
w42 register https://my-agent.example.com
|
|
230
|
+
w42 register https://my-agent.example.com --tags "nlp,summarization"
|
|
151
231
|
```
|
|
152
232
|
|
|
153
233
|
| Option | Description |
|
|
154
234
|
|---|---|
|
|
155
|
-
| `--
|
|
156
|
-
| `--
|
|
235
|
+
| `--tags <tags>` | Comma-separated discovery tags |
|
|
236
|
+
| `--categories <cats>` | Comma-separated categories |
|
|
157
237
|
|
|
158
|
-
|
|
238
|
+
---
|
|
159
239
|
|
|
160
|
-
|
|
240
|
+
### `w42 telemetry`
|
|
161
241
|
|
|
162
|
-
|
|
242
|
+
Control usage telemetry.
|
|
163
243
|
|
|
164
244
|
```bash
|
|
165
|
-
|
|
245
|
+
w42 telemetry # Show current state
|
|
246
|
+
w42 telemetry on # Enable
|
|
247
|
+
w42 telemetry off # Disable
|
|
166
248
|
```
|
|
167
249
|
|
|
168
250
|
---
|
|
169
251
|
|
|
170
|
-
|
|
252
|
+
## AP2 Payment flow
|
|
171
253
|
|
|
172
|
-
|
|
254
|
+
The full end-to-end flow for session-based (human approval) payments:
|
|
173
255
|
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
256
|
+
```
|
|
257
|
+
1. w42 send @merchant/agent "I want to buy X"
|
|
258
|
+
→ Agent sends back a CartMandate
|
|
259
|
+
→ CLI auto-saves it as tx_xxx, prints next step
|
|
260
|
+
|
|
261
|
+
2. w42 cart sign tx_xxx
|
|
262
|
+
→ Creates a payment session, returns signing_url
|
|
263
|
+
→ User opens the URL and approves in the browser
|
|
264
|
+
|
|
265
|
+
3. w42 cart poll tx_xxx
|
|
266
|
+
→ Polls until status = "completed"
|
|
267
|
+
→ Stores the full PaymentMandate on tx_xxx
|
|
268
|
+
|
|
269
|
+
4. w42 send @merchant/agent "paid" --pay tx_xxx
|
|
270
|
+
→ Sends the PaymentMandate to the merchant agent
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For intent-based (automatic) payments, replace steps 2–3 with:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
2. w42 cart checkout tx_xxx --intent <nick>
|
|
277
|
+
→ Charges the saved card, stores PaymentMandate immediately
|
|
178
278
|
```
|
|
179
279
|
|
|
180
280
|
---
|
package/dist/commands/auth.js
CHANGED
|
@@ -59,7 +59,8 @@ authCommand
|
|
|
59
59
|
}
|
|
60
60
|
catch (error) {
|
|
61
61
|
spinner.fail("Failed to start auth flow");
|
|
62
|
-
console.error(error);
|
|
62
|
+
console.error(chalk.red(String(error)));
|
|
63
|
+
console.error(chalk.dim("Check your network connection and try again."));
|
|
63
64
|
process.exit(1);
|
|
64
65
|
}
|
|
65
66
|
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { ApiError, apiGet, apiPost, hintForError } from "../utils/api.js";
|
|
5
|
+
import { requireAuth, setConfigValue } from "../utils/config.js";
|
|
6
|
+
import { getTx, listTxs, updateTx } from "../utils/tx-store.js";
|
|
7
|
+
function handleIntentError(err, intentNick) {
|
|
8
|
+
const code = err instanceof ApiError ? err.code : undefined;
|
|
9
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10
|
+
switch (code) {
|
|
11
|
+
case "INTENT_NOT_FOUND":
|
|
12
|
+
setConfigValue(`intents.${intentNick}`, "");
|
|
13
|
+
console.error(chalk.red(`Intent "${intentNick}" not found.`));
|
|
14
|
+
console.error(chalk.dim(`Run \`w42 intent list\` to see available intents.`));
|
|
15
|
+
break;
|
|
16
|
+
case "INTENT_INACTIVE":
|
|
17
|
+
case "INTENT_EXPIRED":
|
|
18
|
+
case "INTENT_BUDGET_EXHAUSTED":
|
|
19
|
+
setConfigValue(`intents.${intentNick}`, "");
|
|
20
|
+
console.error(chalk.red(`Intent "${intentNick}" is no longer valid: ${msg}`));
|
|
21
|
+
console.error(chalk.dim(`Run \`w42 intent list\` to see available intents.`));
|
|
22
|
+
break;
|
|
23
|
+
case "INTENT_PERIOD_BUDGET_EXHAUSTED":
|
|
24
|
+
console.error(chalk.red(`Intent "${intentNick}": ${msg}`));
|
|
25
|
+
console.error(chalk.dim(`Try again later or run \`w42 intent list\` to find another intent.`));
|
|
26
|
+
break;
|
|
27
|
+
case "INTENT_AMOUNT_EXCEEDED":
|
|
28
|
+
console.error(chalk.red(`Intent "${intentNick}": ${msg}`));
|
|
29
|
+
console.error(chalk.dim(`Use \`w42 cart sign\` for manual approval instead.`));
|
|
30
|
+
break;
|
|
31
|
+
case "INTENT_AGENT_NOT_AUTHORIZED":
|
|
32
|
+
console.error(chalk.red(`Intent "${intentNick}" does not cover this merchant.`));
|
|
33
|
+
console.error(chalk.dim(`Use \`w42 cart sign\` for manual approval instead.`));
|
|
34
|
+
break;
|
|
35
|
+
case "INTENT_CURRENCY_MISMATCH":
|
|
36
|
+
console.error(chalk.red(`Intent "${intentNick}": ${msg}`));
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
console.error(chalk.red(`Checkout failed: ${msg}`));
|
|
40
|
+
}
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
export const cartCommand = new Command("cart").description("Manage AP2 cart payments — sign, poll, checkout, and list");
|
|
44
|
+
// ─── sign ─────────────────────────────────────────────────
|
|
45
|
+
cartCommand
|
|
46
|
+
.command("sign")
|
|
47
|
+
.description("Create a payment session for human approval")
|
|
48
|
+
.argument("<tx_id>", "Transaction ID from tx-store")
|
|
49
|
+
.action(async (txId) => {
|
|
50
|
+
requireAuth();
|
|
51
|
+
const tx = getTx(txId);
|
|
52
|
+
if (!tx) {
|
|
53
|
+
console.error(chalk.red(`Transaction ${txId} not found. Run: w42 cart list`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
// Check that the cart supports WEB42_WALLET payment method
|
|
57
|
+
try {
|
|
58
|
+
const contents = tx.cartMandate.contents;
|
|
59
|
+
const pr = contents?.payment_request;
|
|
60
|
+
const methodData = pr?.method_data;
|
|
61
|
+
const supportsWeb42 = methodData?.some((m) => m.supported_methods === "WEB42_WALLET");
|
|
62
|
+
if (!supportsWeb42) {
|
|
63
|
+
console.error(chalk.yellow(`⚠ This cart does not accept Web42 Wallet as a payment method.\n` +
|
|
64
|
+
` Tell your shopping agent: "I cannot pay with Web42 Wallet — ` +
|
|
65
|
+
`please ask the user how they would like to proceed."`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
console.error(chalk.red("Could not read payment method data from cart"));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
// Extract total from stored cart
|
|
74
|
+
let totalCents = 0;
|
|
75
|
+
let currency = "usd";
|
|
76
|
+
try {
|
|
77
|
+
const contents = tx.cartMandate.contents;
|
|
78
|
+
const pr = contents?.payment_request;
|
|
79
|
+
const details = pr?.details;
|
|
80
|
+
const total = details?.total;
|
|
81
|
+
totalCents = Math.round(total.amount.value * 100);
|
|
82
|
+
currency = total.amount.currency.toLowerCase();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
console.error(chalk.red("Could not extract total from stored cart"));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const spinner = ora("Creating payment session...").start();
|
|
89
|
+
try {
|
|
90
|
+
const res = await apiPost("/api/pay/session", {
|
|
91
|
+
agent_slug: tx.agentSlug,
|
|
92
|
+
cart: tx.cartMandate,
|
|
93
|
+
total_cents: totalCents,
|
|
94
|
+
currency,
|
|
95
|
+
});
|
|
96
|
+
spinner.stop();
|
|
97
|
+
updateTx(txId, {
|
|
98
|
+
sessionCode: res.code,
|
|
99
|
+
signingUrl: res.signing_url,
|
|
100
|
+
status: "session_created",
|
|
101
|
+
});
|
|
102
|
+
console.log(JSON.stringify({ tx: txId, signing_url: res.signing_url }, null, 2));
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
spinner.fail("Failed to create session");
|
|
106
|
+
console.error(chalk.red(String(err)));
|
|
107
|
+
console.error(chalk.dim(hintForError(err)));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
// ─── poll ─────────────────────────────────────────────────
|
|
112
|
+
cartCommand
|
|
113
|
+
.command("poll")
|
|
114
|
+
.description("Check the status of a payment session")
|
|
115
|
+
.argument("<tx_id>", "Transaction ID")
|
|
116
|
+
.action(async (txId) => {
|
|
117
|
+
requireAuth();
|
|
118
|
+
const tx = getTx(txId);
|
|
119
|
+
if (!tx) {
|
|
120
|
+
console.error(chalk.red(`Transaction ${txId} not found. Run: w42 cart list`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
if (!tx.sessionCode) {
|
|
124
|
+
console.error(chalk.red(`No session created yet. Run: w42 cart sign ${txId}`));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const spinner = ora("Fetching session...").start();
|
|
128
|
+
try {
|
|
129
|
+
const res = await apiGet(`/api/pay/session/${encodeURIComponent(tx.sessionCode)}`);
|
|
130
|
+
spinner.stop();
|
|
131
|
+
if (res.status === "completed" && res.payment_mandate) {
|
|
132
|
+
updateTx(txId, {
|
|
133
|
+
paymentMandate: res.payment_mandate,
|
|
134
|
+
status: "approved",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
console.log(JSON.stringify({ tx: txId, ...res }, null, 2));
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
spinner.fail("Failed to fetch session");
|
|
141
|
+
console.error(chalk.red(String(err)));
|
|
142
|
+
console.error(chalk.dim(hintForError(err)));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// ─── checkout ─────────────────────────────────────────────
|
|
147
|
+
cartCommand
|
|
148
|
+
.command("checkout")
|
|
149
|
+
.description("Execute a payment against a matching intent (no human needed)")
|
|
150
|
+
.argument("<tx_id>", "Transaction ID from tx-store")
|
|
151
|
+
.requiredOption("--intent <nick>", "Intent nick to use")
|
|
152
|
+
.action(async (txId, opts) => {
|
|
153
|
+
requireAuth();
|
|
154
|
+
const tx = getTx(txId);
|
|
155
|
+
if (!tx) {
|
|
156
|
+
console.error(chalk.red(`Transaction ${txId} not found. Run: w42 cart list`));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
// Check that the cart supports WEB42_WALLET payment method
|
|
160
|
+
try {
|
|
161
|
+
const contents = tx.cartMandate.contents;
|
|
162
|
+
const pr = contents?.payment_request;
|
|
163
|
+
const methodData = pr?.method_data;
|
|
164
|
+
const supportsWeb42 = methodData?.some((m) => m.supported_methods === "WEB42_WALLET");
|
|
165
|
+
if (!supportsWeb42) {
|
|
166
|
+
console.error(chalk.yellow(`⚠ This cart does not accept Web42 Wallet as a payment method.\n` +
|
|
167
|
+
` Tell your shopping agent: "I cannot pay with Web42 Wallet — ` +
|
|
168
|
+
`please ask the user how they would like to proceed."`));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
console.error(chalk.red("Could not read payment method data from cart"));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const spinner = ora("Processing checkout...").start();
|
|
177
|
+
try {
|
|
178
|
+
const res = await apiPost("/api/pay/checkout", {
|
|
179
|
+
cart: tx.cartMandate,
|
|
180
|
+
agent_slug: tx.agentSlug,
|
|
181
|
+
intent_nick: opts.intent,
|
|
182
|
+
});
|
|
183
|
+
spinner.stop();
|
|
184
|
+
if (res.payment_mandate) {
|
|
185
|
+
updateTx(txId, {
|
|
186
|
+
paymentMandate: res.payment_mandate,
|
|
187
|
+
status: "approved",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
console.log(JSON.stringify({ tx: txId, ...res }, null, 2));
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
spinner.fail("Checkout failed");
|
|
194
|
+
handleIntentError(err, opts.intent);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
// ─── list ─────────────────────────────────────────────────
|
|
198
|
+
cartCommand
|
|
199
|
+
.command("list")
|
|
200
|
+
.description("List local payment transactions")
|
|
201
|
+
.option("--status <status>", "Filter by status (cart_received, session_created, approved, sent)")
|
|
202
|
+
.action((opts) => {
|
|
203
|
+
const txs = listTxs(opts.status);
|
|
204
|
+
if (txs.length === 0) {
|
|
205
|
+
console.log(chalk.dim("No transactions found."));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
for (const tx of txs) {
|
|
209
|
+
const cart = tx.cartMandate;
|
|
210
|
+
const contents = cart?.contents;
|
|
211
|
+
const pr = contents?.payment_request;
|
|
212
|
+
const details = pr?.details;
|
|
213
|
+
const total = details?.total;
|
|
214
|
+
const amount = total?.amount
|
|
215
|
+
? `${total.amount.currency} ${total.amount.value?.toFixed(2)}`
|
|
216
|
+
: "?";
|
|
217
|
+
console.log(`${chalk.cyan(tx.id)} ${chalk.dim(tx.status.padEnd(16))} ${amount} ${chalk.dim(tx.agentSlug)}`);
|
|
218
|
+
}
|
|
219
|
+
});
|