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.
@@ -0,0 +1,16 @@
1
+ import { resolveApiKeyWithAutoClaim } from "../lib/auth-flow.js";
2
+ export async function runSearch(apiClient, query) {
3
+ const apiKey = await resolveApiKeyWithAutoClaim(apiClient);
4
+ const response = await apiClient.search(apiKey, query);
5
+ if (response.services.length === 0) {
6
+ console.log(`No services matched "${response.query}".`);
7
+ return;
8
+ }
9
+ for (const service of response.services) {
10
+ console.log(service.name);
11
+ console.log(`Description: ${service.description}`);
12
+ console.log(`Domain: ${service.domain}`);
13
+ console.log(`Skill URL: ${service.skill_url ?? "n/a"}`);
14
+ console.log("");
15
+ }
16
+ }
@@ -0,0 +1,36 @@
1
+ import bcrypt from "bcryptjs";
2
+ import crypto from "node:crypto";
3
+ import { saveCredentials } from "../lib/credentials.js";
4
+ const POLL_INTERVAL_MS = 2_000;
5
+ const SETUP_TIMEOUT_MS = 10 * 60 * 1000;
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ function generateApiKey() {
10
+ return `sk_agent_${crypto.randomBytes(32).toString("hex")}`;
11
+ }
12
+ export async function runSetup(apiClient) {
13
+ const setup = await apiClient.createSetup();
14
+ console.log(`Open this URL to complete setup:\n${setup.setup_url}\n`);
15
+ console.log("Waiting for setup to complete...");
16
+ const started = Date.now();
17
+ while (Date.now() - started < SETUP_TIMEOUT_MS) {
18
+ const status = await apiClient.getSetupStatus(setup.setup_id);
19
+ if (status.status === "ready") {
20
+ const apiKey = generateApiKey();
21
+ const apiKeyHash = await bcrypt.hash(apiKey, 12);
22
+ await apiClient.claimSetup(setup.setup_id, apiKeyHash);
23
+ await saveCredentials(apiKey);
24
+ console.log("Ready! Your agent can now spend.");
25
+ return;
26
+ }
27
+ if (status.status === "claimed") {
28
+ throw new Error("This setup session was already claimed. Run agentspend setup again.");
29
+ }
30
+ if (status.status === "expired") {
31
+ throw new Error("Setup timed out. Run agentspend setup again.");
32
+ }
33
+ await sleep(POLL_INTERVAL_MS);
34
+ }
35
+ throw new Error("Setup timed out. Run agentspend setup again.");
36
+ }
@@ -0,0 +1,7 @@
1
+ import { resolveApiKeyWithAutoClaim } from "../lib/auth-flow.js";
2
+ import { printStatus } from "../lib/output.js";
3
+ export async function runStatus(apiClient) {
4
+ const apiKey = await resolveApiKeyWithAutoClaim(apiClient);
5
+ const status = await apiClient.status(apiKey);
6
+ printStatus(status);
7
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "./cli.js";
3
+ const LOCAL_API_BASE_URL = "http://127.0.0.1:8787";
4
+ runCli({
5
+ baseUrl: LOCAL_API_BASE_URL,
6
+ programName: "agentspend-dev",
7
+ }).catch((error) => {
8
+ console.error(error instanceof Error ? error.message : String(error));
9
+ process.exitCode = 1;
10
+ });
package/dist/index.js CHANGED
@@ -1,16 +1,6 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- const commander_1 = require("commander");
5
- const card_js_1 = require("./commands/card.js");
6
- const wallet_js_1 = require("./commands/wallet.js");
7
- const pay_js_1 = require("./commands/pay.js");
8
- const program = new commander_1.Command();
9
- program
10
- .name("agentspend")
11
- .description("AgentSpend CLI — manage cards and billing")
12
- .version("0.1.0");
13
- (0, card_js_1.registerCardCommands)(program);
14
- (0, wallet_js_1.registerWalletCommands)(program);
15
- (0, pay_js_1.registerPayCommand)(program);
16
- program.parse();
2
+ import { runCli } from "./cli.js";
3
+ runCli().catch((error) => {
4
+ console.error(error instanceof Error ? error.message : String(error));
5
+ process.exitCode = 1;
6
+ });
@@ -0,0 +1,98 @@
1
+ const API_URL = "https://api.agentspend.co";
2
+ export class ApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(message, status, body) {
6
+ super(message);
7
+ this.name = "ApiError";
8
+ this.status = status;
9
+ this.body = body;
10
+ }
11
+ }
12
+ async function parseBody(response) {
13
+ const contentType = response.headers.get("content-type") ?? "";
14
+ if (contentType.includes("application/json")) {
15
+ try {
16
+ return await response.json();
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ return response.text();
23
+ }
24
+ function errorMessageFromBody(body, fallback) {
25
+ if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
26
+ return body.error;
27
+ }
28
+ if (typeof body === "string" && body.length > 0) {
29
+ return body;
30
+ }
31
+ return fallback;
32
+ }
33
+ export class AgentspendApiClient {
34
+ baseUrl;
35
+ constructor(baseUrl = API_URL) {
36
+ this.baseUrl = baseUrl;
37
+ }
38
+ async request(path, init = {}, apiKey) {
39
+ const headers = {
40
+ ...init.headers,
41
+ };
42
+ if (apiKey) {
43
+ headers.Authorization = `Bearer ${apiKey}`;
44
+ }
45
+ if (init.body && !headers["content-type"] && !headers["Content-Type"]) {
46
+ headers["content-type"] = "application/json";
47
+ }
48
+ const response = await fetch(`${this.baseUrl}${path}`, {
49
+ ...init,
50
+ headers,
51
+ });
52
+ const body = await parseBody(response);
53
+ if (!response.ok) {
54
+ throw new ApiError(errorMessageFromBody(body, `Request failed (${response.status})`), response.status, body);
55
+ }
56
+ return body;
57
+ }
58
+ pay(apiKey, payload) {
59
+ return this.request("/pay", {
60
+ method: "POST",
61
+ body: JSON.stringify(payload),
62
+ }, apiKey);
63
+ }
64
+ check(apiKey, payload) {
65
+ return this.request("/check", {
66
+ method: "POST",
67
+ body: JSON.stringify(payload),
68
+ }, apiKey);
69
+ }
70
+ status(apiKey) {
71
+ return this.request("/status", {
72
+ method: "GET",
73
+ }, apiKey);
74
+ }
75
+ configure(payload, apiKey) {
76
+ return this.request("/configure", {
77
+ method: "POST",
78
+ body: payload ? JSON.stringify(payload) : undefined,
79
+ }, apiKey);
80
+ }
81
+ configureStatus(token) {
82
+ return this.request(`/configure/${encodeURIComponent(token)}/status`, {
83
+ method: "GET",
84
+ });
85
+ }
86
+ claimConfigure(token, apiKeyHash) {
87
+ return this.request(`/configure/${encodeURIComponent(token)}/claim`, {
88
+ method: "POST",
89
+ body: JSON.stringify({ api_key_hash: apiKeyHash }),
90
+ });
91
+ }
92
+ search(apiKey, query) {
93
+ const params = new URLSearchParams({ q: query });
94
+ return this.request(`/search?${params.toString()}`, {
95
+ method: "GET",
96
+ }, apiKey);
97
+ }
98
+ }
@@ -0,0 +1,56 @@
1
+ import bcrypt from "bcryptjs";
2
+ import crypto from "node:crypto";
3
+ import { ApiError } from "./api.js";
4
+ import { clearPendingConfigureToken, readCredentials, readPendingConfigureToken, saveCredentials, } from "./credentials.js";
5
+ function generateApiKey() {
6
+ return `sk_agent_${crypto.randomBytes(32).toString("hex")}`;
7
+ }
8
+ async function getConfigureStatusOrNull(apiClient, token) {
9
+ try {
10
+ return await apiClient.configureStatus(token);
11
+ }
12
+ catch (error) {
13
+ if (error instanceof ApiError && (error.status === 401 || error.status === 404 || error.status === 410)) {
14
+ return null;
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+ export async function claimConfigureToken(apiClient, token) {
20
+ const apiKey = generateApiKey();
21
+ const apiKeyHash = await bcrypt.hash(apiKey, 12);
22
+ await apiClient.claimConfigure(token, apiKeyHash);
23
+ await saveCredentials(apiKey);
24
+ await clearPendingConfigureToken();
25
+ return apiKey;
26
+ }
27
+ export async function getPendingConfigureStatus(apiClient) {
28
+ const token = await readPendingConfigureToken();
29
+ if (!token) {
30
+ return null;
31
+ }
32
+ const status = await getConfigureStatusOrNull(apiClient, token);
33
+ if (!status || status.claim_status === "expired") {
34
+ await clearPendingConfigureToken();
35
+ return null;
36
+ }
37
+ return { token, status };
38
+ }
39
+ export async function resolveApiKeyWithAutoClaim(apiClient) {
40
+ const credentials = await readCredentials();
41
+ if (credentials) {
42
+ return credentials.api_key;
43
+ }
44
+ const pending = await getPendingConfigureStatus(apiClient);
45
+ if (!pending) {
46
+ throw new Error("No API key found. Run `agentspend configure` first.");
47
+ }
48
+ if (pending.status.claim_status === "ready_to_claim") {
49
+ return claimConfigureToken(apiClient, pending.token);
50
+ }
51
+ if (pending.status.claim_status === "awaiting_card") {
52
+ throw new Error(`Card setup required. Open this URL:\n${pending.status.configure_url}`);
53
+ }
54
+ await clearPendingConfigureToken();
55
+ throw new Error("Configure session already claimed. Run `agentspend configure` again.");
56
+ }
@@ -0,0 +1,65 @@
1
+ import { mkdir, readFile, writeFile, chmod, unlink } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ const CREDENTIALS_DIR = path.join(os.homedir(), ".agentspend");
5
+ const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
6
+ const PENDING_CONFIGURE_FILE = path.join(CREDENTIALS_DIR, "pending-configure.json");
7
+ export async function readCredentials() {
8
+ try {
9
+ const raw = await readFile(CREDENTIALS_FILE, "utf-8");
10
+ const parsed = JSON.parse(raw);
11
+ if (!parsed.api_key || !parsed.api_key.startsWith("sk_agent_")) {
12
+ return null;
13
+ }
14
+ return parsed;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ export async function saveCredentials(apiKey) {
21
+ const payload = {
22
+ api_key: apiKey,
23
+ created_at: new Date().toISOString(),
24
+ };
25
+ await mkdir(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
26
+ await writeFile(CREDENTIALS_FILE, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
27
+ try {
28
+ await chmod(CREDENTIALS_FILE, 0o600);
29
+ }
30
+ catch {
31
+ // no-op when chmod is not available on the host filesystem
32
+ }
33
+ }
34
+ export async function requireApiKey() {
35
+ const credentials = await readCredentials();
36
+ if (!credentials) {
37
+ throw new Error(`No API key found at ${CREDENTIALS_FILE}. Run \`agentspend configure\` first.`);
38
+ }
39
+ return credentials.api_key;
40
+ }
41
+ export async function readPendingConfigureToken() {
42
+ try {
43
+ const raw = await readFile(PENDING_CONFIGURE_FILE, "utf-8");
44
+ const parsed = JSON.parse(raw);
45
+ if (!parsed.token || !parsed.token.startsWith("cfs_")) {
46
+ return null;
47
+ }
48
+ return parsed.token;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ export async function savePendingConfigureToken(token) {
55
+ await mkdir(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
56
+ await writeFile(PENDING_CONFIGURE_FILE, `${JSON.stringify({ token, created_at: new Date().toISOString() }, null, 2)}\n`, "utf-8");
57
+ }
58
+ export async function clearPendingConfigureToken() {
59
+ try {
60
+ await unlink(PENDING_CONFIGURE_FILE);
61
+ }
62
+ catch {
63
+ // no-op
64
+ }
65
+ }
@@ -0,0 +1,76 @@
1
+ const USD6_SCALE = 1_000_000;
2
+ function formatUsdNumber(value, minDecimals = 2, maxDecimals = 6) {
3
+ const fixed = value.toFixed(maxDecimals);
4
+ const trimmed = fixed.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
5
+ if (!trimmed.includes(".")) {
6
+ return minDecimals > 0 ? `${trimmed}.${"0".repeat(minDecimals)}` : trimmed;
7
+ }
8
+ const [whole, frac = ""] = trimmed.split(".");
9
+ if (frac.length >= minDecimals) {
10
+ return trimmed;
11
+ }
12
+ return `${whole}.${frac.padEnd(minDecimals, "0")}`;
13
+ }
14
+ export function usd6ToUsd(usd6) {
15
+ return usd6 / USD6_SCALE;
16
+ }
17
+ export function formatUsd(usd) {
18
+ if (!Number.isFinite(usd)) {
19
+ return "unknown";
20
+ }
21
+ return `$${formatUsdNumber(usd, 2, 6)}`;
22
+ }
23
+ export function formatUsdEstimate(estimatedUsd, fallbackUsd) {
24
+ if (typeof estimatedUsd === "number") {
25
+ return formatUsd(estimatedUsd);
26
+ }
27
+ if (typeof fallbackUsd === "number") {
28
+ return formatUsd(fallbackUsd);
29
+ }
30
+ return "unknown";
31
+ }
32
+ export function formatJson(value) {
33
+ if (typeof value === "string") {
34
+ return value;
35
+ }
36
+ return JSON.stringify(value, null, 2);
37
+ }
38
+ export function relativeTime(iso) {
39
+ const timestamp = new Date(iso).getTime();
40
+ const diffMs = Date.now() - timestamp;
41
+ const abs = Math.max(0, Math.floor(diffMs / 1000));
42
+ if (abs < 60) {
43
+ return `${abs}s ago`;
44
+ }
45
+ const minutes = Math.floor(abs / 60);
46
+ if (minutes < 60) {
47
+ return `${minutes}m ago`;
48
+ }
49
+ const hours = Math.floor(minutes / 60);
50
+ if (hours < 24) {
51
+ return `${hours}h ago`;
52
+ }
53
+ const days = Math.floor(hours / 24);
54
+ return `${days}d ago`;
55
+ }
56
+ function formatCharge(charge) {
57
+ const amountUsd = charge.amount_usd ?? usd6ToUsd(charge.amount_usd6);
58
+ const amount = formatUsd(amountUsd).padEnd(10);
59
+ const domain = charge.target_domain.padEnd(24);
60
+ const when = relativeTime(charge.created_at);
61
+ return ` ${amount} ${domain} ${when}`;
62
+ }
63
+ export function printStatus(status) {
64
+ console.log(`Weekly budget: ${formatUsd(status.weekly_budget_usd)}`);
65
+ console.log(`Spent this week: ${formatUsd(status.spent_this_week_usd)}`);
66
+ console.log(`Remaining: ${formatUsd(status.remaining_budget_usd)}`);
67
+ console.log("");
68
+ if (status.recent_charges.length === 0) {
69
+ console.log("Recent charges: none");
70
+ return;
71
+ }
72
+ console.log("Recent charges:");
73
+ for (const charge of status.recent_charges) {
74
+ console.log(formatCharge(charge));
75
+ }
76
+ }
@@ -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.10",
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.1",
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,179 +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("Get configuration URL to set up or reconfigure your card")
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(`Configuration URL: ${result.configure_url}`);
156
- console.log();
157
- console.log("Open this URL in your browser to reconfigure (change weekly limit, swap card, or remove).");
158
- return;
159
- }
160
- // First-time setup flow
161
- const result = await createCard();
162
- await ensureConfigDir();
163
- await (0, promises_1.writeFile)(SETUP_FILE, JSON.stringify({ setup_id: result.setup_id }, null, 2));
164
- await (0, promises_1.chmod)(SETUP_FILE, 0o600);
165
- console.log(`Configuration URL: ${result.setup_url}`);
166
- console.log(`Expires: ${result.expires_at}`);
167
- console.log();
168
- console.log("Complete setup in your browser:");
169
- console.log(" 1. Set your weekly spending limit");
170
- console.log(" 2. Add your card via Stripe");
171
- console.log();
172
- console.log("Then run 'agentspend card status' to verify completion.");
173
- }
174
- catch (err) {
175
- console.error(`\nError: ${err instanceof Error ? err.message : err}`);
176
- process.exit(1);
177
- }
178
- });
179
- }
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerCryptoWalletCommands(program: Command): void;