agentspend 0.1.4 → 0.1.5
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/pay.d.ts +2 -0
- package/dist/commands/pay.js +184 -0
- package/dist/index.js +2 -0
- package/package.json +3 -1
- package/src/commands/pay.ts +224 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
async function readCardConfig() {
|
|
11
|
+
try {
|
|
12
|
+
const data = JSON.parse(await (0, promises_1.readFile)(CARD_FILE, "utf-8"));
|
|
13
|
+
if (typeof data.card_id === "string" && data.card_id) {
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// file doesn't exist or is invalid
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
async function readWalletConfig() {
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(await (0, promises_1.readFile)(WALLET_FILE, "utf-8"));
|
|
25
|
+
if (typeof data.address === "string" && data.address) {
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// file doesn't exist or is invalid
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function parseHeaders(headerArgs) {
|
|
35
|
+
const headers = {};
|
|
36
|
+
for (const h of headerArgs) {
|
|
37
|
+
const idx = h.indexOf(":");
|
|
38
|
+
if (idx === -1) {
|
|
39
|
+
throw new Error(`Invalid header format: "${h}". Use key:value`);
|
|
40
|
+
}
|
|
41
|
+
headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
|
|
42
|
+
}
|
|
43
|
+
return headers;
|
|
44
|
+
}
|
|
45
|
+
async function payWithCard(url, cardId, body, extraHeaders) {
|
|
46
|
+
const headers = {
|
|
47
|
+
...extraHeaders,
|
|
48
|
+
"x-card-id": cardId,
|
|
49
|
+
};
|
|
50
|
+
if (body) {
|
|
51
|
+
headers["content-type"] = "application/json";
|
|
52
|
+
}
|
|
53
|
+
const res = await fetch(url, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers,
|
|
56
|
+
body: body ?? undefined,
|
|
57
|
+
});
|
|
58
|
+
const data = await res.text();
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
console.error(`Request failed (${res.status}): ${data}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
console.log("Payment successful (card)");
|
|
64
|
+
try {
|
|
65
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
console.log(data);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function payWithCrypto(url, walletConfig, body, extraHeaders) {
|
|
72
|
+
const headers = { ...extraHeaders };
|
|
73
|
+
if (body) {
|
|
74
|
+
headers["content-type"] = "application/json";
|
|
75
|
+
}
|
|
76
|
+
// First request — expect 402
|
|
77
|
+
const initialRes = await fetch(url, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers,
|
|
80
|
+
body: body ?? undefined,
|
|
81
|
+
});
|
|
82
|
+
if (initialRes.status !== 402) {
|
|
83
|
+
if (initialRes.ok) {
|
|
84
|
+
console.log("Request succeeded without payment.");
|
|
85
|
+
const data = await initialRes.text();
|
|
86
|
+
try {
|
|
87
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
console.log(data);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const data = await initialRes.text();
|
|
95
|
+
console.error(`Expected 402 but got ${initialRes.status}: ${data}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
// Import x402 and viem dynamically
|
|
99
|
+
const { x402Client } = await import("@x402/core/client");
|
|
100
|
+
const { x402HTTPClient } = await import("@x402/core/http");
|
|
101
|
+
const { registerExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
102
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
103
|
+
const account = privateKeyToAccount(walletConfig.private_key);
|
|
104
|
+
const coreClient = new x402Client();
|
|
105
|
+
registerExactEvmScheme(coreClient, { signer: account });
|
|
106
|
+
const httpClient = new x402HTTPClient(coreClient);
|
|
107
|
+
// Decode payment requirements from 402 response
|
|
108
|
+
const paymentRequired = httpClient.getPaymentRequiredResponse((name) => initialRes.headers.get(name), await initialRes.clone().json().catch(() => undefined));
|
|
109
|
+
// Sign payment
|
|
110
|
+
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
|
|
111
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
112
|
+
// Resend with payment header
|
|
113
|
+
const paidRes = await fetch(url, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { ...headers, ...paymentHeaders },
|
|
116
|
+
body: body ?? undefined,
|
|
117
|
+
});
|
|
118
|
+
const data = await paidRes.text();
|
|
119
|
+
if (!paidRes.ok) {
|
|
120
|
+
console.error(`Paid request failed (${paidRes.status}): ${data}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
console.log("Payment successful (crypto)");
|
|
124
|
+
try {
|
|
125
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
console.log(data);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function registerPayCommand(program) {
|
|
132
|
+
program
|
|
133
|
+
.command("pay")
|
|
134
|
+
.description("Pay a paywall-protected endpoint using card or crypto wallet")
|
|
135
|
+
.argument("<url>", "URL of the paywall-protected endpoint")
|
|
136
|
+
.option("--method <method>", "Payment method: card or crypto (default: auto-detect)")
|
|
137
|
+
.option("--body <json>", "Request body JSON")
|
|
138
|
+
.option("--header <key:value>", "Extra headers (repeatable)", (val, prev) => {
|
|
139
|
+
prev.push(val);
|
|
140
|
+
return prev;
|
|
141
|
+
}, [])
|
|
142
|
+
.action(async (url, opts) => {
|
|
143
|
+
try {
|
|
144
|
+
const extraHeaders = parseHeaders(opts.header);
|
|
145
|
+
if (opts.method === "card") {
|
|
146
|
+
const card = await readCardConfig();
|
|
147
|
+
if (!card) {
|
|
148
|
+
console.error("No card configured. Run: agentspend card setup");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
await payWithCard(url, card.card_id, opts.body, extraHeaders);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (opts.method === "crypto") {
|
|
155
|
+
const wallet = await readWalletConfig();
|
|
156
|
+
if (!wallet) {
|
|
157
|
+
console.error("No wallet configured. Run: agentspend wallet create");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Auto-detect: try card first, then crypto
|
|
164
|
+
const card = await readCardConfig();
|
|
165
|
+
if (card) {
|
|
166
|
+
await payWithCard(url, card.card_id, opts.body, extraHeaders);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const wallet = await readWalletConfig();
|
|
170
|
+
if (wallet) {
|
|
171
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
console.error("No payment method configured.");
|
|
175
|
+
console.error("Set up a card: agentspend card setup");
|
|
176
|
+
console.error("Or create a wallet: agentspend wallet create");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
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.5",
|
|
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
|
},
|
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
|
|
10
|
+
interface CardConfig {
|
|
11
|
+
card_id: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface WalletConfig {
|
|
15
|
+
address: string;
|
|
16
|
+
network: string;
|
|
17
|
+
private_key: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function readCardConfig(): Promise<CardConfig | null> {
|
|
21
|
+
try {
|
|
22
|
+
const data = JSON.parse(await readFile(CARD_FILE, "utf-8"));
|
|
23
|
+
if (typeof data.card_id === "string" && data.card_id) {
|
|
24
|
+
return data as CardConfig;
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// file doesn't exist or is invalid
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function readWalletConfig(): Promise<WalletConfig | null> {
|
|
33
|
+
try {
|
|
34
|
+
const data = JSON.parse(await readFile(WALLET_FILE, "utf-8"));
|
|
35
|
+
if (typeof data.address === "string" && data.address) {
|
|
36
|
+
return data as WalletConfig;
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// file doesn't exist or is invalid
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseHeaders(headerArgs: string[]): Record<string, string> {
|
|
45
|
+
const headers: Record<string, string> = {};
|
|
46
|
+
for (const h of headerArgs) {
|
|
47
|
+
const idx = h.indexOf(":");
|
|
48
|
+
if (idx === -1) {
|
|
49
|
+
throw new Error(`Invalid header format: "${h}". Use key:value`);
|
|
50
|
+
}
|
|
51
|
+
headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
|
|
52
|
+
}
|
|
53
|
+
return headers;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function payWithCard(
|
|
57
|
+
url: string,
|
|
58
|
+
cardId: string,
|
|
59
|
+
body: string | undefined,
|
|
60
|
+
extraHeaders: Record<string, string>
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
const headers: Record<string, string> = {
|
|
63
|
+
...extraHeaders,
|
|
64
|
+
"x-card-id": cardId,
|
|
65
|
+
};
|
|
66
|
+
if (body) {
|
|
67
|
+
headers["content-type"] = "application/json";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const res = await fetch(url, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers,
|
|
73
|
+
body: body ?? undefined,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const data = await res.text();
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
console.error(`Request failed (${res.status}): ${data}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log("Payment successful (card)");
|
|
83
|
+
try {
|
|
84
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
85
|
+
} catch {
|
|
86
|
+
console.log(data);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function payWithCrypto(
|
|
91
|
+
url: string,
|
|
92
|
+
walletConfig: WalletConfig,
|
|
93
|
+
body: string | undefined,
|
|
94
|
+
extraHeaders: Record<string, string>
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const headers: Record<string, string> = { ...extraHeaders };
|
|
97
|
+
if (body) {
|
|
98
|
+
headers["content-type"] = "application/json";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// First request — expect 402
|
|
102
|
+
const initialRes = await fetch(url, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers,
|
|
105
|
+
body: body ?? undefined,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (initialRes.status !== 402) {
|
|
109
|
+
if (initialRes.ok) {
|
|
110
|
+
console.log("Request succeeded without payment.");
|
|
111
|
+
const data = await initialRes.text();
|
|
112
|
+
try {
|
|
113
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
114
|
+
} catch {
|
|
115
|
+
console.log(data);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const data = await initialRes.text();
|
|
120
|
+
console.error(`Expected 402 but got ${initialRes.status}: ${data}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Import x402 and viem dynamically
|
|
125
|
+
const { x402Client } = await import("@x402/core/client");
|
|
126
|
+
const { x402HTTPClient } = await import("@x402/core/http");
|
|
127
|
+
const { registerExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
128
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
129
|
+
|
|
130
|
+
const account = privateKeyToAccount(walletConfig.private_key as `0x${string}`);
|
|
131
|
+
|
|
132
|
+
const coreClient = new x402Client();
|
|
133
|
+
registerExactEvmScheme(coreClient, { signer: account });
|
|
134
|
+
const httpClient = new x402HTTPClient(coreClient);
|
|
135
|
+
|
|
136
|
+
// Decode payment requirements from 402 response
|
|
137
|
+
const paymentRequired = httpClient.getPaymentRequiredResponse(
|
|
138
|
+
(name: string) => initialRes.headers.get(name),
|
|
139
|
+
await initialRes.clone().json().catch(() => undefined)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Sign payment
|
|
143
|
+
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
|
|
144
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
145
|
+
|
|
146
|
+
// Resend with payment header
|
|
147
|
+
const paidRes = await fetch(url, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: { ...headers, ...paymentHeaders },
|
|
150
|
+
body: body ?? undefined,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const data = await paidRes.text();
|
|
154
|
+
if (!paidRes.ok) {
|
|
155
|
+
console.error(`Paid request failed (${paidRes.status}): ${data}`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log("Payment successful (crypto)");
|
|
160
|
+
try {
|
|
161
|
+
console.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
162
|
+
} catch {
|
|
163
|
+
console.log(data);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function registerPayCommand(program: Command): void {
|
|
168
|
+
program
|
|
169
|
+
.command("pay")
|
|
170
|
+
.description("Pay a paywall-protected endpoint using card or crypto wallet")
|
|
171
|
+
.argument("<url>", "URL of the paywall-protected endpoint")
|
|
172
|
+
.option("--method <method>", "Payment method: card or crypto (default: auto-detect)")
|
|
173
|
+
.option("--body <json>", "Request body JSON")
|
|
174
|
+
.option("--header <key:value>", "Extra headers (repeatable)", (val: string, prev: string[]) => {
|
|
175
|
+
prev.push(val);
|
|
176
|
+
return prev;
|
|
177
|
+
}, [] as string[])
|
|
178
|
+
.action(async (url: string, opts: { method?: string; body?: string; header: string[] }) => {
|
|
179
|
+
try {
|
|
180
|
+
const extraHeaders = parseHeaders(opts.header);
|
|
181
|
+
|
|
182
|
+
if (opts.method === "card") {
|
|
183
|
+
const card = await readCardConfig();
|
|
184
|
+
if (!card) {
|
|
185
|
+
console.error("No card configured. Run: agentspend card setup");
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
await payWithCard(url, card.card_id, opts.body, extraHeaders);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (opts.method === "crypto") {
|
|
193
|
+
const wallet = await readWalletConfig();
|
|
194
|
+
if (!wallet) {
|
|
195
|
+
console.error("No wallet configured. Run: agentspend wallet create");
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Auto-detect: try card first, then crypto
|
|
203
|
+
const card = await readCardConfig();
|
|
204
|
+
if (card) {
|
|
205
|
+
await payWithCard(url, card.card_id, opts.body, extraHeaders);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const wallet = await readWalletConfig();
|
|
210
|
+
if (wallet) {
|
|
211
|
+
await payWithCrypto(url, wallet, opts.body, extraHeaders);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.error("No payment method configured.");
|
|
216
|
+
console.error("Set up a card: agentspend card setup");
|
|
217
|
+
console.error("Or create a wallet: agentspend wallet create");
|
|
218
|
+
process.exit(1);
|
|
219
|
+
} catch (err: unknown) {
|
|
220
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
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();
|