@web42/w42 0.1.16 → 0.1.17

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.
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const payCommand: Command;
@@ -0,0 +1,236 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import ora from "ora";
4
+ import { apiGet, apiPost } from "../utils/api.js";
5
+ import { requireAuth, setConfigValue, } from "../utils/config.js";
6
+ // ─── Wallet ───────────────────────────────────────────────
7
+ const walletCommand = new Command("wallet")
8
+ .description("View or top up your wallet balance")
9
+ .action(async () => {
10
+ requireAuth();
11
+ const spinner = ora("Fetching wallet...").start();
12
+ try {
13
+ const res = await apiGet("/api/pay/wallet");
14
+ spinner.stop();
15
+ console.log(JSON.stringify(res, null, 2));
16
+ }
17
+ catch (err) {
18
+ spinner.fail("Failed to fetch wallet");
19
+ console.error(chalk.red(String(err)));
20
+ process.exit(1);
21
+ }
22
+ });
23
+ walletCommand
24
+ .command("topup")
25
+ .description("Add funds to your wallet")
26
+ .argument("<amount>", "Amount in dollars (e.g. 50.00)")
27
+ .action(async (amountStr) => {
28
+ requireAuth();
29
+ const amountCents = Math.round(parseFloat(amountStr) * 100);
30
+ if (isNaN(amountCents) || amountCents <= 0) {
31
+ console.error(chalk.red("Amount must be a positive number"));
32
+ process.exit(1);
33
+ }
34
+ const spinner = ora("Topping up wallet...").start();
35
+ try {
36
+ const res = await apiPost("/api/pay/wallet/topup", { amount_cents: amountCents });
37
+ spinner.stop();
38
+ console.log(JSON.stringify(res, null, 2));
39
+ }
40
+ catch (err) {
41
+ spinner.fail("Failed to top up wallet");
42
+ console.error(chalk.red(String(err)));
43
+ process.exit(1);
44
+ }
45
+ });
46
+ // ─── Intent ───────────────────────────────────────────────
47
+ const intentCommand = new Command("intent").description("Manage payment intents");
48
+ intentCommand
49
+ .command("get")
50
+ .description("Fetch an intent by nick")
51
+ .requiredOption("--nick <nick>", "Intent nick")
52
+ .action(async (opts) => {
53
+ requireAuth();
54
+ const spinner = ora(`Fetching intent ${opts.nick}...`).start();
55
+ try {
56
+ const res = await apiGet(`/api/pay/intent/${encodeURIComponent(opts.nick)}`);
57
+ spinner.stop();
58
+ // Cache if active
59
+ if (res.status === "active") {
60
+ setConfigValue(`intents.${opts.nick}`, JSON.stringify(res));
61
+ }
62
+ console.log(JSON.stringify(res, null, 2));
63
+ }
64
+ catch (err) {
65
+ spinner.fail("Failed to fetch intent");
66
+ console.error(chalk.red(String(err)));
67
+ process.exit(1);
68
+ }
69
+ });
70
+ intentCommand
71
+ .command("list")
72
+ .description("List all your intents")
73
+ .action(async () => {
74
+ requireAuth();
75
+ const spinner = ora("Fetching intents...").start();
76
+ try {
77
+ const intents = await apiGet("/api/pay/intent");
78
+ spinner.stop();
79
+ // Sync active intents to cache, remove stale ones
80
+ const activeNicks = new Set();
81
+ for (const intent of intents) {
82
+ if (intent.status === "active" && typeof intent.nick === "string") {
83
+ activeNicks.add(intent.nick);
84
+ setConfigValue(`intents.${intent.nick}`, JSON.stringify(intent));
85
+ }
86
+ }
87
+ console.log(JSON.stringify(intents, null, 2));
88
+ }
89
+ catch (err) {
90
+ spinner.fail("Failed to list intents");
91
+ console.error(chalk.red(String(err)));
92
+ process.exit(1);
93
+ }
94
+ });
95
+ intentCommand
96
+ .command("revoke")
97
+ .description("Revoke an active intent")
98
+ .requiredOption("--nick <nick>", "Intent nick")
99
+ .action(async (opts) => {
100
+ requireAuth();
101
+ const spinner = ora(`Revoking intent ${opts.nick}...`).start();
102
+ try {
103
+ const res = await apiPost(`/api/pay/intent/${encodeURIComponent(opts.nick)}/revoke`, {});
104
+ spinner.stop();
105
+ // Remove from cache
106
+ setConfigValue(`intents.${opts.nick}`, "");
107
+ console.log(JSON.stringify(res, null, 2));
108
+ }
109
+ catch (err) {
110
+ spinner.fail("Failed to revoke intent");
111
+ console.error(chalk.red(String(err)));
112
+ process.exit(1);
113
+ }
114
+ });
115
+ // ─── Checkout ─────────────────────────────────────────────
116
+ const checkoutCommand = new Command("checkout")
117
+ .description("Execute a payment against a matching intent (no human needed)")
118
+ .requiredOption("--cart <json>", "CartMandate JSON")
119
+ .requiredOption("--agent <slug>", "Merchant agent slug")
120
+ .requiredOption("--intent <nick>", "Intent nick to use")
121
+ .action(async (opts) => {
122
+ requireAuth();
123
+ let cart;
124
+ try {
125
+ cart = JSON.parse(opts.cart);
126
+ }
127
+ catch {
128
+ console.error(chalk.red("Invalid cart JSON"));
129
+ process.exit(1);
130
+ }
131
+ const spinner = ora("Processing checkout...").start();
132
+ try {
133
+ const res = await apiPost("/api/pay/checkout", {
134
+ cart,
135
+ agent_slug: opts.agent,
136
+ intent_nick: opts.intent,
137
+ });
138
+ spinner.stop();
139
+ console.log(JSON.stringify(res, null, 2));
140
+ }
141
+ catch (err) {
142
+ spinner.fail("Checkout failed");
143
+ console.error(chalk.red(String(err)));
144
+ process.exit(1);
145
+ }
146
+ });
147
+ // ─── Sign (payment session for human approval) ───────────
148
+ const signCommand = new Command("sign").description("Create a payment session for human approval");
149
+ signCommand
150
+ .command("create")
151
+ .description("Create a new payment session")
152
+ .requiredOption("--cart <json>", "CartMandate JSON")
153
+ .requiredOption("--agent <slug>", "Merchant agent slug")
154
+ .action(async (opts) => {
155
+ requireAuth();
156
+ let cart;
157
+ try {
158
+ cart = JSON.parse(opts.cart);
159
+ }
160
+ catch {
161
+ console.error(chalk.red("Invalid cart JSON"));
162
+ process.exit(1);
163
+ }
164
+ // Extract total from cart
165
+ let totalCents = 0;
166
+ let currency = "usd";
167
+ try {
168
+ const c = cart;
169
+ const contents = c.contents;
170
+ const pr = contents?.payment_request;
171
+ const details = pr?.details;
172
+ const total = details?.total;
173
+ totalCents = Math.round(total.amount.value * 100);
174
+ currency = total.amount.currency.toLowerCase();
175
+ }
176
+ catch {
177
+ console.error(chalk.red("Could not extract total from cart"));
178
+ process.exit(1);
179
+ }
180
+ const spinner = ora("Creating payment session...").start();
181
+ try {
182
+ const res = await apiPost("/api/pay/session", {
183
+ agent_slug: opts.agent,
184
+ cart,
185
+ total_cents: totalCents,
186
+ currency,
187
+ });
188
+ spinner.stop();
189
+ console.log(JSON.stringify(res, null, 2));
190
+ }
191
+ catch (err) {
192
+ spinner.fail("Failed to create session");
193
+ console.error(chalk.red(String(err)));
194
+ process.exit(1);
195
+ }
196
+ });
197
+ signCommand
198
+ .command("poll")
199
+ .description("Poll a payment session until completed or expired")
200
+ .argument("<code>", "Session code")
201
+ .action(async (code) => {
202
+ requireAuth();
203
+ const maxAttempts = 30;
204
+ const intervalMs = 2000;
205
+ const spinner = ora("Waiting for approval...").start();
206
+ for (let i = 0; i < maxAttempts; i++) {
207
+ try {
208
+ const res = await apiGet(`/api/pay/session/${encodeURIComponent(code)}`);
209
+ if (res.status === "completed") {
210
+ spinner.stop();
211
+ console.log(JSON.stringify(res, null, 2));
212
+ return;
213
+ }
214
+ if (res.status === "expired") {
215
+ spinner.fail("Session expired");
216
+ process.exit(1);
217
+ }
218
+ // Still pending — wait and retry
219
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
220
+ }
221
+ catch (err) {
222
+ spinner.fail("Failed to poll session");
223
+ console.error(chalk.red(String(err)));
224
+ process.exit(1);
225
+ }
226
+ }
227
+ spinner.fail("Session still pending. Run again to continue polling.");
228
+ process.exit(1);
229
+ });
230
+ // ─── Root pay command ─────────────────────────────────────
231
+ export const payCommand = new Command("pay")
232
+ .description("AP2 payment mandates — wallet, intents, checkout, signing")
233
+ .addCommand(walletCommand)
234
+ .addCommand(intentCommand)
235
+ .addCommand(checkoutCommand)
236
+ .addCommand(signCommand);
@@ -83,6 +83,7 @@ export const sendCommand = new Command("send")
83
83
  .option("--new", "Start a new conversation (clears saved context)")
84
84
  .option("--context <id>", "Use a specific context ID")
85
85
  .option("--task-id <id>", "Reply to a specific task (e.g. one in input-required state)")
86
+ .option("--pay <token>", "Attach a payment token as an ap2.mandates.PaymentMandate data part")
86
87
  .action(async (rawAgent, userMessage, opts) => {
87
88
  // Normalize slug: @user/name → @user~name (DB format)
88
89
  const agent = rawAgent.includes("/") && !isUrl(rawAgent)
@@ -196,7 +197,37 @@ export const sendCommand = new Command("send")
196
197
  message: {
197
198
  messageId: uuidv4(),
198
199
  role: "user",
199
- parts: [{ kind: "text", text: userMessage }],
200
+ parts: [
201
+ { kind: "text", text: userMessage },
202
+ ...(opts.pay
203
+ ? [
204
+ {
205
+ kind: "data",
206
+ data: {
207
+ "ap2.mandates.PaymentMandate": {
208
+ payment_mandate_contents: {
209
+ payment_mandate_id: "",
210
+ payment_details_id: "",
211
+ payment_details_total: {
212
+ label: "Total",
213
+ amount: { currency: "USD", value: 0 },
214
+ refund_period: 3,
215
+ },
216
+ payment_response: {
217
+ request_id: "",
218
+ method_name: "WEB42_WALLET",
219
+ details: {},
220
+ },
221
+ merchant_agent: agent,
222
+ timestamp: new Date().toISOString(),
223
+ },
224
+ user_authorization: opts.pay,
225
+ },
226
+ },
227
+ },
228
+ ]
229
+ : []),
230
+ ],
200
231
  kind: "message",
201
232
  contextId,
202
233
  ...(opts.taskId ? { taskId: opts.taskId } : {}),
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { authCommand } from "./commands/auth.js";
4
+ import { payCommand } from "./commands/pay.js";
4
5
  import { registerCommand } from "./commands/register.js";
5
6
  import { searchCommand } from "./commands/search.js";
6
7
  import { sendCommand } from "./commands/send.js";
@@ -21,6 +22,7 @@ program
21
22
  }
22
23
  });
23
24
  program.addCommand(authCommand);
25
+ program.addCommand(payCommand);
24
26
  program.addCommand(registerCommand);
25
27
  program.addCommand(searchCommand);
26
28
  program.addCommand(sendCommand);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "0.1.16";
1
+ export declare const CLI_VERSION = "0.1.17";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = "0.1.16";
1
+ export const CLI_VERSION = "0.1.17";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web42/w42",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "CLI for the Web42 Agent Network — discover, register, and communicate with A2A agents",
5
5
  "type": "module",
6
6
  "bin": {