agentspend 0.1.6 → 0.1.8

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.
@@ -27,7 +27,19 @@ async function readSetupId() {
27
27
  catch {
28
28
  // file doesn't exist or is invalid
29
29
  }
30
- throw new Error("No setup_id found. Run 'agentspend card setup' first.");
30
+ throw new Error("No setup_id found. Run 'agentspend card configure' first.");
31
+ }
32
+ async function readCardFile() {
33
+ try {
34
+ const data = JSON.parse(await (0, promises_1.readFile)(CARD_FILE, "utf-8"));
35
+ if (typeof data.card_id === "string" && typeof data.card_secret === "string") {
36
+ return { card_id: data.card_id, card_secret: data.card_secret };
37
+ }
38
+ }
39
+ catch {
40
+ // file doesn't exist or is invalid
41
+ }
42
+ return null;
31
43
  }
32
44
  async function createCard() {
33
45
  const response = await fetch(`${API_BASE}/v1/card/create`, {
@@ -41,6 +53,18 @@ async function createCard() {
41
53
  }
42
54
  return (await response.json());
43
55
  }
56
+ async function requestConfigure(cardId, cardSecret) {
57
+ const response = await fetch(`${API_BASE}/v1/card/configure`, {
58
+ method: "POST",
59
+ headers: { "content-type": "application/json" },
60
+ body: JSON.stringify({ card_id: cardId, card_secret: cardSecret })
61
+ });
62
+ if (!response.ok) {
63
+ const body = await response.text();
64
+ throw new Error(`Failed to open configure page (${response.status}): ${body}`);
65
+ }
66
+ return (await response.json());
67
+ }
44
68
  async function getSetupStatus(setupId) {
45
69
  const response = await fetch(`${API_BASE}/v1/card/setup/${encodeURIComponent(setupId)}`);
46
70
  if (!response.ok) {
@@ -49,18 +73,61 @@ async function getSetupStatus(setupId) {
49
73
  }
50
74
  return (await response.json());
51
75
  }
76
+ async function getCardStatus(cardId, cardSecret) {
77
+ const response = await fetch(`${API_BASE}/v1/card/${encodeURIComponent(cardId)}/status`, {
78
+ headers: { "x-card-secret": cardSecret }
79
+ });
80
+ if (!response.ok) {
81
+ const body = await response.text();
82
+ throw new Error(`Failed to get card status (${response.status}): ${body}`);
83
+ }
84
+ return (await response.json());
85
+ }
52
86
  function sleep(ms) {
53
87
  return new Promise((resolve) => setTimeout(resolve, ms));
54
88
  }
89
+ function formatCents(cents) {
90
+ return `$${(cents / 100).toFixed(2)}`;
91
+ }
55
92
  function registerCardCommands(program) {
56
93
  const card = program
57
94
  .command("card")
58
95
  .description("Manage AgentSpend cards");
59
96
  card
60
97
  .command("status")
61
- .description("Check the setup status of a pending card")
98
+ .description("Show card dashboard: weekly budget, services, and recent charges")
62
99
  .action(async () => {
63
100
  try {
101
+ // If card.json exists, show full dashboard
102
+ const cardData = await readCardFile();
103
+ if (cardData) {
104
+ const status = await getCardStatus(cardData.card_id, cardData.card_secret);
105
+ console.log(`Card ID: ${status.card_id}`);
106
+ console.log(`Weekly budget: ${formatCents(status.weekly_spent_cents)} / ${formatCents(status.weekly_limit_cents)} used this week`);
107
+ console.log(`Remaining: ${formatCents(status.weekly_remaining_cents)}`);
108
+ console.log();
109
+ if (status.services.length > 0) {
110
+ console.log("Authorized services:");
111
+ for (const svc of status.services) {
112
+ console.log(` - ${svc.name} (${svc.status}, since ${new Date(svc.created_at).toLocaleDateString()})`);
113
+ }
114
+ }
115
+ else {
116
+ console.log("Authorized services: No services authorized yet");
117
+ }
118
+ console.log();
119
+ if (status.recent_charges.length > 0) {
120
+ console.log("Recent charges:");
121
+ for (const ch of status.recent_charges) {
122
+ console.log(` - ${ch.service_name}: ${formatCents(ch.amount_cents)} on ${new Date(ch.created_at).toLocaleDateString()}`);
123
+ }
124
+ }
125
+ else {
126
+ console.log("Recent charges: No charges yet");
127
+ }
128
+ return;
129
+ }
130
+ // Fall back to checking pending setup status
64
131
  const setupId = await readSetupId();
65
132
  const status = await getSetupStatus(setupId);
66
133
  console.log(`Setup ID: ${status.setup_id}`);
@@ -76,13 +143,22 @@ function registerCardCommands(program) {
76
143
  }
77
144
  });
78
145
  card
79
- .command("setup")
80
- .description("Set up a card and wait until setup is complete")
146
+ .command("configure")
147
+ .description("Set up or reconfigure your card opens the configuration page in your browser")
81
148
  .action(async () => {
82
149
  try {
150
+ // Check if card.json exists — reconfigure flow
151
+ const cardData = await readCardFile();
152
+ if (cardData) {
153
+ const result = await requestConfigure(cardData.card_id, cardData.card_secret);
154
+ console.log("Opened configuration page in browser.");
155
+ openUrl(result.configure_url);
156
+ return;
157
+ }
158
+ // First-time setup flow
83
159
  const result = await createCard();
84
160
  console.log(`Setup ID: ${result.setup_id}`);
85
- console.log(`Opening setup URL in browser...`);
161
+ console.log(`Opening configuration page in browser...`);
86
162
  openUrl(result.setup_url);
87
163
  await ensureConfigDir();
88
164
  await (0, promises_1.writeFile)(SETUP_FILE, JSON.stringify({ setup_id: result.setup_id }, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentspend",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "CLI for AgentSpend — manage cards and crypto wallets for AI agent payments",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,20 @@ interface CardSetupStatusResponse {
21
21
  card_secret?: string;
22
22
  }
23
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
+
24
38
  const CONFIG_DIR = join(homedir(), ".agentspend");
25
39
  const SETUP_FILE = join(CONFIG_DIR, "setup.json");
26
40
  const CARD_FILE = join(CONFIG_DIR, "card.json");
@@ -44,7 +58,19 @@ async function readSetupId(): Promise<string> {
44
58
  } catch {
45
59
  // file doesn't exist or is invalid
46
60
  }
47
- throw new Error("No setup_id found. Run 'agentspend card setup' first.");
61
+ throw new Error("No setup_id found. Run 'agentspend card configure' first.");
62
+ }
63
+
64
+ async function readCardFile(): Promise<{ card_id: string; card_secret: string } | null> {
65
+ try {
66
+ const data = JSON.parse(await readFile(CARD_FILE, "utf-8"));
67
+ if (typeof data.card_id === "string" && typeof data.card_secret === "string") {
68
+ return { card_id: data.card_id, card_secret: data.card_secret };
69
+ }
70
+ } catch {
71
+ // file doesn't exist or is invalid
72
+ }
73
+ return null;
48
74
  }
49
75
 
50
76
  async function createCard(): Promise<CardCreateResponse> {
@@ -62,6 +88,21 @@ async function createCard(): Promise<CardCreateResponse> {
62
88
  return (await response.json()) as CardCreateResponse;
63
89
  }
64
90
 
91
+ async function requestConfigure(cardId: string, cardSecret: string): Promise<CardConfigureResponse> {
92
+ const response = await fetch(`${API_BASE}/v1/card/configure`, {
93
+ method: "POST",
94
+ headers: { "content-type": "application/json" },
95
+ body: JSON.stringify({ card_id: cardId, card_secret: cardSecret })
96
+ });
97
+
98
+ if (!response.ok) {
99
+ const body = await response.text();
100
+ throw new Error(`Failed to open configure page (${response.status}): ${body}`);
101
+ }
102
+
103
+ return (await response.json()) as CardConfigureResponse;
104
+ }
105
+
65
106
  async function getSetupStatus(setupId: string): Promise<CardSetupStatusResponse> {
66
107
  const response = await fetch(`${API_BASE}/v1/card/setup/${encodeURIComponent(setupId)}`);
67
108
 
@@ -73,10 +114,27 @@ async function getSetupStatus(setupId: string): Promise<CardSetupStatusResponse>
73
114
  return (await response.json()) as CardSetupStatusResponse;
74
115
  }
75
116
 
117
+ async function getCardStatus(cardId: string, cardSecret: string): Promise<CardStatusResponse> {
118
+ const response = await fetch(`${API_BASE}/v1/card/${encodeURIComponent(cardId)}/status`, {
119
+ headers: { "x-card-secret": cardSecret }
120
+ });
121
+
122
+ if (!response.ok) {
123
+ const body = await response.text();
124
+ throw new Error(`Failed to get card status (${response.status}): ${body}`);
125
+ }
126
+
127
+ return (await response.json()) as CardStatusResponse;
128
+ }
129
+
76
130
  function sleep(ms: number): Promise<void> {
77
131
  return new Promise((resolve) => setTimeout(resolve, ms));
78
132
  }
79
133
 
134
+ function formatCents(cents: number): string {
135
+ return `$${(cents / 100).toFixed(2)}`;
136
+ }
137
+
80
138
  export function registerCardCommands(program: Command): void {
81
139
  const card = program
82
140
  .command("card")
@@ -84,9 +142,42 @@ export function registerCardCommands(program: Command): void {
84
142
 
85
143
  card
86
144
  .command("status")
87
- .description("Check the setup status of a pending card")
145
+ .description("Show card dashboard: weekly budget, services, and recent charges")
88
146
  .action(async () => {
89
147
  try {
148
+ // If card.json exists, show full dashboard
149
+ const cardData = await readCardFile();
150
+ if (cardData) {
151
+ const status = await getCardStatus(cardData.card_id, cardData.card_secret);
152
+
153
+ console.log(`Card ID: ${status.card_id}`);
154
+ console.log(`Weekly budget: ${formatCents(status.weekly_spent_cents)} / ${formatCents(status.weekly_limit_cents)} used this week`);
155
+ console.log(`Remaining: ${formatCents(status.weekly_remaining_cents)}`);
156
+
157
+ console.log();
158
+ if (status.services.length > 0) {
159
+ console.log("Authorized services:");
160
+ for (const svc of status.services) {
161
+ console.log(` - ${svc.name} (${svc.status}, since ${new Date(svc.created_at).toLocaleDateString()})`);
162
+ }
163
+ } else {
164
+ console.log("Authorized services: No services authorized yet");
165
+ }
166
+
167
+ console.log();
168
+ if (status.recent_charges.length > 0) {
169
+ console.log("Recent charges:");
170
+ for (const ch of status.recent_charges) {
171
+ console.log(` - ${ch.service_name}: ${formatCents(ch.amount_cents)} on ${new Date(ch.created_at).toLocaleDateString()}`);
172
+ }
173
+ } else {
174
+ console.log("Recent charges: No charges yet");
175
+ }
176
+
177
+ return;
178
+ }
179
+
180
+ // Fall back to checking pending setup status
90
181
  const setupId = await readSetupId();
91
182
  const status = await getSetupStatus(setupId);
92
183
  console.log(`Setup ID: ${status.setup_id}`);
@@ -102,13 +193,23 @@ export function registerCardCommands(program: Command): void {
102
193
  });
103
194
 
104
195
  card
105
- .command("setup")
106
- .description("Set up a card and wait until setup is complete")
196
+ .command("configure")
197
+ .description("Set up or reconfigure your card opens the configuration page in your browser")
107
198
  .action(async () => {
108
199
  try {
200
+ // Check if card.json exists — reconfigure flow
201
+ const cardData = await readCardFile();
202
+ if (cardData) {
203
+ const result = await requestConfigure(cardData.card_id, cardData.card_secret);
204
+ console.log("Opened configuration page in browser.");
205
+ openUrl(result.configure_url);
206
+ return;
207
+ }
208
+
209
+ // First-time setup flow
109
210
  const result = await createCard();
110
211
  console.log(`Setup ID: ${result.setup_id}`);
111
- console.log(`Opening setup URL in browser...`);
212
+ console.log(`Opening configuration page in browser...`);
112
213
  openUrl(result.setup_url);
113
214
 
114
215
  await ensureConfigDir();