agentspend 0.1.9 → 0.2.0

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.
@@ -0,0 +1,35 @@
1
+ const METHOD_TOKEN_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
2
+ export function normalizeMethod(method) {
3
+ const normalized = (method ?? "GET").trim().toUpperCase();
4
+ if (!normalized || !METHOD_TOKEN_PATTERN.test(normalized)) {
5
+ throw new Error(`Invalid HTTP method: ${method ?? ""}`);
6
+ }
7
+ return normalized;
8
+ }
9
+ export function parseHeaders(rawHeaders) {
10
+ const parsed = {};
11
+ for (const header of rawHeaders ?? []) {
12
+ const separator = header.indexOf(":");
13
+ if (separator === -1) {
14
+ throw new Error(`Invalid header format: ${header}. Expected key:value.`);
15
+ }
16
+ const key = header.slice(0, separator).trim();
17
+ const value = header.slice(separator + 1).trim();
18
+ if (!key || !value) {
19
+ throw new Error(`Invalid header format: ${header}. Expected key:value.`);
20
+ }
21
+ parsed[key] = value;
22
+ }
23
+ return parsed;
24
+ }
25
+ export function parseBody(rawBody) {
26
+ if (rawBody === undefined) {
27
+ return undefined;
28
+ }
29
+ try {
30
+ return JSON.parse(rawBody);
31
+ }
32
+ catch {
33
+ return rawBody;
34
+ }
35
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,27 +1,26 @@
1
1
  {
2
2
  "name": "agentspend",
3
- "version": "0.1.9",
4
- "description": "CLI for AgentSpend manage cards and crypto wallets for AI agent payments",
5
- "homepage": "https://github.com/jpbonch/agentspend",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/jpbonch/agentspend.git"
10
- },
11
- "engines": {
12
- "node": ">=18"
13
- },
3
+ "version": "0.2.0",
4
+ "description": "AgentSpend CLI for managed x402 spending",
5
+ "files": ["dist", "SKILL.md"],
6
+ "type": "module",
14
7
  "bin": {
15
8
  "agentspend": "dist/index.js"
16
9
  },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "dev": "tsx src/index.ts",
13
+ "dev:local": "tsx src/dev-index.ts",
14
+ "typecheck": "tsc -p tsconfig.json --noEmit"
15
+ },
17
16
  "dependencies": {
18
- "@x402/core": "^2.3.1",
19
- "@x402/evm": "^2.3.1",
20
- "commander": "^13.0.0",
21
- "viem": "^2.0.0"
17
+ "bcryptjs": "^2.4.3",
18
+ "commander": "^12.1.0"
22
19
  },
23
- "scripts": {
24
- "build": "tsc",
25
- "typecheck": "tsc --noEmit"
20
+ "devDependencies": {
21
+ "@types/bcryptjs": "^2.4.6",
22
+ "@types/node": "^22.10.2",
23
+ "tsx": "^4.19.2",
24
+ "typescript": "^5.7.2"
26
25
  }
27
26
  }
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerCardCommands(program: Command): void;
@@ -1,197 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerCardCommands = registerCardCommands;
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 node_child_process_1 = require("node:child_process");
8
- const node_process_1 = require("node:process");
9
- const CONFIG_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".agentspend");
10
- const SETUP_FILE = (0, node_path_1.join)(CONFIG_DIR, "setup.json");
11
- const CARD_FILE = (0, node_path_1.join)(CONFIG_DIR, "card.json");
12
- const API_BASE = process.env.AGENTSPEND_API_URL ?? "https://api.agentspend.co";
13
- async function ensureConfigDir() {
14
- await (0, promises_1.mkdir)(CONFIG_DIR, { recursive: true });
15
- await (0, promises_1.chmod)(CONFIG_DIR, 0o700);
16
- }
17
- function openUrl(url) {
18
- const cmd = node_process_1.platform === "darwin" ? "open" : node_process_1.platform === "win32" ? "start" : "xdg-open";
19
- (0, node_child_process_1.exec)(`${cmd} ${JSON.stringify(url)}`);
20
- }
21
- async function readSetupId() {
22
- try {
23
- const data = JSON.parse(await (0, promises_1.readFile)(SETUP_FILE, "utf-8"));
24
- if (typeof data.setup_id === "string" && data.setup_id) {
25
- return data.setup_id;
26
- }
27
- }
28
- catch {
29
- // file doesn't exist or is invalid
30
- }
31
- throw new Error("No setup_id found. Run 'agentspend card configure' first.");
32
- }
33
- async function readCardFile() {
34
- try {
35
- const data = JSON.parse(await (0, promises_1.readFile)(CARD_FILE, "utf-8"));
36
- if (typeof data.card_id === "string" && typeof data.card_secret === "string") {
37
- return { card_id: data.card_id, card_secret: data.card_secret };
38
- }
39
- }
40
- catch {
41
- // file doesn't exist or is invalid
42
- }
43
- return null;
44
- }
45
- async function createCard() {
46
- const response = await fetch(`${API_BASE}/v1/card/create`, {
47
- method: "POST",
48
- headers: { "content-type": "application/json" },
49
- body: JSON.stringify({})
50
- });
51
- if (!response.ok) {
52
- const body = await response.text();
53
- throw new Error(`Failed to create card (${response.status}): ${body}`);
54
- }
55
- return (await response.json());
56
- }
57
- async function requestConfigure(cardId, cardSecret) {
58
- const response = await fetch(`${API_BASE}/v1/card/configure`, {
59
- method: "POST",
60
- headers: { "content-type": "application/json" },
61
- body: JSON.stringify({ card_id: cardId, card_secret: cardSecret })
62
- });
63
- if (!response.ok) {
64
- const body = await response.text();
65
- throw new Error(`Failed to open configure page (${response.status}): ${body}`);
66
- }
67
- return (await response.json());
68
- }
69
- async function getSetupStatus(setupId) {
70
- const response = await fetch(`${API_BASE}/v1/card/setup/${encodeURIComponent(setupId)}`);
71
- if (!response.ok) {
72
- const body = await response.text();
73
- throw new Error(`Failed to get setup status (${response.status}): ${body}`);
74
- }
75
- return (await response.json());
76
- }
77
- async function getCardStatus(cardId, cardSecret) {
78
- const response = await fetch(`${API_BASE}/v1/card/${encodeURIComponent(cardId)}/status`, {
79
- headers: { "x-card-secret": cardSecret }
80
- });
81
- if (!response.ok) {
82
- const body = await response.text();
83
- throw new Error(`Failed to get card status (${response.status}): ${body}`);
84
- }
85
- return (await response.json());
86
- }
87
- function sleep(ms) {
88
- return new Promise((resolve) => setTimeout(resolve, ms));
89
- }
90
- function formatCents(cents) {
91
- return `$${(cents / 100).toFixed(2)}`;
92
- }
93
- function registerCardCommands(program) {
94
- const card = program
95
- .command("card")
96
- .description("Manage AgentSpend cards");
97
- card
98
- .command("status")
99
- .description("Show card dashboard: weekly budget, services, and recent charges")
100
- .action(async () => {
101
- try {
102
- // If card.json exists, show full dashboard
103
- const cardData = await readCardFile();
104
- if (cardData) {
105
- const status = await getCardStatus(cardData.card_id, cardData.card_secret);
106
- console.log(`Card ID: ${status.card_id}`);
107
- console.log(`Weekly budget: ${formatCents(status.weekly_spent_cents)} / ${formatCents(status.weekly_limit_cents)} used this week`);
108
- console.log(`Remaining: ${formatCents(status.weekly_remaining_cents)}`);
109
- console.log();
110
- if (status.services.length > 0) {
111
- console.log("Authorized services:");
112
- for (const svc of status.services) {
113
- console.log(` - ${svc.name} (${svc.status}, since ${new Date(svc.created_at).toLocaleDateString()})`);
114
- }
115
- }
116
- else {
117
- console.log("Authorized services: No services authorized yet");
118
- }
119
- console.log();
120
- if (status.recent_charges.length > 0) {
121
- console.log("Recent charges:");
122
- for (const ch of status.recent_charges) {
123
- console.log(` - ${ch.service_name}: ${formatCents(ch.amount_cents)} on ${new Date(ch.created_at).toLocaleDateString()}`);
124
- }
125
- }
126
- else {
127
- console.log("Recent charges: No charges yet");
128
- }
129
- return;
130
- }
131
- // Fall back to checking pending setup status
132
- const setupId = await readSetupId();
133
- const status = await getSetupStatus(setupId);
134
- console.log(`Setup ID: ${status.setup_id}`);
135
- console.log(`Status: ${status.status}`);
136
- console.log(`Expires: ${status.expires_at}`);
137
- if (status.card_id) {
138
- console.log(`Card ID: ${status.card_id}`);
139
- }
140
- }
141
- catch (err) {
142
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
143
- process.exit(1);
144
- }
145
- });
146
- card
147
- .command("configure")
148
- .description("Set up or reconfigure your card — opens the configuration page in your browser")
149
- .action(async () => {
150
- try {
151
- // Check if card.json exists — reconfigure flow
152
- const cardData = await readCardFile();
153
- if (cardData) {
154
- const result = await requestConfigure(cardData.card_id, cardData.card_secret);
155
- console.log("Opened configuration page in browser.");
156
- openUrl(result.configure_url);
157
- return;
158
- }
159
- // First-time setup flow
160
- const result = await createCard();
161
- console.log(`Setup ID: ${result.setup_id}`);
162
- console.log(`Opening configuration page in browser...`);
163
- openUrl(result.setup_url);
164
- await ensureConfigDir();
165
- await (0, promises_1.writeFile)(SETUP_FILE, JSON.stringify({ setup_id: result.setup_id }, null, 2));
166
- await (0, promises_1.chmod)(SETUP_FILE, 0o600);
167
- console.log("Waiting for setup to complete...");
168
- while (true) {
169
- await sleep(3000);
170
- const status = await getSetupStatus(result.setup_id);
171
- if (status.status === "ready") {
172
- console.log(`Card is ready!`);
173
- if (status.card_id) {
174
- await (0, promises_1.writeFile)(CARD_FILE, JSON.stringify({
175
- card_id: status.card_id,
176
- card_secret: status.card_secret,
177
- }, null, 2));
178
- await (0, promises_1.chmod)(CARD_FILE, 0o600);
179
- console.log(`Card ID saved to ${CARD_FILE}`);
180
- }
181
- // Clean up setup file
182
- await (0, promises_1.unlink)(SETUP_FILE).catch(() => { });
183
- break;
184
- }
185
- if (status.status === "expired" || status.status === "failed") {
186
- await (0, promises_1.unlink)(SETUP_FILE).catch(() => { });
187
- throw new Error(`Setup ${status.status}. Please try again.`);
188
- }
189
- process.stdout.write(".");
190
- }
191
- }
192
- catch (err) {
193
- console.error(`\nError: ${err instanceof Error ? err.message : err}`);
194
- process.exit(1);
195
- }
196
- });
197
- }
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerCryptoWalletCommands(program: Command): void;
@@ -1,113 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerCryptoWalletCommands = registerCryptoWalletCommands;
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 CRYPTO_WALLET_FILE = (0, node_path_1.join)(CONFIG_DIR, "crypto-wallet.json");
9
- async function ensureConfigDir() {
10
- await (0, promises_1.mkdir)(CONFIG_DIR, { recursive: true });
11
- }
12
- async function readCryptoWalletConfig() {
13
- try {
14
- const data = JSON.parse(await (0, promises_1.readFile)(CRYPTO_WALLET_FILE, "utf-8"));
15
- if (typeof data.address === "string" && data.address) {
16
- return data;
17
- }
18
- }
19
- catch {
20
- // file doesn't exist or is invalid
21
- }
22
- return null;
23
- }
24
- function registerCryptoWalletCommands(program) {
25
- const cryptoWallet = program
26
- .command("crypto-wallet")
27
- .description("Manage AgentSpend crypto wallets");
28
- cryptoWallet
29
- .command("setup")
30
- .description("Set up a crypto wallet from a private key")
31
- .requiredOption("--private-key <key>", "Ethereum private key (0x...)")
32
- .option("--network <network>", "Chain identifier", "eip155:8453")
33
- .action(async (opts) => {
34
- try {
35
- const privateKey = opts.privateKey.trim();
36
- if (!privateKey.startsWith("0x") || privateKey.length !== 66) {
37
- console.error("Error: Private key must be a 0x-prefixed 32-byte hex string (66 characters)");
38
- process.exit(1);
39
- }
40
- // Derive address from private key using viem
41
- let address;
42
- try {
43
- const { privateKeyToAccount } = await import("viem/accounts");
44
- const account = privateKeyToAccount(privateKey);
45
- address = account.address;
46
- }
47
- catch (err) {
48
- console.error(`Error: Failed to derive address from private key: ${err instanceof Error ? err.message : err}`);
49
- process.exit(1);
50
- }
51
- await ensureConfigDir();
52
- const config = {
53
- address,
54
- network: opts.network
55
- };
56
- // Store the private key (in production, this should be encrypted)
57
- // For now, store it so @x402/fetch can use it
58
- const fullConfig = {
59
- ...config,
60
- private_key: privateKey
61
- };
62
- await (0, promises_1.writeFile)(CRYPTO_WALLET_FILE, JSON.stringify(fullConfig, null, 2));
63
- console.log(`Crypto wallet configured.`);
64
- console.log(` Address: ${address}`);
65
- console.log(` Network: ${opts.network}`);
66
- console.log(` Saved to: ${CRYPTO_WALLET_FILE}`);
67
- }
68
- catch (err) {
69
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
70
- process.exit(1);
71
- }
72
- });
73
- cryptoWallet
74
- .command("status")
75
- .description("Show configured crypto wallet address, network, and USDC balance")
76
- .action(async () => {
77
- try {
78
- const config = await readCryptoWalletConfig();
79
- if (!config) {
80
- console.log("No crypto wallet configured.");
81
- console.log("Run: agentspend crypto-wallet setup --private-key 0x...");
82
- process.exit(0);
83
- }
84
- console.log(`Address: ${config.address}`);
85
- console.log(`Network: ${config.network}`);
86
- // Fetch USDC balance on Base
87
- try {
88
- const { createPublicClient, http, parseAbi } = await import("viem");
89
- const { base } = await import("viem/chains");
90
- const client = createPublicClient({
91
- chain: base,
92
- transport: http()
93
- });
94
- const usdcAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
95
- const balance = await client.readContract({
96
- address: usdcAddress,
97
- abi: parseAbi(["function balanceOf(address) view returns (uint256)"]),
98
- functionName: "balanceOf",
99
- args: [config.address]
100
- });
101
- const usdcBalance = Number(balance) / 1e6;
102
- console.log(`USDC Balance: ${usdcBalance.toFixed(2)} USDC`);
103
- }
104
- catch {
105
- console.log("USDC Balance: (unable to fetch)");
106
- }
107
- }
108
- catch (err) {
109
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
110
- process.exit(1);
111
- }
112
- });
113
- }
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerPayCommand(program: Command): void;
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerWalletCommands(program: Command): void;
@@ -1,102 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerWalletCommands = registerWalletCommands;
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 WALLET_FILE = (0, node_path_1.join)(CONFIG_DIR, "wallet.json");
9
- async function ensureConfigDir() {
10
- await (0, promises_1.mkdir)(CONFIG_DIR, { recursive: true });
11
- await (0, promises_1.chmod)(CONFIG_DIR, 0o700);
12
- }
13
- async function readWalletConfig() {
14
- try {
15
- const data = JSON.parse(await (0, promises_1.readFile)(WALLET_FILE, "utf-8"));
16
- if (typeof data.address === "string" && data.address) {
17
- return data;
18
- }
19
- }
20
- catch {
21
- // file doesn't exist or is invalid
22
- }
23
- return null;
24
- }
25
- function registerWalletCommands(program) {
26
- const wallet = program
27
- .command("wallet")
28
- .description("Manage AgentSpend crypto wallets");
29
- wallet
30
- .command("create")
31
- .description("Generate a new crypto wallet for x402 payments")
32
- .option("--network <network>", "Chain identifier", "eip155:8453")
33
- .action(async (opts) => {
34
- try {
35
- const existing = await readWalletConfig();
36
- if (existing) {
37
- console.log(`Wallet already exists.`);
38
- console.log(` Address: ${existing.address}`);
39
- console.log(` Network: ${existing.network}`);
40
- console.log(` Saved at: ${WALLET_FILE}`);
41
- process.exit(0);
42
- }
43
- const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
44
- const privateKey = generatePrivateKey();
45
- const account = privateKeyToAccount(privateKey);
46
- await ensureConfigDir();
47
- const config = {
48
- address: account.address,
49
- network: opts.network,
50
- private_key: privateKey,
51
- };
52
- await (0, promises_1.writeFile)(WALLET_FILE, JSON.stringify(config, null, 2));
53
- await (0, promises_1.chmod)(WALLET_FILE, 0o600);
54
- console.log(`Wallet created. Address: ${account.address} — Fund it with USDC on Base.`);
55
- console.log(`Private key saved to ${WALLET_FILE} (owner-only permissions). Keep this file secure.`);
56
- }
57
- catch (err) {
58
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
59
- process.exit(1);
60
- }
61
- });
62
- wallet
63
- .command("status")
64
- .description("Show wallet address, network, and USDC balance")
65
- .action(async () => {
66
- try {
67
- const config = await readWalletConfig();
68
- if (!config) {
69
- console.log("No wallet configured.");
70
- console.log("Run: agentspend wallet create");
71
- process.exit(0);
72
- }
73
- console.log(`Address: ${config.address}`);
74
- console.log(`Network: ${config.network}`);
75
- // Fetch USDC balance on Base
76
- try {
77
- const { createPublicClient, http, parseAbi } = await import("viem");
78
- const { base } = await import("viem/chains");
79
- const client = createPublicClient({
80
- chain: base,
81
- transport: http(),
82
- });
83
- const usdcAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
84
- const balance = await client.readContract({
85
- address: usdcAddress,
86
- abi: parseAbi(["function balanceOf(address) view returns (uint256)"]),
87
- functionName: "balanceOf",
88
- args: [config.address],
89
- });
90
- const usdcBalance = Number(balance) / 1e6;
91
- console.log(`USDC Balance: ${usdcBalance.toFixed(2)} USDC`);
92
- }
93
- catch {
94
- console.log("USDC Balance: (unable to fetch)");
95
- }
96
- }
97
- catch (err) {
98
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
99
- process.exit(1);
100
- }
101
- });
102
- }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};