agentspend 0.1.4 → 0.1.6
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/dist/commands/card.js +4 -1
- package/dist/commands/pay.d.ts +2 -0
- package/dist/commands/pay.js +227 -0
- package/dist/index.js +2 -0
- package/package.json +3 -1
- package/src/commands/card.ts +5 -1
- package/src/commands/pay.ts +270 -0
- package/src/index.ts +2 -0
package/dist/commands/card.js
CHANGED
|
@@ -93,7 +93,10 @@ function registerCardCommands(program) {
|
|
|
93
93
|
if (status.status === "ready") {
|
|
94
94
|
console.log(`Card is ready!`);
|
|
95
95
|
if (status.card_id) {
|
|
96
|
-
await (0, promises_1.writeFile)(CARD_FILE, JSON.stringify({
|
|
96
|
+
await (0, promises_1.writeFile)(CARD_FILE, JSON.stringify({
|
|
97
|
+
card_id: status.card_id,
|
|
98
|
+
card_secret: status.card_secret,
|
|
99
|
+
}, null, 2));
|
|
97
100
|
console.log(`Card ID saved to ${CARD_FILE}`);
|
|
98
101
|
}
|
|
99
102
|
// Clean up setup file
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerPayCommand = registerPayCommand;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_os_1 = require("node:os");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const CONFIG_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".agentspend");
|
|
8
|
+
const CARD_FILE = (0, node_path_1.join)(CONFIG_DIR, "card.json");
|
|
9
|
+
const WALLET_FILE = (0, node_path_1.join)(CONFIG_DIR, "wallet.json");
|
|
10
|
+
const API_BASE = process.env.AGENTSPEND_API_URL ?? "https://api.agentspend.co";
|
|
11
|
+
async function readCardConfig() {
|
|
12
|
+
try {
|
|
13
|
+
const data = JSON.parse(await (0, promises_1.readFile)(CARD_FILE, "utf-8"));
|
|
14
|
+
if (typeof data.card_id === "string" && data.card_id && typeof data.card_secret === "string" && data.card_secret) {
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// file doesn't exist or is invalid
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
async function readWalletConfig() {
|
|
24
|
+
try {
|
|
25
|
+
const data = JSON.parse(await (0, promises_1.readFile)(WALLET_FILE, "utf-8"));
|
|
26
|
+
if (typeof data.address === "string" && data.address) {
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// file doesn't exist or is invalid
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function parseHeaders(headerArgs) {
|
|
36
|
+
const headers = {};
|
|
37
|
+
for (const h of headerArgs) {
|
|
38
|
+
const idx = h.indexOf(":");
|
|
39
|
+
if (idx === -1) {
|
|
40
|
+
throw new Error(`Invalid header format: "${h}". Use key:value`);
|
|
41
|
+
}
|
|
42
|
+
headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
|
|
43
|
+
}
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
46
|
+
async function payWithCard(url, cardConfig, body, extraHeaders) {
|
|
47
|
+
const headers = {
|
|
48
|
+
...extraHeaders,
|
|
49
|
+
"x-card-id": cardConfig.card_id,
|
|
50
|
+
};
|
|
51
|
+
if (body) {
|
|
52
|
+
headers["content-type"] = "application/json";
|
|
53
|
+
}
|
|
54
|
+
// Try with x-card-id
|
|
55
|
+
const res = await fetch(url, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers,
|
|
58
|
+
body: body ?? undefined,
|
|
59
|
+
});
|
|
60
|
+
// Success
|
|
61
|
+
if (res.ok) {
|
|
62
|
+
console.log("Payment successful (card)");
|
|
63
|
+
const data = await res.text();
|
|
64
|
+
try {
|
|
65
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
console.log(data);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// 402 with agentspend.service_id — need to bind
|
|
73
|
+
if (res.status === 402) {
|
|
74
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
75
|
+
const agentspend = errorBody?.agentspend;
|
|
76
|
+
const serviceId = agentspend?.service_id;
|
|
77
|
+
if (serviceId) {
|
|
78
|
+
console.log(`Binding to service ${serviceId}...`);
|
|
79
|
+
const bindRes = await fetch(`${API_BASE}/v1/card/${encodeURIComponent(cardConfig.card_id)}/bind`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "content-type": "application/json" },
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
card_secret: cardConfig.card_secret,
|
|
84
|
+
service_id: serviceId,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
if (!bindRes.ok) {
|
|
88
|
+
const bindError = await bindRes.text();
|
|
89
|
+
console.error(`Failed to bind (${bindRes.status}): ${bindError}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
console.log("Bound. Retrying payment...");
|
|
93
|
+
// Retry with x-card-id
|
|
94
|
+
const retryRes = await fetch(url, { method: "POST", headers, body: body ?? undefined });
|
|
95
|
+
if (!retryRes.ok) {
|
|
96
|
+
console.error(`Retry failed (${retryRes.status}): ${await retryRes.text()}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
console.log("Payment successful (card)");
|
|
100
|
+
const data = await retryRes.text();
|
|
101
|
+
try {
|
|
102
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
console.log(data);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Other error
|
|
111
|
+
console.error(`Request failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
async function payWithCrypto(url, walletConfig, body, extraHeaders) {
|
|
115
|
+
const headers = { ...extraHeaders };
|
|
116
|
+
if (body) {
|
|
117
|
+
headers["content-type"] = "application/json";
|
|
118
|
+
}
|
|
119
|
+
// First request — expect 402
|
|
120
|
+
const initialRes = await fetch(url, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers,
|
|
123
|
+
body: body ?? undefined,
|
|
124
|
+
});
|
|
125
|
+
if (initialRes.status !== 402) {
|
|
126
|
+
if (initialRes.ok) {
|
|
127
|
+
console.log("Request succeeded without payment.");
|
|
128
|
+
const data = await initialRes.text();
|
|
129
|
+
try {
|
|
130
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
console.log(data);
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const data = await initialRes.text();
|
|
138
|
+
console.error(`Expected 402 but got ${initialRes.status}: ${data}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
// Import x402 and viem dynamically
|
|
142
|
+
const { x402Client } = await import("@x402/core/client");
|
|
143
|
+
const { x402HTTPClient } = await import("@x402/core/http");
|
|
144
|
+
const { registerExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
145
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
146
|
+
const account = privateKeyToAccount(walletConfig.private_key);
|
|
147
|
+
const coreClient = new x402Client();
|
|
148
|
+
registerExactEvmScheme(coreClient, { signer: account });
|
|
149
|
+
const httpClient = new x402HTTPClient(coreClient);
|
|
150
|
+
// Decode payment requirements from 402 response
|
|
151
|
+
const paymentRequired = httpClient.getPaymentRequiredResponse((name) => initialRes.headers.get(name), await initialRes.clone().json().catch(() => undefined));
|
|
152
|
+
// Sign payment
|
|
153
|
+
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
|
|
154
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
155
|
+
// Resend with payment header
|
|
156
|
+
const paidRes = await fetch(url, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { ...headers, ...paymentHeaders },
|
|
159
|
+
body: body ?? undefined,
|
|
160
|
+
});
|
|
161
|
+
const data = await paidRes.text();
|
|
162
|
+
if (!paidRes.ok) {
|
|
163
|
+
console.error(`Paid request failed (${paidRes.status}): ${data}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
console.log("Payment successful (crypto)");
|
|
167
|
+
try {
|
|
168
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
console.log(data);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function registerPayCommand(program) {
|
|
175
|
+
program
|
|
176
|
+
.command("pay")
|
|
177
|
+
.description("Pay a paywall-protected endpoint using card or crypto wallet")
|
|
178
|
+
.argument("<url>", "URL of the paywall-protected endpoint")
|
|
179
|
+
.option("--method <method>", "Payment method: card or crypto (default: auto-detect)")
|
|
180
|
+
.option("--body <json>", "Request body JSON")
|
|
181
|
+
.option("--header <key:value>", "Extra headers (repeatable)", (val, prev) => {
|
|
182
|
+
prev.push(val);
|
|
183
|
+
return prev;
|
|
184
|
+
}, [])
|
|
185
|
+
.action(async (url, opts) => {
|
|
186
|
+
try {
|
|
187
|
+
const extraHeaders = parseHeaders(opts.header);
|
|
188
|
+
if (opts.method === "card") {
|
|
189
|
+
const card = await readCardConfig();
|
|
190
|
+
if (!card) {
|
|
191
|
+
console.error("No card configured. Run: agentspend card setup");
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
await payWithCard(url, card, opts.body, extraHeaders);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (opts.method === "crypto") {
|
|
198
|
+
const wallet = await readWalletConfig();
|
|
199
|
+
if (!wallet) {
|
|
200
|
+
console.error("No wallet configured. Run: agentspend wallet create");
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Auto-detect: try card first, then crypto
|
|
207
|
+
const card = await readCardConfig();
|
|
208
|
+
if (card) {
|
|
209
|
+
await payWithCard(url, card, opts.body, extraHeaders);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const wallet = await readWalletConfig();
|
|
213
|
+
if (wallet) {
|
|
214
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
console.error("No payment method configured.");
|
|
218
|
+
console.error("Set up a card: agentspend card setup");
|
|
219
|
+
console.error("Or create a wallet: agentspend wallet create");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const card_js_1 = require("./commands/card.js");
|
|
6
6
|
const wallet_js_1 = require("./commands/wallet.js");
|
|
7
|
+
const pay_js_1 = require("./commands/pay.js");
|
|
7
8
|
const program = new commander_1.Command();
|
|
8
9
|
program
|
|
9
10
|
.name("agentspend")
|
|
@@ -11,4 +12,5 @@ program
|
|
|
11
12
|
.version("0.1.0");
|
|
12
13
|
(0, card_js_1.registerCardCommands)(program);
|
|
13
14
|
(0, wallet_js_1.registerWalletCommands)(program);
|
|
15
|
+
(0, pay_js_1.registerPayCommand)(program);
|
|
14
16
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentspend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "CLI for AgentSpend — manage cards and crypto wallets for AI agent payments",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"agentspend": "dist/index.js"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@x402/core": "^2.3.1",
|
|
14
|
+
"@x402/evm": "^2.3.1",
|
|
13
15
|
"commander": "^13.0.0",
|
|
14
16
|
"viem": "^2.0.0"
|
|
15
17
|
},
|
package/src/commands/card.ts
CHANGED
|
@@ -18,6 +18,7 @@ interface CardSetupStatusResponse {
|
|
|
18
18
|
status: CardSetupStatus;
|
|
19
19
|
expires_at: string;
|
|
20
20
|
card_id?: string;
|
|
21
|
+
card_secret?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const CONFIG_DIR = join(homedir(), ".agentspend");
|
|
@@ -121,7 +122,10 @@ export function registerCardCommands(program: Command): void {
|
|
|
121
122
|
if (status.status === "ready") {
|
|
122
123
|
console.log(`Card is ready!`);
|
|
123
124
|
if (status.card_id) {
|
|
124
|
-
await writeFile(CARD_FILE, JSON.stringify({
|
|
125
|
+
await writeFile(CARD_FILE, JSON.stringify({
|
|
126
|
+
card_id: status.card_id,
|
|
127
|
+
card_secret: status.card_secret,
|
|
128
|
+
}, null, 2));
|
|
125
129
|
console.log(`Card ID saved to ${CARD_FILE}`);
|
|
126
130
|
}
|
|
127
131
|
// Clean up setup file
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = join(homedir(), ".agentspend");
|
|
7
|
+
const CARD_FILE = join(CONFIG_DIR, "card.json");
|
|
8
|
+
const WALLET_FILE = join(CONFIG_DIR, "wallet.json");
|
|
9
|
+
const API_BASE = process.env.AGENTSPEND_API_URL ?? "https://api.agentspend.co";
|
|
10
|
+
|
|
11
|
+
interface CardConfig {
|
|
12
|
+
card_id: string;
|
|
13
|
+
card_secret: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface WalletConfig {
|
|
17
|
+
address: string;
|
|
18
|
+
network: string;
|
|
19
|
+
private_key: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function readCardConfig(): Promise<CardConfig | null> {
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(await readFile(CARD_FILE, "utf-8"));
|
|
25
|
+
if (typeof data.card_id === "string" && data.card_id && typeof data.card_secret === "string" && data.card_secret) {
|
|
26
|
+
return data as CardConfig;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// file doesn't exist or is invalid
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function readWalletConfig(): Promise<WalletConfig | null> {
|
|
35
|
+
try {
|
|
36
|
+
const data = JSON.parse(await readFile(WALLET_FILE, "utf-8"));
|
|
37
|
+
if (typeof data.address === "string" && data.address) {
|
|
38
|
+
return data as WalletConfig;
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// file doesn't exist or is invalid
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseHeaders(headerArgs: string[]): Record<string, string> {
|
|
47
|
+
const headers: Record<string, string> = {};
|
|
48
|
+
for (const h of headerArgs) {
|
|
49
|
+
const idx = h.indexOf(":");
|
|
50
|
+
if (idx === -1) {
|
|
51
|
+
throw new Error(`Invalid header format: "${h}". Use key:value`);
|
|
52
|
+
}
|
|
53
|
+
headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
|
|
54
|
+
}
|
|
55
|
+
return headers;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function payWithCard(
|
|
59
|
+
url: string,
|
|
60
|
+
cardConfig: CardConfig,
|
|
61
|
+
body: string | undefined,
|
|
62
|
+
extraHeaders: Record<string, string>
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const headers: Record<string, string> = {
|
|
65
|
+
...extraHeaders,
|
|
66
|
+
"x-card-id": cardConfig.card_id,
|
|
67
|
+
};
|
|
68
|
+
if (body) {
|
|
69
|
+
headers["content-type"] = "application/json";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Try with x-card-id
|
|
73
|
+
const res = await fetch(url, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers,
|
|
76
|
+
body: body ?? undefined,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Success
|
|
80
|
+
if (res.ok) {
|
|
81
|
+
console.log("Payment successful (card)");
|
|
82
|
+
const data = await res.text();
|
|
83
|
+
try { console.log(JSON.stringify(JSON.parse(data), null, 2)); }
|
|
84
|
+
catch { console.log(data); }
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 402 with agentspend.service_id — need to bind
|
|
89
|
+
if (res.status === 402) {
|
|
90
|
+
const errorBody = await res.json().catch(() => ({})) as Record<string, unknown>;
|
|
91
|
+
const agentspend = errorBody?.agentspend as Record<string, unknown> | undefined;
|
|
92
|
+
const serviceId = agentspend?.service_id as string | undefined;
|
|
93
|
+
|
|
94
|
+
if (serviceId) {
|
|
95
|
+
console.log(`Binding to service ${serviceId}...`);
|
|
96
|
+
const bindRes = await fetch(
|
|
97
|
+
`${API_BASE}/v1/card/${encodeURIComponent(cardConfig.card_id)}/bind`,
|
|
98
|
+
{
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "content-type": "application/json" },
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
card_secret: cardConfig.card_secret,
|
|
103
|
+
service_id: serviceId,
|
|
104
|
+
}),
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!bindRes.ok) {
|
|
109
|
+
const bindError = await bindRes.text();
|
|
110
|
+
console.error(`Failed to bind (${bindRes.status}): ${bindError}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log("Bound. Retrying payment...");
|
|
115
|
+
|
|
116
|
+
// Retry with x-card-id
|
|
117
|
+
const retryRes = await fetch(url, { method: "POST", headers, body: body ?? undefined });
|
|
118
|
+
if (!retryRes.ok) {
|
|
119
|
+
console.error(`Retry failed (${retryRes.status}): ${await retryRes.text()}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log("Payment successful (card)");
|
|
124
|
+
const data = await retryRes.text();
|
|
125
|
+
try { console.log(JSON.stringify(JSON.parse(data), null, 2)); }
|
|
126
|
+
catch { console.log(data); }
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Other error
|
|
132
|
+
console.error(`Request failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function payWithCrypto(
|
|
137
|
+
url: string,
|
|
138
|
+
walletConfig: WalletConfig,
|
|
139
|
+
body: string | undefined,
|
|
140
|
+
extraHeaders: Record<string, string>
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
const headers: Record<string, string> = { ...extraHeaders };
|
|
143
|
+
if (body) {
|
|
144
|
+
headers["content-type"] = "application/json";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// First request — expect 402
|
|
148
|
+
const initialRes = await fetch(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers,
|
|
151
|
+
body: body ?? undefined,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (initialRes.status !== 402) {
|
|
155
|
+
if (initialRes.ok) {
|
|
156
|
+
console.log("Request succeeded without payment.");
|
|
157
|
+
const data = await initialRes.text();
|
|
158
|
+
try {
|
|
159
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
160
|
+
} catch {
|
|
161
|
+
console.log(data);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const data = await initialRes.text();
|
|
166
|
+
console.error(`Expected 402 but got ${initialRes.status}: ${data}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Import x402 and viem dynamically
|
|
171
|
+
const { x402Client } = await import("@x402/core/client");
|
|
172
|
+
const { x402HTTPClient } = await import("@x402/core/http");
|
|
173
|
+
const { registerExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
174
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
175
|
+
|
|
176
|
+
const account = privateKeyToAccount(walletConfig.private_key as `0x${string}`);
|
|
177
|
+
|
|
178
|
+
const coreClient = new x402Client();
|
|
179
|
+
registerExactEvmScheme(coreClient, { signer: account });
|
|
180
|
+
const httpClient = new x402HTTPClient(coreClient);
|
|
181
|
+
|
|
182
|
+
// Decode payment requirements from 402 response
|
|
183
|
+
const paymentRequired = httpClient.getPaymentRequiredResponse(
|
|
184
|
+
(name: string) => initialRes.headers.get(name),
|
|
185
|
+
await initialRes.clone().json().catch(() => undefined)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Sign payment
|
|
189
|
+
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
|
|
190
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
191
|
+
|
|
192
|
+
// Resend with payment header
|
|
193
|
+
const paidRes = await fetch(url, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: { ...headers, ...paymentHeaders },
|
|
196
|
+
body: body ?? undefined,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const data = await paidRes.text();
|
|
200
|
+
if (!paidRes.ok) {
|
|
201
|
+
console.error(`Paid request failed (${paidRes.status}): ${data}`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log("Payment successful (crypto)");
|
|
206
|
+
try {
|
|
207
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
208
|
+
} catch {
|
|
209
|
+
console.log(data);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function registerPayCommand(program: Command): void {
|
|
214
|
+
program
|
|
215
|
+
.command("pay")
|
|
216
|
+
.description("Pay a paywall-protected endpoint using card or crypto wallet")
|
|
217
|
+
.argument("<url>", "URL of the paywall-protected endpoint")
|
|
218
|
+
.option("--method <method>", "Payment method: card or crypto (default: auto-detect)")
|
|
219
|
+
.option("--body <json>", "Request body JSON")
|
|
220
|
+
.option("--header <key:value>", "Extra headers (repeatable)", (val: string, prev: string[]) => {
|
|
221
|
+
prev.push(val);
|
|
222
|
+
return prev;
|
|
223
|
+
}, [] as string[])
|
|
224
|
+
.action(async (url: string, opts: { method?: string; body?: string; header: string[] }) => {
|
|
225
|
+
try {
|
|
226
|
+
const extraHeaders = parseHeaders(opts.header);
|
|
227
|
+
|
|
228
|
+
if (opts.method === "card") {
|
|
229
|
+
const card = await readCardConfig();
|
|
230
|
+
if (!card) {
|
|
231
|
+
console.error("No card configured. Run: agentspend card setup");
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
await payWithCard(url, card, opts.body, extraHeaders);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (opts.method === "crypto") {
|
|
239
|
+
const wallet = await readWalletConfig();
|
|
240
|
+
if (!wallet) {
|
|
241
|
+
console.error("No wallet configured. Run: agentspend wallet create");
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Auto-detect: try card first, then crypto
|
|
249
|
+
const card = await readCardConfig();
|
|
250
|
+
if (card) {
|
|
251
|
+
await payWithCard(url, card, opts.body, extraHeaders);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const wallet = await readWalletConfig();
|
|
256
|
+
if (wallet) {
|
|
257
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.error("No payment method configured.");
|
|
262
|
+
console.error("Set up a card: agentspend card setup");
|
|
263
|
+
console.error("Or create a wallet: agentspend wallet create");
|
|
264
|
+
process.exit(1);
|
|
265
|
+
} catch (err: unknown) {
|
|
266
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { registerCardCommands } from "./commands/card.js";
|
|
5
5
|
import { registerWalletCommands } from "./commands/wallet.js";
|
|
6
|
+
import { registerPayCommand } from "./commands/pay.js";
|
|
6
7
|
|
|
7
8
|
const program = new Command();
|
|
8
9
|
|
|
@@ -13,5 +14,6 @@ program
|
|
|
13
14
|
|
|
14
15
|
registerCardCommands(program);
|
|
15
16
|
registerWalletCommands(program);
|
|
17
|
+
registerPayCommand(program);
|
|
16
18
|
|
|
17
19
|
program.parse();
|