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.
@@ -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({ card_id: status.card_id }, null, 2));
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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerPayCommand(program: Command): void;
@@ -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.4",
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
  },
@@ -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({ card_id: status.card_id }, null, 2));
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();