@targlobal/mission-control 1.3.3 → 1.4.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/index.ts CHANGED
@@ -6,10 +6,10 @@ import ora from 'ora';
6
6
  import Table from 'cli-table3';
7
7
  import inquirer from 'inquirer';
8
8
  import { getConfig, setConfig, isAuthenticated, clearConfig, setUser, getUser } from './config';
9
- import { api, Task, Board, Payout, PayoutMetrics, resetApiClient } from './api';
9
+ import { api, Task, Board, Payout, PayoutMetrics, TarPayPayout, resetApiClient } from './api';
10
10
  import { execSync, spawn } from 'child_process';
11
11
 
12
- const VERSION = '1.3.3';
12
+ const VERSION = '1.4.0';
13
13
  const program = new Command();
14
14
 
15
15
  // Set terminal title
@@ -709,6 +709,7 @@ const PLAN_COLORS: Record<string, (s: string) => string> = {
709
709
  validator_v2: chalk.blue,
710
710
  booster: chalk.green,
711
711
  dumpster: chalk.gray,
712
+ christmas: chalk.red,
712
713
  };
713
714
 
714
715
  const PLAN_ICONS: Record<string, string> = {
@@ -718,6 +719,46 @@ const PLAN_ICONS: Record<string, string> = {
718
719
  validator_v2: '🔐',
719
720
  booster: '🚀',
720
721
  dumpster: '🗑️',
722
+ christmas: '🎄',
723
+ };
724
+
725
+ // Helper to get percentage (preset or custom input)
726
+ const getPercentageWithCustom = async (): Promise<number | null> => {
727
+ const { percentage } = await inquirer.prompt([
728
+ {
729
+ type: 'list',
730
+ name: 'percentage',
731
+ message: 'Select percentage of remaining amount to pay:',
732
+ choices: [
733
+ { name: '10%', value: 10 },
734
+ { name: '25%', value: 25 },
735
+ { name: '50%', value: 50 },
736
+ { name: '75%', value: 75 },
737
+ { name: '100% (Full)', value: 100 },
738
+ { name: chalk.cyan('Custom...'), value: -1 },
739
+ ],
740
+ },
741
+ ]);
742
+
743
+ if (percentage === -1) {
744
+ const { customPct } = await inquirer.prompt([
745
+ {
746
+ type: 'input',
747
+ name: 'customPct',
748
+ message: 'Enter custom percentage (1-100):',
749
+ validate: (input: string) => {
750
+ const num = parseFloat(input);
751
+ if (isNaN(num) || num < 1 || num > 100) {
752
+ return 'Please enter a number between 1 and 100';
753
+ }
754
+ return true;
755
+ },
756
+ },
757
+ ]);
758
+ return parseFloat(customPct);
759
+ }
760
+
761
+ return percentage;
721
762
  };
722
763
 
723
764
  const formatAmount = (amount: number, crypto?: string): string => {
@@ -1232,6 +1273,434 @@ const processPayoutsWithProgress = async (ids: number[], action: 'approve' | 'ca
1232
1273
  }
1233
1274
  };
1234
1275
 
1276
+ // ==================== CHRISTMAS/TARPAY PAYOUT FUNCTIONS ====================
1277
+
1278
+ const listChristmasPayoutsCmd = async (options: { status?: string; page?: number }) => {
1279
+ const spinner = ora('Loading Christmas payouts...').start();
1280
+ const compact = isCompact();
1281
+
1282
+ try {
1283
+ const data = await api.getTarPayPayouts({
1284
+ plan: 'christmas',
1285
+ status: options.status || 'pending_review',
1286
+ limit: compact ? 10 : 20,
1287
+ offset: ((options.page || 1) - 1) * (compact ? 10 : 20),
1288
+ });
1289
+ const payouts: TarPayPayout[] = data.results || [];
1290
+ spinner.stop();
1291
+
1292
+ if (payouts.length === 0) {
1293
+ console.log(chalk.dim('\n No Christmas payouts found\n'));
1294
+ return;
1295
+ }
1296
+
1297
+ // Info banner about $50 threshold
1298
+ console.log('');
1299
+ console.log(chalk.red('🎄 CHRISTMAS PAYOUTS') + chalk.dim(' (TarPay)'));
1300
+ console.log(chalk.dim(' Payouts > $50 require manual approval. < $50 are auto-sent.'));
1301
+ console.log('');
1302
+
1303
+ if (compact) {
1304
+ payouts.forEach((p) => {
1305
+ const isPartial = p.has_partial_payments && (p.paid_percentage || 0) > 0;
1306
+ const statusIcon = p.status === 'completed' ? chalk.green('✓') :
1307
+ p.status === 'failed' ? chalk.red('✗') :
1308
+ isPartial ? chalk.cyan('%') : chalk.yellow('●');
1309
+ const email = p.user_email.length > 12 ? p.user_email.substring(0, 10) + '..' : p.user_email;
1310
+ const displayAmt = isPartial ? (p.remaining_amount || parseFloat(p.amount)) : parseFloat(p.amount);
1311
+ const amountStr = isPartial ? `${displayAmt.toFixed(2)}(${Math.round(p.paid_percentage || 0)}%)` : displayAmt.toFixed(2);
1312
+ console.log(` ${statusIcon} ${chalk.dim('#' + p.id)} ${chalk.red('xmas')} ${email} ${chalk.green(amountStr)} ${p.currency}`);
1313
+ });
1314
+ console.log(chalk.dim(`\n ${data.count} total\n`));
1315
+ } else {
1316
+ const table = new Table({
1317
+ head: [
1318
+ chalk.cyan('ID'),
1319
+ chalk.cyan('User'),
1320
+ chalk.cyan('Remaining'),
1321
+ chalk.cyan('Crypto'),
1322
+ chalk.cyan('Progress'),
1323
+ chalk.cyan('Status'),
1324
+ ],
1325
+ style: { head: [], border: [] },
1326
+ colWidths: [8, 24, 14, 10, 12, 14],
1327
+ });
1328
+
1329
+ payouts.forEach((p) => {
1330
+ const isPartial = p.has_partial_payments && (p.paid_percentage || 0) > 0;
1331
+ const paidPct = p.paid_percentage || 0;
1332
+ const displayAmount = isPartial ? (p.remaining_amount || parseFloat(p.amount)) : parseFloat(p.amount);
1333
+
1334
+ let statusIcon;
1335
+ if (p.status === 'completed') {
1336
+ statusIcon = chalk.green('✓ Done');
1337
+ } else if (p.status === 'failed') {
1338
+ statusIcon = chalk.red('✗ Failed');
1339
+ } else if (isPartial) {
1340
+ statusIcon = chalk.cyan(`% ${Math.round(paidPct)}%`);
1341
+ } else if (p.status === 'pending_review') {
1342
+ statusIcon = chalk.yellow('● Review');
1343
+ } else {
1344
+ statusIcon = chalk.blue('~ ' + p.status);
1345
+ }
1346
+
1347
+ // Progress bar for partial payments
1348
+ let progressStr = '-';
1349
+ if (isPartial) {
1350
+ const filled = Math.round(paidPct / 10);
1351
+ const empty = 10 - filled;
1352
+ progressStr = chalk.green('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
1353
+ }
1354
+
1355
+ table.push([
1356
+ chalk.dim(String(p.id)),
1357
+ p.user_email.length > 22 ? p.user_email.substring(0, 20) + '..' : p.user_email,
1358
+ chalk.green('$' + displayAmount.toFixed(2)),
1359
+ p.currency,
1360
+ progressStr,
1361
+ statusIcon,
1362
+ ]);
1363
+ });
1364
+
1365
+ console.log(table.toString());
1366
+ console.log(chalk.dim(`\n ${data.count} total\n`));
1367
+ }
1368
+
1369
+ } catch (error: any) {
1370
+ spinner.fail(chalk.red('Failed to load Christmas payouts'));
1371
+ console.log(chalk.dim(` ${error.response?.data?.detail || error.message}\n`));
1372
+ }
1373
+ };
1374
+
1375
+ const selectChristmasPayoutsInteractive = async (status?: string): Promise<number[]> => {
1376
+ const spinner = ora('Loading Christmas payouts...').start();
1377
+
1378
+ try {
1379
+ const data = await api.getTarPayPayouts({
1380
+ plan: 'christmas',
1381
+ status: status || 'pending_review',
1382
+ limit: 100,
1383
+ });
1384
+ const payouts: TarPayPayout[] = data.results || [];
1385
+ spinner.stop();
1386
+
1387
+ if (payouts.length === 0) {
1388
+ console.log(chalk.dim('\n No Christmas payouts found\n'));
1389
+ return [];
1390
+ }
1391
+
1392
+ const choices = payouts.map((p) => {
1393
+ const isPartial = p.has_partial_payments && (p.paid_percentage || 0) > 0;
1394
+ const displayAmount = isPartial ? (p.remaining_amount || parseFloat(p.amount)) : parseFloat(p.amount);
1395
+ const progressStr = isPartial ? chalk.cyan(` (${Math.round(p.paid_percentage || 0)}% paid)`) : '';
1396
+ return {
1397
+ name: `${chalk.red('🎄')} ${chalk.dim('#' + p.id)} ${p.user_email.substring(0, 25).padEnd(25)} ${chalk.green('$' + displayAmount.toFixed(2).padStart(10))} ${chalk.dim(p.currency)}${progressStr}`,
1398
+ value: p.id,
1399
+ short: `#${p.id}`,
1400
+ };
1401
+ });
1402
+
1403
+ const { selected } = await inquirer.prompt([
1404
+ {
1405
+ type: 'checkbox',
1406
+ name: 'selected',
1407
+ message: 'Select Christmas payouts to process:',
1408
+ choices,
1409
+ pageSize: 15,
1410
+ loop: false,
1411
+ },
1412
+ ]);
1413
+
1414
+ return selected;
1415
+ } catch (error: any) {
1416
+ spinner.fail(chalk.red('Failed to load Christmas payouts'));
1417
+ return [];
1418
+ }
1419
+ };
1420
+
1421
+ const processChristmasPayoutsWithProgress = async (ids: number[], action: 'approve' | 'reject' | 'partial', percentage?: number) => {
1422
+ const actionLabel = action === 'partial' ? `PARTIAL ${percentage}%` : action.toUpperCase();
1423
+ console.log('\n' + chalk.red('╔══════════════════════════════════════════════════════════════╗'));
1424
+ console.log(chalk.red('║') + chalk.bold.white(` 🎄 CHRISTMAS ${actionLabel} - ${ids.length} PAYOUT(S)... `.substring(0, 60)) + chalk.red('║'));
1425
+ console.log(chalk.red('╠══════════════════════════════════════════════════════════════╣'));
1426
+
1427
+ const startTime = Date.now();
1428
+
1429
+ // Show progress
1430
+ let processed = 0;
1431
+ const progressBar = (current: number, total: number) => {
1432
+ const percent = Math.round((current / total) * 100);
1433
+ const filled = Math.round(percent / 2);
1434
+ const empty = 50 - filled;
1435
+ return chalk.green('█'.repeat(filled)) + chalk.dim('░'.repeat(empty)) + ` ${percent}%`;
1436
+ };
1437
+
1438
+ const progressInterval = setInterval(() => {
1439
+ processed = Math.min(processed + Math.random() * 10, 90);
1440
+ process.stdout.write(`\r${chalk.red('║')} ${progressBar(processed, 100)} ${chalk.red('║')}`);
1441
+ }, 200);
1442
+
1443
+ try {
1444
+ let result: any;
1445
+ let successCount = 0;
1446
+ let failedCount = 0;
1447
+ const errors: string[] = [];
1448
+
1449
+ if (action === 'partial') {
1450
+ result = await api.batchTarPayPartialPayouts(ids, percentage!);
1451
+ successCount = result.success_count || 0;
1452
+ failedCount = result.failed_count || 0;
1453
+ } else {
1454
+ // Process approve/reject one by one
1455
+ for (const id of ids) {
1456
+ try {
1457
+ if (action === 'approve') {
1458
+ await api.approveTarPayPayout(id);
1459
+ } else {
1460
+ await api.rejectTarPayPayout(id);
1461
+ }
1462
+ successCount++;
1463
+ } catch (e: any) {
1464
+ failedCount++;
1465
+ errors.push(`#${id}: ${e.response?.data?.detail || e.message}`);
1466
+ }
1467
+ }
1468
+ result = { success: true, success_count: successCount, failed_count: failedCount, errors };
1469
+ }
1470
+
1471
+ clearInterval(progressInterval);
1472
+ processed = 100;
1473
+ process.stdout.write(`\r${chalk.red('║')} ${progressBar(100, 100)} ${chalk.red('║')}\n`);
1474
+
1475
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
1476
+ console.log(chalk.red('╠══════════════════════════════════════════════════════════════╣'));
1477
+
1478
+ let icon, verb;
1479
+ if (action === 'approve') {
1480
+ icon = '✓'; verb = 'APPROVED';
1481
+ } else if (action === 'partial') {
1482
+ icon = '%'; verb = `PARTIAL ${percentage}% SENT`;
1483
+ } else {
1484
+ icon = '↩'; verb = 'REJECTED';
1485
+ }
1486
+ console.log(chalk.red('║') + chalk.green(` ${icon} ${verb} `.substring(0, 60)) + chalk.red('║'));
1487
+ console.log(chalk.red('║') + ` Successful: ${chalk.bold.green(String(successCount).padEnd(5))} Failed: ${chalk.bold.red(String(failedCount).padEnd(5))} Time: ${chalk.dim(elapsed + 's')}`.padEnd(62) + chalk.red('║'));
1488
+
1489
+ console.log(chalk.red('╚══════════════════════════════════════════════════════════════╝\n'));
1490
+
1491
+ // Show errors if any
1492
+ const errList = result.errors || errors;
1493
+ if (errList.length > 0) {
1494
+ console.log(chalk.red(' Errors:'));
1495
+ errList.slice(0, 5).forEach((err: any) => {
1496
+ const errMsg = typeof err === 'string' ? err : `Payout ${err.payout_id}: ${err.error}`;
1497
+ console.log(chalk.dim(` - ${errMsg}`));
1498
+ });
1499
+ if (errList.length > 5) {
1500
+ console.log(chalk.dim(` ... and ${errList.length - 5} more errors`));
1501
+ }
1502
+ console.log('');
1503
+ }
1504
+
1505
+ return result;
1506
+ } catch (error: any) {
1507
+ clearInterval(progressInterval);
1508
+ console.log(chalk.red('║') + chalk.red(' ✗ ERROR: ' + (error.response?.data?.detail || error.message).substring(0, 50).padEnd(52)) + chalk.red('║'));
1509
+ console.log(chalk.red('╚══════════════════════════════════════════════════════════════╝\n'));
1510
+ return { success: false };
1511
+ }
1512
+ };
1513
+
1514
+ const runChristmasPayoutsShell = async () => {
1515
+ setTitle('Christmas Payouts');
1516
+
1517
+ // Show initial list
1518
+ await listChristmasPayoutsCmd({ status: 'pending_review' });
1519
+
1520
+ const runLoop = async () => {
1521
+ const { command } = await inquirer.prompt([
1522
+ {
1523
+ type: 'input',
1524
+ name: 'command',
1525
+ message: chalk.red('christmas') + chalk.cyan(' > '),
1526
+ prefix: '',
1527
+ },
1528
+ ]);
1529
+
1530
+ const parts = command.trim().split(/\s+/);
1531
+ const cmd = parts[0]?.toLowerCase();
1532
+
1533
+ if (cmd === 'back' || cmd === 'exit' || cmd === 'q') {
1534
+ setTitle('Payouts');
1535
+ return;
1536
+ }
1537
+
1538
+ if (cmd === '') {
1539
+ return runLoop();
1540
+ }
1541
+
1542
+ try {
1543
+ switch (cmd) {
1544
+ case 'refresh':
1545
+ case 'r':
1546
+ case 'list':
1547
+ case 'ls':
1548
+ await listChristmasPayoutsCmd({ status: 'pending_review' });
1549
+ break;
1550
+ case 'all':
1551
+ await listChristmasPayoutsCmd({});
1552
+ break;
1553
+ case 'done':
1554
+ case 'completed':
1555
+ await listChristmasPayoutsCmd({ status: 'completed' });
1556
+ break;
1557
+ case 'failed':
1558
+ await listChristmasPayoutsCmd({ status: 'failed' });
1559
+ break;
1560
+ case 'select':
1561
+ case 's':
1562
+ const selectedIds = await selectChristmasPayoutsInteractive('pending_review');
1563
+ if (selectedIds.length > 0) {
1564
+ const { action } = await inquirer.prompt([
1565
+ {
1566
+ type: 'list',
1567
+ name: 'action',
1568
+ message: `What do you want to do with ${selectedIds.length} selected payout(s)?`,
1569
+ choices: [
1570
+ { name: chalk.green('✓ Approve & Send 100%'), value: 'approve' },
1571
+ { name: chalk.cyan('% Pay Partial % (custom amount)'), value: 'partial' },
1572
+ { name: chalk.yellow('↩ Reject'), value: 'reject' },
1573
+ { name: chalk.dim('✗ Cancel'), value: 'none' },
1574
+ ],
1575
+ },
1576
+ ]);
1577
+
1578
+ if (action === 'approve') {
1579
+ const { confirm } = await inquirer.prompt([
1580
+ {
1581
+ type: 'confirm',
1582
+ name: 'confirm',
1583
+ message: chalk.yellow(`⚠️ Confirm: Approve ${selectedIds.length} payout(s)?`),
1584
+ default: false,
1585
+ },
1586
+ ]);
1587
+ if (confirm) {
1588
+ await processChristmasPayoutsWithProgress(selectedIds, 'approve');
1589
+ }
1590
+ } else if (action === 'partial') {
1591
+ const percentage = await getPercentageWithCustom();
1592
+ if (percentage !== null) {
1593
+ const { confirm } = await inquirer.prompt([
1594
+ {
1595
+ type: 'confirm',
1596
+ name: 'confirm',
1597
+ message: chalk.yellow(`⚠️ Confirm: Pay ${percentage}% of ${selectedIds.length} payout(s)?`),
1598
+ default: false,
1599
+ },
1600
+ ]);
1601
+ if (confirm) {
1602
+ await processChristmasPayoutsWithProgress(selectedIds, 'partial', percentage);
1603
+ }
1604
+ }
1605
+ } else if (action === 'reject') {
1606
+ const { confirm } = await inquirer.prompt([
1607
+ {
1608
+ type: 'confirm',
1609
+ name: 'confirm',
1610
+ message: chalk.yellow(`⚠️ Confirm: Reject ${selectedIds.length} payout(s)?`),
1611
+ default: false,
1612
+ },
1613
+ ]);
1614
+ if (confirm) {
1615
+ await processChristmasPayoutsWithProgress(selectedIds, 'reject');
1616
+ }
1617
+ }
1618
+ }
1619
+ break;
1620
+ case 'approve':
1621
+ case 'a':
1622
+ const approveIds = await selectChristmasPayoutsInteractive('pending_review');
1623
+ if (approveIds.length > 0) {
1624
+ const { confirm } = await inquirer.prompt([
1625
+ {
1626
+ type: 'confirm',
1627
+ name: 'confirm',
1628
+ message: chalk.yellow(`⚠️ Approve ${approveIds.length} payout(s)?`),
1629
+ default: false,
1630
+ },
1631
+ ]);
1632
+ if (confirm) {
1633
+ await processChristmasPayoutsWithProgress(approveIds, 'approve');
1634
+ }
1635
+ }
1636
+ break;
1637
+ case 'partial':
1638
+ case 'p':
1639
+ const partialIds = await selectChristmasPayoutsInteractive('pending_review');
1640
+ if (partialIds.length > 0) {
1641
+ const percentage = await getPercentageWithCustom();
1642
+ if (percentage !== null) {
1643
+ const { confirm } = await inquirer.prompt([
1644
+ {
1645
+ type: 'confirm',
1646
+ name: 'confirm',
1647
+ message: chalk.yellow(`⚠️ Pay ${percentage}% of ${partialIds.length} payout(s)?`),
1648
+ default: false,
1649
+ },
1650
+ ]);
1651
+ if (confirm) {
1652
+ await processChristmasPayoutsWithProgress(partialIds, 'partial', percentage);
1653
+ }
1654
+ }
1655
+ }
1656
+ break;
1657
+ case 'reject':
1658
+ const rejectIds = await selectChristmasPayoutsInteractive('pending_review');
1659
+ if (rejectIds.length > 0) {
1660
+ const { confirm } = await inquirer.prompt([
1661
+ {
1662
+ type: 'confirm',
1663
+ name: 'confirm',
1664
+ message: chalk.yellow(`⚠️ Reject ${rejectIds.length} payout(s)?`),
1665
+ default: false,
1666
+ },
1667
+ ]);
1668
+ if (confirm) {
1669
+ await processChristmasPayoutsWithProgress(rejectIds, 'reject');
1670
+ }
1671
+ }
1672
+ break;
1673
+ case 'help':
1674
+ case '?':
1675
+ console.log(chalk.red('\n 🎄 Christmas Payout Commands:'));
1676
+ console.log(chalk.dim(' ────────────────────────────────'));
1677
+ console.log(' refresh, r Refresh pending payouts');
1678
+ console.log(' list, ls List pending_review payouts');
1679
+ console.log(' all List all payouts (any status)');
1680
+ console.log(' done List completed payouts');
1681
+ console.log(' failed List failed payouts');
1682
+ console.log(' select, s Interactive selection mode');
1683
+ console.log(' approve, a Select & approve payouts');
1684
+ console.log(chalk.cyan(' partial, p Select & pay partial %'));
1685
+ console.log(' reject Select & reject payouts');
1686
+ console.log(' back, q Return to main payouts');
1687
+ console.log(chalk.dim('\n Note: Payouts > $50 need approval, < $50 auto-sent'));
1688
+ console.log('');
1689
+ break;
1690
+ default:
1691
+ console.log(chalk.red(` Unknown command: ${cmd}`));
1692
+ console.log(chalk.dim(' Type "help" for available commands\n'));
1693
+ }
1694
+ } catch (e: any) {
1695
+ console.log(chalk.red(` Error: ${e.message}\n`));
1696
+ }
1697
+
1698
+ await runLoop();
1699
+ };
1700
+
1701
+ await runLoop();
1702
+ };
1703
+
1235
1704
  const runPayoutsShell = async () => {
1236
1705
  setTitle('Payouts');
1237
1706
  await showPayoutDashboard();
@@ -1303,30 +1772,19 @@ const runPayoutsShell = async () => {
1303
1772
  await processPayoutsWithProgress(selectedIds, 'approve');
1304
1773
  }
1305
1774
  } else if (action === 'partial') {
1306
- const { percentage } = await inquirer.prompt([
1307
- {
1308
- type: 'list',
1309
- name: 'percentage',
1310
- message: 'Select percentage of remaining amount to pay:',
1311
- choices: [
1312
- { name: '10%', value: 10 },
1313
- { name: '25%', value: 25 },
1314
- { name: '50%', value: 50 },
1315
- { name: '75%', value: 75 },
1316
- { name: '100% (Full)', value: 100 },
1317
- ],
1318
- },
1319
- ]);
1320
- const { confirm } = await inquirer.prompt([
1321
- {
1322
- type: 'confirm',
1323
- name: 'confirm',
1324
- message: chalk.yellow(`⚠️ Confirm: Pay ${percentage}% of ${selectedIds.length} payout(s)?`),
1325
- default: false,
1326
- },
1327
- ]);
1328
- if (confirm) {
1329
- await processPayoutsWithProgress(selectedIds, 'partial', percentage);
1775
+ const percentage = await getPercentageWithCustom();
1776
+ if (percentage !== null) {
1777
+ const { confirm } = await inquirer.prompt([
1778
+ {
1779
+ type: 'confirm',
1780
+ name: 'confirm',
1781
+ message: chalk.yellow(`⚠️ Confirm: Pay ${percentage}% of ${selectedIds.length} payout(s)?`),
1782
+ default: false,
1783
+ },
1784
+ ]);
1785
+ if (confirm) {
1786
+ await processPayoutsWithProgress(selectedIds, 'partial', percentage);
1787
+ }
1330
1788
  }
1331
1789
  } else if (action === 'cancel') {
1332
1790
  const { confirm } = await inquirer.prompt([
@@ -1395,30 +1853,19 @@ const runPayoutsShell = async () => {
1395
1853
  const partialPlan = args.find((a: string) => !a.startsWith('-') && isNaN(Number(a)));
1396
1854
  const partialSelected = await selectPayoutsInteractive(partialPlan);
1397
1855
  if (partialSelected.length > 0) {
1398
- const { partialPercentage } = await inquirer.prompt([
1399
- {
1400
- type: 'list',
1401
- name: 'partialPercentage',
1402
- message: 'Select percentage of remaining amount to pay:',
1403
- choices: [
1404
- { name: '10%', value: 10 },
1405
- { name: '25%', value: 25 },
1406
- { name: '50%', value: 50 },
1407
- { name: '75%', value: 75 },
1408
- { name: '100% (Full)', value: 100 },
1409
- ],
1410
- },
1411
- ]);
1412
- const { confirmPartial } = await inquirer.prompt([
1413
- {
1414
- type: 'confirm',
1415
- name: 'confirmPartial',
1416
- message: chalk.yellow(`⚠️ Pay ${partialPercentage}% of ${partialSelected.length} payout(s)?`),
1417
- default: false,
1418
- },
1419
- ]);
1420
- if (confirmPartial) {
1421
- await processPayoutsWithProgress(partialSelected, 'partial', partialPercentage);
1856
+ const partialPercentage = await getPercentageWithCustom();
1857
+ if (partialPercentage !== null) {
1858
+ const { confirmPartial } = await inquirer.prompt([
1859
+ {
1860
+ type: 'confirm',
1861
+ name: 'confirmPartial',
1862
+ message: chalk.yellow(`⚠️ Pay ${partialPercentage}% of ${partialSelected.length} payout(s)?`),
1863
+ default: false,
1864
+ },
1865
+ ]);
1866
+ if (confirmPartial) {
1867
+ await processPayoutsWithProgress(partialSelected, 'partial', partialPercentage);
1868
+ }
1422
1869
  }
1423
1870
  }
1424
1871
  break;
@@ -1429,6 +1876,11 @@ const runPayoutsShell = async () => {
1429
1876
  case 'validator_v2':
1430
1877
  await listPayoutsCmd({ plan: cmd });
1431
1878
  break;
1879
+ case 'christmas':
1880
+ case 'xmas':
1881
+ // Christmas payouts subshell
1882
+ await runChristmasPayoutsShell();
1883
+ break;
1432
1884
  case 'process':
1433
1885
  // Process specific plan with interactive selection
1434
1886
  const { processPlan } = await inquirer.prompt([
@@ -1509,6 +1961,7 @@ const runPayoutsShell = async () => {
1509
1961
  console.log(' cancel [ids] Cancel payouts (interactive if no IDs)');
1510
1962
  console.log(' process Process payouts by plan (interactive)');
1511
1963
  console.log(' hermes/alpha/etc List payouts for specific plan');
1964
+ console.log(chalk.red(' christmas, xmas 🎄 Christmas payouts (TarPay)'));
1512
1965
  console.log(' back, q Return to main menu');
1513
1966
  console.log('');
1514
1967
  break;