@paylobster/cli 4.3.1 → 4.5.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 (50) hide show
  1. package/README.md +82 -40
  2. package/dist/src/commands/dashboard.d.ts +3 -0
  3. package/dist/src/commands/dashboard.d.ts.map +1 -0
  4. package/dist/src/commands/dashboard.js +67 -0
  5. package/dist/src/commands/dashboard.js.map +1 -0
  6. package/dist/src/commands/export.d.ts +3 -0
  7. package/dist/src/commands/export.d.ts.map +1 -0
  8. package/dist/src/commands/export.js +97 -0
  9. package/dist/src/commands/export.js.map +1 -0
  10. package/dist/src/commands/health.d.ts +3 -0
  11. package/dist/src/commands/health.d.ts.map +1 -0
  12. package/dist/src/commands/health.js +165 -0
  13. package/dist/src/commands/health.js.map +1 -0
  14. package/dist/src/commands/invest.d.ts +3 -0
  15. package/dist/src/commands/invest.d.ts.map +1 -0
  16. package/dist/src/commands/invest.js +721 -0
  17. package/dist/src/commands/invest.js.map +1 -0
  18. package/dist/src/commands/quickstart.d.ts +3 -0
  19. package/dist/src/commands/quickstart.d.ts.map +1 -0
  20. package/dist/src/commands/quickstart.js +138 -0
  21. package/dist/src/commands/quickstart.js.map +1 -0
  22. package/dist/src/commands/search.d.ts +3 -0
  23. package/dist/src/commands/search.d.ts.map +1 -0
  24. package/dist/src/commands/search.js +126 -0
  25. package/dist/src/commands/search.js.map +1 -0
  26. package/dist/src/commands/trust-graph.d.ts +3 -0
  27. package/dist/src/commands/trust-graph.d.ts.map +1 -0
  28. package/dist/src/commands/trust-graph.js +282 -0
  29. package/dist/src/commands/trust-graph.js.map +1 -0
  30. package/dist/src/commands/whoami.d.ts +3 -0
  31. package/dist/src/commands/whoami.d.ts.map +1 -0
  32. package/dist/src/commands/whoami.js +160 -0
  33. package/dist/src/commands/whoami.js.map +1 -0
  34. package/dist/src/index.js +18 -2
  35. package/dist/src/index.js.map +1 -1
  36. package/dist/src/lib/contracts.d.ts +177 -23
  37. package/dist/src/lib/contracts.d.ts.map +1 -1
  38. package/dist/src/lib/contracts.js +151 -0
  39. package/dist/src/lib/contracts.js.map +1 -1
  40. package/package.json +4 -2
  41. package/src/commands/dashboard.ts +69 -0
  42. package/src/commands/export.ts +132 -0
  43. package/src/commands/health.ts +212 -0
  44. package/src/commands/invest.ts +810 -0
  45. package/src/commands/quickstart.ts +150 -0
  46. package/src/commands/search.ts +151 -0
  47. package/src/commands/trust-graph.ts +267 -0
  48. package/src/commands/whoami.ts +172 -0
  49. package/src/index.ts +18 -2
  50. package/src/lib/contracts.ts +159 -0
@@ -0,0 +1,150 @@
1
+ import { Command } from 'commander';
2
+ import { getAgentInfo, registerAgent } from '../lib/contracts';
3
+ import { getWalletAddress } from '../lib/wallet';
4
+ import { error, success } from '../lib/display';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import readline from 'readline';
8
+ import { exec } from 'child_process';
9
+ import { promisify } from 'util';
10
+
11
+ const execAsync = promisify(exec);
12
+
13
+ function prompt(question: string): Promise<string> {
14
+ const rl = readline.createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout,
17
+ });
18
+
19
+ return new Promise((resolve) => {
20
+ rl.question(question, (answer) => {
21
+ rl.close();
22
+ resolve(answer.trim());
23
+ });
24
+ });
25
+ }
26
+
27
+ export function createQuickstartCommand(): Command {
28
+ const cmd = new Command('quickstart')
29
+ .description('šŸš€ Interactive wizard to get started with PayLobster')
30
+ .action(async () => {
31
+ console.log(chalk.cyan.bold('\nšŸ¦ž PayLobster Quickstart Wizard\n'));
32
+ console.log(chalk.gray('Let\'s get you set up with PayLobster in just a few steps.\n'));
33
+
34
+ try {
35
+ // Step 1: Check wallet configuration
36
+ console.log(chalk.cyan('Step 1/5:'), chalk.bold('Wallet Configuration'));
37
+
38
+ let address: `0x${string}`;
39
+ try {
40
+ address = getWalletAddress() as `0x${string}`;
41
+ } catch (err) {
42
+ console.log(chalk.yellow('āš ļø No wallet configured yet.'));
43
+ console.log('Please run:', chalk.green('plob auth setup'));
44
+ console.log('Then come back and run', chalk.green('plob quickstart'), 'again.\n');
45
+ return;
46
+ }
47
+ console.log(chalk.green('āœ“'), 'Wallet configured:', chalk.gray(address));
48
+
49
+ // Check if already registered
50
+ const spinner = ora('Checking registration status...').start();
51
+ const agentInfo = await getAgentInfo(address);
52
+ spinner.stop();
53
+
54
+ if (agentInfo.registered) {
55
+ console.log(chalk.green('āœ“'), 'Already registered as:', chalk.bold(agentInfo.name));
56
+ console.log(chalk.yellow('\nYou\'re all set! Run'), chalk.green('plob whoami'), chalk.yellow('to see your profile.\n'));
57
+ return;
58
+ }
59
+
60
+ console.log(chalk.gray('Not registered yet. Let\'s continue...\n'));
61
+
62
+ // Step 2: Register agent identity
63
+ console.log(chalk.cyan('Step 2/5:'), chalk.bold('Agent Identity'));
64
+
65
+ const name = await prompt(chalk.white('Agent name: '));
66
+ if (!name) {
67
+ console.log(chalk.red('Name is required. Exiting.\n'));
68
+ return;
69
+ }
70
+
71
+ const capabilities = await prompt(chalk.white('Capabilities (comma-separated, e.g., "code review, testing, docs"): '));
72
+ if (!capabilities) {
73
+ console.log(chalk.red('Capabilities are required. Exiting.\n'));
74
+ return;
75
+ }
76
+
77
+ spinner.start('Registering agent identity...');
78
+ try {
79
+ const tokenId = await registerAgent(name, capabilities);
80
+ spinner.succeed(chalk.green('āœ“ Agent registered! Token ID: ') + chalk.bold(tokenId.toString()));
81
+ } catch (err: any) {
82
+ spinner.fail(chalk.red('Failed to register agent'));
83
+ error(`Registration error: ${err.message}`);
84
+ return;
85
+ }
86
+
87
+ // Step 3: Create treasury
88
+ console.log(chalk.cyan('\nStep 3/5:'), chalk.bold('Treasury Setup'));
89
+ console.log(chalk.gray('A treasury manages your funds with budgets and spending rules.'));
90
+
91
+ const createTreasury = await prompt(chalk.white('Create a treasury? (y/n): '));
92
+
93
+ if (createTreasury.toLowerCase() === 'y') {
94
+ const treasuryName = await prompt(chalk.white('Treasury name: ')) || `${name} Treasury`;
95
+
96
+ spinner.start('Creating treasury...');
97
+ try {
98
+ await execAsync(`plob treasury create --name "${treasuryName}"`);
99
+ spinner.succeed(chalk.green('āœ“ Treasury created: ') + chalk.bold(treasuryName));
100
+ } catch (err: any) {
101
+ spinner.fail(chalk.red('Failed to create treasury'));
102
+ console.log(chalk.yellow('You can create one later with:'), chalk.green('plob treasury create'));
103
+ }
104
+
105
+ // Step 4: Set default budget
106
+ console.log(chalk.cyan('\nStep 4/5:'), chalk.bold('Budget Allocation'));
107
+ console.log(chalk.gray('Recommended: 40% operations, 30% growth, 20% reserve, 10% rewards'));
108
+
109
+ const setBudget = await prompt(chalk.white('Use default budget allocation? (y/n): '));
110
+
111
+ if (setBudget.toLowerCase() === 'y') {
112
+ spinner.start('Setting budget...');
113
+ try {
114
+ await execAsync('plob treasury budget --operations 40 --growth 30 --reserve 20 --rewards 10');
115
+ spinner.succeed(chalk.green('āœ“ Budget configured'));
116
+ } catch (err: any) {
117
+ spinner.fail(chalk.red('Failed to set budget'));
118
+ console.log(chalk.yellow('You can set it later with:'), chalk.green('plob treasury budget'));
119
+ }
120
+ }
121
+ } else {
122
+ console.log(chalk.gray('Skipped treasury setup.'));
123
+ }
124
+
125
+ // Step 5: Summary
126
+ console.log(chalk.cyan('\nStep 5/5:'), chalk.bold('Summary'));
127
+ console.log(chalk.green.bold('\nšŸŽ‰ You\'re all set!\n'));
128
+
129
+ console.log(chalk.bold('What you created:'));
130
+ console.log(chalk.green(' āœ“'), 'Agent identity:', chalk.bold(name));
131
+ console.log(chalk.green(' āœ“'), 'Capabilities:', chalk.gray(capabilities));
132
+ if (createTreasury.toLowerCase() === 'y') {
133
+ console.log(chalk.green(' āœ“'), 'Treasury with budget allocation');
134
+ }
135
+
136
+ console.log(chalk.bold('\nNext steps:'));
137
+ console.log(chalk.cyan(' •'), 'View your profile:', chalk.green('plob whoami'));
138
+ console.log(chalk.cyan(' •'), 'Check system health:', chalk.green('plob health'));
139
+ console.log(chalk.cyan(' •'), 'Open dashboard:', chalk.green('plob dashboard'));
140
+ console.log(chalk.cyan(' •'), 'Make your first payment:', chalk.green('plob pay <address> <amount>'));
141
+ console.log();
142
+
143
+ } catch (err: any) {
144
+ error(`Quickstart failed: ${err.message}`);
145
+ process.exit(1);
146
+ }
147
+ });
148
+
149
+ return cmd;
150
+ }
@@ -0,0 +1,151 @@
1
+ import { Command } from 'commander';
2
+ import { getPublicClient, getContracts, getAgentInfo, getReputation } from '../lib/contracts';
3
+ import { loadConfig } from '../lib/config';
4
+ import { error, outputJSON } from '../lib/display';
5
+ import type { OutputOptions } from '../lib/types';
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import type { Address } from 'viem';
9
+
10
+ interface AgentSearchResult {
11
+ address: Address;
12
+ name: string;
13
+ capabilities: string;
14
+ reputationScore: number;
15
+ tokenId: string;
16
+ }
17
+
18
+ // ABI for AgentRegistered event
19
+ const AGENT_REGISTERED_EVENT = {
20
+ anonymous: false,
21
+ inputs: [
22
+ { indexed: true, name: 'agent', type: 'address' },
23
+ { indexed: true, name: 'tokenId', type: 'uint256' },
24
+ { indexed: false, name: 'name', type: 'string' },
25
+ { indexed: false, name: 'capabilities', type: 'string' },
26
+ ],
27
+ name: 'AgentRegistered',
28
+ type: 'event',
29
+ } as const;
30
+
31
+ export function createSearchCommand(): Command {
32
+ const cmd = new Command('search')
33
+ .description('šŸ” Search for agents by name or capability')
34
+ .argument('<query>', 'Search query (name or capability)')
35
+ .option('--json', 'Output as JSON')
36
+ .option('--limit <number>', 'Maximum number of results', '10')
37
+ .action(async (query: string, options: OutputOptions & { limit?: string }) => {
38
+ const spinner = ora('Searching for agents...').start();
39
+
40
+ try {
41
+ const config = loadConfig();
42
+ const client = getPublicClient(config.network);
43
+ const contracts = getContracts(config.network);
44
+ const limit = parseInt(options.limit || '10');
45
+
46
+ // Get AgentRegistered events
47
+ // Note: This will scan the entire chain history which may be slow
48
+ // In production, you'd want to use a subgraph or indexer
49
+ const logs = await client.getLogs({
50
+ address: contracts.IDENTITY,
51
+ event: AGENT_REGISTERED_EVENT,
52
+ fromBlock: 'earliest',
53
+ toBlock: 'latest',
54
+ });
55
+
56
+ spinner.text = 'Processing results...';
57
+
58
+ // Filter by query (case-insensitive)
59
+ const queryLower = query.toLowerCase();
60
+ const matchingLogs = logs.filter((log) => {
61
+ const name = (log.args.name || '').toLowerCase();
62
+ const capabilities = (log.args.capabilities || '').toLowerCase();
63
+ return name.includes(queryLower) || capabilities.includes(queryLower);
64
+ });
65
+
66
+ // Limit results
67
+ const limitedLogs = matchingLogs.slice(0, limit);
68
+
69
+ // Fetch reputation for each agent
70
+ const results: AgentSearchResult[] = await Promise.all(
71
+ limitedLogs.map(async (log) => {
72
+ const address = log.args.agent!;
73
+ const reputation = await getReputation(address);
74
+
75
+ return {
76
+ address,
77
+ name: log.args.name || 'Unknown',
78
+ capabilities: log.args.capabilities || 'None',
79
+ reputationScore: Number(reputation.score),
80
+ tokenId: log.args.tokenId?.toString() || '0',
81
+ };
82
+ })
83
+ );
84
+
85
+ spinner.stop();
86
+
87
+ // JSON output
88
+ if (outputJSON({
89
+ query,
90
+ results,
91
+ total: matchingLogs.length,
92
+ showing: results.length,
93
+ }, options)) {
94
+ return;
95
+ }
96
+
97
+ // Pretty output
98
+ console.log('\n' + chalk.cyan.bold('═══════════════════════════════════════════════════════════'));
99
+ console.log(chalk.cyan.bold(' šŸ” Agent Search'));
100
+ console.log(chalk.cyan.bold('═══════════════════════════════════════════════════════════\n'));
101
+
102
+ console.log(chalk.gray(' Query: '), chalk.white(query));
103
+ console.log(chalk.gray(' Results: '), chalk.white(`${results.length} / ${matchingLogs.length}`));
104
+ console.log();
105
+
106
+ if (results.length === 0) {
107
+ console.log(chalk.yellow(' No agents found matching your query.'));
108
+ console.log();
109
+ return;
110
+ }
111
+
112
+ // Display results
113
+ for (let i = 0; i < results.length; i++) {
114
+ const agent = results[i];
115
+
116
+ let reputationColor = chalk.red;
117
+ if (agent.reputationScore >= 80) reputationColor = chalk.green;
118
+ else if (agent.reputationScore >= 50) reputationColor = chalk.yellow;
119
+
120
+ console.log(chalk.bold(` ${i + 1}. ${agent.name}`), chalk.gray(`(#${agent.tokenId})`));
121
+ console.log(chalk.gray(' Address: '), chalk.dim(agent.address));
122
+ console.log(chalk.gray(' Reputation: '), reputationColor(agent.reputationScore.toString()));
123
+ console.log(chalk.gray(' Capabilities: '), chalk.white(agent.capabilities));
124
+ console.log();
125
+ }
126
+
127
+ if (matchingLogs.length > results.length) {
128
+ console.log(chalk.gray(` ... and ${matchingLogs.length - results.length} more results`));
129
+ console.log(chalk.gray(' Use --limit to see more'));
130
+ console.log();
131
+ }
132
+
133
+ console.log(chalk.cyan.bold('═══════════════════════════════════════════════════════════\n'));
134
+
135
+ } catch (err: any) {
136
+ spinner.stop();
137
+ error(`Search failed: ${err.message}`);
138
+
139
+ // Provide helpful error message for common issues
140
+ if (err.message.includes('block range')) {
141
+ console.log(chalk.yellow('\nāš ļø The search requires scanning many blocks.'));
142
+ console.log(chalk.gray('This may be slow or hit rate limits with public RPCs.'));
143
+ console.log(chalk.gray('Consider using a dedicated RPC endpoint or subgraph.\n'));
144
+ }
145
+
146
+ process.exit(1);
147
+ }
148
+ });
149
+
150
+ return cmd;
151
+ }
@@ -0,0 +1,267 @@
1
+ import { Command } from 'commander';
2
+ import { getTrustGraph } from '../lib/contracts';
3
+ import { error, success, outputJSON, header, info } from '../lib/display';
4
+ import type { OutputOptions } from '../lib/types';
5
+ import type { Address } from 'viem';
6
+
7
+ export function createTrustGraphCommand(): Command {
8
+ const cmd = new Command('trust')
9
+ .description('Manage and query trust network')
10
+ .addHelpText('after', `
11
+ Examples:
12
+ $ plob trust endorse 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb 85
13
+ $ plob trust revoke 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
14
+ $ plob trust check 0xAlice 0xBob
15
+ $ plob trust score 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
16
+ $ plob trust endorsements 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
17
+ `);
18
+
19
+ // Endorse agent
20
+ cmd
21
+ .command('endorse')
22
+ .description('Endorse another agent with a trust level')
23
+ .argument('<address>', 'Agent address to endorse')
24
+ .argument('<score>', 'Trust score (1-100)')
25
+ .option('--json', 'Output as JSON')
26
+ .action(async (addressArg: string, scoreArg: string, options: OutputOptions) => {
27
+ try {
28
+ const address = addressArg as Address;
29
+ const score = parseInt(scoreArg);
30
+
31
+ if (score < 1 || score > 100) {
32
+ error('Trust score must be between 1 and 100');
33
+ process.exit(1);
34
+ }
35
+
36
+ const trustGraph = getTrustGraph();
37
+
38
+ info(`Endorsing ${address} with trust level ${score}...`);
39
+ const hash = await trustGraph.endorseAgent(address, score);
40
+
41
+ if (outputJSON({ transactionHash: hash, endorsed: address, trustLevel: score }, options)) {
42
+ return;
43
+ }
44
+
45
+ success(`Endorsed ${address} with trust level ${score}/100`);
46
+ console.log(`Transaction: ${hash}`);
47
+ } catch (err) {
48
+ error(`Failed to endorse agent: ${err}`);
49
+ process.exit(1);
50
+ }
51
+ });
52
+
53
+ // Revoke endorsement
54
+ cmd
55
+ .command('revoke')
56
+ .description('Revoke an endorsement')
57
+ .argument('<address>', 'Agent address to revoke endorsement from')
58
+ .option('--json', 'Output as JSON')
59
+ .action(async (addressArg: string, options: OutputOptions) => {
60
+ try {
61
+ const address = addressArg as Address;
62
+ const trustGraph = getTrustGraph();
63
+
64
+ info(`Revoking endorsement of ${address}...`);
65
+ const hash = await trustGraph.revokeEndorsement(address);
66
+
67
+ if (outputJSON({ transactionHash: hash, revoked: address }, options)) {
68
+ return;
69
+ }
70
+
71
+ success(`Revoked endorsement of ${address}`);
72
+ console.log(`Transaction: ${hash}`);
73
+ } catch (err) {
74
+ error(`Failed to revoke endorsement: ${err}`);
75
+ process.exit(1);
76
+ }
77
+ });
78
+
79
+ // Check trust between two agents
80
+ cmd
81
+ .command('check')
82
+ .description('Check trust level between two agents')
83
+ .argument('<from>', 'Source agent address')
84
+ .argument('<to>', 'Target agent address')
85
+ .option('--inferred', 'Calculate inferred trust through network')
86
+ .option('--depth <number>', 'Max hops for inferred trust (default: 4)', '4')
87
+ .option('--json', 'Output as JSON')
88
+ .action(async (fromArg: string, toArg: string, options: any) => {
89
+ try {
90
+ const from = fromArg as Address;
91
+ const to = toArg as Address;
92
+ const trustGraph = getTrustGraph();
93
+
94
+ if (options.inferred) {
95
+ // Get inferred trust through network
96
+ const maxDepth = parseInt(options.depth);
97
+ const result = await trustGraph.getInferredTrust(from, to, maxDepth);
98
+
99
+ if (outputJSON({ from, to, trustScore: result.trustScore, pathLength: result.pathLength, type: 'inferred' }, options)) {
100
+ return;
101
+ }
102
+
103
+ header('Inferred Trust');
104
+ console.log();
105
+ console.log('From: ', from);
106
+ console.log('To: ', to);
107
+ console.log('Trust Score: ', result.trustScore);
108
+ console.log('Path Length: ', result.pathLength, 'hops');
109
+ console.log();
110
+ } else {
111
+ // Get direct trust
112
+ const trustLevel = await trustGraph.getDirectTrust(from, to);
113
+
114
+ if (outputJSON({ from, to, trustLevel, type: 'direct' }, options)) {
115
+ return;
116
+ }
117
+
118
+ header('Direct Trust');
119
+ console.log();
120
+ console.log('From: ', from);
121
+ console.log('To: ', to);
122
+ console.log('Trust Level: ', trustLevel === 0 ? 'No direct endorsement' : `${trustLevel}/100`);
123
+ console.log();
124
+ }
125
+ } catch (err) {
126
+ error(`Failed to check trust: ${err}`);
127
+ process.exit(1);
128
+ }
129
+ });
130
+
131
+ // Get aggregate trust score
132
+ cmd
133
+ .command('score')
134
+ .description('Get aggregate trust score for an agent')
135
+ .argument('<address>', 'Agent address')
136
+ .option('--json', 'Output as JSON')
137
+ .action(async (addressArg: string, options: OutputOptions) => {
138
+ try {
139
+ const address = addressArg as Address;
140
+ const trustGraph = getTrustGraph();
141
+
142
+ const [aggregateScore, endorsers] = await Promise.all([
143
+ trustGraph.getAggregateTrust(address),
144
+ trustGraph.getEndorsers(address),
145
+ ]);
146
+
147
+ if (outputJSON({ address, aggregateScore, endorserCount: endorsers.length }, options)) {
148
+ return;
149
+ }
150
+
151
+ header('Trust Score');
152
+ console.log();
153
+ console.log('Agent: ', address);
154
+ console.log('Aggregate Score: ', aggregateScore);
155
+ console.log('Endorser Count: ', endorsers.length);
156
+ console.log();
157
+
158
+ if (aggregateScore >= 80) {
159
+ console.log('āœ… Highly trusted in the network');
160
+ } else if (aggregateScore >= 60) {
161
+ console.log('āœ… Well trusted in the network');
162
+ } else if (aggregateScore >= 40) {
163
+ console.log('āš ļø Moderately trusted');
164
+ } else if (aggregateScore > 0) {
165
+ console.log('āš ļø Limited trust in the network');
166
+ } else {
167
+ console.log('āŒ No endorsements yet');
168
+ }
169
+ console.log();
170
+ } catch (err) {
171
+ error(`Failed to get trust score: ${err}`);
172
+ process.exit(1);
173
+ }
174
+ });
175
+
176
+ // Get endorsements (agents this address has endorsed)
177
+ cmd
178
+ .command('endorsements')
179
+ .description('List all agents endorsed by an address')
180
+ .argument('[address]', 'Agent address (default: your wallet)')
181
+ .option('--json', 'Output as JSON')
182
+ .action(async (addressArg: string | undefined, options: OutputOptions) => {
183
+ try {
184
+ let address: Address;
185
+
186
+ if (addressArg) {
187
+ address = addressArg as Address;
188
+ } else {
189
+ const { getWalletAddress } = await import('../lib/wallet');
190
+ address = getWalletAddress() as Address;
191
+ }
192
+
193
+ const trustGraph = getTrustGraph();
194
+ const endorsed = await trustGraph.getEndorsements(address);
195
+
196
+ if (outputJSON({ address, endorsed, count: endorsed.length }, options)) {
197
+ return;
198
+ }
199
+
200
+ header('Endorsements');
201
+ console.log();
202
+ console.log('Endorser: ', address);
203
+ console.log('Endorsed: ', endorsed.length, 'agents');
204
+ console.log();
205
+
206
+ if (endorsed.length > 0) {
207
+ endorsed.forEach((agent, i) => {
208
+ console.log(` ${i + 1}. ${agent}`);
209
+ });
210
+ console.log();
211
+ } else {
212
+ console.log('No endorsements yet.');
213
+ console.log();
214
+ }
215
+ } catch (err) {
216
+ error(`Failed to get endorsements: ${err}`);
217
+ process.exit(1);
218
+ }
219
+ });
220
+
221
+ // Get endorsers (agents who endorse this address)
222
+ cmd
223
+ .command('endorsers')
224
+ .description('List all agents who endorse an address')
225
+ .argument('[address]', 'Agent address (default: your wallet)')
226
+ .option('--json', 'Output as JSON')
227
+ .action(async (addressArg: string | undefined, options: OutputOptions) => {
228
+ try {
229
+ let address: Address;
230
+
231
+ if (addressArg) {
232
+ address = addressArg as Address;
233
+ } else {
234
+ const { getWalletAddress } = await import('../lib/wallet');
235
+ address = getWalletAddress() as Address;
236
+ }
237
+
238
+ const trustGraph = getTrustGraph();
239
+ const endorsers = await trustGraph.getEndorsers(address);
240
+
241
+ if (outputJSON({ address, endorsers, count: endorsers.length }, options)) {
242
+ return;
243
+ }
244
+
245
+ header('Endorsers');
246
+ console.log();
247
+ console.log('Endorsed: ', address);
248
+ console.log('Endorsers: ', endorsers.length, 'agents');
249
+ console.log();
250
+
251
+ if (endorsers.length > 0) {
252
+ endorsers.forEach((agent, i) => {
253
+ console.log(` ${i + 1}. ${agent}`);
254
+ });
255
+ console.log();
256
+ } else {
257
+ console.log('No endorsers yet.');
258
+ console.log();
259
+ }
260
+ } catch (err) {
261
+ error(`Failed to get endorsers: ${err}`);
262
+ process.exit(1);
263
+ }
264
+ });
265
+
266
+ return cmd;
267
+ }