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/src/agents.ts ADDED
@@ -0,0 +1,117 @@
1
+ import type {
2
+ RegisterAgentRequest,
3
+ AgentResponse,
4
+ AgentListResponse,
5
+ RotateKeysResponse,
6
+ SetCircuitBreakerRequest,
7
+ CircuitBreakerResponse,
8
+ AgentTopupRequest,
9
+ AgentTopupResponse,
10
+ AgentSweepRequest,
11
+ AgentSweepResponse,
12
+ AgentEarningRequest,
13
+ AgentBalanceResponse,
14
+ LedgerHistoryResponse,
15
+ } from '@arispay/shared';
16
+ import type { HttpClient } from './client.js';
17
+
18
+ export interface ChildAgentSummary {
19
+ id: string;
20
+ name: string;
21
+ status: string;
22
+ permissions: string[];
23
+ delegatedBudget: number | null;
24
+ delegatedBudgetDay: number | null;
25
+ delegatedBudgetMo: number | null;
26
+ createdAt: string;
27
+ }
28
+
29
+ export class AgentService {
30
+ constructor(private client: HttpClient) {}
31
+
32
+ async register(params: RegisterAgentRequest): Promise<AgentResponse> {
33
+ return this.client.post<AgentResponse>('/v1/agents', params);
34
+ }
35
+
36
+ async get(agentId: string): Promise<AgentResponse> {
37
+ return this.client.get<AgentResponse>(`/v1/agents/${agentId}`);
38
+ }
39
+
40
+ async list(params?: { name?: string; status?: string; limit?: number; offset?: number }): Promise<AgentListResponse> {
41
+ const query = new URLSearchParams();
42
+ if (params?.name) query.set('name', params.name);
43
+ if (params?.status) query.set('status', params.status);
44
+ if (params?.limit) query.set('limit', String(params.limit));
45
+ if (params?.offset) query.set('offset', String(params.offset));
46
+ const qs = query.toString();
47
+ return this.client.get<AgentListResponse>(`/v1/agents${qs ? `?${qs}` : ''}`);
48
+ }
49
+
50
+ async listChildren(agentId: string): Promise<ChildAgentSummary[]> {
51
+ return this.client.get<ChildAgentSummary[]>(`/v1/agents/${agentId}/children`);
52
+ }
53
+
54
+ async resume(agentId: string): Promise<{ status: string; resumedAt: string }> {
55
+ return this.client.post<{ status: string; resumedAt: string }>(`/v1/agents/${agentId}/resume`);
56
+ }
57
+
58
+ async setCircuitBreaker(agentId: string, params: SetCircuitBreakerRequest): Promise<CircuitBreakerResponse> {
59
+ return this.client.put<CircuitBreakerResponse>(`/v1/agents/${agentId}/circuit-breaker`, params);
60
+ }
61
+
62
+ async getCircuitBreaker(agentId: string): Promise<CircuitBreakerResponse> {
63
+ return this.client.get<CircuitBreakerResponse>(`/v1/agents/${agentId}/circuit-breaker`);
64
+ }
65
+
66
+ async rotateKeys(agentId: string): Promise<RotateKeysResponse> {
67
+ return this.client.post<RotateKeysResponse>(`/v1/agents/${agentId}/rotate-keys`);
68
+ }
69
+
70
+ // ── Autonomous Agent Methods ──────────────────────────
71
+
72
+ /**
73
+ * Get the current balance for an autonomous agent.
74
+ */
75
+ async getBalance(agentId: string): Promise<AgentBalanceResponse> {
76
+ return this.client.get<AgentBalanceResponse>(`/v1/agents/${agentId}/balance`);
77
+ }
78
+
79
+ /**
80
+ * Fund an autonomous agent's balance (topup).
81
+ */
82
+ async topup(agentId: string, params: AgentTopupRequest): Promise<AgentTopupResponse> {
83
+ return this.client.post<AgentTopupResponse>(`/v1/agents/${agentId}/topup`, params);
84
+ }
85
+
86
+ /**
87
+ * Withdraw from an autonomous agent's balance (sweep).
88
+ * Omit `amount` to sweep everything above the sweep threshold.
89
+ */
90
+ async sweep(agentId: string, params?: AgentSweepRequest): Promise<AgentSweepResponse> {
91
+ return this.client.post<AgentSweepResponse>(`/v1/agents/${agentId}/sweep`, params ?? {});
92
+ }
93
+
94
+ /**
95
+ * Get paginated ledger history for an autonomous agent.
96
+ */
97
+ async getLedger(
98
+ agentId: string,
99
+ params?: { limit?: number; offset?: number; type?: string; from?: string; to?: string },
100
+ ): Promise<LedgerHistoryResponse> {
101
+ const query = new URLSearchParams();
102
+ if (params?.limit) query.set('limit', String(params.limit));
103
+ if (params?.offset) query.set('offset', String(params.offset));
104
+ if (params?.type) query.set('type', params.type);
105
+ if (params?.from) query.set('from', params.from);
106
+ if (params?.to) query.set('to', params.to);
107
+ const qs = query.toString();
108
+ return this.client.get<LedgerHistoryResponse>(`/v1/agents/${agentId}/ledger${qs ? `?${qs}` : ''}`);
109
+ }
110
+
111
+ /**
112
+ * Record an earning (inbound money) for an autonomous agent.
113
+ */
114
+ async recordEarning(agentId: string, params: AgentEarningRequest): Promise<AgentTopupResponse> {
115
+ return this.client.post<AgentTopupResponse>(`/v1/agents/${agentId}/earn`, params);
116
+ }
117
+ }
package/src/cli-b.ts ADDED
@@ -0,0 +1,373 @@
1
+ /**
2
+ * PayAgent CLI — Device authorization flow
3
+ *
4
+ * npm install payagent
5
+ * payagent login → shows code, developer confirms on any browser
6
+ *
7
+ * No localhost server. Works in VMs, containers, SSH, WSL.
8
+ */
9
+
10
+ import { randomBytes } from 'node:crypto';
11
+ import {
12
+ type Brand,
13
+ type Config,
14
+ createBrand,
15
+ configFile,
16
+ loadConfig,
17
+ saveConfig,
18
+ parseFlags,
19
+ error,
20
+ success,
21
+ dim,
22
+ bold,
23
+ cyan,
24
+ getVersion,
25
+ cmdAgents,
26
+ cmdUsers,
27
+ cmdPay,
28
+ cmdPaymentGet,
29
+ cmdTransactions,
30
+ cmdWebhooks,
31
+ cmdStatus,
32
+ } from './cli-shared.js';
33
+
34
+ const BRAND: Brand = createBrand('payagent', 'payagent login');
35
+
36
+ // ── Dashboard URLs ──────────────────────────────────────────────────
37
+
38
+ function getApiUrl(config: Config): string {
39
+ if (config.baseUrl) return config.baseUrl;
40
+ return config.environment === 'production'
41
+ ? 'https://api.arispay.app'
42
+ : 'https://sandbox.api.arispay.app';
43
+ }
44
+
45
+ function getDeviceUrl(config: Config): string {
46
+ if (config.baseUrl) {
47
+ return config.baseUrl.replace('://api.', '://app.').replace('://sandbox.api.', '://app.');
48
+ }
49
+ return config.environment === 'production'
50
+ ? 'https://app.arispay.app'
51
+ : 'https://app.sandbox.arispay.app';
52
+ }
53
+
54
+ // ── Device flow helpers ─────────────────────────────────────────────
55
+
56
+ function generateUserCode(): string {
57
+ // 8-char alphanumeric, formatted as XXXX-XXXX for readability
58
+ const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // no 0/O/1/I ambiguity
59
+ let code = '';
60
+ const bytes = randomBytes(8);
61
+ for (let i = 0; i < 8; i++) {
62
+ code += chars[bytes[i] % chars.length];
63
+ }
64
+ return `${code.slice(0, 4)}-${code.slice(4)}`;
65
+ }
66
+
67
+ interface DeviceCodeResponse {
68
+ deviceCode: string;
69
+ userCode: string;
70
+ verificationUrl: string;
71
+ expiresIn: number;
72
+ interval: number;
73
+ }
74
+
75
+ interface TokenResponse {
76
+ accessToken: string;
77
+ refreshToken?: string;
78
+ expiresAt?: number;
79
+ email?: string;
80
+ environment?: string;
81
+ }
82
+
83
+ async function requestDeviceCode(apiUrl: string): Promise<DeviceCodeResponse> {
84
+ const res = await fetch(`${apiUrl}/v1/auth/device/code`, {
85
+ method: 'POST',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body: JSON.stringify({ clientId: 'payagent-cli' }),
88
+ signal: AbortSignal.timeout(15_000),
89
+ });
90
+
91
+ if (!res.ok) {
92
+ const body = await res.json().catch(() => ({}));
93
+ throw new Error((body as any).error?.message || `Device code request failed (HTTP ${res.status})`);
94
+ }
95
+
96
+ return res.json() as Promise<DeviceCodeResponse>;
97
+ }
98
+
99
+ async function pollForToken(
100
+ apiUrl: string,
101
+ deviceCode: string,
102
+ intervalSec: number,
103
+ expiresIn: number,
104
+ ): Promise<TokenResponse> {
105
+ const deadline = Date.now() + expiresIn * 1000;
106
+
107
+ while (Date.now() < deadline) {
108
+ await sleep(intervalSec * 1000);
109
+
110
+ const res = await fetch(`${apiUrl}/v1/auth/device/token`, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({ deviceCode, clientId: 'payagent-cli' }),
114
+ signal: AbortSignal.timeout(15_000),
115
+ });
116
+
117
+ const body = await res.json() as any;
118
+
119
+ if (res.ok && body.accessToken) {
120
+ return body as TokenResponse;
121
+ }
122
+
123
+ // Standard device flow responses
124
+ const errorCode = body.error?.code || body.error;
125
+ if (errorCode === 'authorization_pending') {
126
+ continue; // keep polling
127
+ } else if (errorCode === 'slow_down') {
128
+ intervalSec = Math.min(intervalSec + 1, 10); // back off
129
+ continue;
130
+ } else if (errorCode === 'expired_token') {
131
+ throw new Error('Login expired. Run `payagent login` to try again.');
132
+ } else if (errorCode === 'access_denied') {
133
+ throw new Error('Login was denied.');
134
+ } else {
135
+ throw new Error(body.error?.message || `Token request failed (HTTP ${res.status})`);
136
+ }
137
+ }
138
+
139
+ throw new Error('Login timed out. Run `payagent login` to try again.');
140
+ }
141
+
142
+ function sleep(ms: number): Promise<void> {
143
+ return new Promise((resolve) => setTimeout(resolve, ms));
144
+ }
145
+
146
+ // ── Login (device flow) ─────────────────────────────────────────────
147
+
148
+ async function cmdLogin(args: string[]): Promise<void> {
149
+ const { flags } = parseFlags(args);
150
+ const config = loadConfig(BRAND);
151
+
152
+ // CI escape hatch: payagent login --api-key <key>
153
+ if (flags['api-key']) {
154
+ saveConfig(BRAND, {
155
+ ...config,
156
+ apiKey: flags['api-key'],
157
+ environment: (flags['env'] as Config['environment']) || config.environment,
158
+ });
159
+ success('API key saved');
160
+ return;
161
+ }
162
+
163
+ const apiUrl = getApiUrl(config);
164
+ const dashboardUrl = getDeviceUrl(config);
165
+
166
+ console.log(`\n${bold('PayAgent Login')}\n`);
167
+
168
+ // Step 1: Request device code from API
169
+ let device: DeviceCodeResponse;
170
+ try {
171
+ device = await requestDeviceCode(apiUrl);
172
+ } catch (e: any) {
173
+ error(e.message || 'Could not initiate login');
174
+ }
175
+
176
+ const verificationUrl = device!.verificationUrl || `${dashboardUrl}/device`;
177
+
178
+ // Step 2: Show code to developer
179
+ console.log(` Open this URL on any device:\n`);
180
+ console.log(` ${cyan(verificationUrl)}\n`);
181
+ console.log(` Then enter this code:\n`);
182
+ console.log(` ${bold(device!.userCode)}\n`);
183
+
184
+ // Step 3: Poll for token
185
+ const spinner = startSpinner(' Waiting for confirmation');
186
+
187
+ try {
188
+ const token = await pollForToken(
189
+ apiUrl,
190
+ device!.deviceCode,
191
+ device!.interval || 5,
192
+ device!.expiresIn || 300,
193
+ );
194
+ spinner.stop();
195
+
196
+ // Save credentials
197
+ saveConfig(BRAND, {
198
+ ...config,
199
+ accessToken: token.accessToken,
200
+ refreshToken: token.refreshToken,
201
+ expiresAt: token.expiresAt,
202
+ email: token.email,
203
+ environment: (token.environment as Config['environment']) || config.environment,
204
+ });
205
+
206
+ console.log();
207
+ success(`Logged in as ${bold(token.email || 'developer')}`);
208
+ if (token.environment) {
209
+ console.log(` ${bold('Environment:')} ${token.environment}`);
210
+ }
211
+ console.log(` ${bold('Config:')} ${dim(configFile(BRAND))}`);
212
+ console.log();
213
+ } catch (e: any) {
214
+ spinner.stop();
215
+ console.log();
216
+ error(e.message || 'Authentication failed');
217
+ }
218
+ }
219
+
220
+ // ── Logout ──────────────────────────────────────────────────────────
221
+
222
+ async function cmdLogout(): Promise<void> {
223
+ const config = loadConfig(BRAND);
224
+
225
+ if (!config.accessToken && !config.apiKey && !config.email) {
226
+ console.log(dim('\n Not logged in.\n'));
227
+ return;
228
+ }
229
+
230
+ // Clear auth fields, keep environment/baseUrl preferences
231
+ saveConfig(BRAND, {
232
+ environment: config.environment,
233
+ baseUrl: config.baseUrl,
234
+ });
235
+
236
+ success('Logged out');
237
+ console.log(` ${dim('Cleared credentials from')} ${configFile(BRAND)}\n`);
238
+ }
239
+
240
+ // ── Spinner ─────────────────────────────────────────────────────────
241
+
242
+ function startSpinner(message: string): { stop: () => void } {
243
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
244
+ let i = 0;
245
+ const interval = setInterval(() => {
246
+ process.stdout.write(`\r${frames[i++ % frames.length]} ${message}`);
247
+ }, 80);
248
+
249
+ return {
250
+ stop() {
251
+ clearInterval(interval);
252
+ process.stdout.write('\r' + ' '.repeat(message.length + 4) + '\r');
253
+ },
254
+ };
255
+ }
256
+
257
+ // ── Whoami ──────────────────────────────────────────────────────────
258
+
259
+ async function cmdWhoami(): Promise<void> {
260
+ const config = loadConfig(BRAND);
261
+ if (config.email) {
262
+ console.log(config.email);
263
+ } else if (config.apiKey || config.accessToken) {
264
+ const key = (config.apiKey || config.accessToken)!;
265
+ console.log(`api-key: ${key.slice(0, 12)}…${key.slice(-4)}`);
266
+ } else {
267
+ error('Not logged in. Run: payagent login');
268
+ }
269
+ }
270
+
271
+ // ── Main ────────────────────────────────────────────────────────────
272
+
273
+ const HELP = `
274
+ ${bold('payagent')} — Agentic payment infrastructure CLI
275
+
276
+ ${bold('Usage:')}
277
+ payagent <command> [options]
278
+
279
+ ${bold('Auth:')}
280
+ ${cyan('login')} Authenticate via device code
281
+ ${cyan('logout')} Clear stored credentials
282
+ ${cyan('whoami')} Show current identity
283
+
284
+ ${bold('Commands:')}
285
+ ${cyan('status')} Show config & connection status
286
+ ${cyan('agents')} Manage AI payment agents
287
+ ${cyan('users')} Manage end users & payment methods
288
+ ${cyan('pay')} Create a payment
289
+ ${cyan('payment <id>')} Get payment details
290
+ ${cyan('transactions')} List transactions
291
+ ${cyan('webhooks')} Manage webhook endpoints
292
+
293
+ ${bold('Examples:')}
294
+ payagent login
295
+ payagent agents create --name "BookingBot" --mode autonomous
296
+ payagent agents list
297
+ payagent pay --agent <id> --amount 2000 --memo "Dinner for 2"
298
+ payagent transactions --agent <id> --from 2025-01-01
299
+
300
+ ${bold('Environment:')}
301
+ PAYAGENT_API_KEY Override auth (CI/scripting)
302
+ PAYAGENT_ENV Override environment (sandbox|production)
303
+ PAYAGENT_BASE_URL Override API base URL
304
+
305
+ ${bold('Config:')} ~/.payagent/config.json
306
+ `;
307
+
308
+ async function main(): Promise<void> {
309
+ const args = process.argv.slice(2);
310
+ const command = args[0];
311
+ const rest = args.slice(1);
312
+
313
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
314
+ console.log(HELP);
315
+ return;
316
+ }
317
+
318
+ if (command === '--version' || command === '-v') {
319
+ console.log(getVersion());
320
+ return;
321
+ }
322
+
323
+ try {
324
+ switch (command) {
325
+ case 'login':
326
+ await cmdLogin(rest);
327
+ break;
328
+ case 'logout':
329
+ await cmdLogout();
330
+ break;
331
+ case 'whoami':
332
+ await cmdWhoami();
333
+ break;
334
+ case 'status':
335
+ await cmdStatus(BRAND);
336
+ break;
337
+ case 'agents':
338
+ case 'agent':
339
+ await cmdAgents(rest, BRAND);
340
+ break;
341
+ case 'users':
342
+ case 'user':
343
+ await cmdUsers(rest, BRAND);
344
+ break;
345
+ case 'pay':
346
+ await cmdPay(rest, BRAND);
347
+ break;
348
+ case 'payment':
349
+ await cmdPaymentGet(rest, BRAND);
350
+ break;
351
+ case 'transactions':
352
+ case 'txs':
353
+ await cmdTransactions(rest, BRAND);
354
+ break;
355
+ case 'webhooks':
356
+ case 'webhook':
357
+ await cmdWebhooks(rest, BRAND);
358
+ break;
359
+ default:
360
+ console.error(`Unknown command: ${command}`);
361
+ console.log(HELP);
362
+ process.exit(1);
363
+ }
364
+ } catch (e: any) {
365
+ if (e.code && e.message && e.statusCode) {
366
+ error(`[${e.code}] ${e.message} (HTTP ${e.statusCode})`);
367
+ } else {
368
+ error(e.message || String(e));
369
+ }
370
+ }
371
+ }
372
+
373
+ main();