agentspend 0.1.10 → 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.
- package/README.md +20 -25
- package/SKILL.md +118 -0
- package/dist/cli.js +63 -0
- package/dist/commands/check.js +40 -0
- package/dist/commands/configure.js +99 -0
- package/dist/commands/pay.js +89 -206
- package/dist/commands/search.js +16 -0
- package/dist/commands/setup.js +36 -0
- package/dist/commands/status.js +7 -0
- package/dist/dev-index.js +10 -0
- package/dist/index.js +5 -15
- package/dist/lib/api.js +98 -0
- package/dist/lib/credentials.js +65 -0
- package/dist/lib/output.js +76 -0
- package/dist/lib/request-options.js +35 -0
- package/dist/types.js +1 -0
- package/package.json +17 -18
- package/dist/commands/card.d.ts +0 -2
- package/dist/commands/card.js +0 -179
- package/dist/commands/crypto-wallet.d.ts +0 -2
- package/dist/commands/crypto-wallet.js +0 -113
- package/dist/commands/pay.d.ts +0 -2
- package/dist/commands/wallet.d.ts +0 -2
- package/dist/commands/wallet.js +0 -102
- package/dist/index.d.ts +0 -2
- package/src/commands/card.ts +0 -230
- package/src/commands/pay.ts +0 -270
- package/src/commands/wallet.ts +0 -118
- package/src/index.ts +0 -19
- package/tsconfig.json +0 -8
package/src/commands/card.ts
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { readFile, writeFile, mkdir, unlink, chmod } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { exec } from "node:child_process";
|
|
6
|
-
import { platform } from "node:process";
|
|
7
|
-
type CardSetupStatus = "awaiting_card" | "ready" | "expired" | "failed";
|
|
8
|
-
|
|
9
|
-
interface CardCreateResponse {
|
|
10
|
-
setup_id: string;
|
|
11
|
-
setup_url: string;
|
|
12
|
-
status: CardSetupStatus;
|
|
13
|
-
expires_at: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface CardSetupStatusResponse {
|
|
17
|
-
setup_id: string;
|
|
18
|
-
status: CardSetupStatus;
|
|
19
|
-
expires_at: string;
|
|
20
|
-
card_id?: string;
|
|
21
|
-
card_secret?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface CardConfigureResponse {
|
|
25
|
-
setup_id: string;
|
|
26
|
-
configure_url: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface CardStatusResponse {
|
|
30
|
-
card_id: string;
|
|
31
|
-
weekly_limit_cents: number;
|
|
32
|
-
weekly_spent_cents: number;
|
|
33
|
-
weekly_remaining_cents: number;
|
|
34
|
-
services: { name: string; status: string; created_at: string }[];
|
|
35
|
-
recent_charges: { service_name: string; amount_cents: number; created_at: string }[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const CONFIG_DIR = join(homedir(), ".agentspend");
|
|
39
|
-
const SETUP_FILE = join(CONFIG_DIR, "setup.json");
|
|
40
|
-
const CARD_FILE = join(CONFIG_DIR, "card.json");
|
|
41
|
-
const API_BASE = process.env.AGENTSPEND_API_URL ?? "https://api.agentspend.co";
|
|
42
|
-
|
|
43
|
-
async function ensureConfigDir(): Promise<void> {
|
|
44
|
-
await mkdir(CONFIG_DIR, { recursive: true });
|
|
45
|
-
await chmod(CONFIG_DIR, 0o700);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function openUrl(url: string): void {
|
|
49
|
-
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
50
|
-
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function readSetupId(): Promise<string> {
|
|
54
|
-
try {
|
|
55
|
-
const data = JSON.parse(await readFile(SETUP_FILE, "utf-8"));
|
|
56
|
-
if (typeof data.setup_id === "string" && data.setup_id) {
|
|
57
|
-
return data.setup_id;
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// file doesn't exist or is invalid
|
|
61
|
-
}
|
|
62
|
-
throw new Error("No setup_id found. Run 'agentspend card configure' first.");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function readCardFile(): Promise<{ card_id: string; card_secret: string } | null> {
|
|
66
|
-
try {
|
|
67
|
-
const data = JSON.parse(await readFile(CARD_FILE, "utf-8"));
|
|
68
|
-
if (typeof data.card_id === "string" && typeof data.card_secret === "string") {
|
|
69
|
-
return { card_id: data.card_id, card_secret: data.card_secret };
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// file doesn't exist or is invalid
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function createCard(): Promise<CardCreateResponse> {
|
|
78
|
-
const response = await fetch(`${API_BASE}/v1/card/create`, {
|
|
79
|
-
method: "POST",
|
|
80
|
-
headers: { "content-type": "application/json" },
|
|
81
|
-
body: JSON.stringify({})
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
if (!response.ok) {
|
|
85
|
-
const body = await response.text();
|
|
86
|
-
throw new Error(`Failed to create card (${response.status}): ${body}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (await response.json()) as CardCreateResponse;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function requestConfigure(cardId: string, cardSecret: string): Promise<CardConfigureResponse> {
|
|
93
|
-
const response = await fetch(`${API_BASE}/v1/card/configure`, {
|
|
94
|
-
method: "POST",
|
|
95
|
-
headers: { "content-type": "application/json" },
|
|
96
|
-
body: JSON.stringify({ card_id: cardId, card_secret: cardSecret })
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
const body = await response.text();
|
|
101
|
-
throw new Error(`Failed to open configure page (${response.status}): ${body}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return (await response.json()) as CardConfigureResponse;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function getSetupStatus(setupId: string): Promise<CardSetupStatusResponse> {
|
|
108
|
-
const response = await fetch(`${API_BASE}/v1/card/setup/${encodeURIComponent(setupId)}`);
|
|
109
|
-
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
const body = await response.text();
|
|
112
|
-
throw new Error(`Failed to get setup status (${response.status}): ${body}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return (await response.json()) as CardSetupStatusResponse;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function getCardStatus(cardId: string, cardSecret: string): Promise<CardStatusResponse> {
|
|
119
|
-
const response = await fetch(`${API_BASE}/v1/card/${encodeURIComponent(cardId)}/status`, {
|
|
120
|
-
headers: { "x-card-secret": cardSecret }
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (!response.ok) {
|
|
124
|
-
const body = await response.text();
|
|
125
|
-
throw new Error(`Failed to get card status (${response.status}): ${body}`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return (await response.json()) as CardStatusResponse;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function sleep(ms: number): Promise<void> {
|
|
132
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function formatCents(cents: number): string {
|
|
136
|
-
return `$${(cents / 100).toFixed(2)}`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function registerCardCommands(program: Command): void {
|
|
140
|
-
const card = program
|
|
141
|
-
.command("card")
|
|
142
|
-
.description("Manage AgentSpend cards");
|
|
143
|
-
|
|
144
|
-
card
|
|
145
|
-
.command("status")
|
|
146
|
-
.description("Show card dashboard: weekly budget, services, and recent charges")
|
|
147
|
-
.action(async () => {
|
|
148
|
-
try {
|
|
149
|
-
// If card.json exists, show full dashboard
|
|
150
|
-
const cardData = await readCardFile();
|
|
151
|
-
if (cardData) {
|
|
152
|
-
const status = await getCardStatus(cardData.card_id, cardData.card_secret);
|
|
153
|
-
|
|
154
|
-
console.log(`Card ID: ${status.card_id}`);
|
|
155
|
-
console.log(`Weekly budget: ${formatCents(status.weekly_spent_cents)} / ${formatCents(status.weekly_limit_cents)} used this week`);
|
|
156
|
-
console.log(`Remaining: ${formatCents(status.weekly_remaining_cents)}`);
|
|
157
|
-
|
|
158
|
-
console.log();
|
|
159
|
-
if (status.services.length > 0) {
|
|
160
|
-
console.log("Authorized services:");
|
|
161
|
-
for (const svc of status.services) {
|
|
162
|
-
console.log(` - ${svc.name} (${svc.status}, since ${new Date(svc.created_at).toLocaleDateString()})`);
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
console.log("Authorized services: No services authorized yet");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
console.log();
|
|
169
|
-
if (status.recent_charges.length > 0) {
|
|
170
|
-
console.log("Recent charges:");
|
|
171
|
-
for (const ch of status.recent_charges) {
|
|
172
|
-
console.log(` - ${ch.service_name}: ${formatCents(ch.amount_cents)} on ${new Date(ch.created_at).toLocaleDateString()}`);
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
console.log("Recent charges: No charges yet");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Fall back to checking pending setup status
|
|
182
|
-
const setupId = await readSetupId();
|
|
183
|
-
const status = await getSetupStatus(setupId);
|
|
184
|
-
console.log(`Setup ID: ${status.setup_id}`);
|
|
185
|
-
console.log(`Status: ${status.status}`);
|
|
186
|
-
console.log(`Expires: ${status.expires_at}`);
|
|
187
|
-
if (status.card_id) {
|
|
188
|
-
console.log(`Card ID: ${status.card_id}`);
|
|
189
|
-
}
|
|
190
|
-
} catch (err: unknown) {
|
|
191
|
-
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
card
|
|
197
|
-
.command("configure")
|
|
198
|
-
.description("Get configuration URL to set up or reconfigure your card")
|
|
199
|
-
.action(async () => {
|
|
200
|
-
try {
|
|
201
|
-
// Check if card.json exists — reconfigure flow
|
|
202
|
-
const cardData = await readCardFile();
|
|
203
|
-
if (cardData) {
|
|
204
|
-
const result = await requestConfigure(cardData.card_id, cardData.card_secret);
|
|
205
|
-
console.log(`Configuration URL: ${result.configure_url}`);
|
|
206
|
-
console.log();
|
|
207
|
-
console.log("Open this URL in your browser to reconfigure (change weekly limit, swap card, or remove).");
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// First-time setup flow
|
|
212
|
-
const result = await createCard();
|
|
213
|
-
await ensureConfigDir();
|
|
214
|
-
await writeFile(SETUP_FILE, JSON.stringify({ setup_id: result.setup_id }, null, 2));
|
|
215
|
-
await chmod(SETUP_FILE, 0o600);
|
|
216
|
-
|
|
217
|
-
console.log(`Configuration URL: ${result.setup_url}`);
|
|
218
|
-
console.log(`Expires: ${result.expires_at}`);
|
|
219
|
-
console.log();
|
|
220
|
-
console.log("Complete setup in your browser:");
|
|
221
|
-
console.log(" 1. Set your weekly spending limit");
|
|
222
|
-
console.log(" 2. Add your card via Stripe");
|
|
223
|
-
console.log();
|
|
224
|
-
console.log("Then run 'agentspend card status' to verify completion.");
|
|
225
|
-
} catch (err: unknown) {
|
|
226
|
-
console.error(`\nError: ${err instanceof Error ? err.message : err}`);
|
|
227
|
-
process.exit(1);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
package/src/commands/pay.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/wallet.ts
DELETED
|
@@ -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();
|