arispay 0.1.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.
@@ -0,0 +1,650 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { createInterface } from 'node:readline';
5
+ import { ArisPay } from './index.js';
6
+ import type { ArisPayClient } from './index.js';
7
+
8
+ // ── Branding ────────────────────────────────────────────────────────
9
+
10
+ export interface Brand {
11
+ cmd: string; // "arispay" or "payagent"
12
+ configDir: string; // "~/.arispay" or "~/.payagent"
13
+ envPrefix: string; // "ARISPAY" or "PAYAGENT"
14
+ authHint: string; // "arispay init" or "payagent login"
15
+ }
16
+
17
+ export function createBrand(cmd: string, authHint: string): Brand {
18
+ const configDir = join(homedir(), `.${cmd}`);
19
+ const envPrefix = cmd.toUpperCase();
20
+ return { cmd, configDir, envPrefix, authHint };
21
+ }
22
+
23
+ // ── Config ──────────────────────────────────────────────────────────
24
+
25
+ export interface Config {
26
+ apiKey?: string;
27
+ environment?: 'sandbox' | 'production';
28
+ baseUrl?: string;
29
+ // PayAgent (browser login) fields
30
+ accessToken?: string;
31
+ refreshToken?: string;
32
+ expiresAt?: number;
33
+ email?: string;
34
+ }
35
+
36
+ export function configFile(brand: Brand): string {
37
+ return join(brand.configDir, 'config.json');
38
+ }
39
+
40
+ export function loadConfig(brand: Brand): Config {
41
+ const file = configFile(brand);
42
+ const fileConfig: Config = existsSync(file)
43
+ ? JSON.parse(readFileSync(file, 'utf-8'))
44
+ : {};
45
+
46
+ return {
47
+ ...fileConfig,
48
+ apiKey: env(`${brand.envPrefix}_API_KEY`) || fileConfig.apiKey,
49
+ environment: (env(`${brand.envPrefix}_ENV`) as Config['environment']) || fileConfig.environment || 'sandbox',
50
+ baseUrl: env(`${brand.envPrefix}_BASE_URL`) || fileConfig.baseUrl,
51
+ };
52
+ }
53
+
54
+ export function saveConfig(brand: Brand, config: Config): void {
55
+ if (!existsSync(brand.configDir)) mkdirSync(brand.configDir, { recursive: true });
56
+ writeFileSync(configFile(brand), JSON.stringify(config, null, 2) + '\n');
57
+ }
58
+
59
+ export function getClient(brand: Brand): ArisPayClient {
60
+ const config = loadConfig(brand);
61
+ const key = config.apiKey || config.accessToken;
62
+ if (!key) {
63
+ error(`No credentials configured. Run: ${brand.authHint}`);
64
+ }
65
+ return ArisPay.init({
66
+ apiKey: key!,
67
+ environment: config.environment,
68
+ baseUrl: config.baseUrl,
69
+ });
70
+ }
71
+
72
+ // ── Helpers ─────────────────────────────────────────────────────────
73
+
74
+ export function env(key: string): string | undefined {
75
+ return process.env[key];
76
+ }
77
+
78
+ export function error(msg: string): never {
79
+ console.error(`\x1b[31m✗\x1b[0m ${msg}`);
80
+ process.exit(1);
81
+ }
82
+
83
+ export function success(msg: string): void {
84
+ console.log(`\x1b[32m✓\x1b[0m ${msg}`);
85
+ }
86
+
87
+ export function dim(msg: string): string {
88
+ return `\x1b[2m${msg}\x1b[0m`;
89
+ }
90
+
91
+ export function bold(msg: string): string {
92
+ return `\x1b[1m${msg}\x1b[0m`;
93
+ }
94
+
95
+ export function cyan(msg: string): string {
96
+ return `\x1b[36m${msg}\x1b[0m`;
97
+ }
98
+
99
+ export function yellow(msg: string): string {
100
+ return `\x1b[33m${msg}\x1b[0m`;
101
+ }
102
+
103
+ export function json(data: unknown): void {
104
+ console.log(JSON.stringify(data, null, 2));
105
+ }
106
+
107
+ export function table(rows: Record<string, unknown>[]): void {
108
+ if (rows.length === 0) {
109
+ console.log(dim(' (none)'));
110
+ return;
111
+ }
112
+ const keys = Object.keys(rows[0]);
113
+ const widths = keys.map((k) =>
114
+ Math.max(k.length, ...rows.map((r) => String(r[k] ?? '').length)),
115
+ );
116
+
117
+ console.log(keys.map((k, i) => bold(k.padEnd(widths[i]))).join(' '));
118
+ console.log(widths.map((w) => '─'.repeat(w)).join('──'));
119
+
120
+ for (const row of rows) {
121
+ console.log(keys.map((k, i) => String(row[k] ?? '').padEnd(widths[i])).join(' '));
122
+ }
123
+ }
124
+
125
+ export function prompt(question: string): Promise<string> {
126
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
127
+ return new Promise((resolve) => {
128
+ rl.question(question, (answer) => {
129
+ rl.close();
130
+ resolve(answer.trim());
131
+ });
132
+ });
133
+ }
134
+
135
+ export function promptSecret(question: string): Promise<string> {
136
+ return new Promise((resolve) => {
137
+ process.stdout.write(question);
138
+ const rl = createInterface({ input: process.stdin, terminal: false });
139
+ if (process.stdin.isTTY) {
140
+ process.stdin.setRawMode?.(true);
141
+ }
142
+ let input = '';
143
+ const onData = (ch: Buffer) => {
144
+ const c = ch.toString();
145
+ if (c === '\n' || c === '\r') {
146
+ process.stdin.removeListener('data', onData);
147
+ if (process.stdin.isTTY) process.stdin.setRawMode?.(false);
148
+ rl.close();
149
+ process.stdout.write('\n');
150
+ resolve(input.trim());
151
+ } else if (c === '\u007f' || c === '\b') {
152
+ input = input.slice(0, -1);
153
+ } else if (c === '\u0003') {
154
+ process.exit(0);
155
+ } else {
156
+ input += c;
157
+ }
158
+ };
159
+ if (process.stdin.isTTY) {
160
+ process.stdin.resume();
161
+ process.stdin.on('data', onData);
162
+ } else {
163
+ rl.on('line', (line) => {
164
+ rl.close();
165
+ resolve(line.trim());
166
+ });
167
+ }
168
+ });
169
+ }
170
+
171
+ export function parseFlags(args: string[]): { flags: Record<string, string>; positional: string[] } {
172
+ const flags: Record<string, string> = {};
173
+ const positional: string[] = [];
174
+
175
+ for (let i = 0; i < args.length; i++) {
176
+ const arg = args[i];
177
+ if (arg.startsWith('--')) {
178
+ const eqIdx = arg.indexOf('=');
179
+ if (eqIdx !== -1) {
180
+ flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
181
+ } else {
182
+ flags[arg.slice(2)] = args[++i] ?? 'true';
183
+ }
184
+ } else if (arg.startsWith('-') && arg.length === 2) {
185
+ flags[arg.slice(1)] = args[++i] ?? 'true';
186
+ } else {
187
+ positional.push(arg);
188
+ }
189
+ }
190
+
191
+ return { flags, positional };
192
+ }
193
+
194
+ export function requireFlag(flags: Record<string, string>, key: string, label?: string): string {
195
+ const val = flags[key];
196
+ if (!val) error(`Missing required flag: --${key}${label ? ` (${label})` : ''}`);
197
+ return val;
198
+ }
199
+
200
+ // ── Shared Commands ─────────────────────────────────────────────────
201
+
202
+ export async function cmdAgents(args: string[], brand: Brand): Promise<void> {
203
+ const [sub, ...rest] = args;
204
+ if (!sub || sub === 'help' || sub === '--help') {
205
+ return cmdAgentsHelp(brand);
206
+ }
207
+ const { flags, positional } = parseFlags(rest);
208
+ const client = getClient(brand);
209
+ const $ = brand.cmd;
210
+
211
+ switch (sub) {
212
+ case 'list':
213
+ case 'ls': {
214
+ const agents = await client.agents.list({
215
+ limit: flags['limit'] ? Number(flags['limit']) : 20,
216
+ offset: flags['offset'] ? Number(flags['offset']) : undefined,
217
+ status: flags['status'],
218
+ name: flags['name'],
219
+ });
220
+ table(
221
+ (agents as any).agents?.map((a: any) => ({
222
+ id: a.id.slice(0, 12) + '…',
223
+ name: a.name,
224
+ mode: a.mode || 'platform',
225
+ status: a.status,
226
+ created: a.createdAt?.slice(0, 10),
227
+ })) ?? [],
228
+ );
229
+ break;
230
+ }
231
+
232
+ case 'create': {
233
+ const name = flags['name'] || positional[0];
234
+ if (!name) error(`Usage: ${$} agents create --name <name> [--mode platform|autonomous]`);
235
+ const agent = await client.agents.register({
236
+ name,
237
+ description: flags['description'],
238
+ permissions: (flags['permissions'] || 'browse,payment').split(',') as any,
239
+ mode: (flags['mode'] as 'platform' | 'autonomous') || undefined,
240
+ sweepThreshold: flags['sweep-threshold'] ? Number(flags['sweep-threshold']) : undefined,
241
+ });
242
+ success(`Agent created: ${(agent as any).id}`);
243
+ json(agent);
244
+ break;
245
+ }
246
+
247
+ case 'get': {
248
+ const id = flags['id'] || positional[0];
249
+ if (!id) error(`Usage: ${$} agents get <id>`);
250
+ const agent = await client.agents.get(id);
251
+ json(agent);
252
+ break;
253
+ }
254
+
255
+ case 'rotate-keys': {
256
+ const id = flags['id'] || positional[0];
257
+ if (!id) error(`Usage: ${$} agents rotate-keys <id>`);
258
+ const result = await client.agents.rotateKeys(id);
259
+ success('Keys rotated');
260
+ json(result);
261
+ break;
262
+ }
263
+
264
+ case 'balance': {
265
+ const id = flags['id'] || positional[0];
266
+ if (!id) error(`Usage: ${$} agents balance <id>`);
267
+ const balance = await client.agents.getBalance(id);
268
+ const cents = (balance as any).balance ?? (balance as any).balanceCents ?? 0;
269
+ console.log(`\n ${bold('Balance:')} ${cyan('$' + (cents / 100).toFixed(2))} ${(balance as any).currency || 'USD'}\n`);
270
+ break;
271
+ }
272
+
273
+ case 'ledger': {
274
+ const id = flags['id'] || positional[0];
275
+ if (!id) error(`Usage: ${$} agents ledger <id>`);
276
+ const ledger = await client.agents.getLedger(id, {
277
+ limit: flags['limit'] ? Number(flags['limit']) : 20,
278
+ });
279
+ table(
280
+ ((ledger as any).entries ?? []).map((e: any) => ({
281
+ type: e.type,
282
+ direction: e.direction,
283
+ amount: '$' + (e.amount / 100).toFixed(2),
284
+ balance: '$' + (e.balanceAfter / 100).toFixed(2),
285
+ description: (e.description || '').slice(0, 30),
286
+ date: e.createdAt?.slice(0, 10),
287
+ })),
288
+ );
289
+ break;
290
+ }
291
+
292
+ case 'topup': {
293
+ const id = flags['id'] || positional[0];
294
+ if (!id) error(`Usage: ${$} agents topup <id> --amount <cents> --user <userId>`);
295
+ const amount = Number(requireFlag(flags, 'amount', 'in cents'));
296
+ const userId = requireFlag(flags, 'user', 'end user ID');
297
+ const result = await client.agents.topup(id, { amount, userId });
298
+ success(`Topped up $${(amount / 100).toFixed(2)}`);
299
+ json(result);
300
+ break;
301
+ }
302
+
303
+ case 'sweep': {
304
+ const id = flags['id'] || positional[0];
305
+ if (!id) error(`Usage: ${$} agents sweep <id> [--amount <cents>]`);
306
+ const result = await client.agents.sweep(id, {
307
+ amount: flags['amount'] ? Number(flags['amount']) : undefined,
308
+ });
309
+ success('Sweep complete');
310
+ json(result);
311
+ break;
312
+ }
313
+
314
+ case 'resume': {
315
+ const id = flags['id'] || positional[0];
316
+ if (!id) error(`Usage: ${$} agents resume <id>`);
317
+ const result = await client.agents.resume(id);
318
+ success(`Agent resumed at ${(result as any).resumedAt}`);
319
+ break;
320
+ }
321
+
322
+ default:
323
+ cmdAgentsHelp(brand);
324
+ }
325
+ }
326
+
327
+ function cmdAgentsHelp(brand: Brand): void {
328
+ const $ = brand.cmd;
329
+ console.log(`
330
+ ${bold(`${$} agents`)} — Manage AI payment agents
331
+
332
+ ${cyan(`${$} agents list`)} List agents
333
+ ${cyan(`${$} agents create`)} Register a new agent
334
+ ${cyan(`${$} agents get <id>`)} Get agent details
335
+ ${cyan(`${$} agents balance <id>`)} Check agent balance
336
+ ${cyan(`${$} agents ledger <id>`)} View agent ledger
337
+ ${cyan(`${$} agents topup <id>`)} Fund agent from user's card
338
+ ${cyan(`${$} agents sweep <id>`)} Withdraw agent balance
339
+ ${cyan(`${$} agents rotate-keys <id>`)} Rotate TAP keypair
340
+ ${cyan(`${$} agents resume <id>`)} Resume suspended agent
341
+ `);
342
+ }
343
+
344
+ export async function cmdUsers(args: string[], brand: Brand): Promise<void> {
345
+ const [sub, ...rest] = args;
346
+ if (!sub || sub === 'help' || sub === '--help') {
347
+ return cmdUsersHelp(brand);
348
+ }
349
+ const { flags, positional } = parseFlags(rest);
350
+ const client = getClient(brand);
351
+ const $ = brand.cmd;
352
+
353
+ switch (sub) {
354
+ case 'create': {
355
+ const externalId = flags['external-id'] || positional[0];
356
+ if (!externalId) error(`Usage: ${$} users create --external-id <id> [--email <email>]`);
357
+ const user = await client.users.create({
358
+ externalId,
359
+ email: flags['email'],
360
+ });
361
+ success(`User created: ${(user as any).id}`);
362
+ json(user);
363
+ break;
364
+ }
365
+
366
+ case 'get': {
367
+ const id = flags['id'] || positional[0];
368
+ if (!id) error(`Usage: ${$} users get <id>`);
369
+ const user = await client.users.get(id);
370
+ json(user);
371
+ break;
372
+ }
373
+
374
+ case 'find': {
375
+ const externalId = flags['external-id'] || positional[0];
376
+ if (!externalId) error(`Usage: ${$} users find <externalId>`);
377
+ const user = await client.users.getByExternalId(externalId);
378
+ json(user);
379
+ break;
380
+ }
381
+
382
+ case 'add-card': {
383
+ const id = flags['id'] || positional[0];
384
+ if (!id) error(`Usage: ${$} users add-card <userId> --return-url <url>`);
385
+ const returnUrl = requireFlag(flags, 'return-url');
386
+ const result = await client.users.addPaymentMethod({
387
+ userId: id,
388
+ type: 'card',
389
+ returnUrl,
390
+ });
391
+ success('Card setup initiated');
392
+ console.log(`\n ${bold('Setup URL:')} ${(result as any).setupUrl}`);
393
+ console.log(` ${bold('Session ID:')} ${(result as any).sessionId}\n`);
394
+ break;
395
+ }
396
+
397
+ case 'add-wallet': {
398
+ const id = flags['id'] || positional[0];
399
+ if (!id) error(`Usage: ${$} users add-wallet <userId> --address <addr> --chain <chain>`);
400
+ const walletAddress = requireFlag(flags, 'address', 'wallet address');
401
+ const chain = requireFlag(flags, 'chain', 'ethereum|base|polygon|solana');
402
+ const result = await client.users.addPaymentMethod({
403
+ userId: id,
404
+ type: 'wallet',
405
+ walletAddress,
406
+ chain: chain as any,
407
+ });
408
+ success('Wallet registered');
409
+ json(result);
410
+ break;
411
+ }
412
+
413
+ case 'set-limits': {
414
+ const id = flags['id'] || positional[0];
415
+ if (!id) error(`Usage: ${$} users set-limits <userId> --agent <agentId> [--per-tx <cents>] [--daily <cents>] [--monthly <cents>]`);
416
+ const agentId = requireFlag(flags, 'agent', 'agent ID');
417
+ const result = await client.users.setLimits({
418
+ userId: id,
419
+ agentId,
420
+ maxPerTransaction: flags['per-tx'] ? Number(flags['per-tx']) : undefined,
421
+ maxDaily: flags['daily'] ? Number(flags['daily']) : undefined,
422
+ maxMonthly: flags['monthly'] ? Number(flags['monthly']) : undefined,
423
+ });
424
+ success('Spend limits updated');
425
+ json(result);
426
+ break;
427
+ }
428
+
429
+ case 'wallet-status': {
430
+ const id = flags['id'] || positional[0];
431
+ if (!id) error(`Usage: ${$} users wallet-status <userId>`);
432
+ const status = await client.users.getWalletStatus(id);
433
+ json(status);
434
+ break;
435
+ }
436
+
437
+ default:
438
+ cmdUsersHelp(brand);
439
+ }
440
+ }
441
+
442
+ function cmdUsersHelp(brand: Brand): void {
443
+ const $ = brand.cmd;
444
+ console.log(`
445
+ ${bold(`${$} users`)} — Manage end users & payment methods
446
+
447
+ ${cyan(`${$} users create`)} Create end user
448
+ ${cyan(`${$} users get <id>`)} Get user by ID
449
+ ${cyan(`${$} users find <extId>`)} Find user by external ID
450
+ ${cyan(`${$} users add-card <id>`)} Start card tokenization
451
+ ${cyan(`${$} users add-wallet <id>`)} Register crypto wallet
452
+ ${cyan(`${$} users set-limits <id>`)} Set spend limits
453
+ ${cyan(`${$} users wallet-status <id>`)} Check wallet USDC status
454
+ `);
455
+ }
456
+
457
+ export async function cmdPay(args: string[], brand: Brand): Promise<void> {
458
+ const { flags } = parseFlags(args);
459
+ const client = getClient(brand);
460
+
461
+ const agentId = requireFlag(flags, 'agent', 'agent ID');
462
+ const amount = Number(requireFlag(flags, 'amount', 'amount in cents'));
463
+ const currency = flags['currency'] || 'USD';
464
+ const memo = requireFlag(flags, 'memo', 'payment memo');
465
+ const idempotencyKey = flags['idempotency-key'] || `cli_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
466
+
467
+ const params: any = {
468
+ agentId,
469
+ amount,
470
+ currency,
471
+ memo,
472
+ idempotencyKey,
473
+ };
474
+
475
+ if (flags['user']) params.userId = flags['user'];
476
+ if (flags['merchant-url']) params.merchantUrl = flags['merchant-url'];
477
+ if (flags['merchant-name']) params.merchantName = flags['merchant-name'];
478
+ if (flags['rail']) params.rail = flags['rail'];
479
+ if (flags['chain']) params.chain = flags['chain'];
480
+ if (flags['recipient']) params.recipientAddress = flags['recipient'];
481
+ if (flags['token']) params.token = flags['token'];
482
+ if (flags['metadata']) {
483
+ try {
484
+ params.metadata = JSON.parse(flags['metadata']);
485
+ } catch {
486
+ error('--metadata must be valid JSON');
487
+ }
488
+ }
489
+
490
+ console.log(`\n ${dim('Creating payment...')}\n`);
491
+ const payment = await client.payments.create(params);
492
+
493
+ const p = payment as any;
494
+ if (p.status === 'succeeded') {
495
+ success(`Payment ${bold(p.id)} — $${(amount / 100).toFixed(2)} ${currency}`);
496
+ } else if (p.status === 'requires_action') {
497
+ console.log(` ${bold('Status:')} requires_action (3DS)`);
498
+ if (p.nextAction?.type === 'redirect') {
499
+ console.log(` ${bold('Redirect:')} ${p.nextAction.url}`);
500
+ }
501
+ } else {
502
+ console.log(` ${bold('Status:')} ${p.status}`);
503
+ }
504
+
505
+ console.log();
506
+ json(payment);
507
+ }
508
+
509
+ export async function cmdPaymentGet(args: string[], brand: Brand): Promise<void> {
510
+ const { positional } = parseFlags(args);
511
+ const id = positional[0];
512
+ if (!id) error(`Usage: ${brand.cmd} payment <id>`);
513
+ const client = getClient(brand);
514
+ const payment = await client.payments.get(id);
515
+ json(payment);
516
+ }
517
+
518
+ export async function cmdTransactions(args: string[], brand: Brand): Promise<void> {
519
+ const { flags } = parseFlags(args);
520
+ const client = getClient(brand);
521
+
522
+ const result = await client.transactions.list({
523
+ agentId: flags['agent'],
524
+ userId: flags['user'],
525
+ from: flags['from'],
526
+ to: flags['to'],
527
+ limit: flags['limit'] ? Number(flags['limit']) : 20,
528
+ offset: flags['offset'] ? Number(flags['offset']) : undefined,
529
+ });
530
+
531
+ const payments = (result as any).payments ?? (result as any).transactions ?? [];
532
+
533
+ table(
534
+ payments.map((p: any) => ({
535
+ id: p.id.slice(0, 12) + '…',
536
+ amount: '$' + (p.amount / 100).toFixed(2),
537
+ status: p.status,
538
+ rail: p.rail || 'card',
539
+ memo: (p.memo || '').slice(0, 25),
540
+ date: p.createdAt?.slice(0, 10),
541
+ })),
542
+ );
543
+ }
544
+
545
+ export async function cmdWebhooks(args: string[], brand: Brand): Promise<void> {
546
+ const [sub, ...rest] = args;
547
+ if (!sub || sub === 'help' || sub === '--help') {
548
+ return cmdWebhooksHelp(brand);
549
+ }
550
+ const { flags, positional } = parseFlags(rest);
551
+ const client = getClient(brand);
552
+ const $ = brand.cmd;
553
+
554
+ switch (sub) {
555
+ case 'list':
556
+ case 'ls': {
557
+ const result = await client.webhooks.list();
558
+ const webhooks = Array.isArray(result) ? result : (result as any).data ?? [];
559
+ table(
560
+ webhooks.map((w: any) => ({
561
+ id: w.id.slice(0, 12) + '…',
562
+ url: w.url.slice(0, 40),
563
+ events: (w.events || []).join(', ').slice(0, 30),
564
+ status: w.status || 'active',
565
+ })),
566
+ );
567
+ break;
568
+ }
569
+
570
+ case 'create': {
571
+ const url = flags['url'] || positional[0];
572
+ if (!url) error(`Usage: ${$} webhooks create --url <url> --events <evt1,evt2>`);
573
+ const events = (flags['events'] || 'payment.succeeded,payment.failed').split(',');
574
+ const result = await client.webhooks.register({ url, events });
575
+ success('Webhook created');
576
+ console.log(`\n ${bold('Secret:')} ${(result as any).secret}`);
577
+ console.log(dim(' Store this — it won\'t be shown again.\n'));
578
+ json(result);
579
+ break;
580
+ }
581
+
582
+ case 'delete':
583
+ case 'rm': {
584
+ const id = flags['id'] || positional[0];
585
+ if (!id) error(`Usage: ${$} webhooks delete <id>`);
586
+ await client.webhooks.delete(id);
587
+ success(`Webhook ${id} deleted`);
588
+ break;
589
+ }
590
+
591
+ default:
592
+ cmdWebhooksHelp(brand);
593
+ }
594
+ }
595
+
596
+ function cmdWebhooksHelp(brand: Brand): void {
597
+ const $ = brand.cmd;
598
+ console.log(`
599
+ ${bold(`${$} webhooks`)} — Manage webhook endpoints
600
+
601
+ ${cyan(`${$} webhooks list`)} List webhooks
602
+ ${cyan(`${$} webhooks create`)} Register new webhook
603
+ ${cyan(`${$} webhooks delete <id>`)} Delete webhook
604
+ `);
605
+ }
606
+
607
+ export async function cmdStatus(brand: Brand): Promise<void> {
608
+ const config = loadConfig(brand);
609
+
610
+ console.log(`\n${bold(`${brand.cmd} status`)}\n`);
611
+
612
+ const key = config.apiKey || config.accessToken;
613
+ if (config.email) {
614
+ console.log(` ${bold('Logged in:')} ${config.email}`);
615
+ }
616
+ if (key) {
617
+ const masked = key.slice(0, 12) + '…' + key.slice(-4);
618
+ console.log(` ${bold('Auth:')} ${masked}`);
619
+ } else {
620
+ console.log(` ${bold('Auth:')} ${dim(`not configured — run: ${brand.authHint}`)}`);
621
+ }
622
+ console.log(` ${bold('Environment:')} ${config.environment || 'sandbox'}`);
623
+ if (config.baseUrl) console.log(` ${bold('Base URL:')} ${config.baseUrl}`);
624
+ console.log(` ${bold('Config:')} ${configFile(brand)}`);
625
+
626
+ if (key) {
627
+ try {
628
+ const client = getClient(brand);
629
+ await client.transactions.list({ limit: 1 });
630
+ console.log(` ${bold('API:')} \x1b[32mconnected\x1b[0m`);
631
+ } catch (e: any) {
632
+ if (e.statusCode === 401) {
633
+ console.log(` ${bold('API:')} \x1b[31minvalid / expired\x1b[0m`);
634
+ } else {
635
+ console.log(` ${bold('API:')} \x1b[33munreachable\x1b[0m ${dim(e.message || '')}`);
636
+ }
637
+ }
638
+ }
639
+
640
+ console.log();
641
+ }
642
+
643
+ export function getVersion(): string {
644
+ try {
645
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
646
+ return pkg.version;
647
+ } catch {
648
+ return '0.1.0';
649
+ }
650
+ }