agentspend 0.1.3 → 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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # AgentSpend
2
+
3
+ CLI for managing AI agent payment methods — cards (Stripe) and crypto wallets (USDC on Base).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g agentspend
9
+ ```
10
+
11
+ ## Card setup (Stripe)
12
+
13
+ ```bash
14
+ # Start card setup — opens Stripe in your browser
15
+ agentspend card setup
16
+
17
+ # Check setup status
18
+ agentspend card status
19
+ ```
20
+
21
+ `card setup` creates a card via the AgentSpend API, opens the Stripe setup URL in your browser, and polls until the card is ready. The `card_id` is saved to `~/.agentspend/card.json`.
22
+
23
+ Agents use the `card_id` to pay services by sending `x-card-id: card_xxx` in request headers.
24
+
25
+ ## Wallet (crypto)
26
+
27
+ ```bash
28
+ # Generate a new wallet for x402 payments
29
+ agentspend wallet create
30
+
31
+ # Show address, network, and USDC balance
32
+ agentspend wallet status
33
+ ```
34
+
35
+ `wallet create` generates a local private key and saves it to `~/.agentspend/wallet.json`. Fund the address with USDC on Base to pay x402-enabled services.
36
+
37
+ ## Configuration
38
+
39
+ | Variable | Description |
40
+ |----------|-------------|
41
+ | `AGENTSPEND_API_URL` | Platform API base URL (default: `https://api.agentspend.co`) |
42
+
43
+ Config files are stored in `~/.agentspend/`.
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerPayCommand(program: Command): void;
@@ -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",
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();