@getbags/cash 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/parse.js ADDED
@@ -0,0 +1,482 @@
1
+ import { walletDepositsVisible } from './deposits-visible.js';
2
+ function takeFormat(rest, usage) {
3
+ let format = 'pretty';
4
+ const out = [];
5
+ for (let i = 0; i < rest.length; i++) {
6
+ const arg = rest[i];
7
+ if (arg === '--format') {
8
+ const value = rest[++i];
9
+ if (value !== 'json' && value !== 'pretty')
10
+ return { error: usage };
11
+ format = value;
12
+ }
13
+ else if (arg.startsWith('--format=')) {
14
+ const value = arg.slice('--format='.length);
15
+ if (value !== 'json' && value !== 'pretty')
16
+ return { error: usage };
17
+ format = value;
18
+ }
19
+ else {
20
+ out.push(arg);
21
+ }
22
+ }
23
+ return { format, rest: out };
24
+ }
25
+ export function parseArgs(argv) {
26
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h' || argv[0] === 'help') {
27
+ return { command: 'help' };
28
+ }
29
+ const [cmd, ...rest] = argv;
30
+ switch (cmd) {
31
+ case 'redeem': {
32
+ const code = rest[0];
33
+ if (!code || code.startsWith('-')) {
34
+ return { command: 'error', message: 'usage: bags redeem <code>' };
35
+ }
36
+ return { command: 'redeem', code };
37
+ }
38
+ case 'balance':
39
+ return { command: 'balance' };
40
+ case 'discover': {
41
+ const parsed = takeFormat(rest, 'usage: bags discover [query] [--format json|pretty]');
42
+ if ('error' in parsed)
43
+ return { command: 'error', message: parsed.error };
44
+ const queryWords = [];
45
+ for (const arg of parsed.rest) {
46
+ if (arg.startsWith('-')) {
47
+ return { command: 'error', message: `unknown flag: ${arg}` };
48
+ }
49
+ queryWords.push(arg);
50
+ }
51
+ const query = queryWords.join(' ').trim();
52
+ return { command: 'discover', format: parsed.format, query: query || undefined };
53
+ }
54
+ case 'fetch': {
55
+ let url;
56
+ let method = 'GET';
57
+ let body;
58
+ let maxPriceCents;
59
+ let paymentProtocol;
60
+ for (let i = 0; i < rest.length; i++) {
61
+ const arg = rest[i];
62
+ if (arg === '-m' || arg === '--method') {
63
+ const value = rest[++i];
64
+ if (!value) {
65
+ return { command: 'error', message: 'usage: bags fetch <url> [-m METHOD]' };
66
+ }
67
+ method = value;
68
+ }
69
+ else if (arg.startsWith('--method=')) {
70
+ method = arg.slice('--method='.length);
71
+ }
72
+ else if (arg === '-b' || arg === '--body') {
73
+ const value = rest[++i];
74
+ if (value === undefined) {
75
+ return { command: 'error', message: 'usage: bags fetch <url> [-b JSON]' };
76
+ }
77
+ body = value;
78
+ }
79
+ else if (arg.startsWith('--body=')) {
80
+ body = arg.slice('--body='.length);
81
+ }
82
+ else if (arg === '--max-price-cents') {
83
+ const value = rest[++i];
84
+ if (value === undefined || Number.isNaN(Number(value))) {
85
+ return {
86
+ command: 'error',
87
+ message: 'usage: bags fetch <url> [--max-price-cents N]',
88
+ };
89
+ }
90
+ maxPriceCents = Number(value);
91
+ }
92
+ else if (arg.startsWith('--max-price-cents=')) {
93
+ const value = arg.slice('--max-price-cents='.length);
94
+ if (Number.isNaN(Number(value))) {
95
+ return {
96
+ command: 'error',
97
+ message: 'usage: bags fetch <url> [--max-price-cents N]',
98
+ };
99
+ }
100
+ maxPriceCents = Number(value);
101
+ }
102
+ else if (arg === '--payment-protocol') {
103
+ const value = rest[++i];
104
+ if (value !== 'x402' && value !== 'mpp' && value !== 'auto') {
105
+ return {
106
+ command: 'error',
107
+ message: 'usage: bags fetch <url> [--payment-protocol x402|mpp|auto]',
108
+ };
109
+ }
110
+ paymentProtocol = value;
111
+ }
112
+ else if (arg.startsWith('--payment-protocol=')) {
113
+ const value = arg.slice('--payment-protocol='.length);
114
+ if (value !== 'x402' && value !== 'mpp' && value !== 'auto') {
115
+ return {
116
+ command: 'error',
117
+ message: 'usage: bags fetch <url> [--payment-protocol x402|mpp|auto]',
118
+ };
119
+ }
120
+ paymentProtocol = value;
121
+ }
122
+ else if (arg.startsWith('-')) {
123
+ return { command: 'error', message: `unknown flag: ${arg}` };
124
+ }
125
+ else if (!url) {
126
+ url = arg;
127
+ }
128
+ else {
129
+ return { command: 'error', message: 'usage: bags fetch <url> [flags]' };
130
+ }
131
+ }
132
+ if (!url) {
133
+ return { command: 'error', message: 'usage: bags fetch <url> [flags]' };
134
+ }
135
+ return { command: 'fetch', url, method, body, maxPriceCents, paymentProtocol };
136
+ }
137
+ case 'sessions': {
138
+ const action = rest[0];
139
+ if (action !== 'list' && action !== 'close') {
140
+ return { command: 'error', message: 'usage: bags sessions list|close [id] [--all --yes]' };
141
+ }
142
+ const parsed = takeFormat(rest.slice(1), 'usage: bags sessions list|close [--format json|pretty]');
143
+ if ('error' in parsed)
144
+ return { command: 'error', message: parsed.error };
145
+ let sessionId;
146
+ let channelId;
147
+ let all = false;
148
+ let yes = false;
149
+ for (let i = 0; i < parsed.rest.length; i++) {
150
+ const arg = parsed.rest[i];
151
+ if (arg === '--all')
152
+ all = true;
153
+ else if (arg === '--yes' || arg === '-y')
154
+ yes = true;
155
+ else if (arg === '--id') {
156
+ const value = parsed.rest[++i];
157
+ if (!value)
158
+ return { command: 'error', message: 'usage: bags sessions close --id <session-id>' };
159
+ sessionId = value;
160
+ }
161
+ else if (arg.startsWith('--id=')) {
162
+ sessionId = arg.slice('--id='.length);
163
+ }
164
+ else if (arg === '--channel') {
165
+ const value = parsed.rest[++i];
166
+ if (!value) {
167
+ return { command: 'error', message: 'usage: bags sessions close --channel <channel-id>' };
168
+ }
169
+ channelId = value;
170
+ }
171
+ else if (arg.startsWith('--channel=')) {
172
+ channelId = arg.slice('--channel='.length);
173
+ }
174
+ else if (arg.startsWith('-')) {
175
+ return { command: 'error', message: `unknown flag: ${arg}` };
176
+ }
177
+ else if (!sessionId && !channelId) {
178
+ // Positional: prefer UUID → session id, else channel id.
179
+ if (/^[0-9a-f-]{36}$/i.test(arg))
180
+ sessionId = arg;
181
+ else
182
+ channelId = arg;
183
+ }
184
+ else {
185
+ return { command: 'error', message: 'usage: bags sessions list|close [id] [--all --yes]' };
186
+ }
187
+ }
188
+ if (action === 'close' && !all && !sessionId && !channelId) {
189
+ return {
190
+ command: 'error',
191
+ message: 'usage: bags sessions close <session-id|channel-id> | --all --yes',
192
+ };
193
+ }
194
+ if (action === 'close' && all && !yes) {
195
+ return {
196
+ command: 'error',
197
+ message: 'close --all requires --yes (mppx / AgentCash parity)',
198
+ };
199
+ }
200
+ return { command: 'sessions', action, sessionId, channelId, all, yes, format: parsed.format };
201
+ }
202
+ case 'init':
203
+ return { command: 'init' };
204
+ case 'fund': {
205
+ let open = false;
206
+ let create = false;
207
+ let network;
208
+ for (let i = 0; i < rest.length; i++) {
209
+ const arg = rest[i];
210
+ if (arg === '--open')
211
+ open = true;
212
+ else if (arg === '--create')
213
+ create = true;
214
+ else if (arg === '--network') {
215
+ const value = rest[++i];
216
+ if (!value)
217
+ return { command: 'error', message: 'usage: bags fund [--network base|solana]' };
218
+ network = value;
219
+ }
220
+ else if (arg.startsWith('--network=')) {
221
+ network = arg.slice('--network='.length);
222
+ }
223
+ else if (arg.startsWith('-')) {
224
+ return { command: 'error', message: 'usage: bags fund [--open] [--create] [--network NAME]' };
225
+ }
226
+ else {
227
+ return { command: 'error', message: 'usage: bags fund [--open] [--create] [--network NAME]' };
228
+ }
229
+ }
230
+ return { command: 'fund', open, create, network };
231
+ }
232
+ case 'transactions': {
233
+ const parsed = takeFormat(rest, 'usage: bags transactions [--limit N] [--cursor C] [--format json|pretty]');
234
+ if ('error' in parsed)
235
+ return { command: 'error', message: parsed.error };
236
+ let limit;
237
+ let cursor;
238
+ for (let i = 0; i < parsed.rest.length; i++) {
239
+ const arg = parsed.rest[i];
240
+ if (arg === '--limit') {
241
+ const value = parsed.rest[++i];
242
+ if (value === undefined || Number.isNaN(Number(value))) {
243
+ return { command: 'error', message: 'usage: bags transactions [--limit N]' };
244
+ }
245
+ limit = Number(value);
246
+ }
247
+ else if (arg.startsWith('--limit=')) {
248
+ const value = arg.slice('--limit='.length);
249
+ if (Number.isNaN(Number(value))) {
250
+ return { command: 'error', message: 'usage: bags transactions [--limit N]' };
251
+ }
252
+ limit = Number(value);
253
+ }
254
+ else if (arg === '--cursor') {
255
+ const value = parsed.rest[++i];
256
+ if (!value)
257
+ return { command: 'error', message: 'usage: bags transactions [--cursor C]' };
258
+ cursor = value;
259
+ }
260
+ else if (arg.startsWith('--cursor=')) {
261
+ cursor = arg.slice('--cursor='.length);
262
+ }
263
+ else if (arg.startsWith('-')) {
264
+ return { command: 'error', message: `unknown flag: ${arg}` };
265
+ }
266
+ else {
267
+ return {
268
+ command: 'error',
269
+ message: 'usage: bags transactions [--limit N] [--cursor C] [--format json|pretty]',
270
+ };
271
+ }
272
+ }
273
+ return { command: 'transactions', limit, cursor, format: parsed.format };
274
+ }
275
+ case 'policies': {
276
+ const action = rest[0];
277
+ if (action !== 'get' && action !== 'set') {
278
+ return { command: 'error', message: 'usage: bags policies get|set [--key=value ...]' };
279
+ }
280
+ const parsed = takeFormat(rest.slice(1), 'usage: bags policies get|set [--format json|pretty]');
281
+ if ('error' in parsed)
282
+ return { command: 'error', message: parsed.error };
283
+ const sets = {};
284
+ for (const arg of parsed.rest) {
285
+ if (arg.startsWith('--') && arg.includes('=')) {
286
+ const eq = arg.indexOf('=');
287
+ const key = arg.slice(2, eq);
288
+ const value = arg.slice(eq + 1);
289
+ if (!key)
290
+ return { command: 'error', message: 'usage: bags policies set --key=value' };
291
+ sets[key] = value;
292
+ }
293
+ else if (arg.startsWith('-')) {
294
+ return { command: 'error', message: `unknown flag: ${arg}` };
295
+ }
296
+ else {
297
+ return { command: 'error', message: 'usage: bags policies get|set [--key=value ...]' };
298
+ }
299
+ }
300
+ if (action === 'set' && Object.keys(sets).length === 0) {
301
+ return {
302
+ command: 'error',
303
+ message: 'usage: bags policies set --per-call-cap-cents=50 [--daily-cap-cents=100]',
304
+ };
305
+ }
306
+ return { command: 'policies', action, format: parsed.format, sets };
307
+ }
308
+ case 'keys': {
309
+ const action = rest[0];
310
+ if (action !== 'create' && action !== 'list' && action !== 'revoke') {
311
+ return { command: 'error', message: 'usage: bags keys create|list|revoke' };
312
+ }
313
+ const parsed = takeFormat(rest.slice(1), 'usage: bags keys create|list|revoke [--format json|pretty]');
314
+ if ('error' in parsed)
315
+ return { command: 'error', message: parsed.error };
316
+ let label;
317
+ let id;
318
+ for (let i = 0; i < parsed.rest.length; i++) {
319
+ const arg = parsed.rest[i];
320
+ if (arg === '--label') {
321
+ const value = parsed.rest[++i];
322
+ if (!value)
323
+ return { command: 'error', message: 'usage: bags keys create --label NAME' };
324
+ label = value;
325
+ }
326
+ else if (arg.startsWith('--label=')) {
327
+ label = arg.slice('--label='.length);
328
+ }
329
+ else if (arg === '--id') {
330
+ const value = parsed.rest[++i];
331
+ if (!value)
332
+ return { command: 'error', message: 'usage: bags keys revoke --id ID' };
333
+ id = value;
334
+ }
335
+ else if (arg.startsWith('--id=')) {
336
+ id = arg.slice('--id='.length);
337
+ }
338
+ else if (arg.startsWith('-')) {
339
+ return { command: 'error', message: `unknown flag: ${arg}` };
340
+ }
341
+ else if (action === 'revoke' && !id) {
342
+ id = arg;
343
+ }
344
+ else {
345
+ return { command: 'error', message: 'usage: bags keys create|list|revoke' };
346
+ }
347
+ }
348
+ if (action === 'revoke' && !id) {
349
+ return { command: 'error', message: 'usage: bags keys revoke --id <key-id>' };
350
+ }
351
+ return { command: 'keys', action, label, id, format: parsed.format };
352
+ }
353
+ case 'check': {
354
+ let url;
355
+ let method = 'GET';
356
+ let body;
357
+ let format = 'pretty';
358
+ for (let i = 0; i < rest.length; i++) {
359
+ const arg = rest[i];
360
+ if (arg === '-m' || arg === '--method') {
361
+ const value = rest[++i];
362
+ if (!value)
363
+ return { command: 'error', message: 'usage: bags check <url> [-m METHOD]' };
364
+ method = value;
365
+ }
366
+ else if (arg.startsWith('--method=')) {
367
+ method = arg.slice('--method='.length);
368
+ }
369
+ else if (arg === '-b' || arg === '--body') {
370
+ const value = rest[++i];
371
+ if (value === undefined)
372
+ return { command: 'error', message: 'usage: bags check <url> [-b JSON]' };
373
+ body = value;
374
+ }
375
+ else if (arg.startsWith('--body=')) {
376
+ body = arg.slice('--body='.length);
377
+ }
378
+ else if (arg === '--format') {
379
+ const value = rest[++i];
380
+ if (value !== 'json' && value !== 'pretty') {
381
+ return { command: 'error', message: 'usage: bags check <url> [--format json|pretty]' };
382
+ }
383
+ format = value;
384
+ }
385
+ else if (arg.startsWith('--format=')) {
386
+ const value = arg.slice('--format='.length);
387
+ if (value !== 'json' && value !== 'pretty') {
388
+ return { command: 'error', message: 'usage: bags check <url> [--format json|pretty]' };
389
+ }
390
+ format = value;
391
+ }
392
+ else if (arg.startsWith('-')) {
393
+ return { command: 'error', message: `unknown flag: ${arg}` };
394
+ }
395
+ else if (!url) {
396
+ url = arg;
397
+ }
398
+ else {
399
+ return { command: 'error', message: 'usage: bags check <url> [flags]' };
400
+ }
401
+ }
402
+ if (!url)
403
+ return { command: 'error', message: 'usage: bags check <url> [flags]' };
404
+ return { command: 'check', url, method, body, format };
405
+ }
406
+ case 'search': {
407
+ const parsed = takeFormat(rest, 'usage: bags search <query> [--format json|pretty]');
408
+ if ('error' in parsed)
409
+ return { command: 'error', message: parsed.error };
410
+ const queryWords = [];
411
+ for (const arg of parsed.rest) {
412
+ if (arg.startsWith('-'))
413
+ return { command: 'error', message: `unknown flag: ${arg}` };
414
+ queryWords.push(arg);
415
+ }
416
+ const query = queryWords.join(' ').trim();
417
+ if (!query)
418
+ return { command: 'error', message: 'usage: bags search <query>' };
419
+ return { command: 'search', query, format: parsed.format };
420
+ }
421
+ case 'register': {
422
+ const origin = rest[0];
423
+ if (!origin || origin.startsWith('-')) {
424
+ return { command: 'error', message: 'usage: bags register <origin>' };
425
+ }
426
+ return { command: 'register', origin };
427
+ }
428
+ default:
429
+ return { command: 'error', message: `unknown command: ${cmd}` };
430
+ }
431
+ }
432
+ function buildUsage() {
433
+ const fundLine = walletDepositsVisible()
434
+ ? ' fund [--open] [--create] Print or create deposit addresses (USDC)\n'
435
+ : '';
436
+ return `BAGS Cash — hosted agent wallet for x402 + MPP
437
+
438
+ Usage:
439
+ npx @getbags/cash <command>
440
+
441
+ Workflow: balance → discover|search → check → fetch
442
+ MPP streams: fetch (opens session) → sessions close
443
+
444
+ Commands:
445
+ redeem <code> Redeem an invite code and save the wallet token
446
+ balance Show wallet balance and caps
447
+ ${fundLine} discover [query] List BAGS merchant endpoints (BAGS first)
448
+ search <query> NL search: BAGS catalog + ecosystem index
449
+ check <url> Probe an endpoint for schema + exact quote (no pay)
450
+ fetch <url> [flags] Pay and proxy an x402 / MPP endpoint
451
+ sessions list|close List or close open MPP tempo:session channels
452
+ transactions List wallet ledger / spend history
453
+ policies get|set Read or update spend policies
454
+ keys create|list|revoke Manage per-agent wallet keys
455
+ register <origin> Register an origin into the discover index
456
+ init Install the bags-cash agent skill into this repo
457
+
458
+ Hosted skill (prompt paste):
459
+ Set up https://www.getbags.app/wallet/skill.md
460
+ Onboard: https://www.getbags.app/wallet/onboard
461
+
462
+ Fetch / check flags:
463
+ -m, --method <METHOD> HTTP method (default: GET)
464
+ -b, --body <JSON> Request body as a JSON string
465
+ --max-price-cents <N> Cap the payment amount (fetch only)
466
+ --payment-protocol x402|mpp|auto Force rail (default: auto)
467
+ --format json|pretty Machine or human output
468
+
469
+ Sessions:
470
+ bags sessions list
471
+ bags sessions close <id> Close one channel (session uuid or channel id)
472
+ bags sessions close --all --yes
473
+
474
+ Env:
475
+ BAGS_API_URL API base URL (default: https://www.getbags.app)
476
+ Staging example: https://staging.getbags.app
477
+
478
+ Token is stored at ~/.bags/credentials.json (hosted wallet — no local private keys)
479
+ `;
480
+ }
481
+ /** Help text; recomputed so WALLET_DEPOSITS_VISIBLE is respected at process start. */
482
+ export const USAGE = buildUsage();
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@getbags/cash",
3
+ "version": "0.2.0",
4
+ "description": "BAGS Cash CLI — hosted agent wallet for x402 (balance → discover → check → fetch)",
5
+ "type": "module",
6
+ "bin": {
7
+ "bags": "dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "SKILL.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsc --watch",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "prepublishOnly": "bun run build"
22
+ },
23
+ "keywords": [
24
+ "bags",
25
+ "x402",
26
+ "agentic-commerce",
27
+ "wallet",
28
+ "cli"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/getbagsapp/agentic-payments.git",
33
+ "directory": "packages/cli"
34
+ },
35
+ "homepage": "https://getbags.app",
36
+ "author": "BAGS <hello@getbags.app>",
37
+ "license": "MIT",
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^25.0.3",
43
+ "typescript": "6.0.2",
44
+ "vitest": "^4.0.18"
45
+ }
46
+ }