agentspend 0.1.10 → 0.2.1

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.
@@ -1,270 +0,0 @@
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
- }
@@ -1,118 +0,0 @@
1
- import { Command } from "commander";
2
- import { readFile, writeFile, mkdir, chmod } 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 WALLET_FILE = join(CONFIG_DIR, "wallet.json");
8
-
9
- async function ensureConfigDir(): Promise<void> {
10
- await mkdir(CONFIG_DIR, { recursive: true });
11
- await chmod(CONFIG_DIR, 0o700);
12
- }
13
-
14
- interface WalletConfig {
15
- address: string;
16
- network: string;
17
- private_key: string;
18
- }
19
-
20
- async function readWalletConfig(): Promise<WalletConfig | null> {
21
- try {
22
- const data = JSON.parse(await readFile(WALLET_FILE, "utf-8"));
23
- if (typeof data.address === "string" && data.address) {
24
- return data as WalletConfig;
25
- }
26
- } catch {
27
- // file doesn't exist or is invalid
28
- }
29
- return null;
30
- }
31
-
32
- export function registerWalletCommands(program: Command): void {
33
- const wallet = program
34
- .command("wallet")
35
- .description("Manage AgentSpend crypto wallets");
36
-
37
- wallet
38
- .command("create")
39
- .description("Generate a new crypto wallet for x402 payments")
40
- .option("--network <network>", "Chain identifier", "eip155:8453")
41
- .action(async (opts: { network: string }) => {
42
- try {
43
- const existing = await readWalletConfig();
44
- if (existing) {
45
- console.log(`Wallet already exists.`);
46
- console.log(` Address: ${existing.address}`);
47
- console.log(` Network: ${existing.network}`);
48
- console.log(` Saved at: ${WALLET_FILE}`);
49
- process.exit(0);
50
- }
51
-
52
- const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
53
- const privateKey = generatePrivateKey();
54
- const account = privateKeyToAccount(privateKey);
55
-
56
- await ensureConfigDir();
57
-
58
- const config: WalletConfig = {
59
- address: account.address,
60
- network: opts.network,
61
- private_key: privateKey,
62
- };
63
-
64
- await writeFile(WALLET_FILE, JSON.stringify(config, null, 2));
65
- await chmod(WALLET_FILE, 0o600);
66
-
67
- console.log(`Wallet created. Address: ${account.address} — Fund it with USDC on Base.`);
68
- console.log(`Private key saved to ${WALLET_FILE} (owner-only permissions). Keep this file secure.`);
69
- } catch (err: unknown) {
70
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
71
- process.exit(1);
72
- }
73
- });
74
-
75
- wallet
76
- .command("status")
77
- .description("Show wallet address, network, and USDC balance")
78
- .action(async () => {
79
- try {
80
- const config = await readWalletConfig();
81
- if (!config) {
82
- console.log("No wallet configured.");
83
- console.log("Run: agentspend wallet create");
84
- process.exit(0);
85
- }
86
-
87
- console.log(`Address: ${config.address}`);
88
- console.log(`Network: ${config.network}`);
89
-
90
- // Fetch USDC balance on Base
91
- try {
92
- const { createPublicClient, http, parseAbi } = await import("viem");
93
- const { base } = await import("viem/chains");
94
-
95
- const client = createPublicClient({
96
- chain: base,
97
- transport: http(),
98
- });
99
-
100
- const usdcAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as `0x${string}`;
101
- const balance = await client.readContract({
102
- address: usdcAddress,
103
- abi: parseAbi(["function balanceOf(address) view returns (uint256)"]),
104
- functionName: "balanceOf",
105
- args: [config.address as `0x${string}`],
106
- });
107
-
108
- const usdcBalance = Number(balance) / 1e6;
109
- console.log(`USDC Balance: ${usdcBalance.toFixed(2)} USDC`);
110
- } catch {
111
- console.log("USDC Balance: (unable to fetch)");
112
- }
113
- } catch (err: unknown) {
114
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
115
- process.exit(1);
116
- }
117
- });
118
- }
package/src/index.ts DELETED
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from "commander";
4
- import { registerCardCommands } from "./commands/card.js";
5
- import { registerWalletCommands } from "./commands/wallet.js";
6
- import { registerPayCommand } from "./commands/pay.js";
7
-
8
- const program = new Command();
9
-
10
- program
11
- .name("agentspend")
12
- .description("AgentSpend CLI — manage cards and billing")
13
- .version("0.1.0");
14
-
15
- registerCardCommands(program);
16
- registerWalletCommands(program);
17
- registerPayCommand(program);
18
-
19
- program.parse();
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../tsconfig.base.json",
3
- "compilerOptions": {
4
- "rootDir": "src",
5
- "outDir": "dist"
6
- },
7
- "include": ["src"]
8
- }