@paylobster/cli 4.0.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.
Files changed (89) hide show
  1. package/BUILD_SUMMARY.md +429 -0
  2. package/CHANGELOG.md +78 -0
  3. package/CONTRIBUTING.md +368 -0
  4. package/EXAMPLES.md +432 -0
  5. package/LICENSE +21 -0
  6. package/QUICKSTART.md +189 -0
  7. package/README.md +377 -0
  8. package/TEST_REPORT.md +191 -0
  9. package/bin/plob.js +9 -0
  10. package/bin/plob.ts +9 -0
  11. package/demo.sh +154 -0
  12. package/dist/bin/plob.d.ts +7 -0
  13. package/dist/bin/plob.d.ts.map +1 -0
  14. package/dist/bin/plob.js +10 -0
  15. package/dist/bin/plob.js.map +1 -0
  16. package/dist/src/commands/auth.d.ts +3 -0
  17. package/dist/src/commands/auth.d.ts.map +1 -0
  18. package/dist/src/commands/auth.js +75 -0
  19. package/dist/src/commands/auth.js.map +1 -0
  20. package/dist/src/commands/config.d.ts +3 -0
  21. package/dist/src/commands/config.d.ts.map +1 -0
  22. package/dist/src/commands/config.js +79 -0
  23. package/dist/src/commands/config.js.map +1 -0
  24. package/dist/src/commands/escrow.d.ts +3 -0
  25. package/dist/src/commands/escrow.d.ts.map +1 -0
  26. package/dist/src/commands/escrow.js +193 -0
  27. package/dist/src/commands/escrow.js.map +1 -0
  28. package/dist/src/commands/mandate.d.ts +8 -0
  29. package/dist/src/commands/mandate.d.ts.map +1 -0
  30. package/dist/src/commands/mandate.js +54 -0
  31. package/dist/src/commands/mandate.js.map +1 -0
  32. package/dist/src/commands/pay.d.ts +6 -0
  33. package/dist/src/commands/pay.d.ts.map +1 -0
  34. package/dist/src/commands/pay.js +77 -0
  35. package/dist/src/commands/pay.js.map +1 -0
  36. package/dist/src/commands/register.d.ts +3 -0
  37. package/dist/src/commands/register.d.ts.map +1 -0
  38. package/dist/src/commands/register.js +51 -0
  39. package/dist/src/commands/register.js.map +1 -0
  40. package/dist/src/commands/reputation.d.ts +3 -0
  41. package/dist/src/commands/reputation.d.ts.map +1 -0
  42. package/dist/src/commands/reputation.js +116 -0
  43. package/dist/src/commands/reputation.js.map +1 -0
  44. package/dist/src/commands/status.d.ts +3 -0
  45. package/dist/src/commands/status.d.ts.map +1 -0
  46. package/dist/src/commands/status.js +82 -0
  47. package/dist/src/commands/status.js.map +1 -0
  48. package/dist/src/index.d.ts +3 -0
  49. package/dist/src/index.d.ts.map +1 -0
  50. package/dist/src/index.js +59 -0
  51. package/dist/src/index.js.map +1 -0
  52. package/dist/src/lib/config.d.ts +26 -0
  53. package/dist/src/lib/config.d.ts.map +1 -0
  54. package/dist/src/lib/config.js +91 -0
  55. package/dist/src/lib/config.js.map +1 -0
  56. package/dist/src/lib/contracts.d.ts +18798 -0
  57. package/dist/src/lib/contracts.d.ts.map +1 -0
  58. package/dist/src/lib/contracts.js +361 -0
  59. package/dist/src/lib/contracts.js.map +1 -0
  60. package/dist/src/lib/display.d.ts +83 -0
  61. package/dist/src/lib/display.d.ts.map +1 -0
  62. package/dist/src/lib/display.js +293 -0
  63. package/dist/src/lib/display.js.map +1 -0
  64. package/dist/src/lib/types.d.ts +49 -0
  65. package/dist/src/lib/types.d.ts.map +1 -0
  66. package/dist/src/lib/types.js +3 -0
  67. package/dist/src/lib/types.js.map +1 -0
  68. package/dist/src/lib/wallet.d.ts +30 -0
  69. package/dist/src/lib/wallet.d.ts.map +1 -0
  70. package/dist/src/lib/wallet.js +143 -0
  71. package/dist/src/lib/wallet.js.map +1 -0
  72. package/jest.config.js +15 -0
  73. package/package.json +55 -0
  74. package/src/__tests__/cli.test.ts +38 -0
  75. package/src/commands/auth.ts +75 -0
  76. package/src/commands/config.ts +84 -0
  77. package/src/commands/escrow.ts +222 -0
  78. package/src/commands/mandate.ts +56 -0
  79. package/src/commands/pay.ts +96 -0
  80. package/src/commands/register.ts +57 -0
  81. package/src/commands/reputation.ts +84 -0
  82. package/src/commands/status.ts +91 -0
  83. package/src/index.ts +63 -0
  84. package/src/lib/config.ts +90 -0
  85. package/src/lib/contracts.ts +392 -0
  86. package/src/lib/display.ts +265 -0
  87. package/src/lib/types.ts +57 -0
  88. package/src/lib/wallet.ts +146 -0
  89. package/tsconfig.json +21 -0
@@ -0,0 +1,265 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import type { OutputOptions } from './types';
4
+
5
+ /**
6
+ * Print JSON output if --json flag is set
7
+ */
8
+ export function outputJSON(data: any, options?: OutputOptions): boolean {
9
+ if (options?.json) {
10
+ console.log(JSON.stringify(data, null, 2));
11
+ return true;
12
+ }
13
+ return false;
14
+ }
15
+
16
+ /**
17
+ * Print success message
18
+ */
19
+ export function success(message: string): void {
20
+ console.log(chalk.green('✅ ' + message));
21
+ }
22
+
23
+ /**
24
+ * Print error message
25
+ */
26
+ export function error(message: string): void {
27
+ console.error(chalk.red('❌ ' + message));
28
+ }
29
+
30
+ /**
31
+ * Print warning message
32
+ */
33
+ export function warning(message: string): void {
34
+ console.warn(chalk.yellow('⚠️ ' + message));
35
+ }
36
+
37
+ /**
38
+ * Print info message
39
+ */
40
+ export function info(message: string): void {
41
+ console.log(chalk.blue('ℹ️ ' + message));
42
+ }
43
+
44
+ /**
45
+ * Print section header
46
+ */
47
+ export function header(text: string): void {
48
+ console.log();
49
+ console.log(chalk.bold.cyan('🦞 ' + text));
50
+ console.log(chalk.gray('─'.repeat(text.length + 3)));
51
+ }
52
+
53
+ /**
54
+ * Create a table
55
+ */
56
+ export function createTable(options?: any): Table.Table {
57
+ return new Table({
58
+ chars: {
59
+ top: '─',
60
+ 'top-mid': '┬',
61
+ 'top-left': '┌',
62
+ 'top-right': '┐',
63
+ bottom: '─',
64
+ 'bottom-mid': '┴',
65
+ 'bottom-left': '└',
66
+ 'bottom-right': '┘',
67
+ left: '│',
68
+ 'left-mid': '├',
69
+ mid: '─',
70
+ 'mid-mid': '┼',
71
+ right: '│',
72
+ 'right-mid': '┤',
73
+ middle: '│',
74
+ },
75
+ style: {
76
+ head: ['cyan', 'bold'],
77
+ border: ['gray'],
78
+ },
79
+ ...options,
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Print key-value pairs
85
+ */
86
+ export function printKeyValue(data: Record<string, any>): void {
87
+ const maxKeyLength = Math.max(...Object.keys(data).map(k => k.length));
88
+
89
+ for (const [key, value] of Object.entries(data)) {
90
+ const paddedKey = key.padEnd(maxKeyLength);
91
+ console.log(chalk.gray(paddedKey + ':'), chalk.white(value));
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Format address (shorten)
97
+ */
98
+ export function formatAddress(address: string): string {
99
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
100
+ }
101
+
102
+ /**
103
+ * Format status with color
104
+ */
105
+ export function formatStatus(status: number | string): string {
106
+ const statusMap: Record<string, { text: string; color: any }> = {
107
+ 0: { text: 'Pending', color: chalk.yellow },
108
+ 1: { text: 'Active', color: chalk.blue },
109
+ 2: { text: 'Released', color: chalk.green },
110
+ 3: { text: 'Disputed', color: chalk.red },
111
+ pending: { text: 'Pending', color: chalk.yellow },
112
+ active: { text: 'Active', color: chalk.blue },
113
+ released: { text: 'Released', color: chalk.green },
114
+ disputed: { text: 'Disputed', color: chalk.red },
115
+ };
116
+
117
+ const statusKey = status.toString();
118
+ const statusInfo = statusMap[statusKey] || { text: status.toString(), color: chalk.white };
119
+
120
+ return statusInfo.color(statusInfo.text);
121
+ }
122
+
123
+ /**
124
+ * Format boolean as emoji
125
+ */
126
+ export function formatBoolean(value: boolean): string {
127
+ return value ? chalk.green('✅') : chalk.red('❌');
128
+ }
129
+
130
+ /**
131
+ * Format number with commas
132
+ */
133
+ export function formatNumber(num: number | string): string {
134
+ return Number(num).toLocaleString();
135
+ }
136
+
137
+ /**
138
+ * Print a horizontal divider
139
+ */
140
+ export function divider(): void {
141
+ console.log(chalk.gray('─'.repeat(50)));
142
+ }
143
+
144
+ /**
145
+ * Print agent status header
146
+ */
147
+ export function printAgentHeader(name: string, address: string): void {
148
+ console.log();
149
+ console.log(chalk.bold.cyan('🦞 PayLobster Agent Status'));
150
+ divider();
151
+ console.log(chalk.gray('Name: ') + chalk.white.bold(name));
152
+ console.log(chalk.gray('Address: ') + chalk.white(address));
153
+ divider();
154
+ }
155
+
156
+ /**
157
+ * Print escrow table
158
+ */
159
+ export function printEscrowTable(escrows: any[]): void {
160
+ if (escrows.length === 0) {
161
+ info('No escrows found');
162
+ return;
163
+ }
164
+
165
+ const table = createTable({
166
+ head: ['ID', 'From', 'To', 'Amount', 'Status'],
167
+ });
168
+
169
+ for (const escrow of escrows) {
170
+ table.push([
171
+ escrow.id,
172
+ formatAddress(escrow.from),
173
+ formatAddress(escrow.to),
174
+ escrow.amount,
175
+ formatStatus(escrow.status),
176
+ ]);
177
+ }
178
+
179
+ console.log(table.toString());
180
+ }
181
+
182
+ /**
183
+ * Spinner/loading indicator using ora
184
+ */
185
+ export async function withSpinner<T>(
186
+ text: string,
187
+ fn: () => Promise<T>
188
+ ): Promise<T> {
189
+ const ora = (await import('ora')).default;
190
+ const spinner = ora(text).start();
191
+
192
+ try {
193
+ const result = await fn();
194
+ spinner.succeed();
195
+ return result;
196
+ } catch (error) {
197
+ spinner.fail();
198
+ throw error;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Format network name
204
+ */
205
+ export function formatNetwork(network: string): string {
206
+ const networkMap: Record<string, string> = {
207
+ mainnet: 'Base Mainnet',
208
+ sepolia: 'Base Sepolia',
209
+ };
210
+ return networkMap[network] || network;
211
+ }
212
+
213
+ /**
214
+ * Print configuration table
215
+ */
216
+ export function printConfig(config: Record<string, any>): void {
217
+ const table = createTable({
218
+ head: ['Setting', 'Value'],
219
+ });
220
+
221
+ for (const [key, value] of Object.entries(config)) {
222
+ if (key === 'wallet' && typeof value === 'object') {
223
+ table.push([key, value.type || 'env']);
224
+ } else {
225
+ table.push([key, value?.toString() || 'not set']);
226
+ }
227
+ }
228
+
229
+ console.log(table.toString());
230
+ }
231
+
232
+ /**
233
+ * Print reputation score with visual indicator
234
+ */
235
+ export function printReputation(score: number): string {
236
+ const stars = Math.floor(score / 20); // Convert 0-100 to 0-5 stars
237
+ const fullStar = '★';
238
+ const emptyStar = '☆';
239
+
240
+ const starsStr = fullStar.repeat(stars) + emptyStar.repeat(5 - stars);
241
+
242
+ if (score >= 80) {
243
+ return chalk.green(starsStr + ` ${score}`);
244
+ } else if (score >= 60) {
245
+ return chalk.yellow(starsStr + ` ${score}`);
246
+ } else {
247
+ return chalk.red(starsStr + ` ${score}`);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Confirm action (simple yes/no prompt)
253
+ */
254
+ export async function confirm(message: string): Promise<boolean> {
255
+ const inquirer = (await import('inquirer')).default;
256
+ const { confirmed } = await inquirer.prompt([
257
+ {
258
+ type: 'confirm',
259
+ name: 'confirmed',
260
+ message,
261
+ default: false,
262
+ },
263
+ ]);
264
+ return confirmed;
265
+ }
@@ -0,0 +1,57 @@
1
+ import type { Address } from 'viem';
2
+
3
+ export type Network = 'mainnet' | 'sepolia';
4
+
5
+ export interface CLIConfig {
6
+ network: Network;
7
+ rpcUrl?: string;
8
+ wallet?: {
9
+ type: 'private-key' | 'keystore' | 'env';
10
+ path?: string;
11
+ };
12
+ indexerUrl?: string;
13
+ }
14
+
15
+ export interface AgentInfo {
16
+ name: string;
17
+ tokenId: bigint;
18
+ registered: boolean;
19
+ address: Address;
20
+ }
21
+
22
+ export interface Reputation {
23
+ score: bigint;
24
+ trustVector: bigint;
25
+ }
26
+
27
+ export interface CreditStatus {
28
+ limit: bigint;
29
+ available: bigint;
30
+ inUse: bigint;
31
+ }
32
+
33
+ export interface EscrowInfo {
34
+ sender: Address;
35
+ recipient: Address;
36
+ amount: bigint;
37
+ token: Address;
38
+ status: number; // 0=pending, 1=active, 2=released, 3=disputed
39
+ }
40
+
41
+ export interface Balance {
42
+ usdc: bigint;
43
+ eth: bigint;
44
+ }
45
+
46
+ export interface ServiceListing {
47
+ serviceId: string;
48
+ provider: Address;
49
+ name: string;
50
+ category: string;
51
+ price: bigint;
52
+ reputation: number;
53
+ }
54
+
55
+ export interface OutputOptions {
56
+ json?: boolean;
57
+ }
@@ -0,0 +1,146 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { createWalletClient, http, type WalletClient, type Account } from 'viem';
5
+ import { privateKeyToAccount } from 'viem/accounts';
6
+ import { base, baseSepolia } from 'viem/chains';
7
+ import { loadConfig, getRpcUrl } from './config';
8
+ import type { Network } from './types';
9
+
10
+ const KEYSTORE_DIR = path.join(os.homedir(), '.plob', 'keystore');
11
+
12
+ /**
13
+ * Load wallet from configured source
14
+ */
15
+ export async function loadWallet(): Promise<{ client: WalletClient; account: Account }> {
16
+ const config = loadConfig();
17
+ const network = config.network;
18
+ const chain = network === 'mainnet' ? base : baseSepolia;
19
+
20
+ let privateKey: `0x${string}`;
21
+
22
+ // Try different sources in order:
23
+ // 1. Environment variable PRIVATE_KEY or PAYLOBSTER_PRIVATE_KEY
24
+ // 2. Keystore file
25
+ // 3. Config file (not recommended for production)
26
+
27
+ if (process.env.PRIVATE_KEY) {
28
+ privateKey = process.env.PRIVATE_KEY as `0x${string}`;
29
+ } else if (process.env.PAYLOBSTER_PRIVATE_KEY) {
30
+ privateKey = process.env.PAYLOBSTER_PRIVATE_KEY as `0x${string}`;
31
+ } else if (config.wallet?.type === 'keystore' && config.wallet.path) {
32
+ privateKey = loadFromKeystore(config.wallet.path);
33
+ } else {
34
+ throw new Error(
35
+ 'No wallet configured. Run `plob auth --private-key <key>` or set PRIVATE_KEY env var'
36
+ );
37
+ }
38
+
39
+ // Validate private key format
40
+ if (!privateKey.startsWith('0x') || privateKey.length !== 66) {
41
+ throw new Error('Invalid private key format. Must be 0x-prefixed 64-character hex string');
42
+ }
43
+
44
+ const account = privateKeyToAccount(privateKey);
45
+
46
+ const client = createWalletClient({
47
+ account,
48
+ chain,
49
+ transport: http(getRpcUrl(network)),
50
+ });
51
+
52
+ return { client, account };
53
+ }
54
+
55
+ /**
56
+ * Save private key to keystore (encrypted)
57
+ * Note: For production, use a proper keystore with password encryption
58
+ */
59
+ export function saveToKeystore(privateKey: string, filename?: string): string {
60
+ try {
61
+ if (!fs.existsSync(KEYSTORE_DIR)) {
62
+ fs.mkdirSync(KEYSTORE_DIR, { recursive: true, mode: 0o700 });
63
+ }
64
+
65
+ const keystoreFile = filename || 'default.key';
66
+ const keystorePath = path.join(KEYSTORE_DIR, keystoreFile);
67
+
68
+ // WARNING: This is a simple file storage. For production, use proper encryption!
69
+ fs.writeFileSync(keystorePath, privateKey, { mode: 0o600 });
70
+
71
+ return keystorePath;
72
+ } catch (error) {
73
+ throw new Error(`Failed to save keystore: ${error}`);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Load private key from keystore
79
+ */
80
+ export function loadFromKeystore(filename: string): `0x${string}` {
81
+ try {
82
+ let keystorePath: string;
83
+
84
+ if (path.isAbsolute(filename)) {
85
+ keystorePath = filename;
86
+ } else {
87
+ keystorePath = path.join(KEYSTORE_DIR, filename);
88
+ }
89
+
90
+ if (!fs.existsSync(keystorePath)) {
91
+ throw new Error(`Keystore file not found: ${keystorePath}`);
92
+ }
93
+
94
+ const privateKey = fs.readFileSync(keystorePath, 'utf-8').trim();
95
+ return privateKey as `0x${string}`;
96
+ } catch (error) {
97
+ throw new Error(`Failed to load keystore: ${error}`);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Delete keystore file
103
+ */
104
+ export function deleteKeystore(filename: string): void {
105
+ const keystorePath = path.join(KEYSTORE_DIR, filename);
106
+ if (fs.existsSync(keystorePath)) {
107
+ fs.unlinkSync(keystorePath);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * List all keystore files
113
+ */
114
+ export function listKeystores(): string[] {
115
+ if (!fs.existsSync(KEYSTORE_DIR)) {
116
+ return [];
117
+ }
118
+ return fs.readdirSync(KEYSTORE_DIR).filter(file => file.endsWith('.key'));
119
+ }
120
+
121
+ /**
122
+ * Get wallet address without loading full client
123
+ */
124
+ export function getWalletAddress(): string {
125
+ try {
126
+ let privateKey: `0x${string}`;
127
+
128
+ if (process.env.PRIVATE_KEY) {
129
+ privateKey = process.env.PRIVATE_KEY as `0x${string}`;
130
+ } else if (process.env.PAYLOBSTER_PRIVATE_KEY) {
131
+ privateKey = process.env.PAYLOBSTER_PRIVATE_KEY as `0x${string}`;
132
+ } else {
133
+ const config = loadConfig();
134
+ if (config.wallet?.type === 'keystore' && config.wallet.path) {
135
+ privateKey = loadFromKeystore(config.wallet.path);
136
+ } else {
137
+ throw new Error('No wallet configured');
138
+ }
139
+ }
140
+
141
+ const account = privateKeyToAccount(privateKey);
142
+ return account.address;
143
+ } catch (error) {
144
+ throw new Error(`Failed to get wallet address: ${error}`);
145
+ }
146
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022", "DOM"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "moduleResolution": "node",
17
+ "types": ["node"]
18
+ },
19
+ "include": ["src/**/*", "bin/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }