@paylobster/cli 4.6.0 → 4.6.1

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.
Files changed (38) hide show
  1. package/dist/src/commands/alerts.d.ts +3 -0
  2. package/dist/src/commands/alerts.d.ts.map +1 -0
  3. package/dist/src/commands/alerts.js +181 -0
  4. package/dist/src/commands/alerts.js.map +1 -0
  5. package/dist/src/commands/init.d.ts +3 -0
  6. package/dist/src/commands/init.d.ts.map +1 -0
  7. package/dist/src/commands/init.js +232 -0
  8. package/dist/src/commands/init.js.map +1 -0
  9. package/dist/src/commands/link.d.ts +6 -0
  10. package/dist/src/commands/link.d.ts.map +1 -0
  11. package/dist/src/commands/link.js +188 -0
  12. package/dist/src/commands/link.js.map +1 -0
  13. package/dist/src/commands/refund.d.ts +6 -0
  14. package/dist/src/commands/refund.d.ts.map +1 -0
  15. package/dist/src/commands/refund.js +199 -0
  16. package/dist/src/commands/refund.js.map +1 -0
  17. package/dist/src/commands/swap.d.ts.map +1 -1
  18. package/dist/src/commands/swap.js +93 -1
  19. package/dist/src/commands/swap.js.map +1 -1
  20. package/dist/src/commands/webhook.d.ts +6 -0
  21. package/dist/src/commands/webhook.d.ts.map +1 -0
  22. package/dist/src/commands/webhook.js +208 -0
  23. package/dist/src/commands/webhook.js.map +1 -0
  24. package/dist/src/index.js +10 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/lib/contracts.d.ts +23 -161
  27. package/dist/src/lib/contracts.d.ts.map +1 -1
  28. package/dist/src/lib/contracts.js +6 -6
  29. package/dist/src/lib/contracts.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/commands/alerts.ts +210 -0
  32. package/src/commands/init.ts +256 -0
  33. package/src/commands/link.ts +240 -0
  34. package/src/commands/refund.ts +250 -0
  35. package/src/commands/swap.ts +120 -1
  36. package/src/commands/webhook.ts +260 -0
  37. package/src/index.ts +10 -0
  38. package/src/lib/contracts.ts +6 -6
@@ -0,0 +1,256 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import readline from 'readline';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import crypto from 'crypto';
8
+ import { exec } from 'child_process';
9
+ import { promisify } from 'util';
10
+
11
+ const execAsync = promisify(exec);
12
+
13
+ const CONFIG_DIR = path.join(process.env.HOME || '~', '.paylobster');
14
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
15
+ const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials');
16
+
17
+ function prompt(question: string): Promise<string> {
18
+ const rl = readline.createInterface({
19
+ input: process.stdin,
20
+ output: process.stdout,
21
+ });
22
+ return new Promise((resolve) => {
23
+ rl.question(question, (answer) => {
24
+ rl.close();
25
+ resolve(answer.trim());
26
+ });
27
+ });
28
+ }
29
+
30
+ function selectPrompt(question: string, options: { label: string; value: string }[]): Promise<string> {
31
+ return new Promise((resolve) => {
32
+ const rl = readline.createInterface({
33
+ input: process.stdin,
34
+ output: process.stdout,
35
+ });
36
+
37
+ console.log(chalk.bold.white(`\n ${question}\n`));
38
+ options.forEach((opt, i) => {
39
+ console.log(chalk.cyan(` ${i + 1}) `) + chalk.white(opt.label));
40
+ });
41
+ console.log();
42
+
43
+ rl.question(chalk.gray(' Enter choice (number): '), (answer) => {
44
+ rl.close();
45
+ const idx = parseInt(answer.trim(), 10) - 1;
46
+ if (idx >= 0 && idx < options.length) {
47
+ resolve(options[idx].value);
48
+ } else {
49
+ // Default to first option
50
+ resolve(options[0].value);
51
+ }
52
+ });
53
+ });
54
+ }
55
+
56
+ function generateApiKey(): string {
57
+ const bytes = crypto.randomBytes(24);
58
+ return `pl_live_${bytes.toString('base64url').slice(0, 32)}`;
59
+ }
60
+
61
+ function ensureConfigDir(): void {
62
+ if (!fs.existsSync(CONFIG_DIR)) {
63
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
64
+ }
65
+ }
66
+
67
+ function openBrowser(url: string): void {
68
+ const platform = process.platform;
69
+ const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
70
+ try {
71
+ require('child_process').execSync(`${cmd} "${url}"`, { stdio: 'ignore' });
72
+ } catch {
73
+ console.log(chalk.gray(` Open this URL in your browser: ${url}`));
74
+ }
75
+ }
76
+
77
+ export function createInitCommand(): Command {
78
+ const cmd = new Command('init')
79
+ .description('🦞 Interactive setup wizard — get started with Pay Lobster in 60 seconds')
80
+ .option('--wallet <address>', 'Pre-set wallet address')
81
+ .option('--role <role>', 'Pre-set role (developer, agent-operator, business)')
82
+ .option('--network <network>', 'Network to use (base, base-sepolia)', 'base')
83
+ .action(async (options) => {
84
+ // ─── Welcome Banner ───
85
+ console.log();
86
+ console.log(chalk.cyan(' ┌─────────────────────────────────────────┐'));
87
+ console.log(chalk.cyan(' │') + ' ' + chalk.cyan('│'));
88
+ console.log(chalk.cyan(' │') + chalk.bold.white(' 🦞 Welcome to Pay Lobster! ') + chalk.cyan('│'));
89
+ console.log(chalk.cyan(' │') + chalk.gray(' Payments made simple on Base ') + chalk.cyan('│'));
90
+ console.log(chalk.cyan(' │') + ' ' + chalk.cyan('│'));
91
+ console.log(chalk.cyan(' └─────────────────────────────────────────┘'));
92
+ console.log();
93
+ console.log(chalk.gray(' Let\'s get you set up in under 60 seconds.\n'));
94
+
95
+ // ─── Step 1: Wallet Address ───
96
+ console.log(chalk.bold.blue(' Step 1 of 3') + chalk.gray(' — Connect your wallet\n'));
97
+
98
+ let walletAddress = options.wallet || '';
99
+
100
+ if (!walletAddress) {
101
+ const walletChoice = await selectPrompt('How would you like to connect?', [
102
+ { label: '📋 Enter an existing wallet address', value: 'enter' },
103
+ { label: '🌐 Create a Smart Wallet (opens browser)', value: 'create' },
104
+ ]);
105
+
106
+ if (walletChoice === 'create') {
107
+ const spinner = ora({
108
+ text: chalk.white('Opening Pay Lobster to create your wallet...'),
109
+ spinner: 'dots',
110
+ }).start();
111
+
112
+ openBrowser('https://paylobster.com/onboard?source=cli');
113
+
114
+ spinner.succeed(chalk.green('Browser opened!'));
115
+ console.log(chalk.gray(' Create your wallet with just a passkey — no seed phrase needed.\n'));
116
+
117
+ walletAddress = await prompt(chalk.white(' Paste your wallet address here: '));
118
+ } else {
119
+ walletAddress = await prompt(chalk.white(' Enter your wallet address (0x...): '));
120
+ }
121
+ }
122
+
123
+ // Validate address format
124
+ if (!walletAddress.match(/^0x[a-fA-F0-9]{40}$/)) {
125
+ if (walletAddress.length > 0) {
126
+ console.log(chalk.yellow('\n ⚠ That doesn\'t look like a valid address, but we\'ll save it anyway.'));
127
+ } else {
128
+ walletAddress = '0x0000000000000000000000000000000000000000';
129
+ console.log(chalk.gray('\n No address provided — you can set one later with: paylobster config set wallet <address>'));
130
+ }
131
+ } else {
132
+ console.log(chalk.green(`\n ✓ Wallet: ${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}\n`));
133
+ }
134
+
135
+ // ─── Step 2: Role ───
136
+ console.log(chalk.bold.blue(' Step 2 of 3') + chalk.gray(' — Tell us about yourself\n'));
137
+
138
+ let role = options.role || '';
139
+
140
+ if (!role) {
141
+ role = await selectPrompt('What best describes you?', [
142
+ { label: '💻 Developer — building with the SDK or API', value: 'developer' },
143
+ { label: '🤖 Agent operator — running AI agents', value: 'agent-operator' },
144
+ { label: '🏢 Business — accepting payments', value: 'business' },
145
+ ]);
146
+ }
147
+
148
+ const roleLabels: Record<string, string> = {
149
+ 'developer': '💻 Developer',
150
+ 'agent-operator': '🤖 Agent Operator',
151
+ 'business': '🏢 Business',
152
+ };
153
+
154
+ console.log(chalk.green(`\n ✓ Role: ${roleLabels[role] || role}\n`));
155
+
156
+ // ─── Step 3: Generate API Key & Save Config ───
157
+ console.log(chalk.bold.blue(' Step 3 of 3') + chalk.gray(' — Setting up your environment\n'));
158
+
159
+ const spinner = ora({
160
+ text: chalk.white('Generating your API key...'),
161
+ spinner: 'dots',
162
+ }).start();
163
+
164
+ // Simulate a brief delay for UX
165
+ await new Promise((r) => setTimeout(r, 800));
166
+
167
+ const apiKey = generateApiKey();
168
+ const network = options.network || 'base';
169
+
170
+ // Save config
171
+ ensureConfigDir();
172
+
173
+ const config = {
174
+ version: 1,
175
+ wallet: walletAddress,
176
+ role,
177
+ network,
178
+ createdAt: new Date().toISOString(),
179
+ defaults: {
180
+ currency: 'USDC',
181
+ confirmations: 1,
182
+ ...(role === 'developer' ? { testnet: true, network: 'base-sepolia' } : {}),
183
+ ...(role === 'agent-operator' ? { autoApprove: false, maxEscrow: '100' } : {}),
184
+ ...(role === 'business' ? { webhookUrl: '', autoSettle: true } : {}),
185
+ },
186
+ };
187
+
188
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
189
+
190
+ // Save credentials
191
+ const credentials = [
192
+ `# Pay Lobster credentials`,
193
+ `# Generated: ${new Date().toISOString()}`,
194
+ `PAYLOBSTER_API_KEY=${apiKey}`,
195
+ `PAYLOBSTER_WALLET=${walletAddress}`,
196
+ `PAYLOBSTER_NETWORK=${network}`,
197
+ ].join('\n');
198
+
199
+ fs.writeFileSync(CREDENTIALS_FILE, credentials, 'utf-8');
200
+ fs.chmodSync(CREDENTIALS_FILE, 0o600);
201
+
202
+ spinner.succeed(chalk.green('All set!'));
203
+
204
+ // ─── Summary ───
205
+ console.log();
206
+ console.log(chalk.cyan(' ┌─────────────────────────────────────────┐'));
207
+ console.log(chalk.cyan(' │') + chalk.bold.white(' ✅ You\'re ready to go! ') + chalk.cyan('│'));
208
+ console.log(chalk.cyan(' └─────────────────────────────────────────┘'));
209
+ console.log();
210
+ console.log(chalk.white(' Config saved to: ') + chalk.gray(CONFIG_FILE));
211
+ console.log(chalk.white(' API key saved to: ') + chalk.gray(CREDENTIALS_FILE));
212
+ console.log(chalk.white(' API key: ') + chalk.yellow(apiKey.slice(0, 12) + '...' + apiKey.slice(-4)));
213
+ console.log();
214
+
215
+ // ─── Next Steps (role-specific) ───
216
+ console.log(chalk.bold.white(' 🚀 Try your first command:\n'));
217
+
218
+ if (role === 'developer') {
219
+ console.log(chalk.gray(' # Create a test escrow'));
220
+ console.log(chalk.cyan(' paylobster escrow create --to 0x1234...abcd --amount 1 --desc "Test payment"'));
221
+ console.log();
222
+ console.log(chalk.gray(' # Check your reputation'));
223
+ console.log(chalk.cyan(' paylobster reputation check'));
224
+ console.log();
225
+ console.log(chalk.gray(' # View SDK docs'));
226
+ console.log(chalk.cyan(' paylobster docs --open'));
227
+ } else if (role === 'agent-operator') {
228
+ console.log(chalk.gray(' # Register your AI agent'));
229
+ console.log(chalk.cyan(' paylobster register --name "My Agent" --type ai-agent'));
230
+ console.log();
231
+ console.log(chalk.gray(' # Create an escrow payment'));
232
+ console.log(chalk.cyan(' paylobster escrow create --to 0x1234...abcd --amount 5 --desc "Agent task"'));
233
+ console.log();
234
+ console.log(chalk.gray(' # Set up MCP server'));
235
+ console.log(chalk.cyan(' paylobster quickstart --mcp'));
236
+ } else if (role === 'business') {
237
+ console.log(chalk.gray(' # Create a payment link'));
238
+ console.log(chalk.cyan(' paylobster pay create-link --amount 50 --desc "Invoice #001"'));
239
+ console.log();
240
+ console.log(chalk.gray(' # Check incoming payments'));
241
+ console.log(chalk.cyan(' paylobster dashboard'));
242
+ console.log();
243
+ console.log(chalk.gray(' # View your balance'));
244
+ console.log(chalk.cyan(' paylobster status'));
245
+ }
246
+
247
+ console.log();
248
+ console.log(chalk.gray(' ─────────────────────────────────────────'));
249
+ console.log(chalk.gray(' 📖 Docs: ') + chalk.blue('https://paylobster.com/docs'));
250
+ console.log(chalk.gray(' 💬 Support: ') + chalk.blue('https://paylobster.com/discord'));
251
+ console.log(chalk.gray(' 🦞 Web app: ') + chalk.blue('https://paylobster.com'));
252
+ console.log();
253
+ });
254
+
255
+ return cmd;
256
+ }
@@ -0,0 +1,240 @@
1
+ import { Command } from 'commander';
2
+ import { success, error, info, withSpinner, outputJSON, formatAddress } from '../lib/display';
3
+ import type { OutputOptions } from '../lib/types';
4
+
5
+ const API_URL = process.env.PAYLOBSTER_API_URL || 'https://paylobster.com';
6
+
7
+ interface PaymentLink {
8
+ id: string;
9
+ url: string;
10
+ shortUrl: string;
11
+ amount: string;
12
+ token: string;
13
+ memo: string;
14
+ recipient: string;
15
+ mode: 'direct' | 'escrow';
16
+ expiresAt: string | null;
17
+ createdAt: string;
18
+ paid?: boolean;
19
+ txHash?: string | null;
20
+ }
21
+
22
+ /**
23
+ * Payment links command
24
+ */
25
+ export function createLinkCommand(): Command {
26
+ const cmd = new Command('link')
27
+ .description('Create and manage payment links');
28
+
29
+ // Create subcommand
30
+ cmd
31
+ .command('create')
32
+ .description('Create a payment link')
33
+ .requiredOption('--amount <amount>', 'Amount in tokens')
34
+ .requiredOption('--recipient <address>', 'Recipient address')
35
+ .option('--memo <text>', 'Payment memo/description')
36
+ .option('--token <symbol>', 'Token symbol', 'USDC')
37
+ .option('--mode <mode>', 'Payment mode: direct or escrow', 'direct')
38
+ .option('--expires-in <seconds>', 'Expiration time in seconds')
39
+ .option('--json', 'Output as JSON')
40
+ .action(async (options: {
41
+ amount: string;
42
+ recipient: string;
43
+ memo?: string;
44
+ token?: string;
45
+ mode?: string;
46
+ expiresIn?: string;
47
+ } & OutputOptions) => {
48
+ try {
49
+ // Validate recipient address
50
+ if (!options.recipient.startsWith('0x') || options.recipient.length !== 42) {
51
+ error('Invalid recipient address format');
52
+ process.exit(1);
53
+ }
54
+
55
+ // Validate amount
56
+ const amount = parseFloat(options.amount);
57
+ if (isNaN(amount) || amount <= 0) {
58
+ error('Invalid amount. Must be a positive number');
59
+ process.exit(1);
60
+ }
61
+
62
+ // Validate mode
63
+ if (options.mode && !['direct', 'escrow'].includes(options.mode)) {
64
+ error('Mode must be either "direct" or "escrow"');
65
+ process.exit(1);
66
+ }
67
+
68
+ const link = await withSpinner(
69
+ 'Creating payment link...',
70
+ async () => {
71
+ const response = await fetch(`${API_URL}/api/v3/links`, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ },
76
+ body: JSON.stringify({
77
+ amount: options.amount,
78
+ recipient: options.recipient,
79
+ memo: options.memo,
80
+ token: options.token,
81
+ mode: options.mode,
82
+ expiresIn: options.expiresIn ? parseInt(options.expiresIn) : undefined,
83
+ }),
84
+ });
85
+
86
+ if (!response.ok) {
87
+ const err = await response.json() as { error?: string };
88
+ throw new Error(err.error || 'Failed to create payment link');
89
+ }
90
+
91
+ return response.json() as Promise<PaymentLink>;
92
+ }
93
+ );
94
+
95
+ if (outputJSON(link, options)) {
96
+ return;
97
+ }
98
+
99
+ success('Payment link created!');
100
+ console.log();
101
+ console.log(' Link ID: ', link.id);
102
+ console.log(' URL: ', link.url);
103
+ console.log(' Amount: ', link.amount, link.token);
104
+ console.log(' Recipient: ', formatAddress(link.recipient));
105
+ console.log(' Mode: ', link.mode);
106
+ if (link.memo) {
107
+ console.log(' Memo: ', link.memo);
108
+ }
109
+ if (link.expiresAt) {
110
+ console.log(' Expires: ', new Date(link.expiresAt).toLocaleString());
111
+ }
112
+ console.log();
113
+ info('Share this link with the payer!');
114
+ } catch (err) {
115
+ error(`Failed to create payment link: ${(err as Error).message}`);
116
+ process.exit(1);
117
+ }
118
+ });
119
+
120
+ // Get subcommand
121
+ cmd
122
+ .command('get <code>')
123
+ .description('Get payment link details')
124
+ .option('--json', 'Output as JSON')
125
+ .action(async (code: string, options: OutputOptions) => {
126
+ try {
127
+ const link = await withSpinner(
128
+ 'Fetching payment link...',
129
+ async () => {
130
+ const response = await fetch(`${API_URL}/api/v3/links/${code}`, {
131
+ method: 'GET',
132
+ headers: {
133
+ 'Content-Type': 'application/json',
134
+ },
135
+ });
136
+
137
+ if (!response.ok) {
138
+ const err = await response.json() as { error?: string };
139
+ throw new Error(err.error || 'Failed to fetch payment link');
140
+ }
141
+
142
+ return response.json() as Promise<PaymentLink & { expired?: boolean }>;
143
+ }
144
+ );
145
+
146
+ if (outputJSON(link, options)) {
147
+ return;
148
+ }
149
+
150
+ console.log();
151
+ console.log('Payment Link Details:');
152
+ console.log(' Link ID: ', link.id);
153
+ console.log(' Amount: ', link.amount, link.token);
154
+ console.log(' Recipient: ', formatAddress(link.recipient));
155
+ console.log(' Mode: ', link.mode);
156
+ console.log(' Status: ', link.paid ? '✅ Paid' : link.expired ? '❌ Expired' : '⏳ Pending');
157
+ if (link.memo) {
158
+ console.log(' Memo: ', link.memo);
159
+ }
160
+ if (link.txHash) {
161
+ console.log(' TX Hash: ', link.txHash);
162
+ }
163
+ console.log(' Created: ', new Date(link.createdAt).toLocaleString());
164
+ if (link.expiresAt) {
165
+ console.log(' Expires: ', new Date(link.expiresAt).toLocaleString());
166
+ }
167
+ console.log();
168
+ } catch (err) {
169
+ error(`Failed to fetch payment link: ${(err as Error).message}`);
170
+ process.exit(1);
171
+ }
172
+ });
173
+
174
+ // List subcommand
175
+ cmd
176
+ .command('list <recipient>')
177
+ .description('List payment links for a recipient')
178
+ .option('--json', 'Output as JSON')
179
+ .action(async (recipient: string, options: OutputOptions) => {
180
+ try {
181
+ // Validate recipient address
182
+ if (!recipient.startsWith('0x') || recipient.length !== 42) {
183
+ error('Invalid recipient address format');
184
+ process.exit(1);
185
+ }
186
+
187
+ const result = await withSpinner(
188
+ 'Fetching payment links...',
189
+ async () => {
190
+ const response = await fetch(
191
+ `${API_URL}/api/v3/links?recipient=${encodeURIComponent(recipient)}`,
192
+ {
193
+ method: 'GET',
194
+ headers: {
195
+ 'Content-Type': 'application/json',
196
+ },
197
+ }
198
+ );
199
+
200
+ if (!response.ok) {
201
+ const err = await response.json() as { error?: string };
202
+ throw new Error(err.error || 'Failed to fetch payment links');
203
+ }
204
+
205
+ return response.json() as Promise<{ links: PaymentLink[]; total: number }>;
206
+ }
207
+ );
208
+
209
+ if (outputJSON(result, options)) {
210
+ return;
211
+ }
212
+
213
+ if (result.links.length === 0) {
214
+ info('No payment links found for this recipient');
215
+ return;
216
+ }
217
+
218
+ console.log();
219
+ console.log(`Found ${result.total} payment link(s):`);
220
+ console.log();
221
+
222
+ for (const link of result.links) {
223
+ console.log(` ${link.id}`);
224
+ console.log(` Amount: ${link.amount} ${link.token}`);
225
+ console.log(` Status: ${link.paid ? '✅ Paid' : '⏳ Pending'}`);
226
+ console.log(` Mode: ${link.mode}`);
227
+ if (link.memo) {
228
+ console.log(` Memo: ${link.memo}`);
229
+ }
230
+ console.log(` Created: ${new Date(link.createdAt).toLocaleString()}`);
231
+ console.log();
232
+ }
233
+ } catch (err) {
234
+ error(`Failed to fetch payment links: ${(err as Error).message}`);
235
+ process.exit(1);
236
+ }
237
+ });
238
+
239
+ return cmd;
240
+ }