pnpfucius 2.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.
package/src/cli.js ADDED
@@ -0,0 +1,948 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import inquirer from 'inquirer';
6
+ import { PrivacyOracleAgent } from './agent.js';
7
+ import { generatePrivacyMarket, generateMultipleMarkets, listCategories, getMarketsByCategory, PRIVACY_CATEGORIES } from './privacy-markets.js';
8
+ import { getConfig, validateConfig } from './config.js';
9
+ import { PrivacyOracleDaemon } from './daemon/index.js';
10
+ import { createMarketStore } from './storage/market-store.js';
11
+ import { createAggregator, formatNumber, formatDuration } from './analytics/aggregator.js';
12
+ import { withSpinner, StepProgress, successLine, errorLine, infoLine } from './utils/spinner.js';
13
+ import { listSupportedTokens, checkConfidentialTransferSupport } from './collateral/privacy-tokens.js';
14
+ import { Connection } from '@solana/web3.js';
15
+ import { AIMarketGenerator } from './ai/market-generator.js';
16
+ import { AIScorer } from './ai/scorer.js';
17
+ import { AIResolver } from './ai/resolver.js';
18
+
19
+ const program = new Command();
20
+
21
+ program
22
+ .name('privacy-oracle')
23
+ .description('AI agent for creating privacy-themed prediction markets on Solana')
24
+ .version('1.1.0');
25
+
26
+ program
27
+ .command('create')
28
+ .description('Create a new privacy-themed prediction market')
29
+ .option('-q, --question <question>', 'Custom market question')
30
+ .option('-d, --days <days>', 'Duration in days', '30')
31
+ .option('-l, --liquidity <amount>', 'Initial liquidity in base units', '1000000')
32
+ .option('--p2p', 'Create as P2P market instead of AMM')
33
+ .option('--side <side>', 'Side for P2P market (yes/no)', 'yes')
34
+ .option('-v, --verbose', 'Verbose output')
35
+ .action(async (options) => {
36
+ try {
37
+ const config = getConfig();
38
+ const validation = validateConfig(config);
39
+
40
+ if (!validation.valid) {
41
+ validation.errors.forEach(e => errorLine(e));
42
+ process.exit(1);
43
+ }
44
+
45
+ validation.warnings.forEach(w => console.log(chalk.yellow('Warning:'), w));
46
+
47
+ const agent = new PrivacyOracleAgent({ verbose: options.verbose });
48
+
49
+ const steps = new StepProgress([
50
+ 'Generating market question',
51
+ 'Building transaction',
52
+ 'Submitting to Solana',
53
+ 'Confirming transaction'
54
+ ]);
55
+
56
+ steps.start();
57
+
58
+ let result;
59
+ if (options.p2p) {
60
+ steps.next('Creating P2P market');
61
+ result = await agent.createP2PMarket({
62
+ question: options.question,
63
+ durationDays: parseInt(options.days, 10),
64
+ amount: BigInt(options.liquidity),
65
+ side: options.side
66
+ });
67
+ } else {
68
+ result = await agent.createPrivacyMarket({
69
+ question: options.question,
70
+ durationDays: parseInt(options.days, 10),
71
+ liquidity: BigInt(options.liquidity)
72
+ });
73
+ }
74
+
75
+ steps.succeed('Market created successfully!');
76
+
77
+ console.log();
78
+ successLine(`Question: ${result.question}`);
79
+ infoLine(`Market: ${result.market}`);
80
+ infoLine(`Signature: ${result.signature}`);
81
+ infoLine(`Duration: ${result.durationDays} days`);
82
+
83
+ } catch (error) {
84
+ console.error(chalk.red('\nError:'), error.message);
85
+ if (error.logs) {
86
+ console.error(chalk.gray('Transaction logs:'), error.logs.join('\n'));
87
+ }
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ program
93
+ .command('batch')
94
+ .description('Create multiple privacy-themed markets')
95
+ .option('-c, --count <count>', 'Number of markets to create', '3')
96
+ .option('-v, --verbose', 'Verbose output')
97
+ .action(async (options) => {
98
+ try {
99
+ const config = getConfig();
100
+ const validation = validateConfig(config);
101
+
102
+ if (!validation.valid) {
103
+ validation.errors.forEach(e => errorLine(e));
104
+ process.exit(1);
105
+ }
106
+
107
+ const agent = new PrivacyOracleAgent({ verbose: options.verbose });
108
+ const count = parseInt(options.count, 10);
109
+
110
+ console.log(chalk.cyan(`\nCreating ${count} privacy-themed markets...\n`));
111
+
112
+ const results = await withSpinner(
113
+ `Creating ${count} markets`,
114
+ async (spinner) => {
115
+ const results = [];
116
+ for (let i = 0; i < count; i++) {
117
+ spinner.text = `Creating market ${i + 1}/${count}...`;
118
+ try {
119
+ const result = await agent.createPrivacyMarket({});
120
+ results.push({ ...result, success: true });
121
+ } catch (error) {
122
+ results.push({ success: false, error: error.message });
123
+ }
124
+ }
125
+ return results;
126
+ },
127
+ { successText: `Created ${count} markets` }
128
+ );
129
+
130
+ console.log();
131
+ let successCount = 0;
132
+ for (const result of results) {
133
+ if (result.success) {
134
+ successCount++;
135
+ successLine(result.question);
136
+ console.log(chalk.gray(` Market: ${result.market}`));
137
+ } else {
138
+ errorLine(result.error || 'Unknown error');
139
+ }
140
+ }
141
+
142
+ console.log(chalk.cyan(`\nCompleted: ${successCount}/${count} markets created successfully`));
143
+
144
+ } catch (error) {
145
+ console.error(chalk.red('Error:'), error.message);
146
+ process.exit(1);
147
+ }
148
+ });
149
+
150
+ program
151
+ .command('generate')
152
+ .description('Generate market ideas without creating them')
153
+ .option('-c, --count <count>', 'Number of ideas to generate', '5')
154
+ .option('-k, --category <category>', 'Filter by category (regulation, technology, adoption, events)')
155
+ .action((options) => {
156
+ const count = parseInt(options.count, 10);
157
+
158
+ let ideas;
159
+ if (options.category) {
160
+ if (!PRIVACY_CATEGORIES[options.category]) {
161
+ errorLine(`Unknown category: ${options.category}`);
162
+ console.log(chalk.gray('Available: regulation, technology, adoption, events'));
163
+ process.exit(1);
164
+ }
165
+ ideas = getMarketsByCategory(options.category).slice(0, count);
166
+ } else {
167
+ ideas = generateMultipleMarkets(count);
168
+ }
169
+
170
+ console.log(chalk.cyan(`\nGenerated ${ideas.length} privacy market ideas:\n`));
171
+
172
+ ideas.forEach((idea, i) => {
173
+ const cat = PRIVACY_CATEGORIES[idea.categoryKey];
174
+ const urgencyColor = cat?.urgency === 'breaking' ? chalk.red :
175
+ cat?.urgency === 'timely' ? chalk.yellow : chalk.gray;
176
+ const sentimentIcon = cat?.sentiment === 'bullish' ? '↑' :
177
+ cat?.sentiment === 'bearish' ? '↓' : '→';
178
+
179
+ console.log(chalk.yellow(`${i + 1}.`), idea.question);
180
+ console.log(chalk.gray(` Category: ${idea.category}`));
181
+ console.log(chalk.gray(` Duration: ${idea.durationDays || 30} days | `),
182
+ urgencyColor(`Urgency: ${cat?.urgency || 'evergreen'}`),
183
+ chalk.gray(` | Sentiment: ${sentimentIcon} ${cat?.sentiment || 'neutral'}`));
184
+ console.log();
185
+ });
186
+ });
187
+
188
+ program
189
+ .command('categories')
190
+ .description('List available market categories')
191
+ .action(() => {
192
+ const cats = listCategories();
193
+
194
+ console.log(chalk.cyan('\nPrivacy Market Categories:\n'));
195
+
196
+ cats.forEach(cat => {
197
+ const category = PRIVACY_CATEGORIES[cat.key];
198
+ const percentage = (cat.weight * 100).toFixed(0);
199
+ const urgencyColor = category?.urgency === 'breaking' ? chalk.red :
200
+ category?.urgency === 'timely' ? chalk.yellow : chalk.gray;
201
+
202
+ console.log(chalk.yellow(cat.name), chalk.gray(`(${percentage}% weight)`));
203
+ console.log(chalk.gray(` Templates: ${cat.templateCount}`),
204
+ chalk.gray(' | '),
205
+ urgencyColor(`Urgency: ${category?.urgency || 'evergreen'}`),
206
+ chalk.gray(' | '),
207
+ chalk.gray(`Sentiment: ${category?.sentiment || 'neutral'}`));
208
+ console.log();
209
+ });
210
+ });
211
+
212
+ program
213
+ .command('list')
214
+ .description('List existing markets')
215
+ .option('-n, --limit <limit>', 'Maximum markets to show', '20')
216
+ .option('-v, --verbose', 'Verbose output')
217
+ .action(async (options) => {
218
+ try {
219
+ const agent = new PrivacyOracleAgent({ verbose: options.verbose });
220
+ const limit = parseInt(options.limit, 10);
221
+
222
+ const addresses = await withSpinner(
223
+ 'Fetching market addresses',
224
+ () => agent.getMarketAddresses(),
225
+ { successText: 'Markets fetched' }
226
+ );
227
+
228
+ const showing = addresses.slice(0, limit);
229
+ console.log(chalk.cyan(`\nFound ${addresses.length} markets. Showing first ${showing.length}:\n`));
230
+
231
+ for (const addr of showing) {
232
+ try {
233
+ const info = await agent.fetchMarketInfo(addr);
234
+ const status = info.resolved ? chalk.green('RESOLVED') :
235
+ info.resolvable ? chalk.yellow('ACTIVE') : chalk.red('NOT RESOLVABLE');
236
+
237
+ console.log(chalk.white(info.question));
238
+ console.log(chalk.gray(` ${addr}`));
239
+ console.log(chalk.gray(` Status: ${status} | Ends: ${info.endTime.toLocaleDateString()}`));
240
+ console.log();
241
+ } catch (e) {
242
+ console.log(chalk.gray(` ${addr} - Error fetching details`));
243
+ }
244
+ }
245
+
246
+ } catch (error) {
247
+ console.error(chalk.red('Error:'), error.message);
248
+ process.exit(1);
249
+ }
250
+ });
251
+
252
+ program
253
+ .command('info <market>')
254
+ .description('Get detailed info about a specific market')
255
+ .option('-v, --verbose', 'Verbose output')
256
+ .action(async (market, options) => {
257
+ try {
258
+ const agent = new PrivacyOracleAgent({ verbose: options.verbose });
259
+ const info = await agent.fetchMarketInfo(market);
260
+
261
+ console.log(chalk.cyan('\nMarket Information:\n'));
262
+ console.log(chalk.yellow('Question:'), info.question);
263
+ console.log(chalk.yellow('Address:'), info.address);
264
+ console.log(chalk.yellow('Creator:'), info.creator);
265
+ console.log(chalk.yellow('Status:'), info.resolved ? 'Resolved' : (info.resolvable ? 'Active' : 'Not Resolvable'));
266
+ console.log(chalk.yellow('End Time:'), info.endTime.toLocaleString());
267
+
268
+ if (info.winningToken) {
269
+ console.log(chalk.yellow('Winner:'), info.winningToken);
270
+ }
271
+
272
+ } catch (error) {
273
+ console.error(chalk.red('Error:'), error.message);
274
+ process.exit(1);
275
+ }
276
+ });
277
+
278
+ program
279
+ .command('config')
280
+ .description('Show current configuration')
281
+ .action(() => {
282
+ const config = getConfig();
283
+ const validation = validateConfig(config);
284
+
285
+ console.log(chalk.cyan('\nCurrent Configuration:\n'));
286
+
287
+ // Network
288
+ console.log(chalk.yellow('Network'));
289
+ infoLine(`Network: ${config.network}`);
290
+ infoLine(`RPC: ${config.heliusKey ? 'Helius (configured)' : chalk.red('Public RPC (rate limited)')}`);
291
+ infoLine(`Wallet: ${config.walletKey ? chalk.green('Configured') : chalk.red('Not configured')}`);
292
+ console.log();
293
+
294
+ // Collateral
295
+ console.log(chalk.yellow('Collateral'));
296
+ infoLine(`Token: ${config.collateralToken}`);
297
+ infoLine(`Mint: ${config.collateralMint.toBase58()}`);
298
+ infoLine(`Prefer Confidential: ${config.preferConfidential ? 'Yes' : 'No'}`);
299
+ console.log();
300
+
301
+ // Markets
302
+ console.log(chalk.yellow('Market Defaults'));
303
+ infoLine(`Liquidity: ${config.defaultLiquidity.toString()}`);
304
+ infoLine(`Duration: ${config.defaultDurationDays} days`);
305
+ console.log();
306
+
307
+ // Daemon
308
+ console.log(chalk.yellow('Daemon Settings'));
309
+ infoLine(`Schedule: ${config.daemon.schedule}`);
310
+ infoLine(`Markets per round: ${config.daemon.marketsPerRound}`);
311
+ infoLine(`Storage: ${config.daemon.storagePath || 'In-memory'}`);
312
+ console.log();
313
+
314
+ // Optional features
315
+ console.log(chalk.yellow('Optional Features'));
316
+ infoLine(`News monitoring: ${config.news.enabled ? chalk.green('Enabled') : 'Disabled'}`);
317
+ infoLine(`Webhook server: ${config.webhook.enabled ? chalk.green('Enabled') : 'Disabled'}`);
318
+ console.log();
319
+
320
+ // Validation
321
+ if (validation.warnings.length > 0) {
322
+ console.log(chalk.yellow('Warnings:'));
323
+ validation.warnings.forEach(w => console.log(chalk.yellow(` ! ${w}`)));
324
+ }
325
+
326
+ if (!validation.valid) {
327
+ console.log(chalk.red('\nErrors:'));
328
+ validation.errors.forEach(e => errorLine(e));
329
+ }
330
+ });
331
+
332
+ // Daemon command
333
+ program
334
+ .command('daemon')
335
+ .description('Run as autonomous daemon, creating markets on a schedule')
336
+ .option('-s, --schedule <schedule>', 'Cron expression or interval (30m, 1h, 24h)', '1h')
337
+ .option('-n, --iterations <count>', 'Max iterations (infinite if omitted)')
338
+ .option('-c, --count <count>', 'Markets per cycle', '1')
339
+ .option('--dry-run', 'Generate markets without creating on-chain')
340
+ .option('--news', 'Enable news monitoring for timely markets')
341
+ .option('--webhooks', 'Enable webhook server for Helius events')
342
+ .option('--webhook-port <port>', 'Webhook server port', '3000')
343
+ .option('-v, --verbose', 'Verbose output')
344
+ .action(async (options) => {
345
+ try {
346
+ const config = getConfig();
347
+ const validation = validateConfig(config);
348
+
349
+ if (!validation.valid && !options.dryRun) {
350
+ validation.errors.forEach(e => errorLine(e));
351
+ process.exit(1);
352
+ }
353
+
354
+ console.log(chalk.cyan('\n=== Privacy Oracle Daemon ===\n'));
355
+
356
+ const daemonConfig = {
357
+ ...config,
358
+ daemon: {
359
+ ...config.daemon,
360
+ schedule: options.schedule,
361
+ marketsPerRound: parseInt(options.count, 10),
362
+ maxIterations: options.iterations ? parseInt(options.iterations, 10) : undefined
363
+ },
364
+ news: {
365
+ ...config.news,
366
+ enabled: options.news || config.news.enabled
367
+ },
368
+ webhook: {
369
+ ...config.webhook,
370
+ enabled: options.webhooks || config.webhook.enabled,
371
+ port: parseInt(options.webhookPort, 10)
372
+ },
373
+ dryRun: options.dryRun,
374
+ verbose: options.verbose
375
+ };
376
+
377
+ infoLine(`Schedule: ${daemonConfig.daemon.schedule}`);
378
+ infoLine(`Markets per cycle: ${daemonConfig.daemon.marketsPerRound}`);
379
+ infoLine(`Max iterations: ${daemonConfig.daemon.maxIterations || 'Infinite'}`);
380
+ infoLine(`Dry run: ${daemonConfig.dryRun ? 'Yes' : 'No'}`);
381
+ infoLine(`News monitoring: ${daemonConfig.news.enabled ? 'Enabled' : 'Disabled'}`);
382
+ infoLine(`Webhooks: ${daemonConfig.webhook.enabled ? `Enabled (port ${daemonConfig.webhook.port})` : 'Disabled'}`);
383
+ console.log();
384
+
385
+ const daemon = new PrivacyOracleDaemon(daemonConfig);
386
+
387
+ await daemon.start();
388
+
389
+ console.log(chalk.green('\nDaemon started. Press Ctrl+C to stop.\n'));
390
+
391
+ } catch (error) {
392
+ console.error(chalk.red('Error:'), error.message);
393
+ process.exit(1);
394
+ }
395
+ });
396
+
397
+ // Stats command
398
+ program
399
+ .command('stats')
400
+ .description('Show market analytics and statistics')
401
+ .option('--period <period>', 'Time period (24h, 7d, 30d)', '7d')
402
+ .action(async (options) => {
403
+ try {
404
+ const config = getConfig();
405
+ const storagePath = config.daemon.storagePath || ':memory:';
406
+
407
+ const store = createMarketStore(storagePath);
408
+ const aggregator = createAggregator(store);
409
+
410
+ const overview = await withSpinner(
411
+ 'Loading analytics',
412
+ () => aggregator.getOverview(),
413
+ { successText: 'Analytics loaded' }
414
+ );
415
+
416
+ console.log(chalk.cyan('\n=== Market Analytics ===\n'));
417
+
418
+ // Summary
419
+ console.log(chalk.yellow('Summary'));
420
+ infoLine(`Total markets: ${overview.summary.totalMarkets}`);
421
+ infoLine(`Active: ${overview.summary.activeMarkets}`);
422
+ infoLine(`Resolved: ${overview.summary.resolvedMarkets}`);
423
+ infoLine(`Cancelled: ${overview.summary.cancelledMarkets}`);
424
+ infoLine(`Created this week: ${overview.summary.recentWeek}`);
425
+ console.log();
426
+
427
+ // Performance
428
+ console.log(chalk.yellow('Performance'));
429
+ infoLine(`Total volume: ${formatNumber(overview.performance.totalVolume || 0)}`);
430
+ infoLine(`Avg duration: ${formatDuration(overview.performance.averageDuration || 0)}`);
431
+ infoLine(`Resolution rate: ${(overview.performance.resolutionRate * 100 || 0).toFixed(1)}%`);
432
+ console.log();
433
+
434
+ // Category breakdown
435
+ if (overview.categoryBreakdown.length > 0) {
436
+ console.log(chalk.yellow('By Category'));
437
+ overview.categoryBreakdown.forEach(cat => {
438
+ const bar = '█'.repeat(Math.ceil(cat.percentage / 5));
439
+ console.log(` ${cat.category}: ${cat.count} (${cat.percentage}%) ${chalk.cyan(bar)}`);
440
+ });
441
+ console.log();
442
+ }
443
+
444
+ // Recent markets
445
+ if (overview.recentMarkets.length > 0) {
446
+ console.log(chalk.yellow('Recent Markets'));
447
+ overview.recentMarkets.slice(0, 5).forEach(m => {
448
+ const statusColor = m.status === 'active' ? chalk.green :
449
+ m.status === 'resolved' ? chalk.blue : chalk.gray;
450
+ console.log(` ${statusColor('●')} ${m.question.slice(0, 60)}...`);
451
+ });
452
+ }
453
+
454
+ store.close();
455
+
456
+ } catch (error) {
457
+ console.error(chalk.red('Error:'), error.message);
458
+ process.exit(1);
459
+ }
460
+ });
461
+
462
+ // Interactive mode
463
+ program
464
+ .command('interactive')
465
+ .alias('i')
466
+ .description('Interactive market creation wizard')
467
+ .action(async () => {
468
+ try {
469
+ const config = getConfig();
470
+ const validation = validateConfig(config);
471
+
472
+ console.log(chalk.cyan('\n=== Privacy Oracle Interactive Mode ===\n'));
473
+
474
+ if (!validation.valid) {
475
+ validation.errors.forEach(e => errorLine(e));
476
+ console.log(chalk.yellow('\nNote: Wallet not configured. Running in preview mode.\n'));
477
+ }
478
+
479
+ // Category selection
480
+ const categories = listCategories();
481
+ const { category } = await inquirer.prompt([{
482
+ type: 'list',
483
+ name: 'category',
484
+ message: 'Select market category:',
485
+ choices: [
486
+ { name: 'Random (AI picks)', value: 'random' },
487
+ ...categories.map(c => ({
488
+ name: `${c.name} (${c.templateCount} templates)`,
489
+ value: c.key
490
+ }))
491
+ ]
492
+ }]);
493
+
494
+ // Generate question
495
+ let market;
496
+ if (category === 'random') {
497
+ market = generatePrivacyMarket();
498
+ } else {
499
+ const categoryMarkets = getMarketsByCategory(category);
500
+ market = categoryMarkets[Math.floor(Math.random() * categoryMarkets.length)];
501
+ }
502
+
503
+ console.log(chalk.cyan('\nGenerated question:'));
504
+ console.log(chalk.white(` "${market.question}"\n`));
505
+
506
+ // Customize?
507
+ const { customize } = await inquirer.prompt([{
508
+ type: 'confirm',
509
+ name: 'customize',
510
+ message: 'Customize this question?',
511
+ default: false
512
+ }]);
513
+
514
+ let finalQuestion = market.question;
515
+ if (customize) {
516
+ const { customQuestion } = await inquirer.prompt([{
517
+ type: 'input',
518
+ name: 'customQuestion',
519
+ message: 'Enter custom question:',
520
+ default: market.question
521
+ }]);
522
+ finalQuestion = customQuestion;
523
+ }
524
+
525
+ // Duration
526
+ const { duration } = await inquirer.prompt([{
527
+ type: 'list',
528
+ name: 'duration',
529
+ message: 'Market duration:',
530
+ choices: [
531
+ { name: '14 days', value: 14 },
532
+ { name: '30 days (recommended)', value: 30 },
533
+ { name: '60 days', value: 60 },
534
+ { name: '90 days', value: 90 },
535
+ { name: 'Custom', value: 'custom' }
536
+ ],
537
+ default: 1
538
+ }]);
539
+
540
+ let finalDuration = duration;
541
+ if (duration === 'custom') {
542
+ const { customDuration } = await inquirer.prompt([{
543
+ type: 'number',
544
+ name: 'customDuration',
545
+ message: 'Enter duration in days:',
546
+ default: 30
547
+ }]);
548
+ finalDuration = customDuration;
549
+ }
550
+
551
+ // Liquidity
552
+ const { liquidity } = await inquirer.prompt([{
553
+ type: 'list',
554
+ name: 'liquidity',
555
+ message: 'Initial liquidity:',
556
+ choices: [
557
+ { name: '500,000 (0.5 USDC)', value: '500000' },
558
+ { name: '1,000,000 (1 USDC) - recommended', value: '1000000' },
559
+ { name: '5,000,000 (5 USDC)', value: '5000000' },
560
+ { name: 'Custom', value: 'custom' }
561
+ ],
562
+ default: 1
563
+ }]);
564
+
565
+ let finalLiquidity = liquidity;
566
+ if (liquidity === 'custom') {
567
+ const { customLiquidity } = await inquirer.prompt([{
568
+ type: 'input',
569
+ name: 'customLiquidity',
570
+ message: 'Enter liquidity in base units:',
571
+ default: '1000000'
572
+ }]);
573
+ finalLiquidity = customLiquidity;
574
+ }
575
+
576
+ // Market type
577
+ const { marketType } = await inquirer.prompt([{
578
+ type: 'list',
579
+ name: 'marketType',
580
+ message: 'Market type:',
581
+ choices: [
582
+ { name: 'AMM (Automated Market Maker) - recommended', value: 'amm' },
583
+ { name: 'P2P (Peer-to-Peer)', value: 'p2p' }
584
+ ],
585
+ default: 0
586
+ }]);
587
+
588
+ // Summary
589
+ console.log(chalk.cyan('\n=== Market Summary ===\n'));
590
+ infoLine(`Question: ${finalQuestion}`);
591
+ infoLine(`Duration: ${finalDuration} days`);
592
+ infoLine(`Liquidity: ${finalLiquidity}`);
593
+ infoLine(`Type: ${marketType.toUpperCase()}`);
594
+ console.log();
595
+
596
+ if (!validation.valid) {
597
+ console.log(chalk.yellow('Cannot create market: wallet not configured'));
598
+ console.log(chalk.gray('Set WALLET_PRIVATE_KEY or PRIVATE_KEY in .env'));
599
+ return;
600
+ }
601
+
602
+ // Confirm
603
+ const { confirm } = await inquirer.prompt([{
604
+ type: 'confirm',
605
+ name: 'confirm',
606
+ message: 'Create this market on Solana?',
607
+ default: true
608
+ }]);
609
+
610
+ if (!confirm) {
611
+ console.log(chalk.yellow('Market creation cancelled.'));
612
+ return;
613
+ }
614
+
615
+ // Create market
616
+ const agent = new PrivacyOracleAgent({ verbose: true });
617
+
618
+ const result = await withSpinner(
619
+ 'Creating market on Solana',
620
+ async () => {
621
+ if (marketType === 'p2p') {
622
+ return agent.createP2PMarket({
623
+ question: finalQuestion,
624
+ durationDays: finalDuration,
625
+ amount: BigInt(finalLiquidity),
626
+ side: 'yes'
627
+ });
628
+ }
629
+ return agent.createPrivacyMarket({
630
+ question: finalQuestion,
631
+ durationDays: finalDuration,
632
+ liquidity: BigInt(finalLiquidity)
633
+ });
634
+ },
635
+ { successText: 'Market created!' }
636
+ );
637
+
638
+ console.log();
639
+ successLine('Market created successfully!');
640
+ infoLine(`Address: ${result.market}`);
641
+ infoLine(`Signature: ${result.signature}`);
642
+
643
+ } catch (error) {
644
+ if (error.name === 'ExitPromptError') {
645
+ console.log(chalk.yellow('\nCancelled.'));
646
+ return;
647
+ }
648
+ console.error(chalk.red('Error:'), error.message);
649
+ process.exit(1);
650
+ }
651
+ });
652
+
653
+ // Tokens command
654
+ program
655
+ .command('tokens')
656
+ .description('List supported collateral tokens')
657
+ .option('--check <mint>', 'Check if a mint supports confidential transfers')
658
+ .action(async (options) => {
659
+ try {
660
+ const config = getConfig();
661
+
662
+ if (options.check) {
663
+ console.log(chalk.cyan('\nChecking mint for confidential transfer support...\n'));
664
+
665
+ const connection = new Connection(config.rpcUrl);
666
+ const result = await withSpinner(
667
+ 'Checking mint',
668
+ () => checkConfidentialTransferSupport(connection, options.check),
669
+ { successText: 'Check complete' }
670
+ );
671
+
672
+ console.log();
673
+ infoLine(`Mint: ${options.check}`);
674
+ infoLine(`Token-2022: ${result.isToken2022 ? chalk.green('Yes') : 'No'}`);
675
+ infoLine(`Confidential transfers: ${result.supported ? chalk.green('Supported') : chalk.yellow('Not supported')}`);
676
+ infoLine(`Details: ${result.reason}`);
677
+ return;
678
+ }
679
+
680
+ console.log(chalk.cyan('\nSupported Collateral Tokens:\n'));
681
+
682
+ const networks = ['mainnet', 'devnet'];
683
+
684
+ for (const network of networks) {
685
+ console.log(chalk.yellow(`${network.charAt(0).toUpperCase() + network.slice(1)}:`));
686
+ const tokens = listSupportedTokens(network);
687
+
688
+ tokens.forEach(token => {
689
+ console.log(` ${chalk.white(token.symbol)}`);
690
+ console.log(chalk.gray(` ${token.address}`));
691
+ });
692
+ console.log();
693
+ }
694
+
695
+ console.log(chalk.gray('Tip: Use a Token-2022 mint with confidential transfer extension'));
696
+ console.log(chalk.gray(' for privacy-focused collateral.'));
697
+ console.log(chalk.gray(' Set COLLATERAL_TOKEN in .env to use a custom mint.'));
698
+
699
+ } catch (error) {
700
+ console.error(chalk.red('Error:'), error.message);
701
+ process.exit(1);
702
+ }
703
+ });
704
+
705
+ // AI-powered generation
706
+ program
707
+ .command('ai-generate')
708
+ .description('Generate market ideas using Claude AI')
709
+ .option('-c, --count <count>', 'Number of markets to generate', '3')
710
+ .option('-t, --topic <topic>', 'Specific topic to generate markets about')
711
+ .option('--create', 'Actually create the markets on-chain')
712
+ .option('-v, --verbose', 'Verbose output')
713
+ .action(async (options) => {
714
+ try {
715
+ const config = getConfig();
716
+
717
+ if (!config.anthropicApiKey) {
718
+ errorLine('ANTHROPIC_API_KEY not configured');
719
+ console.log(chalk.gray('Set ANTHROPIC_API_KEY in .env to use AI features'));
720
+ process.exit(1);
721
+ }
722
+
723
+ const count = parseInt(options.count, 10);
724
+ console.log(chalk.cyan(`\nGenerating ${count} AI-powered market ideas...\n`));
725
+
726
+ const generator = new AIMarketGenerator(config.anthropicApiKey);
727
+
728
+ let results;
729
+ if (options.topic) {
730
+ results = await withSpinner(
731
+ `Generating markets about "${options.topic}"`,
732
+ async () => {
733
+ const markets = [];
734
+ for (let i = 0; i < count; i++) {
735
+ const result = await generator.generateFromTopic(options.topic);
736
+ markets.push({ success: true, market: result });
737
+ }
738
+ return markets;
739
+ },
740
+ { successText: 'Markets generated' }
741
+ );
742
+ } else {
743
+ results = await withSpinner(
744
+ 'Generating diverse markets',
745
+ () => generator.generateDiverseMarkets(count),
746
+ { successText: 'Markets generated' }
747
+ );
748
+ }
749
+
750
+ console.log();
751
+ results.forEach((result, i) => {
752
+ if (result.success) {
753
+ const m = result.market;
754
+ console.log(chalk.yellow(`${i + 1}.`), m.question);
755
+ console.log(chalk.gray(` Category: ${m.categoryName || m.category}`));
756
+ console.log(chalk.gray(` Duration: ${m.suggestedDurationDays} days | Liquidity: ${m.suggestedLiquidityUSDC} USDC`));
757
+ console.log(chalk.gray(` Urgency: ${m.urgency}`));
758
+ console.log(chalk.dim(` Reasoning: ${m.reasoning}`));
759
+ console.log();
760
+ } else {
761
+ errorLine(`Failed: ${result.error}`);
762
+ }
763
+ });
764
+
765
+ // Optionally create markets
766
+ if (options.create) {
767
+ const validation = validateConfig(config);
768
+ if (!validation.valid) {
769
+ console.log(chalk.yellow('\nCannot create markets: wallet not configured'));
770
+ return;
771
+ }
772
+
773
+ const { confirm } = await inquirer.prompt([{
774
+ type: 'confirm',
775
+ name: 'confirm',
776
+ message: `Create ${results.filter(r => r.success).length} markets on Solana?`,
777
+ default: false
778
+ }]);
779
+
780
+ if (confirm) {
781
+ const agent = new PrivacyOracleAgent({ verbose: options.verbose });
782
+
783
+ for (const result of results.filter(r => r.success)) {
784
+ const m = result.market;
785
+ try {
786
+ const created = await withSpinner(
787
+ `Creating: ${m.question.slice(0, 50)}...`,
788
+ () => agent.createPrivacyMarket({
789
+ question: m.question,
790
+ durationDays: m.suggestedDurationDays,
791
+ liquidity: BigInt(m.suggestedLiquidityUSDC * 1000000)
792
+ }),
793
+ { successText: 'Created' }
794
+ );
795
+ successLine(`Market: ${created.market}`);
796
+ } catch (error) {
797
+ errorLine(`Failed: ${error.message}`);
798
+ }
799
+ }
800
+ }
801
+ }
802
+
803
+ } catch (error) {
804
+ console.error(chalk.red('Error:'), error.message);
805
+ process.exit(1);
806
+ }
807
+ });
808
+
809
+ // AI news scoring
810
+ program
811
+ .command('ai-score')
812
+ .description('Score news headlines for privacy relevance using AI')
813
+ .argument('<headlines...>', 'Headlines to score (or use --file)')
814
+ .option('-f, --file <file>', 'Read headlines from file (one per line)')
815
+ .option('--min-score <score>', 'Minimum score to show', '0')
816
+ .action(async (headlines, options) => {
817
+ try {
818
+ const config = getConfig();
819
+
820
+ if (!config.anthropicApiKey) {
821
+ errorLine('ANTHROPIC_API_KEY not configured');
822
+ process.exit(1);
823
+ }
824
+
825
+ const scorer = new AIScorer(config.anthropicApiKey);
826
+ const minScore = parseInt(options.minScore, 10);
827
+
828
+ // Prepare news items
829
+ const newsItems = headlines.map(h => ({ title: h }));
830
+
831
+ console.log(chalk.cyan(`\nScoring ${newsItems.length} headlines for privacy relevance...\n`));
832
+
833
+ const results = await withSpinner(
834
+ 'Scoring headlines',
835
+ () => scorer.scoreBatch(newsItems),
836
+ { successText: 'Scoring complete' }
837
+ );
838
+
839
+ console.log();
840
+ results
841
+ .filter(r => r.success && r.score >= minScore)
842
+ .sort((a, b) => b.score - a.score)
843
+ .forEach(r => {
844
+ const scoreColor = r.score >= 70 ? chalk.green :
845
+ r.score >= 50 ? chalk.yellow : chalk.gray;
846
+
847
+ console.log(scoreColor(`[${r.score}]`), r.newsItem.title);
848
+ console.log(chalk.gray(` Category: ${r.category} | Urgency: ${r.urgency}`));
849
+ if (r.marketPotential) {
850
+ console.log(chalk.cyan(` Market idea: ${r.suggestedMarketAngle}`));
851
+ }
852
+ console.log();
853
+ });
854
+
855
+ } catch (error) {
856
+ console.error(chalk.red('Error:'), error.message);
857
+ process.exit(1);
858
+ }
859
+ });
860
+
861
+ // AI market resolution analysis
862
+ program
863
+ .command('ai-resolve')
864
+ .description('Analyze markets for potential resolution using AI')
865
+ .option('-a, --all', 'Check all active markets')
866
+ .option('-m, --market <address>', 'Check specific market')
867
+ .option('--min-confidence <conf>', 'Minimum confidence to show', '0.5')
868
+ .action(async (options) => {
869
+ try {
870
+ const config = getConfig();
871
+
872
+ if (!config.anthropicApiKey) {
873
+ errorLine('ANTHROPIC_API_KEY not configured');
874
+ process.exit(1);
875
+ }
876
+
877
+ const resolver = new AIResolver(config.anthropicApiKey);
878
+ const minConfidence = parseFloat(options.minConfidence);
879
+
880
+ let markets = [];
881
+
882
+ if (options.market) {
883
+ // Fetch single market
884
+ const agent = new PrivacyOracleAgent({ verbose: false });
885
+ const info = await agent.fetchMarketInfo(options.market);
886
+ markets = [{
887
+ address: options.market,
888
+ question: info.question,
889
+ creationTime: info.startTime?.getTime() || Date.now(),
890
+ endTime: info.endTime?.getTime(),
891
+ durationDays: Math.ceil((info.endTime - info.startTime) / (1000 * 60 * 60 * 24))
892
+ }];
893
+ } else if (options.all) {
894
+ // Load from storage
895
+ const storagePath = config.daemon.storagePath || ':memory:';
896
+ const store = createMarketStore(storagePath);
897
+ markets = store.getAllMarkets({ status: 'active' });
898
+ store.close();
899
+ } else {
900
+ errorLine('Specify --market <address> or --all');
901
+ process.exit(1);
902
+ }
903
+
904
+ if (markets.length === 0) {
905
+ console.log(chalk.yellow('No markets to analyze'));
906
+ return;
907
+ }
908
+
909
+ console.log(chalk.cyan(`\nAnalyzing ${markets.length} markets for resolution...\n`));
910
+
911
+ const results = await withSpinner(
912
+ 'Analyzing markets',
913
+ () => resolver.analyzeMarkets(markets),
914
+ { successText: 'Analysis complete' }
915
+ );
916
+
917
+ console.log();
918
+ results.forEach(r => {
919
+ if (!r.success) {
920
+ errorLine(`Failed to analyze: ${r.error}`);
921
+ return;
922
+ }
923
+
924
+ const a = r.analysis;
925
+ if (a.confidence < minConfidence) return;
926
+
927
+ const confColor = a.confidence >= 0.8 ? chalk.green :
928
+ a.confidence >= 0.5 ? chalk.yellow : chalk.gray;
929
+
930
+ console.log(chalk.white(r.market.question));
931
+ console.log(chalk.gray(` Address: ${r.market.address}`));
932
+ console.log(` Can resolve: ${a.canResolve ? chalk.green('Yes') : chalk.yellow('No')}`);
933
+ if (a.canResolve) {
934
+ console.log(` Outcome: ${a.outcome === 'yes' ? chalk.green('YES') : a.outcome === 'no' ? chalk.red('NO') : chalk.gray('Unknown')}`);
935
+ }
936
+ console.log(` Confidence: ${confColor((a.confidence * 100).toFixed(0) + '%')}`);
937
+ console.log(` Action: ${a.suggestedAction}`);
938
+ console.log(chalk.dim(` Reasoning: ${a.reasoning.slice(0, 200)}...`));
939
+ console.log();
940
+ });
941
+
942
+ } catch (error) {
943
+ console.error(chalk.red('Error:'), error.message);
944
+ process.exit(1);
945
+ }
946
+ });
947
+
948
+ program.parse();