@clawcard/cli 1.1.6 → 2.0.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  On-demand credit cards, email, and phone for your AI agents.
4
4
 
5
- ClawCard gives your agent a full identity — dedicated email inbox, SMS-enabled US phone number, virtual Mastercards with spend limits, and an encrypted credential vault. One API key, simple REST endpoints.
5
+ ClawCard gives your agent a full identity — dedicated email inbox, SMS-enabled US phone number, virtual Mastercards with spend limits, and an encrypted credential vault. Your agent interacts with ClawCard through CLI commands — the API key never enters the agent's context.
6
6
 
7
7
  ## Install
8
8
 
@@ -16,10 +16,10 @@ npm install -g @clawcard/cli
16
16
  # Sign up (requires invite code)
17
17
  clawcard signup
18
18
 
19
- # Or log in
20
- clawcard login
19
+ # Top up your balance (minimum $5)
20
+ clawcard billing topup
21
21
 
22
- # Create an API key
22
+ # Create your agent key
23
23
  clawcard keys create
24
24
 
25
25
  # Set up your agent (installs skill + configures API key)
@@ -28,54 +28,52 @@ clawcard setup
28
28
 
29
29
  `clawcard setup` uses [skills.sh](https://skills.sh) to install the ClawCard skill for your agent. Works with Claude Code, Cursor, Codex, Cline, OpenClaw, and 40+ other agents.
30
30
 
31
- ## Commands
31
+ ## Agent Commands
32
32
 
33
- ### Authentication
34
- | Command | Description |
35
- |---|---|
36
- | `clawcard login` | Log in via browser |
37
- | `clawcard signup` | Sign up with invite code |
38
- | `clawcard logout` | Clear session |
39
- | `clawcard whoami` | Show current account |
33
+ Your agent runs these commands directly. All support `--json` for machine-parseable output.
40
34
 
41
- ### Keys
42
35
  | Command | Description |
43
36
  |---|---|
44
- | `clawcard keys` | Interactive key management |
45
- | `clawcard keys create` | Create a new API key |
46
- | `clawcard keys list` | List all keys |
47
- | `clawcard keys info` | Show key details |
48
- | `clawcard keys revoke` | Revoke a key |
37
+ | `clawcard agent info --json` | Agent identity (email, phone, budget) |
38
+ | `clawcard agent emails --json` | List inbox |
39
+ | `clawcard agent emails send --to --subject --body --json` | Send email |
40
+ | `clawcard agent sms --json` | List SMS messages |
41
+ | `clawcard agent sms send --to --body --json` | Send SMS |
42
+ | `clawcard agent cards --json` | List virtual cards |
43
+ | `clawcard agent cards create --amount --type --memo --json` | Create card |
44
+ | `clawcard agent cards details <id> --json` | Get PAN, CVV, expiry |
45
+ | `clawcard agent cards close <id> --json` | Close card |
46
+ | `clawcard agent creds --json` | List stored credentials |
47
+ | `clawcard agent creds set --service --key --value --json` | Store credential |
48
+ | `clawcard agent creds get --service --key --json` | Retrieve credential |
49
+ | `clawcard agent budget --json` | Check remaining budget |
50
+ | `clawcard agent activity --json` | View activity log |
51
+
52
+ ## User Commands
49
53
 
50
- ### Setup
51
54
  | Command | Description |
52
55
  |---|---|
53
- | `clawcard setup` | Install ClawCard skill for your agent |
54
-
55
- ### Billing
56
- | Command | Description |
57
- |---|---|
58
- | `clawcard billing` | Interactive billing dashboard |
59
- | `clawcard billing balance` | Quick balance check |
56
+ | `clawcard login` | Log in via browser |
57
+ | `clawcard signup` | Sign up with invite code |
58
+ | `clawcard logout` | Clear session |
59
+ | `clawcard whoami` | Show current account |
60
+ | `clawcard agent` | Show agent identity |
61
+ | `clawcard agent fund` | Add budget to your agent |
62
+ | `clawcard keys create` | Create agent key |
63
+ | `clawcard keys revoke` | Revoke key (exports credentials first) |
64
+ | `clawcard setup` | Install ClawCard skill |
65
+ | `clawcard billing` | Billing dashboard |
60
66
  | `clawcard billing topup` | Top up balance |
61
- | `clawcard billing upgrade` | Upgrade subscription |
62
- | `clawcard billing transactions` | View transaction history |
63
-
64
- ### Other
65
- | Command | Description |
66
- |---|---|
67
+ | `clawcard billing balance` | Quick balance check |
67
68
  | `clawcard referral` | Show referral code |
68
69
  | `clawcard help` | Show all commands |
69
70
 
70
- ## What Your Agent Gets
71
-
72
- Each API key comes with:
71
+ ## How It Works
73
72
 
74
- - **Email**Dedicated inbox (@mail.clawcard.sh), send and receive
75
- - **Phone** SMS-enabled US number for verification codes
76
- - **Virtual Cards** Mastercards with per-card spend limits (single-use or merchant-locked)
77
- - **Credential Vault**AES-256 encrypted key-value storage
78
- - **Budget Controls** — Per-key spending limits with full audit trail
73
+ 1. You create an agent key via the CLI it gets an email, phone number, and budget
74
+ 2. `clawcard setup` installs a skill file that teaches your agent the available commands
75
+ 3. Your agent runs `clawcard agent` commands to send emails, create cards, store credentials, etc.
76
+ 4. The API key stays in `~/.clawcard/.env` the agent never sees it
79
77
 
80
78
  ## Pricing
81
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawcard/cli",
3
- "version": "1.1.6",
3
+ "version": "2.0.1",
4
4
  "description": "The ClawCard CLI — manage your agent keys, billing, and setup from the terminal",
5
5
  "bin": {
6
6
  "clawcard": "./bin/clawcard.mjs"
@@ -0,0 +1,115 @@
1
+ import { readFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+ import { BASE_URL } from "./config.js";
5
+
6
+ const ENV_PATH = join(homedir(), ".clawcard", ".env");
7
+
8
+ function getApiKey() {
9
+ try {
10
+ const content = readFileSync(ENV_PATH, "utf-8");
11
+ const match = content.match(/^CLAWCARD_API_KEY=(.+)$/m);
12
+ return match?.[1]?.trim() ?? null;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ async function agentRequest(path, options = {}) {
19
+ const apiKey = getApiKey();
20
+ if (!apiKey) {
21
+ throw new Error("No API key found. Run: clawcard setup");
22
+ }
23
+
24
+ const url = `${BASE_URL}${path}`;
25
+ const headers = {
26
+ "Content-Type": "application/json",
27
+ Authorization: `Bearer ${apiKey}`,
28
+ ...options.headers,
29
+ };
30
+
31
+ const res = await fetch(url, { ...options, headers });
32
+ const body = await res.json().catch(() => null);
33
+
34
+ if (!res.ok) {
35
+ const msg = body?.error || body?.message || `API error ${res.status}`;
36
+ throw new Error(msg);
37
+ }
38
+
39
+ return body;
40
+ }
41
+
42
+ // Identity
43
+ export const getIdentity = () => agentRequest("/api/me");
44
+
45
+ // Email
46
+ export const listEmails = (agentId, params = {}) => {
47
+ const qs = new URLSearchParams();
48
+ if (params.limit) qs.set("limit", params.limit);
49
+ if (params.unread) qs.set("unread", "true");
50
+ if (params.from) qs.set("from", params.from);
51
+ if (params.subject_contains) qs.set("subject_contains", params.subject_contains);
52
+ const query = qs.toString();
53
+ return agentRequest(`/api/agents/${agentId}/emails${query ? "?" + query : ""}`);
54
+ };
55
+ export const sendEmail = (agentId, data) =>
56
+ agentRequest(`/api/agents/${agentId}/emails/send`, {
57
+ method: "POST",
58
+ body: JSON.stringify(data),
59
+ });
60
+ export const markEmailRead = (agentId, emailId) =>
61
+ agentRequest(`/api/agents/${agentId}/emails/${emailId}/read`, {
62
+ method: "POST",
63
+ });
64
+
65
+ // SMS
66
+ export const listSms = (agentId, params = {}) => {
67
+ const qs = new URLSearchParams();
68
+ if (params.limit) qs.set("limit", params.limit);
69
+ const query = qs.toString();
70
+ return agentRequest(`/api/agents/${agentId}/sms${query ? "?" + query : ""}`);
71
+ };
72
+ export const sendSms = (agentId, data) =>
73
+ agentRequest(`/api/agents/${agentId}/sms/send`, {
74
+ method: "POST",
75
+ body: JSON.stringify(data),
76
+ });
77
+
78
+ // Cards
79
+ export const listCards = (agentId) =>
80
+ agentRequest(`/api/agents/${agentId}/cards`);
81
+ export const createCard = (agentId, data) =>
82
+ agentRequest(`/api/agents/${agentId}/cards`, {
83
+ method: "POST",
84
+ body: JSON.stringify(data),
85
+ });
86
+ export const getCardDetails = (agentId, cardId) =>
87
+ agentRequest(`/api/agents/${agentId}/cards/${cardId}`);
88
+ export const updateCard = (agentId, cardId, action) =>
89
+ agentRequest(`/api/agents/${agentId}/cards/${cardId}`, {
90
+ method: "PATCH",
91
+ body: JSON.stringify({ action }),
92
+ });
93
+
94
+ // Credentials
95
+ export const listCredentials = (agentId) =>
96
+ agentRequest(`/api/agents/${agentId}/credentials`);
97
+ export const setCredential = (agentId, data) =>
98
+ agentRequest(`/api/agents/${agentId}/credentials`, {
99
+ method: "POST",
100
+ body: JSON.stringify(data),
101
+ });
102
+ export const getCredential = (agentId, service, key) =>
103
+ agentRequest(`/api/agents/${agentId}/credentials/${service}/${key}`);
104
+
105
+ // Budget
106
+ export const getBudget = (agentId) =>
107
+ agentRequest(`/api/agents/${agentId}/budget`);
108
+
109
+ // Activity
110
+ export const listActivity = (agentId, params = {}) => {
111
+ const qs = new URLSearchParams();
112
+ if (params.limit) qs.set("limit", params.limit);
113
+ const query = qs.toString();
114
+ return agentRequest(`/api/agents/${agentId}/activity${query ? "?" + query : ""}`);
115
+ };
package/src/api.js CHANGED
@@ -51,5 +51,19 @@ export const createCheckout = (amountCents) =>
51
51
  export const createPortal = () =>
52
52
  request("/api/billing/portal", { method: "POST" });
53
53
 
54
+ // Budget
55
+ export const getAgentBudget = (id) => request(`/api/agents/${id}/budget`);
56
+ export const allocateBudget = (id, amountCents) =>
57
+ request(`/api/agents/${id}/budget`, {
58
+ method: "POST",
59
+ body: JSON.stringify({ amountCents }),
60
+ });
61
+
62
+ // Credentials (session auth)
63
+ export const listAgentCredentials = (agentId) =>
64
+ request(`/api/agents/${agentId}/credentials`);
65
+ export const getAgentCredential = (agentId, service, key) =>
66
+ request(`/api/agents/${agentId}/credentials/${service}/${key}`);
67
+
54
68
  // User
55
69
  export const getMe = () => request("/api/me");
@@ -0,0 +1,238 @@
1
+ import chalk from "chalk";
2
+ import {
3
+ getIdentity,
4
+ listEmails,
5
+ sendEmail,
6
+ markEmailRead,
7
+ listSms,
8
+ sendSms,
9
+ listCards,
10
+ createCard,
11
+ getCardDetails,
12
+ updateCard,
13
+ listCredentials,
14
+ setCredential,
15
+ getCredential,
16
+ getBudget,
17
+ listActivity,
18
+ } from "../agent-api.js";
19
+
20
+ const orange = chalk.hex("#FF6B35");
21
+
22
+ async function getAgentId() {
23
+ const me = await getIdentity();
24
+ if (!me.keyId) throw new Error("Could not determine agent ID");
25
+ return me.keyId;
26
+ }
27
+
28
+ function output(data, json) {
29
+ if (json) {
30
+ console.log(JSON.stringify(data, null, 2));
31
+ }
32
+ return data;
33
+ }
34
+
35
+ // ── Info ──
36
+ export async function agentInfoCmd(options) {
37
+ const me = await getIdentity();
38
+ if (options.json) return output(me, true);
39
+
40
+ const budget = await getBudget(me.keyId);
41
+ console.log();
42
+ console.log(` Name: ${orange.bold(me.name || "unnamed")}`);
43
+ console.log(` Email: ${me.email || "-"}`);
44
+ console.log(` Phone: ${me.phone || "-"}`);
45
+ console.log(` Key ID: ${chalk.dim(me.keyId)}`);
46
+ console.log(` Budget: ${chalk.green("$" + ((budget.budgetCents || 0) / 100).toFixed(2))}`);
47
+ console.log();
48
+ }
49
+
50
+ // ── Emails ──
51
+ export async function agentEmailsCmd(options) {
52
+ const agentId = await getAgentId();
53
+ const result = await listEmails(agentId, {
54
+ limit: options.limit,
55
+ unread: options.unread,
56
+ });
57
+ if (options.json) return output(result, true);
58
+
59
+ const emails = result.emails || result;
60
+ if (!emails.length) {
61
+ console.log(" No emails");
62
+ return;
63
+ }
64
+ console.log();
65
+ for (const e of emails) {
66
+ const read = e.isRead ? chalk.dim("read") : orange("NEW");
67
+ console.log(` ${read} ${chalk.dim(e.sender)} ${e.subject}`);
68
+ }
69
+ console.log();
70
+ }
71
+
72
+ export async function agentEmailsSendCmd(options) {
73
+ const agentId = await getAgentId();
74
+ const result = await sendEmail(agentId, {
75
+ to: options.to,
76
+ subject: options.subject,
77
+ body: options.body,
78
+ });
79
+ if (options.json) return output(result, true);
80
+ console.log(` Email sent to ${options.to}`);
81
+ }
82
+
83
+ export async function agentEmailsReadCmd(emailId, options) {
84
+ const agentId = await getAgentId();
85
+ const result = await markEmailRead(agentId, emailId);
86
+ if (options.json) return output(result, true);
87
+ console.log(` Marked as read`);
88
+ }
89
+
90
+ // ── SMS ──
91
+ export async function agentSmsCmd(options) {
92
+ const agentId = await getAgentId();
93
+ const result = await listSms(agentId, { limit: options.limit });
94
+ if (options.json) return output(result, true);
95
+
96
+ const messages = result.messages || result;
97
+ if (!messages.length) {
98
+ console.log(" No messages");
99
+ return;
100
+ }
101
+ console.log();
102
+ for (const m of messages) {
103
+ const dir = m.direction === "inbound" ? chalk.green("←") : chalk.blue("→");
104
+ console.log(` ${dir} ${chalk.dim(m.direction === "inbound" ? m.fromNumber : m.toNumber)} ${m.body}`);
105
+ }
106
+ console.log();
107
+ }
108
+
109
+ export async function agentSmsSendCmd(options) {
110
+ const agentId = await getAgentId();
111
+ const result = await sendSms(agentId, {
112
+ to: options.to,
113
+ body: options.body,
114
+ });
115
+ if (options.json) return output(result, true);
116
+ console.log(` SMS sent to ${options.to}`);
117
+ }
118
+
119
+ // ── Cards ──
120
+ export async function agentCardsCmd(options) {
121
+ const agentId = await getAgentId();
122
+ const result = await listCards(agentId);
123
+ if (options.json) return output(result, true);
124
+
125
+ const cards = result.cards || result;
126
+ if (!cards.length) {
127
+ console.log(" No cards");
128
+ return;
129
+ }
130
+ console.log();
131
+ for (const c of cards) {
132
+ const status = c.status === "open" ? chalk.green(c.status) : chalk.red(c.status);
133
+ console.log(` ${c.id} ****${c.lastFour} ${c.type} $${(c.spendLimitCents / 100).toFixed(2)} ${status} ${chalk.dim(c.memo)}`);
134
+ }
135
+ console.log();
136
+ }
137
+
138
+ export async function agentCardsCreateCmd(options) {
139
+ const agentId = await getAgentId();
140
+ const result = await createCard(agentId, {
141
+ amountCents: parseInt(options.amount),
142
+ type: options.type,
143
+ memo: options.memo || "Agent card",
144
+ });
145
+ if (options.json) return output(result, true);
146
+
147
+ console.log();
148
+ console.log(` Card created: ${result.id}`);
149
+ console.log(` PAN: ${orange(result.pan)}`);
150
+ console.log(` CVV: ${result.cvv}`);
151
+ console.log(` Expiry: ${result.exp_month}/${result.exp_year}`);
152
+ console.log(` Limit: $${(result.spendLimitCents / 100).toFixed(2)}`);
153
+ console.log();
154
+ }
155
+
156
+ export async function agentCardsDetailsCmd(cardId, options) {
157
+ const agentId = await getAgentId();
158
+ const result = await getCardDetails(agentId, cardId);
159
+ if (options.json) return output(result, true);
160
+
161
+ console.log();
162
+ console.log(` PAN: ${orange(result.pan)}`);
163
+ console.log(` CVV: ${result.cvv}`);
164
+ console.log(` Expiry: ${result.exp_month}/${result.exp_year}`);
165
+ console.log(` Status: ${result.status}`);
166
+ console.log(` Limit: $${(result.spendLimitCents / 100).toFixed(2)}`);
167
+ console.log();
168
+ }
169
+
170
+ export async function agentCardsActionCmd(cardId, action, options) {
171
+ const agentId = await getAgentId();
172
+ const result = await updateCard(agentId, cardId, action);
173
+ if (options.json) return output(result, true);
174
+ console.log(` Card ${cardId}: ${action}`);
175
+ }
176
+
177
+ // ── Credentials ──
178
+ export async function agentCredsCmd(options) {
179
+ const agentId = await getAgentId();
180
+ const result = await listCredentials(agentId);
181
+ if (options.json) return output(result, true);
182
+
183
+ const creds = Array.isArray(result) ? result : result.credentials || [];
184
+ if (!creds.length) {
185
+ console.log(" No stored credentials");
186
+ return;
187
+ }
188
+ console.log();
189
+ for (const c of creds) {
190
+ console.log(` ${orange(c.service)} / ${c.key}`);
191
+ }
192
+ console.log();
193
+ }
194
+
195
+ export async function agentCredsSetCmd(options) {
196
+ const agentId = await getAgentId();
197
+ const result = await setCredential(agentId, {
198
+ service: options.service,
199
+ key: options.key,
200
+ value: options.value,
201
+ });
202
+ if (options.json) return output(result, true);
203
+ console.log(` Stored: ${options.service}/${options.key}`);
204
+ }
205
+
206
+ export async function agentCredsGetCmd(options) {
207
+ const agentId = await getAgentId();
208
+ const result = await getCredential(agentId, options.service, options.key);
209
+ if (options.json) return output(result, true);
210
+ console.log(` ${options.service}/${options.key}: ${result.value}`);
211
+ }
212
+
213
+ // ── Budget ──
214
+ export async function agentBudgetCmd(options) {
215
+ const agentId = await getAgentId();
216
+ const result = await getBudget(agentId);
217
+ if (options.json) return output(result, true);
218
+ console.log(` Budget: ${chalk.green("$" + ((result.budgetCents || 0) / 100).toFixed(2))}`);
219
+ }
220
+
221
+ // ── Activity ──
222
+ export async function agentActivityCmd(options) {
223
+ const agentId = await getAgentId();
224
+ const result = await listActivity(agentId, { limit: options.limit });
225
+ if (options.json) return output(result, true);
226
+
227
+ const activity = result.activity || result;
228
+ if (!activity.length) {
229
+ console.log(" No activity");
230
+ return;
231
+ }
232
+ console.log();
233
+ for (const a of activity) {
234
+ const date = new Date(a.createdAt).toLocaleString();
235
+ console.log(` ${chalk.dim(date)} ${orange(a.action)} ${chalk.dim(a.details)}`);
236
+ }
237
+ console.log();
238
+ }
@@ -11,15 +11,32 @@ const COMMANDS = [
11
11
  [" logout", "Clear session"],
12
12
  [" whoami", "Show current account"],
13
13
  ["", ""],
14
- [orange.bold("Agent"), ""],
14
+ [orange.bold("Agent (user)"), ""],
15
15
  [" agent", "Show your agent's identity"],
16
+ [" agent fund", "Add budget to your agent"],
17
+ ["", ""],
18
+ [orange.bold("Agent (machine — pass --json)"), ""],
19
+ [" agent info", "Agent identity"],
20
+ [" agent emails", "List inbox"],
21
+ [" agent emails send", "Send email"],
22
+ [" agent emails read", "Mark email as read"],
23
+ [" agent sms", "List SMS messages"],
24
+ [" agent sms send", "Send SMS"],
25
+ [" agent cards", "List cards"],
26
+ [" agent cards create", "Create card"],
27
+ [" agent cards details", "Card PAN/CVV/expiry"],
28
+ [" agent cards close", "Close card"],
29
+ [" agent cards pause", "Pause card"],
30
+ [" agent cards resume", "Resume card"],
31
+ [" agent creds", "List credentials"],
32
+ [" agent creds set", "Store credential"],
33
+ [" agent creds get", "Retrieve credential"],
34
+ [" agent budget", "Check budget"],
35
+ [" agent activity", "Activity log"],
16
36
  ["", ""],
17
37
  [orange.bold("Keys"), ""],
18
- [" keys", "Interactive key management"],
19
- [" keys create", "Create a new API key"],
20
- [" keys list", "List all keys"],
21
- [" keys info", "Show key details"],
22
- [" keys revoke", "Revoke a key"],
38
+ [" keys create", "Create a new agent key"],
39
+ [" keys revoke", "Revoke agent key"],
23
40
  ["", ""],
24
41
  [orange.bold("Setup"), ""],
25
42
  [" setup", "Install ClawCard skill for your agent"],
@@ -45,7 +62,7 @@ export function helpCommand() {
45
62
  } else if (!desc) {
46
63
  console.log(` ${cmd}`);
47
64
  } else {
48
- console.log(` ${cmd.padEnd(24)}${dim(desc)}`);
65
+ console.log(` ${cmd.padEnd(26)}${dim(desc)}`);
49
66
  }
50
67
  }
51
68
  console.log();
@@ -1,7 +1,7 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import chalk from "chalk";
3
3
  import { requireAuth } from "../auth-guard.js";
4
- import { listAgents, createAgent, getAgent, deleteAgent } from "../api.js";
4
+ import { listAgents, createAgent, getAgent, deleteAgent, getBalance, getAgentBudget, allocateBudget, listAgentCredentials, getAgentCredential } from "../api.js";
5
5
  import { saveKey } from "../config.js";
6
6
 
7
7
  const orange = chalk.hex("#FF6B35");
@@ -63,26 +63,43 @@ export async function keysCreateCommand() {
63
63
  });
64
64
  if (p.isCancel(name)) return;
65
65
 
66
- const spendLimit = await p.text({
67
- message: "Spend limit (dollars)",
68
- placeholder: "50",
66
+ // Check account balance to show how much they can allocate
67
+ let accountBalance = 0;
68
+ try {
69
+ const bal = await getBalance();
70
+ accountBalance = bal.amountCents || 0;
71
+ } catch {}
72
+
73
+ const budget = await p.text({
74
+ message: `How much budget for this agent? (account balance: $${(accountBalance / 100).toFixed(2)})`,
75
+ placeholder: String(Math.min(accountBalance / 100, 20)),
69
76
  validate: (v) => {
70
77
  const n = parseFloat(v);
71
78
  if (isNaN(n) || n <= 0) return "Enter a valid dollar amount";
79
+ if (Math.round(n * 100) > accountBalance) return `Exceeds your account balance of $${(accountBalance / 100).toFixed(2)}`;
72
80
  },
73
81
  });
74
- if (p.isCancel(spendLimit)) return;
82
+ if (p.isCancel(budget)) return;
83
+
84
+ const budgetCents = Math.round(parseFloat(budget) * 100);
75
85
 
76
86
  const s = p.spinner();
77
- s.start("Creating key...");
87
+ s.start("Creating agent...");
78
88
 
79
89
  try {
80
90
  const result = await createAgent({
81
91
  name,
82
- spendLimitCents: Math.round(parseFloat(spendLimit) * 100),
92
+ spendLimitCents: budgetCents,
83
93
  });
84
94
 
85
- s.stop("Key created!");
95
+ // Allocate budget from account balance to agent
96
+ try {
97
+ await allocateBudget(result.id, budgetCents);
98
+ } catch {
99
+ // non-critical — agent created but budget not allocated
100
+ }
101
+
102
+ s.stop("Agent created!");
86
103
 
87
104
  // Save raw key locally so `clawcard setup` can use it later
88
105
  saveKey(result.id, result.apiKey, {
@@ -90,27 +107,32 @@ export async function keysCreateCommand() {
90
107
  keyPrefix: result.keyPrefix,
91
108
  });
92
109
 
93
- p.note(
94
- `${orange.bold(result.apiKey)}\n\n${chalk.dim("This key will only be shown once. Copy it now.")}`,
95
- "Your API Key"
96
- );
97
-
98
- const copy = await p.confirm({ message: "Copy to clipboard?" });
99
- if (copy && !p.isCancel(copy)) {
100
- const { default: clipboardy } = await import("clipboardy");
101
- await clipboardy.write(result.apiKey);
102
- p.log.success("Copied to clipboard");
103
- }
104
-
105
110
  // Show provisioned resources
106
111
  const details = [
107
- `Name: ${orange.bold(result.name || "unnamed")}`,
108
- result.email && `Email: ${result.email}`,
109
- result.phone && `Phone: ${result.phone}`,
110
- `Limit: $${(result.spendLimitCents / 100).toFixed(2)}`,
112
+ `Name: ${orange.bold(result.name || "unnamed")}`,
113
+ result.email && `Email: ${result.email}`,
114
+ result.phone && `Phone: ${result.phone}`,
115
+ `Budget: ${chalk.green("$" + (budgetCents / 100).toFixed(2))}`,
111
116
  ].filter(Boolean).join("\n");
112
117
 
113
118
  p.note(details, "Your Agent");
119
+
120
+ p.log.info(
121
+ chalk.dim("Next step: install the ClawCard skill so your agent knows how to use its email, phone, and cards.")
122
+ );
123
+
124
+ const runSetup = await p.select({
125
+ message: "Set up your agent now?",
126
+ options: [
127
+ { value: "yes", label: "Yes, set up now" },
128
+ { value: "skip", label: "Skip for now", hint: "run clawcard setup later" },
129
+ ],
130
+ });
131
+
132
+ if (!p.isCancel(runSetup) && runSetup === "yes") {
133
+ const { setupCommand } = await import("./setup.js");
134
+ await setupCommand();
135
+ }
114
136
  } catch (err) {
115
137
  s.stop("Failed to create key");
116
138
  p.log.error(err.message);
@@ -208,6 +230,60 @@ export async function keysInfoCommand(keyIdOrPrefix) {
208
230
  }
209
231
  }
210
232
 
233
+ export async function agentFundCommand() {
234
+ requireAuth();
235
+
236
+ const s = p.spinner();
237
+ s.start("Fetching agent...");
238
+
239
+ try {
240
+ const [agents, balance] = await Promise.all([
241
+ listAgents(),
242
+ getBalance(),
243
+ ]);
244
+ const active = agents.filter((a) => a.status === "active");
245
+
246
+ if (!active.length) {
247
+ s.stop("");
248
+ p.log.info("No active agent. Create one with: clawcard keys create");
249
+ return;
250
+ }
251
+
252
+ const agent = active[0];
253
+ const currentBudget = await getAgentBudget(agent.id);
254
+ s.stop("");
255
+
256
+ p.log.info(
257
+ `Account balance: ${chalk.green("$" + (balance.amountCents / 100).toFixed(2))} | Agent budget: ${chalk.green("$" + ((currentBudget.budgetCents || 0) / 100).toFixed(2))}`
258
+ );
259
+
260
+ const amount = await p.text({
261
+ message: "How much to add to agent budget?",
262
+ placeholder: "10",
263
+ validate: (v) => {
264
+ const n = parseFloat(v);
265
+ if (isNaN(n) || n <= 0) return "Enter a valid dollar amount";
266
+ if (Math.round(n * 100) > balance.amountCents)
267
+ return `Exceeds account balance of $${(balance.amountCents / 100).toFixed(2)}`;
268
+ },
269
+ });
270
+ if (p.isCancel(amount)) return;
271
+
272
+ const s2 = p.spinner();
273
+ s2.start("Allocating budget...");
274
+
275
+ const result = await allocateBudget(agent.id, Math.round(parseFloat(amount) * 100));
276
+ s2.stop("Budget allocated!");
277
+
278
+ p.log.success(
279
+ `Agent budget: ${chalk.green("$" + ((result.agentBudgetCents || 0) / 100).toFixed(2))} | Account remaining: ${chalk.green("$" + ((result.balanceRemainingCents || 0) / 100).toFixed(2))}`
280
+ );
281
+ } catch (err) {
282
+ s.stop("Failed");
283
+ p.log.error(err.message);
284
+ }
285
+ }
286
+
211
287
  export async function agentStatusCommand() {
212
288
  requireAuth();
213
289
 
@@ -267,6 +343,39 @@ export async function keysRevokeCommand() {
267
343
  });
268
344
  if (p.isCancel(selected)) return;
269
345
 
346
+ // Check for stored credentials and offer export
347
+ try {
348
+ const creds = await listAgentCredentials(selected);
349
+ const credList = Array.isArray(creds) ? creds : creds.credentials || [];
350
+ if (credList.length > 0) {
351
+ p.log.warn(`This agent has ${credList.length} stored credential(s):`);
352
+ for (const c of credList) {
353
+ p.log.info(` ${orange(c.service)} / ${c.key}`);
354
+ }
355
+
356
+ const exportCreds = await p.confirm({
357
+ message: "Export credentials before revoking?",
358
+ });
359
+
360
+ if (!p.isCancel(exportCreds) && exportCreds) {
361
+ console.log();
362
+ for (const c of credList) {
363
+ try {
364
+ const full = await getAgentCredential(selected, c.service, c.key);
365
+ console.log(` ${orange(c.service)}/${c.key}: ${full.value}`);
366
+ } catch {
367
+ console.log(` ${orange(c.service)}/${c.key}: ${chalk.red("(could not retrieve)")}`);
368
+ }
369
+ }
370
+ console.log();
371
+ }
372
+ }
373
+ } catch {
374
+ // couldn't fetch creds — continue
375
+ }
376
+
377
+ p.log.warn("This will close all cards, delete credentials, and return allocated budget to your account balance.");
378
+
270
379
  const confirm = await p.confirm({
271
380
  message: "Are you sure? This cannot be undone.",
272
381
  });
package/src/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { Command } from "commander";
2
2
  import * as p from "@clack/prompts";
3
3
  import chalk from "chalk";
4
- import { showSplash, checkForUpdate, VERSION } from "./splash.js";
4
+ import { showSplash, performAutoUpdate, VERSION } from "./splash.js";
5
5
  import { isLoggedIn } from "./config.js";
6
6
 
7
7
  const orange = chalk.hex("#FF6B35");
8
+ const isJsonMode = process.argv.includes("--json");
8
9
 
9
- // Show splash on every invocation
10
- showSplash();
11
-
12
- // Check for updates in background (non-blocking)
13
- const updatePromise = checkForUpdate();
10
+ // Suppress splash and auto-update for --json (agent) mode
11
+ if (!isJsonMode) {
12
+ showSplash();
13
+ await performAutoUpdate();
14
+ }
14
15
 
15
16
  const program = new Command();
16
17
  program.name("clawcard").description("The ClawCard CLI").version(VERSION);
@@ -88,13 +89,172 @@ keys
88
89
  await keysRevokeCommand();
89
90
  });
90
91
 
91
- // Agent status
92
- program
93
- .command("agent")
94
- .description("Show your agent's identity (email, phone, key)")
92
+ // Agent
93
+ const agent = program.command("agent").description("Agent commands");
94
+
95
+ agent.action(async () => {
96
+ const { agentStatusCommand } = await import("./commands/keys.js");
97
+ await agentStatusCommand();
98
+ });
99
+
100
+ agent
101
+ .command("fund")
102
+ .description("Add budget to your agent")
95
103
  .action(async () => {
96
- const { agentStatusCommand } = await import("./commands/keys.js");
97
- await agentStatusCommand();
104
+ const { agentFundCommand } = await import("./commands/keys.js");
105
+ await agentFundCommand();
106
+ });
107
+
108
+ agent
109
+ .command("info")
110
+ .description("Show agent identity")
111
+ .option("--json", "Output as JSON")
112
+ .action(async (options) => {
113
+ const { agentInfoCmd } = await import("./commands/agent.js");
114
+ await agentInfoCmd(options);
115
+ });
116
+
117
+ // Agent emails
118
+ const agentEmails = agent.command("emails").description("Agent email inbox");
119
+ agentEmails
120
+ .option("--limit <n>", "Max results", "20")
121
+ .option("--unread", "Unread only")
122
+ .option("--json", "Output as JSON")
123
+ .action(async (options) => {
124
+ const { agentEmailsCmd } = await import("./commands/agent.js");
125
+ await agentEmailsCmd(options);
126
+ });
127
+ agentEmails
128
+ .command("send")
129
+ .requiredOption("--to <email>", "Recipient")
130
+ .requiredOption("--subject <subject>", "Subject line")
131
+ .requiredOption("--body <body>", "Email body")
132
+ .option("--json", "Output as JSON")
133
+ .action(async (options) => {
134
+ const { agentEmailsSendCmd } = await import("./commands/agent.js");
135
+ await agentEmailsSendCmd(options);
136
+ });
137
+ agentEmails
138
+ .command("read <emailId>")
139
+ .option("--json", "Output as JSON")
140
+ .action(async (emailId, options) => {
141
+ const { agentEmailsReadCmd } = await import("./commands/agent.js");
142
+ await agentEmailsReadCmd(emailId, options);
143
+ });
144
+
145
+ // Agent SMS
146
+ const agentSms = agent.command("sms").description("Agent SMS messages");
147
+ agentSms
148
+ .option("--limit <n>", "Max results", "50")
149
+ .option("--json", "Output as JSON")
150
+ .action(async (options) => {
151
+ const { agentSmsCmd } = await import("./commands/agent.js");
152
+ await agentSmsCmd(options);
153
+ });
154
+ agentSms
155
+ .command("send")
156
+ .requiredOption("--to <phone>", "Recipient phone (E.164)")
157
+ .requiredOption("--body <body>", "Message text")
158
+ .option("--json", "Output as JSON")
159
+ .action(async (options) => {
160
+ const { agentSmsSendCmd } = await import("./commands/agent.js");
161
+ await agentSmsSendCmd(options);
162
+ });
163
+
164
+ // Agent cards
165
+ const agentCards = agent.command("cards").description("Virtual cards");
166
+ agentCards
167
+ .option("--json", "Output as JSON")
168
+ .action(async (options) => {
169
+ const { agentCardsCmd } = await import("./commands/agent.js");
170
+ await agentCardsCmd(options);
171
+ });
172
+ agentCards
173
+ .command("create")
174
+ .requiredOption("--amount <cents>", "Spend limit in cents")
175
+ .requiredOption("--type <type>", "single_use or merchant_locked")
176
+ .option("--memo <memo>", "Card description", "Agent card")
177
+ .option("--json", "Output as JSON")
178
+ .action(async (options) => {
179
+ const { agentCardsCreateCmd } = await import("./commands/agent.js");
180
+ await agentCardsCreateCmd(options);
181
+ });
182
+ agentCards
183
+ .command("details <cardId>")
184
+ .option("--json", "Output as JSON")
185
+ .action(async (cardId, options) => {
186
+ const { agentCardsDetailsCmd } = await import("./commands/agent.js");
187
+ await agentCardsDetailsCmd(cardId, options);
188
+ });
189
+ agentCards
190
+ .command("close <cardId>")
191
+ .option("--json", "Output as JSON")
192
+ .action(async (cardId, options) => {
193
+ const { agentCardsActionCmd } = await import("./commands/agent.js");
194
+ await agentCardsActionCmd(cardId, "close", options);
195
+ });
196
+ agentCards
197
+ .command("pause <cardId>")
198
+ .option("--json", "Output as JSON")
199
+ .action(async (cardId, options) => {
200
+ const { agentCardsActionCmd } = await import("./commands/agent.js");
201
+ await agentCardsActionCmd(cardId, "pause", options);
202
+ });
203
+ agentCards
204
+ .command("resume <cardId>")
205
+ .option("--json", "Output as JSON")
206
+ .action(async (cardId, options) => {
207
+ const { agentCardsActionCmd } = await import("./commands/agent.js");
208
+ await agentCardsActionCmd(cardId, "resume", options);
209
+ });
210
+
211
+ // Agent credentials
212
+ const agentCreds = agent.command("creds").description("Credential vault");
213
+ agentCreds
214
+ .option("--json", "Output as JSON")
215
+ .action(async (options) => {
216
+ const { agentCredsCmd } = await import("./commands/agent.js");
217
+ await agentCredsCmd(options);
218
+ });
219
+ agentCreds
220
+ .command("set")
221
+ .requiredOption("--service <name>", "Service name")
222
+ .requiredOption("--key <key>", "Credential key")
223
+ .requiredOption("--value <value>", "Secret value")
224
+ .option("--json", "Output as JSON")
225
+ .action(async (options) => {
226
+ const { agentCredsSetCmd } = await import("./commands/agent.js");
227
+ await agentCredsSetCmd(options);
228
+ });
229
+ agentCreds
230
+ .command("get")
231
+ .requiredOption("--service <name>", "Service name")
232
+ .requiredOption("--key <key>", "Credential key")
233
+ .option("--json", "Output as JSON")
234
+ .action(async (options) => {
235
+ const { agentCredsGetCmd } = await import("./commands/agent.js");
236
+ await agentCredsGetCmd(options);
237
+ });
238
+
239
+ // Agent budget
240
+ agent
241
+ .command("budget")
242
+ .description("Check agent budget")
243
+ .option("--json", "Output as JSON")
244
+ .action(async (options) => {
245
+ const { agentBudgetCmd } = await import("./commands/agent.js");
246
+ await agentBudgetCmd(options);
247
+ });
248
+
249
+ // Agent activity
250
+ agent
251
+ .command("activity")
252
+ .description("View activity log")
253
+ .option("--limit <n>", "Max results", "50")
254
+ .option("--json", "Output as JSON")
255
+ .action(async (options) => {
256
+ const { agentActivityCmd } = await import("./commands/agent.js");
257
+ await agentActivityCmd(options);
98
258
  });
99
259
 
100
260
  // Setup
@@ -178,14 +338,6 @@ program
178
338
  if (process.argv.length <= 2) {
179
339
  const loggedIn = isLoggedIn();
180
340
 
181
- // Show update notice if available (non-blocking, already fetched)
182
- const latest = await updatePromise;
183
- if (latest) {
184
- p.log.warn(
185
- `Update available: ${chalk.dim(VERSION)} → ${orange.bold(latest)} Run ${orange("npm i -g @clawcard/cli@latest")}`
186
- );
187
- }
188
-
189
341
  let options;
190
342
 
191
343
  if (!loggedIn) {
@@ -227,14 +379,17 @@ if (process.argv.length <= 2) {
227
379
 
228
380
  options = [
229
381
  ...(!hasBalance
230
- ? [{ value: "topup", label: "Top up balance", hint: "required — minimum $5 to get started" }]
382
+ ? [{ value: "topup", label: "Top up my account", hint: "required — minimum $5 to get started" }]
231
383
  : !hasAgent
232
384
  ? [{ value: "keys-create", label: "Create agent", hint: "you have balance — create your agent now" }]
233
- : [{ value: "agent", label: "My Agent", hint: "email, phone, key details" }]),
385
+ : [
386
+ { value: "agent", label: "View agent details", hint: "phone, email, budget" },
387
+ { value: "agent-fund", label: "Fund my agent", hint: "allocate budget from your balance" },
388
+ ]),
234
389
  ...(hasAgent
235
- ? [{ value: "setup", label: "Setup", hint: "set up your agent" }]
390
+ ? [{ value: "setup", label: "Configure", hint: "install skill for your agent" }]
236
391
  : []),
237
- { value: "billing", label: "Billing", hint: "check balance & top up" },
392
+ { value: "topup", label: "Top up my account", hint: "add balance (10% fee)" },
238
393
  { value: "referral", label: "Referral", hint: "share & earn $5" },
239
394
  { value: "whoami", label: "Who am I?", hint: "show current account" },
240
395
  { value: "logout", label: "Logout", hint: "clear session" },
@@ -258,6 +413,7 @@ if (process.argv.length <= 2) {
258
413
  logout: () => import("./commands/logout.js").then((m) => m.logoutCommand()),
259
414
  whoami: () => import("./commands/whoami.js").then((m) => m.whoamiCommand()),
260
415
  agent: () => import("./commands/keys.js").then((m) => m.agentStatusCommand()),
416
+ "agent-fund": () => import("./commands/keys.js").then((m) => m.agentFundCommand()),
261
417
  "keys-create": () => import("./commands/keys.js").then((m) => m.keysCreateCommand()),
262
418
  "topup": () => import("./commands/billing.js").then((m) => m.billingTopupCommand()),
263
419
  keys: () => import("./commands/keys.js").then((m) => m.keysCommand()),
package/src/splash.js CHANGED
@@ -1,33 +1,87 @@
1
1
  import chalk from "chalk";
2
- import { readFileSync } from "fs";
2
+ import { readFileSync, existsSync, copyFileSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
+ import { execSync } from "child_process";
6
+ import { homedir } from "os";
7
+ import { getConfig, saveConfig } from "./config.js";
5
8
 
6
9
  const o = chalk.hex("#FF6B35");
7
10
  const d = chalk.dim;
8
11
 
9
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
- const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
13
+ const pkg = JSON.parse(
14
+ readFileSync(join(__dirname, "..", "package.json"), "utf-8")
15
+ );
11
16
  export const VERSION = pkg.version;
12
17
 
18
+ const SKILL_SOURCE = join(__dirname, "..", "skill", "SKILL.md");
19
+ const home = homedir();
20
+
21
+ const SKILL_TARGETS = [
22
+ join(home, ".claude", "skills", "clawcard", "SKILL.md"),
23
+ join(home, ".openclaw", "skills", "clawcard", "SKILL.md"),
24
+ join(home, ".cursor", "skills", "clawcard", "SKILL.md"),
25
+ join(home, ".agents", "skills", "clawcard", "SKILL.md"),
26
+ ];
27
+
13
28
  export async function checkForUpdate() {
14
29
  try {
15
- const res = await fetch("https://registry.npmjs.org/@clawcard/cli/latest", {
16
- signal: AbortSignal.timeout(3000),
17
- });
30
+ const res = await fetch(
31
+ "https://registry.npmjs.org/@clawcard/cli/latest",
32
+ { signal: AbortSignal.timeout(3000) }
33
+ );
18
34
  const data = await res.json();
19
35
  if (data.version && data.version !== VERSION) {
20
36
  return data.version;
21
37
  }
22
38
  } catch {
23
- // offline or timeout — skip silently
39
+ // offline or timeout
24
40
  }
25
41
  return null;
26
42
  }
27
43
 
44
+ export async function performAutoUpdate() {
45
+ const config = getConfig() || {};
46
+ const lastCheck = config.lastUpdateCheck || 0;
47
+ const now = Date.now();
48
+
49
+ // Skip if checked within last hour
50
+ if (now - lastCheck < 3600000) return;
51
+
52
+ // Save check time
53
+ saveConfig({ ...config, lastUpdateCheck: now });
54
+
55
+ const latest = await checkForUpdate();
56
+ if (!latest) return;
57
+
58
+ console.log(` Updating ClawCard CLI to v${latest}...`);
59
+
60
+ try {
61
+ execSync("npm i -g @clawcard/cli@latest", {
62
+ stdio: "pipe",
63
+ timeout: 60000,
64
+ });
65
+ console.log(` Updated to v${latest}`);
66
+
67
+ // Refresh installed SKILL.md files
68
+ if (existsSync(SKILL_SOURCE)) {
69
+ for (const target of SKILL_TARGETS) {
70
+ if (existsSync(target)) {
71
+ copyFileSync(SKILL_SOURCE, target);
72
+ }
73
+ }
74
+ }
75
+ } catch {
76
+ // update failed — continue normally
77
+ }
78
+ }
79
+
28
80
  export function showSplash() {
29
81
  console.log();
30
82
  console.log(` 🦞 ${o.bold("clawcard.sh")} ${d("v" + VERSION)}`);
31
- console.log(` ${o("on-demand credit cards, email and phone for your agents")}`);
83
+ console.log(
84
+ ` ${o("on-demand credit cards, email and phone for your agents")}`
85
+ );
32
86
  console.log();
33
87
  }