@getbags/cash 0.2.0 → 0.3.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 +25 -17
- package/SKILL.md +60 -23
- package/dist/api.d.ts +4 -1
- package/dist/api.js +5 -2
- package/dist/detect.d.ts +33 -0
- package/dist/detect.js +105 -0
- package/dist/doctor.d.ts +47 -0
- package/dist/doctor.js +212 -0
- package/dist/errors.js +6 -2
- package/dist/index.js +46 -28
- package/dist/install-apply.d.ts +29 -0
- package/dist/install-apply.js +55 -0
- package/dist/install-command.d.ts +17 -0
- package/dist/install-command.js +173 -0
- package/dist/install-plan.d.ts +68 -0
- package/dist/install-plan.js +337 -0
- package/dist/mcp-instructions.d.ts +7 -0
- package/dist/mcp-instructions.js +38 -0
- package/dist/mcp-server.d.ts +65 -0
- package/dist/mcp-server.js +276 -0
- package/dist/parse.d.ts +13 -1
- package/dist/parse.js +208 -11
- package/package.json +6 -2
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { CliApiError, apiRequest, checkEndpoint, closeSessions, discover, getBalance, getPolicies, getTransactions, listSessions, redeem, search, setPolicies, walletFetch, } from './api.js';
|
|
5
|
+
import { loadCredentials, saveCredentials as defaultSaveCredentials } from './config.js';
|
|
6
|
+
import { WALLET_MCP_INSTRUCTIONS } from './mcp-instructions.js';
|
|
7
|
+
export const WALLET_MCP_TOOL_NAMES = [
|
|
8
|
+
'wallet_balance',
|
|
9
|
+
'wallet_discover',
|
|
10
|
+
'wallet_search',
|
|
11
|
+
'wallet_check_endpoint',
|
|
12
|
+
'wallet_fetch',
|
|
13
|
+
'wallet_sessions_list',
|
|
14
|
+
'wallet_sessions_close',
|
|
15
|
+
'wallet_sessions_tick',
|
|
16
|
+
'wallet_redeem',
|
|
17
|
+
'wallet_transactions',
|
|
18
|
+
'wallet_get_settings',
|
|
19
|
+
'wallet_update_settings',
|
|
20
|
+
];
|
|
21
|
+
const MISSING_TOKEN_MESSAGE = 'No wallet token found. Run: npx @getbags/cash redeem --test (sandbox) or npx @getbags/cash redeem <code> (live invite).';
|
|
22
|
+
async function defaultTickSession(token, input) {
|
|
23
|
+
return apiRequest('/api/wallet/sessions', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
token,
|
|
26
|
+
body: {
|
|
27
|
+
action: 'tick',
|
|
28
|
+
cumulative_cents: input.cumulative_cents,
|
|
29
|
+
session_id: input.session_id,
|
|
30
|
+
channel_id: input.channel_id,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function textContent(data, isError = false) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }],
|
|
37
|
+
isError,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function missingTokenResult() {
|
|
41
|
+
return textContent({
|
|
42
|
+
status: 'error',
|
|
43
|
+
code: 'AUTH_MISSING_CREDENTIALS',
|
|
44
|
+
message: MISSING_TOKEN_MESSAGE,
|
|
45
|
+
}, true);
|
|
46
|
+
}
|
|
47
|
+
function resolveApi(partial) {
|
|
48
|
+
return {
|
|
49
|
+
getBalance,
|
|
50
|
+
discover,
|
|
51
|
+
search,
|
|
52
|
+
checkEndpoint,
|
|
53
|
+
walletFetch,
|
|
54
|
+
listSessions,
|
|
55
|
+
closeSessions,
|
|
56
|
+
tickSession: defaultTickSession,
|
|
57
|
+
redeem,
|
|
58
|
+
getTransactions,
|
|
59
|
+
getPolicies,
|
|
60
|
+
setPolicies,
|
|
61
|
+
...partial,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function resolveToken(deps) {
|
|
65
|
+
if (deps.getToken)
|
|
66
|
+
return deps.getToken();
|
|
67
|
+
return loadCredentials()?.token ?? null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build the BAGS Cash wallet MCP server (stdio-ready). Token is read from
|
|
71
|
+
* ~/.bags/credentials.json (or deps.getToken) per tool call — never from config.
|
|
72
|
+
*/
|
|
73
|
+
export function createWalletMcpServer(deps = {}) {
|
|
74
|
+
const api = resolveApi(deps.api);
|
|
75
|
+
const persistCredentials = deps.saveCredentials ?? defaultSaveCredentials;
|
|
76
|
+
const server = new McpServer({ name: 'bags-cash', version: '0.3.0' }, { instructions: WALLET_MCP_INSTRUCTIONS });
|
|
77
|
+
async function withToken(run) {
|
|
78
|
+
const token = resolveToken(deps);
|
|
79
|
+
if (!token)
|
|
80
|
+
return missingTokenResult();
|
|
81
|
+
try {
|
|
82
|
+
return await run(token);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err instanceof CliApiError) {
|
|
86
|
+
return textContent(err.message, true);
|
|
87
|
+
}
|
|
88
|
+
return textContent({
|
|
89
|
+
status: 'error',
|
|
90
|
+
message: err instanceof Error ? err.message : String(err),
|
|
91
|
+
}, true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
server.registerTool('wallet_balance', {
|
|
95
|
+
title: 'Wallet balance',
|
|
96
|
+
description: 'Show BAGS agent wallet balance, daily spent/cap, per-call cap, and status.',
|
|
97
|
+
inputSchema: {},
|
|
98
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
99
|
+
}, async () => withToken(async (token) => textContent(await api.getBalance(token))));
|
|
100
|
+
server.registerTool('wallet_discover', {
|
|
101
|
+
title: 'Discover wallet endpoints',
|
|
102
|
+
description: 'List BAGS merchant x402 endpoints. Optional query also searches the open ecosystem index (BAGS results first).',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
query: z.string().optional().describe('Optional search keywords'),
|
|
105
|
+
},
|
|
106
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
107
|
+
}, async (args) => withToken(async (token) => {
|
|
108
|
+
const q = typeof args.query === 'string' ? args.query.trim() : '';
|
|
109
|
+
return textContent(await api.discover(token, q || undefined));
|
|
110
|
+
}));
|
|
111
|
+
server.registerTool('wallet_search', {
|
|
112
|
+
title: 'Search payable endpoints',
|
|
113
|
+
description: 'Natural-language search for x402 endpoints. BAGS catalog first, best-effort ecosystem (Bazaar) second. Returns price, description, schemas when available.',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
query: z.string().min(1).describe('Natural-language or keyword query'),
|
|
116
|
+
},
|
|
117
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
118
|
+
}, async (args) => withToken(async (token) => textContent(await api.search(token, String(args.query ?? '').trim()))));
|
|
119
|
+
server.registerTool('wallet_check_endpoint', {
|
|
120
|
+
title: 'Check endpoint quote',
|
|
121
|
+
description: 'Probe an x402 URL for schema + exact price quote without paying (AgentCash check parity). Use before wallet_fetch.',
|
|
122
|
+
inputSchema: {
|
|
123
|
+
url: z.string().url().describe('Endpoint URL to probe'),
|
|
124
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD']).optional(),
|
|
125
|
+
body: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
|
|
126
|
+
},
|
|
127
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
128
|
+
}, async (args) => withToken(async (token) => textContent(await api.checkEndpoint(token, {
|
|
129
|
+
url: String(args.url),
|
|
130
|
+
method: typeof args.method === 'string' ? args.method : undefined,
|
|
131
|
+
body: args.body,
|
|
132
|
+
}))));
|
|
133
|
+
server.registerTool('wallet_fetch', {
|
|
134
|
+
title: 'Wallet fetch (pay + proxy)',
|
|
135
|
+
description: 'Pay for and proxy an x402- or MPP-protected HTTP request using the BAGS hosted wallet. MPP tempo:session channels stay open until wallet_sessions_close.',
|
|
136
|
+
inputSchema: {
|
|
137
|
+
url: z.string().url(),
|
|
138
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD']).optional(),
|
|
139
|
+
body: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
|
|
140
|
+
max_price_cents: z.number().int().positive().optional(),
|
|
141
|
+
payment_protocol: z
|
|
142
|
+
.enum(['x402', 'mpp', 'auto'])
|
|
143
|
+
.optional()
|
|
144
|
+
.describe('Force rail (default auto: x402 then MPP-only fallback)'),
|
|
145
|
+
},
|
|
146
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
147
|
+
}, async (args) => withToken(async (token) => textContent(await api.walletFetch(token, {
|
|
148
|
+
url: String(args.url),
|
|
149
|
+
method: typeof args.method === 'string' ? args.method : undefined,
|
|
150
|
+
body: args.body,
|
|
151
|
+
max_price_cents: typeof args.max_price_cents === 'number' ? args.max_price_cents : undefined,
|
|
152
|
+
payment_protocol: args.payment_protocol === 'x402' ||
|
|
153
|
+
args.payment_protocol === 'mpp' ||
|
|
154
|
+
args.payment_protocol === 'auto'
|
|
155
|
+
? args.payment_protocol
|
|
156
|
+
: undefined,
|
|
157
|
+
}))));
|
|
158
|
+
server.registerTool('wallet_sessions_list', {
|
|
159
|
+
title: 'List MPP sessions',
|
|
160
|
+
description: 'List open MPP tempo:session channels for the wallet (mppx / AgentCash sessions list parity).',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
status: z.enum(['active', 'open', 'closing', 'closed', 'failed']).optional(),
|
|
163
|
+
},
|
|
164
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
165
|
+
}, async () => withToken(async (token) => textContent(await api.listSessions(token))));
|
|
166
|
+
server.registerTool('wallet_sessions_close', {
|
|
167
|
+
title: 'Close MPP session',
|
|
168
|
+
description: 'Close one or all open MPP tempo:session channels (settles reservation to cumulative spend). Pass all=true to close every open session.',
|
|
169
|
+
inputSchema: {
|
|
170
|
+
session_id: z.string().uuid().optional(),
|
|
171
|
+
channel_id: z.string().min(1).optional(),
|
|
172
|
+
all: z.boolean().optional(),
|
|
173
|
+
},
|
|
174
|
+
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: true },
|
|
175
|
+
}, async (args) => withToken(async (token) => textContent(await api.closeSessions(token, {
|
|
176
|
+
session_id: typeof args.session_id === 'string' ? args.session_id : undefined,
|
|
177
|
+
channel_id: typeof args.channel_id === 'string' ? args.channel_id : undefined,
|
|
178
|
+
all: typeof args.all === 'boolean' ? args.all : undefined,
|
|
179
|
+
}))));
|
|
180
|
+
server.registerTool('wallet_sessions_tick', {
|
|
181
|
+
title: 'Tick MPP session spend',
|
|
182
|
+
description: 'Record cumulative spend against an open MPP session (server rejects above max_deposit). Used for metered/streaming vouchers.',
|
|
183
|
+
inputSchema: {
|
|
184
|
+
cumulative_cents: z.number().int().nonnegative(),
|
|
185
|
+
session_id: z.string().uuid().optional(),
|
|
186
|
+
channel_id: z.string().min(1).optional(),
|
|
187
|
+
},
|
|
188
|
+
annotations: { readOnlyHint: false, openWorldHint: false },
|
|
189
|
+
}, async (args) => withToken(async (token) => textContent(await api.tickSession(token, {
|
|
190
|
+
cumulative_cents: Number(args.cumulative_cents),
|
|
191
|
+
session_id: typeof args.session_id === 'string' ? args.session_id : undefined,
|
|
192
|
+
channel_id: typeof args.channel_id === 'string' ? args.channel_id : undefined,
|
|
193
|
+
}))));
|
|
194
|
+
server.registerTool('wallet_redeem', {
|
|
195
|
+
title: 'Redeem invite code',
|
|
196
|
+
description: 'Redeem a BAGS wallet invite code and persist the token to ~/.bags/credentials.json (never echoed back).',
|
|
197
|
+
inputSchema: {
|
|
198
|
+
code: z.string().min(1).describe('Invite code'),
|
|
199
|
+
},
|
|
200
|
+
annotations: { readOnlyHint: false, openWorldHint: false },
|
|
201
|
+
}, async (args) => {
|
|
202
|
+
try {
|
|
203
|
+
const result = await api.redeem({ code: String(args.code) });
|
|
204
|
+
persistCredentials(result.token);
|
|
205
|
+
return textContent({
|
|
206
|
+
status: 'ok',
|
|
207
|
+
message: 'Wallet token saved to ~/.bags/credentials.json (mode 0o600). Token is not returned — call wallet_balance next.',
|
|
208
|
+
balance_cents: result.balance_cents,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
if (err instanceof CliApiError) {
|
|
213
|
+
return textContent(err.message, true);
|
|
214
|
+
}
|
|
215
|
+
return textContent({
|
|
216
|
+
status: 'error',
|
|
217
|
+
message: err instanceof Error ? err.message : String(err),
|
|
218
|
+
}, true);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
server.registerTool('wallet_transactions', {
|
|
222
|
+
title: 'Wallet transactions',
|
|
223
|
+
description: 'List paginated wallet ledger / spend history.',
|
|
224
|
+
inputSchema: {
|
|
225
|
+
limit: z.number().int().positive().max(100).optional(),
|
|
226
|
+
cursor: z.string().optional(),
|
|
227
|
+
},
|
|
228
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
229
|
+
}, async (args) => withToken(async (token) => textContent(await api.getTransactions(token, {
|
|
230
|
+
limit: typeof args.limit === 'number' ? args.limit : undefined,
|
|
231
|
+
cursor: typeof args.cursor === 'string' ? args.cursor : undefined,
|
|
232
|
+
}))));
|
|
233
|
+
server.registerTool('wallet_get_settings', {
|
|
234
|
+
title: 'Get wallet settings / policies',
|
|
235
|
+
description: 'Read spend policies and wallet settings (caps, status).',
|
|
236
|
+
inputSchema: {},
|
|
237
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
238
|
+
}, async () => withToken(async (token) => {
|
|
239
|
+
try {
|
|
240
|
+
return textContent(await api.getPolicies(token));
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
if (err instanceof CliApiError && err.code === 'NOT_FOUND') {
|
|
244
|
+
const bal = await api.getBalance(token);
|
|
245
|
+
return textContent({
|
|
246
|
+
status: 'success',
|
|
247
|
+
data: { ...bal, note: 'policies API not live; returning balance caps' },
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
server.registerTool('wallet_update_settings', {
|
|
254
|
+
title: 'Update wallet settings / policies',
|
|
255
|
+
description: 'Patch spend policies (e.g. per_call_cap_cents, daily_cap_cents).',
|
|
256
|
+
inputSchema: {
|
|
257
|
+
per_call_cap_cents: z.number().int().positive().optional(),
|
|
258
|
+
daily_cap_cents: z.number().int().positive().optional(),
|
|
259
|
+
},
|
|
260
|
+
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false },
|
|
261
|
+
}, async (args) => withToken(async (token) => {
|
|
262
|
+
const patch = {};
|
|
263
|
+
if (typeof args.per_call_cap_cents === 'number')
|
|
264
|
+
patch.per_call_cap_cents = args.per_call_cap_cents;
|
|
265
|
+
if (typeof args.daily_cap_cents === 'number')
|
|
266
|
+
patch.daily_cap_cents = args.daily_cap_cents;
|
|
267
|
+
return textContent(await api.setPolicies(token, patch));
|
|
268
|
+
}));
|
|
269
|
+
return server;
|
|
270
|
+
}
|
|
271
|
+
/** Connect the wallet MCP server over stdio (for `bags mcp`). */
|
|
272
|
+
export async function runStdioServer() {
|
|
273
|
+
const server = createWalletMcpServer();
|
|
274
|
+
const transport = new StdioServerTransport();
|
|
275
|
+
await server.connect(transport);
|
|
276
|
+
}
|
package/dist/parse.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { ClientId, InstallScope } from './detect.js';
|
|
1
2
|
export type PaymentProtocol = 'x402' | 'mpp' | 'auto';
|
|
2
3
|
export type ParsedArgs = {
|
|
3
4
|
command: 'help';
|
|
4
5
|
} | {
|
|
5
6
|
command: 'redeem';
|
|
6
|
-
code
|
|
7
|
+
code?: string;
|
|
8
|
+
test: boolean;
|
|
7
9
|
} | {
|
|
8
10
|
command: 'balance';
|
|
9
11
|
} | {
|
|
@@ -27,6 +29,16 @@ export type ParsedArgs = {
|
|
|
27
29
|
format: 'json' | 'pretty';
|
|
28
30
|
} | {
|
|
29
31
|
command: 'init';
|
|
32
|
+
dryRun: boolean;
|
|
33
|
+
clients: ClientId[];
|
|
34
|
+
scope: InstallScope;
|
|
35
|
+
} | {
|
|
36
|
+
command: 'mcp';
|
|
37
|
+
} | {
|
|
38
|
+
command: 'doctor';
|
|
39
|
+
clients: ClientId[];
|
|
40
|
+
/** Omitted means check both project and user scopes. */
|
|
41
|
+
scope?: InstallScope;
|
|
30
42
|
} | {
|
|
31
43
|
command: 'fund';
|
|
32
44
|
open: boolean;
|
package/dist/parse.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { walletDepositsVisible } from './deposits-visible.js';
|
|
2
|
+
const VALID_CLIENTS = ['cursor', 'claude', 'codex'];
|
|
3
|
+
function parseClientList(raw) {
|
|
4
|
+
const parts = raw
|
|
5
|
+
.split(',')
|
|
6
|
+
.map((p) => p.trim())
|
|
7
|
+
.filter(Boolean);
|
|
8
|
+
if (parts.length === 0) {
|
|
9
|
+
return { error: 'usage: bags init [--client cursor|claude|codex] (repeatable or comma-separated)' };
|
|
10
|
+
}
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const part of parts) {
|
|
13
|
+
if (part !== 'cursor' && part !== 'claude' && part !== 'codex') {
|
|
14
|
+
return {
|
|
15
|
+
error: 'usage: bags init [--client cursor|claude|codex] (repeatable or comma-separated)',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (!out.includes(part))
|
|
19
|
+
out.push(part);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
2
23
|
function takeFormat(rest, usage) {
|
|
3
24
|
let format = 'pretty';
|
|
4
25
|
const out = [];
|
|
@@ -29,11 +50,35 @@ export function parseArgs(argv) {
|
|
|
29
50
|
const [cmd, ...rest] = argv;
|
|
30
51
|
switch (cmd) {
|
|
31
52
|
case 'redeem': {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
let code;
|
|
54
|
+
let test = false;
|
|
55
|
+
for (const arg of rest) {
|
|
56
|
+
if (arg === '--test') {
|
|
57
|
+
test = true;
|
|
58
|
+
}
|
|
59
|
+
else if (arg.startsWith('-')) {
|
|
60
|
+
return {
|
|
61
|
+
command: 'error',
|
|
62
|
+
message: 'usage: bags redeem <code> | bags redeem --test',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
else if (!code) {
|
|
66
|
+
code = arg;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return {
|
|
70
|
+
command: 'error',
|
|
71
|
+
message: 'usage: bags redeem <code> | bags redeem --test',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!test && !code) {
|
|
76
|
+
return {
|
|
77
|
+
command: 'error',
|
|
78
|
+
message: 'usage: bags redeem <code> | bags redeem --test',
|
|
79
|
+
};
|
|
35
80
|
}
|
|
36
|
-
return { command: 'redeem', code };
|
|
81
|
+
return { command: 'redeem', code, test };
|
|
37
82
|
}
|
|
38
83
|
case 'balance':
|
|
39
84
|
return { command: 'balance' };
|
|
@@ -199,8 +244,145 @@ export function parseArgs(argv) {
|
|
|
199
244
|
}
|
|
200
245
|
return { command: 'sessions', action, sessionId, channelId, all, yes, format: parsed.format };
|
|
201
246
|
}
|
|
202
|
-
case 'init':
|
|
203
|
-
|
|
247
|
+
case 'init': {
|
|
248
|
+
let dryRun = false;
|
|
249
|
+
let scope = 'user';
|
|
250
|
+
const clients = [];
|
|
251
|
+
for (let i = 0; i < rest.length; i++) {
|
|
252
|
+
const arg = rest[i];
|
|
253
|
+
if (arg === '--dry-run') {
|
|
254
|
+
dryRun = true;
|
|
255
|
+
}
|
|
256
|
+
else if (arg === '--scope') {
|
|
257
|
+
const value = rest[++i];
|
|
258
|
+
if (value !== 'project' && value !== 'user') {
|
|
259
|
+
return {
|
|
260
|
+
command: 'error',
|
|
261
|
+
message: 'usage: bags init [--scope project|user]',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
scope = value;
|
|
265
|
+
}
|
|
266
|
+
else if (arg.startsWith('--scope=')) {
|
|
267
|
+
const value = arg.slice('--scope='.length);
|
|
268
|
+
if (value !== 'project' && value !== 'user') {
|
|
269
|
+
return {
|
|
270
|
+
command: 'error',
|
|
271
|
+
message: 'usage: bags init [--scope project|user]',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
scope = value;
|
|
275
|
+
}
|
|
276
|
+
else if (arg === '--client') {
|
|
277
|
+
const value = rest[++i];
|
|
278
|
+
if (!value) {
|
|
279
|
+
return {
|
|
280
|
+
command: 'error',
|
|
281
|
+
message: 'usage: bags init [--client cursor|claude|codex] (repeatable or comma-separated)',
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const parsed = parseClientList(value);
|
|
285
|
+
if ('error' in parsed)
|
|
286
|
+
return { command: 'error', message: parsed.error };
|
|
287
|
+
for (const c of parsed) {
|
|
288
|
+
if (!clients.includes(c))
|
|
289
|
+
clients.push(c);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else if (arg.startsWith('--client=')) {
|
|
293
|
+
const parsed = parseClientList(arg.slice('--client='.length));
|
|
294
|
+
if ('error' in parsed)
|
|
295
|
+
return { command: 'error', message: parsed.error };
|
|
296
|
+
for (const c of parsed) {
|
|
297
|
+
if (!clients.includes(c))
|
|
298
|
+
clients.push(c);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else if (arg.startsWith('-')) {
|
|
302
|
+
return {
|
|
303
|
+
command: 'error',
|
|
304
|
+
message: 'usage: bags init [--dry-run] [--client cursor|claude|codex] [--scope project|user]',
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
return {
|
|
309
|
+
command: 'error',
|
|
310
|
+
message: 'usage: bags init [--dry-run] [--client cursor|claude|codex] [--scope project|user]',
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Keep only known clients (defensive; parseClientList already validates).
|
|
315
|
+
const filtered = clients.filter((c) => VALID_CLIENTS.includes(c));
|
|
316
|
+
return { command: 'init', dryRun, clients: filtered, scope };
|
|
317
|
+
}
|
|
318
|
+
case 'mcp':
|
|
319
|
+
return { command: 'mcp' };
|
|
320
|
+
case 'doctor': {
|
|
321
|
+
let scope;
|
|
322
|
+
const clients = [];
|
|
323
|
+
for (let i = 0; i < rest.length; i++) {
|
|
324
|
+
const arg = rest[i];
|
|
325
|
+
if (arg === '--scope') {
|
|
326
|
+
const value = rest[++i];
|
|
327
|
+
if (value !== 'project' && value !== 'user') {
|
|
328
|
+
return {
|
|
329
|
+
command: 'error',
|
|
330
|
+
message: 'usage: bags doctor [--scope project|user] [--client cursor|claude|codex]',
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
scope = value;
|
|
334
|
+
}
|
|
335
|
+
else if (arg.startsWith('--scope=')) {
|
|
336
|
+
const value = arg.slice('--scope='.length);
|
|
337
|
+
if (value !== 'project' && value !== 'user') {
|
|
338
|
+
return {
|
|
339
|
+
command: 'error',
|
|
340
|
+
message: 'usage: bags doctor [--scope project|user] [--client cursor|claude|codex]',
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
scope = value;
|
|
344
|
+
}
|
|
345
|
+
else if (arg === '--client') {
|
|
346
|
+
const value = rest[++i];
|
|
347
|
+
if (!value) {
|
|
348
|
+
return {
|
|
349
|
+
command: 'error',
|
|
350
|
+
message: 'usage: bags doctor [--client cursor|claude|codex] (repeatable or comma-separated)',
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const parsed = parseClientList(value);
|
|
354
|
+
if ('error' in parsed)
|
|
355
|
+
return { command: 'error', message: parsed.error };
|
|
356
|
+
for (const c of parsed) {
|
|
357
|
+
if (!clients.includes(c))
|
|
358
|
+
clients.push(c);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else if (arg.startsWith('--client=')) {
|
|
362
|
+
const parsed = parseClientList(arg.slice('--client='.length));
|
|
363
|
+
if ('error' in parsed)
|
|
364
|
+
return { command: 'error', message: parsed.error };
|
|
365
|
+
for (const c of parsed) {
|
|
366
|
+
if (!clients.includes(c))
|
|
367
|
+
clients.push(c);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else if (arg.startsWith('-')) {
|
|
371
|
+
return {
|
|
372
|
+
command: 'error',
|
|
373
|
+
message: 'usage: bags doctor [--scope project|user] [--client cursor|claude|codex]',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
return {
|
|
378
|
+
command: 'error',
|
|
379
|
+
message: 'usage: bags doctor [--scope project|user] [--client cursor|claude|codex]',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const filtered = clients.filter((c) => VALID_CLIENTS.includes(c));
|
|
384
|
+
return { command: 'doctor', clients: filtered, scope };
|
|
385
|
+
}
|
|
204
386
|
case 'fund': {
|
|
205
387
|
let open = false;
|
|
206
388
|
let create = false;
|
|
@@ -443,8 +625,9 @@ MPP streams: fetch (opens session) → sessions close
|
|
|
443
625
|
|
|
444
626
|
Commands:
|
|
445
627
|
redeem <code> Redeem an invite code and save the wallet token
|
|
628
|
+
redeem --test Mint a sandbox wallet (no invite; Base Sepolia + Solana devnet)
|
|
446
629
|
balance Show wallet balance and caps
|
|
447
|
-
${fundLine} discover [query] List BAGS merchant endpoints (BAGS first)
|
|
630
|
+
${fundLine} discover [query] List BAGS merchant endpoints (BAGS first; includes \$0.01 /api/v1)
|
|
448
631
|
search <query> NL search: BAGS catalog + ecosystem index
|
|
449
632
|
check <url> Probe an endpoint for schema + exact quote (no pay)
|
|
450
633
|
fetch <url> [flags] Pay and proxy an x402 / MPP endpoint
|
|
@@ -453,10 +636,22 @@ ${fundLine} discover [query] List BAGS merchant endpoints (BAGS fi
|
|
|
453
636
|
policies get|set Read or update spend policies
|
|
454
637
|
keys create|list|revoke Manage per-agent wallet keys
|
|
455
638
|
register <origin> Register an origin into the discover index
|
|
456
|
-
init
|
|
639
|
+
init [flags] Register MCP + skill + routing rule for detected clients
|
|
640
|
+
mcp Serve wallet tools over stdio (JSON-RPC on stdout)
|
|
641
|
+
doctor [flags] Verify token, MCP registration, skill, and routing rule
|
|
642
|
+
|
|
643
|
+
Init flags:
|
|
644
|
+
--dry-run Print the plan without writing files
|
|
645
|
+
--client <name> Force cursor|claude|codex even without a marker dir (repeatable)
|
|
646
|
+
--scope project|user Config scope (default: user — ambient on this machine)
|
|
647
|
+
|
|
648
|
+
Doctor flags:
|
|
649
|
+
--client <name> Target cursor|claude|codex (repeatable or comma-separated)
|
|
650
|
+
--scope project|user Check one scope (default: both project and user)
|
|
457
651
|
|
|
458
652
|
Hosted skill (prompt paste):
|
|
459
653
|
Set up https://www.getbags.app/wallet/skill.md
|
|
654
|
+
Human install: https://www.getbags.app/wallet/install
|
|
460
655
|
Onboard: https://www.getbags.app/wallet/onboard
|
|
461
656
|
|
|
462
657
|
Fetch / check flags:
|
|
@@ -467,9 +662,11 @@ Fetch / check flags:
|
|
|
467
662
|
--format json|pretty Machine or human output
|
|
468
663
|
|
|
469
664
|
Sessions:
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
665
|
+
npx @getbags/cash sessions list
|
|
666
|
+
npx @getbags/cash sessions close <id>
|
|
667
|
+
npx @getbags/cash sessions close --all --yes
|
|
668
|
+
|
|
669
|
+
Alias: after \`npm i -g @getbags/cash\`, \`bags\` is equivalent to \`npx @getbags/cash\`.
|
|
473
670
|
|
|
474
671
|
Env:
|
|
475
672
|
BAGS_API_URL API base URL (default: https://www.getbags.app)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getbags/cash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "BAGS Cash CLI — hosted agent wallet for x402 (balance → discover → check → fetch)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc",
|
|
18
18
|
"dev": "tsc --watch",
|
|
19
|
-
"test": "vitest run",
|
|
19
|
+
"test": "bun run build && vitest run",
|
|
20
20
|
"test:watch": "vitest",
|
|
21
21
|
"prepublishOnly": "bun run build"
|
|
22
22
|
},
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=18"
|
|
40
40
|
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
43
|
+
"zod": "^4.3.6"
|
|
44
|
+
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@types/node": "^25.0.3",
|
|
43
47
|
"typescript": "6.0.2",
|