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.
- package/.turbo/turbo-build.log +4 -0
- package/dist/chunk-4ON4ZXR3.js +953 -0
- package/dist/cli-b.js +277 -0
- package/dist/cli.js +426 -0
- package/dist/index.d.ts +216 -0
- package/dist/index.js +406 -0
- package/package.json +31 -0
- package/src/agents.ts +117 -0
- package/src/cli-b.ts +373 -0
- package/src/cli-shared.ts +650 -0
- package/src/cli.ts +439 -0
- package/src/client.ts +65 -0
- package/src/index.ts +98 -0
- package/src/payments.ts +22 -0
- package/src/transactions.ts +19 -0
- package/src/users.ts +67 -0
- package/src/webhooks.ts +81 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +18 -0
|
@@ -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
|
+
}
|