@targlobal/mission-control 1.3.2 → 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/dist/index.js CHANGED
@@ -45,7 +45,7 @@ const inquirer_1 = __importDefault(require("inquirer"));
45
45
  const config_1 = require("./config");
46
46
  const api_1 = require("./api");
47
47
  const child_process_1 = require("child_process");
48
- const VERSION = '1.3.2';
48
+ const VERSION = '1.4.0';
49
49
  const program = new commander_1.Command();
50
50
  // Set terminal title
51
51
  const setTitle = (context) => {
@@ -705,6 +705,7 @@ const PLAN_COLORS = {
705
705
  validator_v2: chalk_1.default.blue,
706
706
  booster: chalk_1.default.green,
707
707
  dumpster: chalk_1.default.gray,
708
+ christmas: chalk_1.default.red,
708
709
  };
709
710
  const PLAN_ICONS = {
710
711
  hermes: '⚡',
@@ -713,6 +714,43 @@ const PLAN_ICONS = {
713
714
  validator_v2: '🔐',
714
715
  booster: '🚀',
715
716
  dumpster: '🗑️',
717
+ christmas: '🎄',
718
+ };
719
+ // Helper to get percentage (preset or custom input)
720
+ const getPercentageWithCustom = async () => {
721
+ const { percentage } = await inquirer_1.default.prompt([
722
+ {
723
+ type: 'list',
724
+ name: 'percentage',
725
+ message: 'Select percentage of remaining amount to pay:',
726
+ choices: [
727
+ { name: '10%', value: 10 },
728
+ { name: '25%', value: 25 },
729
+ { name: '50%', value: 50 },
730
+ { name: '75%', value: 75 },
731
+ { name: '100% (Full)', value: 100 },
732
+ { name: chalk_1.default.cyan('Custom...'), value: -1 },
733
+ ],
734
+ },
735
+ ]);
736
+ if (percentage === -1) {
737
+ const { customPct } = await inquirer_1.default.prompt([
738
+ {
739
+ type: 'input',
740
+ name: 'customPct',
741
+ message: 'Enter custom percentage (1-100):',
742
+ validate: (input) => {
743
+ const num = parseFloat(input);
744
+ if (isNaN(num) || num < 1 || num > 100) {
745
+ return 'Please enter a number between 1 and 100';
746
+ }
747
+ return true;
748
+ },
749
+ },
750
+ ]);
751
+ return parseFloat(customPct);
752
+ }
753
+ return percentage;
716
754
  };
717
755
  const formatAmount = (amount, crypto) => {
718
756
  const formatted = amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 4 });
@@ -811,12 +849,13 @@ const showPayoutDashboard = async () => {
811
849
  Object.entries(byPlan).forEach(([plan, planPayouts]) => {
812
850
  const planColor = PLAN_COLORS[plan] || chalk_1.default.white;
813
851
  const planIcon = PLAN_ICONS[plan] || '📋';
814
- // Group amounts by crypto type
852
+ // Group REMAINING amounts by crypto type (use remaining_amount if partial, else amount)
815
853
  const byCrypto = {};
816
854
  planPayouts.forEach((p) => {
817
855
  if (!byCrypto[p.crypto_type])
818
856
  byCrypto[p.crypto_type] = 0;
819
- byCrypto[p.crypto_type] += p.amount;
857
+ const displayAmt = p.remaining_amount !== undefined ? p.remaining_amount : p.amount;
858
+ byCrypto[p.crypto_type] += displayAmt;
820
859
  });
821
860
  const cryptoTotals = Object.entries(byCrypto).map(([crypto, amt]) => `${amt.toFixed(2)} ${crypto}`).join(', ');
822
861
  console.log(chalk_1.default.cyan('║') + ` ${planIcon} ${planColor(plan.toUpperCase().padEnd(12))} ${chalk_1.default.dim('|')} ${chalk_1.default.white(planPayouts.length + ' payouts')} ${chalk_1.default.dim('|')} ${chalk_1.default.yellow(cryptoTotals)}`.padEnd(72) + chalk_1.default.cyan('║'));
@@ -824,9 +863,11 @@ const showPayoutDashboard = async () => {
824
863
  planPayouts.slice(0, 3).forEach((p) => {
825
864
  const idStr = chalk_1.default.dim(`#${p.id}`);
826
865
  const emailStr = p.user_email.length > 20 ? p.user_email.substring(0, 18) + '..' : p.user_email;
827
- const amountStr = formatAmount(p.amount, p.crypto_type);
866
+ const displayAmt = p.remaining_amount !== undefined ? p.remaining_amount : p.amount;
867
+ const amountStr = formatAmount(displayAmt, p.crypto_type);
868
+ const pctStr = p.has_partial_payments && (p.paid_percentage || 0) > 0 ? chalk_1.default.cyan(` (${Math.round(p.paid_percentage || 0)}%)`) : '';
828
869
  const walletStr = formatWallet(p.wallet_address);
829
- console.log(chalk_1.default.cyan('║') + ` ${idStr} ${chalk_1.default.white(emailStr.padEnd(20))} ${chalk_1.default.green(amountStr.padStart(14))} ${chalk_1.default.dim(walletStr)}`.padEnd(72) + chalk_1.default.cyan('║'));
870
+ console.log(chalk_1.default.cyan('║') + ` ${idStr} ${chalk_1.default.white(emailStr.padEnd(20))} ${chalk_1.default.green(amountStr.padStart(10))}${pctStr} ${chalk_1.default.dim(walletStr)}`.padEnd(72) + chalk_1.default.cyan('║'));
830
871
  });
831
872
  if (planPayouts.length > 3) {
832
873
  console.log(chalk_1.default.cyan('║') + chalk_1.default.dim(` ... and ${planPayouts.length - 3} more`).padEnd(64) + chalk_1.default.cyan('║'));
@@ -1179,6 +1220,413 @@ const processPayoutsWithProgress = async (ids, action, percentage) => {
1179
1220
  return { success: false };
1180
1221
  }
1181
1222
  };
1223
+ // ==================== CHRISTMAS/TARPAY PAYOUT FUNCTIONS ====================
1224
+ const listChristmasPayoutsCmd = async (options) => {
1225
+ const spinner = (0, ora_1.default)('Loading Christmas payouts...').start();
1226
+ const compact = isCompact();
1227
+ try {
1228
+ const data = await api_1.api.getTarPayPayouts({
1229
+ plan: 'christmas',
1230
+ status: options.status || 'pending_review',
1231
+ limit: compact ? 10 : 20,
1232
+ offset: ((options.page || 1) - 1) * (compact ? 10 : 20),
1233
+ });
1234
+ const payouts = data.results || [];
1235
+ spinner.stop();
1236
+ if (payouts.length === 0) {
1237
+ console.log(chalk_1.default.dim('\n No Christmas payouts found\n'));
1238
+ return;
1239
+ }
1240
+ // Info banner about $50 threshold
1241
+ console.log('');
1242
+ console.log(chalk_1.default.red('🎄 CHRISTMAS PAYOUTS') + chalk_1.default.dim(' (TarPay)'));
1243
+ console.log(chalk_1.default.dim(' Payouts > $50 require manual approval. < $50 are auto-sent.'));
1244
+ console.log('');
1245
+ if (compact) {
1246
+ payouts.forEach((p) => {
1247
+ const isPartial = p.has_partial_payments && (p.paid_percentage || 0) > 0;
1248
+ const statusIcon = p.status === 'completed' ? chalk_1.default.green('✓') :
1249
+ p.status === 'failed' ? chalk_1.default.red('✗') :
1250
+ isPartial ? chalk_1.default.cyan('%') : chalk_1.default.yellow('●');
1251
+ const email = p.user_email.length > 12 ? p.user_email.substring(0, 10) + '..' : p.user_email;
1252
+ const displayAmt = isPartial ? (p.remaining_amount || parseFloat(p.amount)) : parseFloat(p.amount);
1253
+ const amountStr = isPartial ? `${displayAmt.toFixed(2)}(${Math.round(p.paid_percentage || 0)}%)` : displayAmt.toFixed(2);
1254
+ console.log(` ${statusIcon} ${chalk_1.default.dim('#' + p.id)} ${chalk_1.default.red('xmas')} ${email} ${chalk_1.default.green(amountStr)} ${p.currency}`);
1255
+ });
1256
+ console.log(chalk_1.default.dim(`\n ${data.count} total\n`));
1257
+ }
1258
+ else {
1259
+ const table = new cli_table3_1.default({
1260
+ head: [
1261
+ chalk_1.default.cyan('ID'),
1262
+ chalk_1.default.cyan('User'),
1263
+ chalk_1.default.cyan('Remaining'),
1264
+ chalk_1.default.cyan('Crypto'),
1265
+ chalk_1.default.cyan('Progress'),
1266
+ chalk_1.default.cyan('Status'),
1267
+ ],
1268
+ style: { head: [], border: [] },
1269
+ colWidths: [8, 24, 14, 10, 12, 14],
1270
+ });
1271
+ payouts.forEach((p) => {
1272
+ const isPartial = p.has_partial_payments && (p.paid_percentage || 0) > 0;
1273
+ const paidPct = p.paid_percentage || 0;
1274
+ const displayAmount = isPartial ? (p.remaining_amount || parseFloat(p.amount)) : parseFloat(p.amount);
1275
+ let statusIcon;
1276
+ if (p.status === 'completed') {
1277
+ statusIcon = chalk_1.default.green('✓ Done');
1278
+ }
1279
+ else if (p.status === 'failed') {
1280
+ statusIcon = chalk_1.default.red('✗ Failed');
1281
+ }
1282
+ else if (isPartial) {
1283
+ statusIcon = chalk_1.default.cyan(`% ${Math.round(paidPct)}%`);
1284
+ }
1285
+ else if (p.status === 'pending_review') {
1286
+ statusIcon = chalk_1.default.yellow('● Review');
1287
+ }
1288
+ else {
1289
+ statusIcon = chalk_1.default.blue('~ ' + p.status);
1290
+ }
1291
+ // Progress bar for partial payments
1292
+ let progressStr = '-';
1293
+ if (isPartial) {
1294
+ const filled = Math.round(paidPct / 10);
1295
+ const empty = 10 - filled;
1296
+ progressStr = chalk_1.default.green('█'.repeat(filled)) + chalk_1.default.dim('░'.repeat(empty));
1297
+ }
1298
+ table.push([
1299
+ chalk_1.default.dim(String(p.id)),
1300
+ p.user_email.length > 22 ? p.user_email.substring(0, 20) + '..' : p.user_email,
1301
+ chalk_1.default.green('$' + displayAmount.toFixed(2)),
1302
+ p.currency,
1303
+ progressStr,
1304
+ statusIcon,
1305
+ ]);
1306
+ });
1307
+ console.log(table.toString());
1308
+ console.log(chalk_1.default.dim(`\n ${data.count} total\n`));
1309
+ }
1310
+ }
1311
+ catch (error) {
1312
+ spinner.fail(chalk_1.default.red('Failed to load Christmas payouts'));
1313
+ console.log(chalk_1.default.dim(` ${error.response?.data?.detail || error.message}\n`));
1314
+ }
1315
+ };
1316
+ const selectChristmasPayoutsInteractive = async (status) => {
1317
+ const spinner = (0, ora_1.default)('Loading Christmas payouts...').start();
1318
+ try {
1319
+ const data = await api_1.api.getTarPayPayouts({
1320
+ plan: 'christmas',
1321
+ status: status || 'pending_review',
1322
+ limit: 100,
1323
+ });
1324
+ const payouts = data.results || [];
1325
+ spinner.stop();
1326
+ if (payouts.length === 0) {
1327
+ console.log(chalk_1.default.dim('\n No Christmas payouts found\n'));
1328
+ return [];
1329
+ }
1330
+ const choices = payouts.map((p) => {
1331
+ const isPartial = p.has_partial_payments && (p.paid_percentage || 0) > 0;
1332
+ const displayAmount = isPartial ? (p.remaining_amount || parseFloat(p.amount)) : parseFloat(p.amount);
1333
+ const progressStr = isPartial ? chalk_1.default.cyan(` (${Math.round(p.paid_percentage || 0)}% paid)`) : '';
1334
+ return {
1335
+ name: `${chalk_1.default.red('🎄')} ${chalk_1.default.dim('#' + p.id)} ${p.user_email.substring(0, 25).padEnd(25)} ${chalk_1.default.green('$' + displayAmount.toFixed(2).padStart(10))} ${chalk_1.default.dim(p.currency)}${progressStr}`,
1336
+ value: p.id,
1337
+ short: `#${p.id}`,
1338
+ };
1339
+ });
1340
+ const { selected } = await inquirer_1.default.prompt([
1341
+ {
1342
+ type: 'checkbox',
1343
+ name: 'selected',
1344
+ message: 'Select Christmas payouts to process:',
1345
+ choices,
1346
+ pageSize: 15,
1347
+ loop: false,
1348
+ },
1349
+ ]);
1350
+ return selected;
1351
+ }
1352
+ catch (error) {
1353
+ spinner.fail(chalk_1.default.red('Failed to load Christmas payouts'));
1354
+ return [];
1355
+ }
1356
+ };
1357
+ const processChristmasPayoutsWithProgress = async (ids, action, percentage) => {
1358
+ const actionLabel = action === 'partial' ? `PARTIAL ${percentage}%` : action.toUpperCase();
1359
+ console.log('\n' + chalk_1.default.red('╔══════════════════════════════════════════════════════════════╗'));
1360
+ console.log(chalk_1.default.red('║') + chalk_1.default.bold.white(` 🎄 CHRISTMAS ${actionLabel} - ${ids.length} PAYOUT(S)... `.substring(0, 60)) + chalk_1.default.red('║'));
1361
+ console.log(chalk_1.default.red('╠══════════════════════════════════════════════════════════════╣'));
1362
+ const startTime = Date.now();
1363
+ // Show progress
1364
+ let processed = 0;
1365
+ const progressBar = (current, total) => {
1366
+ const percent = Math.round((current / total) * 100);
1367
+ const filled = Math.round(percent / 2);
1368
+ const empty = 50 - filled;
1369
+ return chalk_1.default.green('█'.repeat(filled)) + chalk_1.default.dim('░'.repeat(empty)) + ` ${percent}%`;
1370
+ };
1371
+ const progressInterval = setInterval(() => {
1372
+ processed = Math.min(processed + Math.random() * 10, 90);
1373
+ process.stdout.write(`\r${chalk_1.default.red('║')} ${progressBar(processed, 100)} ${chalk_1.default.red('║')}`);
1374
+ }, 200);
1375
+ try {
1376
+ let result;
1377
+ let successCount = 0;
1378
+ let failedCount = 0;
1379
+ const errors = [];
1380
+ if (action === 'partial') {
1381
+ result = await api_1.api.batchTarPayPartialPayouts(ids, percentage);
1382
+ successCount = result.success_count || 0;
1383
+ failedCount = result.failed_count || 0;
1384
+ }
1385
+ else {
1386
+ // Process approve/reject one by one
1387
+ for (const id of ids) {
1388
+ try {
1389
+ if (action === 'approve') {
1390
+ await api_1.api.approveTarPayPayout(id);
1391
+ }
1392
+ else {
1393
+ await api_1.api.rejectTarPayPayout(id);
1394
+ }
1395
+ successCount++;
1396
+ }
1397
+ catch (e) {
1398
+ failedCount++;
1399
+ errors.push(`#${id}: ${e.response?.data?.detail || e.message}`);
1400
+ }
1401
+ }
1402
+ result = { success: true, success_count: successCount, failed_count: failedCount, errors };
1403
+ }
1404
+ clearInterval(progressInterval);
1405
+ processed = 100;
1406
+ process.stdout.write(`\r${chalk_1.default.red('║')} ${progressBar(100, 100)} ${chalk_1.default.red('║')}\n`);
1407
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
1408
+ console.log(chalk_1.default.red('╠══════════════════════════════════════════════════════════════╣'));
1409
+ let icon, verb;
1410
+ if (action === 'approve') {
1411
+ icon = '✓';
1412
+ verb = 'APPROVED';
1413
+ }
1414
+ else if (action === 'partial') {
1415
+ icon = '%';
1416
+ verb = `PARTIAL ${percentage}% SENT`;
1417
+ }
1418
+ else {
1419
+ icon = '↩';
1420
+ verb = 'REJECTED';
1421
+ }
1422
+ console.log(chalk_1.default.red('║') + chalk_1.default.green(` ${icon} ${verb} `.substring(0, 60)) + chalk_1.default.red('║'));
1423
+ console.log(chalk_1.default.red('║') + ` Successful: ${chalk_1.default.bold.green(String(successCount).padEnd(5))} Failed: ${chalk_1.default.bold.red(String(failedCount).padEnd(5))} Time: ${chalk_1.default.dim(elapsed + 's')}`.padEnd(62) + chalk_1.default.red('║'));
1424
+ console.log(chalk_1.default.red('╚══════════════════════════════════════════════════════════════╝\n'));
1425
+ // Show errors if any
1426
+ const errList = result.errors || errors;
1427
+ if (errList.length > 0) {
1428
+ console.log(chalk_1.default.red(' Errors:'));
1429
+ errList.slice(0, 5).forEach((err) => {
1430
+ const errMsg = typeof err === 'string' ? err : `Payout ${err.payout_id}: ${err.error}`;
1431
+ console.log(chalk_1.default.dim(` - ${errMsg}`));
1432
+ });
1433
+ if (errList.length > 5) {
1434
+ console.log(chalk_1.default.dim(` ... and ${errList.length - 5} more errors`));
1435
+ }
1436
+ console.log('');
1437
+ }
1438
+ return result;
1439
+ }
1440
+ catch (error) {
1441
+ clearInterval(progressInterval);
1442
+ console.log(chalk_1.default.red('║') + chalk_1.default.red(' ✗ ERROR: ' + (error.response?.data?.detail || error.message).substring(0, 50).padEnd(52)) + chalk_1.default.red('║'));
1443
+ console.log(chalk_1.default.red('╚══════════════════════════════════════════════════════════════╝\n'));
1444
+ return { success: false };
1445
+ }
1446
+ };
1447
+ const runChristmasPayoutsShell = async () => {
1448
+ setTitle('Christmas Payouts');
1449
+ // Show initial list
1450
+ await listChristmasPayoutsCmd({ status: 'pending_review' });
1451
+ const runLoop = async () => {
1452
+ const { command } = await inquirer_1.default.prompt([
1453
+ {
1454
+ type: 'input',
1455
+ name: 'command',
1456
+ message: chalk_1.default.red('christmas') + chalk_1.default.cyan(' > '),
1457
+ prefix: '',
1458
+ },
1459
+ ]);
1460
+ const parts = command.trim().split(/\s+/);
1461
+ const cmd = parts[0]?.toLowerCase();
1462
+ if (cmd === 'back' || cmd === 'exit' || cmd === 'q') {
1463
+ setTitle('Payouts');
1464
+ return;
1465
+ }
1466
+ if (cmd === '') {
1467
+ return runLoop();
1468
+ }
1469
+ try {
1470
+ switch (cmd) {
1471
+ case 'refresh':
1472
+ case 'r':
1473
+ case 'list':
1474
+ case 'ls':
1475
+ await listChristmasPayoutsCmd({ status: 'pending_review' });
1476
+ break;
1477
+ case 'all':
1478
+ await listChristmasPayoutsCmd({});
1479
+ break;
1480
+ case 'done':
1481
+ case 'completed':
1482
+ await listChristmasPayoutsCmd({ status: 'completed' });
1483
+ break;
1484
+ case 'failed':
1485
+ await listChristmasPayoutsCmd({ status: 'failed' });
1486
+ break;
1487
+ case 'select':
1488
+ case 's':
1489
+ const selectedIds = await selectChristmasPayoutsInteractive('pending_review');
1490
+ if (selectedIds.length > 0) {
1491
+ const { action } = await inquirer_1.default.prompt([
1492
+ {
1493
+ type: 'list',
1494
+ name: 'action',
1495
+ message: `What do you want to do with ${selectedIds.length} selected payout(s)?`,
1496
+ choices: [
1497
+ { name: chalk_1.default.green('✓ Approve & Send 100%'), value: 'approve' },
1498
+ { name: chalk_1.default.cyan('% Pay Partial % (custom amount)'), value: 'partial' },
1499
+ { name: chalk_1.default.yellow('↩ Reject'), value: 'reject' },
1500
+ { name: chalk_1.default.dim('✗ Cancel'), value: 'none' },
1501
+ ],
1502
+ },
1503
+ ]);
1504
+ if (action === 'approve') {
1505
+ const { confirm } = await inquirer_1.default.prompt([
1506
+ {
1507
+ type: 'confirm',
1508
+ name: 'confirm',
1509
+ message: chalk_1.default.yellow(`⚠️ Confirm: Approve ${selectedIds.length} payout(s)?`),
1510
+ default: false,
1511
+ },
1512
+ ]);
1513
+ if (confirm) {
1514
+ await processChristmasPayoutsWithProgress(selectedIds, 'approve');
1515
+ }
1516
+ }
1517
+ else if (action === 'partial') {
1518
+ const percentage = await getPercentageWithCustom();
1519
+ if (percentage !== null) {
1520
+ const { confirm } = await inquirer_1.default.prompt([
1521
+ {
1522
+ type: 'confirm',
1523
+ name: 'confirm',
1524
+ message: chalk_1.default.yellow(`⚠️ Confirm: Pay ${percentage}% of ${selectedIds.length} payout(s)?`),
1525
+ default: false,
1526
+ },
1527
+ ]);
1528
+ if (confirm) {
1529
+ await processChristmasPayoutsWithProgress(selectedIds, 'partial', percentage);
1530
+ }
1531
+ }
1532
+ }
1533
+ else if (action === 'reject') {
1534
+ const { confirm } = await inquirer_1.default.prompt([
1535
+ {
1536
+ type: 'confirm',
1537
+ name: 'confirm',
1538
+ message: chalk_1.default.yellow(`⚠️ Confirm: Reject ${selectedIds.length} payout(s)?`),
1539
+ default: false,
1540
+ },
1541
+ ]);
1542
+ if (confirm) {
1543
+ await processChristmasPayoutsWithProgress(selectedIds, 'reject');
1544
+ }
1545
+ }
1546
+ }
1547
+ break;
1548
+ case 'approve':
1549
+ case 'a':
1550
+ const approveIds = await selectChristmasPayoutsInteractive('pending_review');
1551
+ if (approveIds.length > 0) {
1552
+ const { confirm } = await inquirer_1.default.prompt([
1553
+ {
1554
+ type: 'confirm',
1555
+ name: 'confirm',
1556
+ message: chalk_1.default.yellow(`⚠️ Approve ${approveIds.length} payout(s)?`),
1557
+ default: false,
1558
+ },
1559
+ ]);
1560
+ if (confirm) {
1561
+ await processChristmasPayoutsWithProgress(approveIds, 'approve');
1562
+ }
1563
+ }
1564
+ break;
1565
+ case 'partial':
1566
+ case 'p':
1567
+ const partialIds = await selectChristmasPayoutsInteractive('pending_review');
1568
+ if (partialIds.length > 0) {
1569
+ const percentage = await getPercentageWithCustom();
1570
+ if (percentage !== null) {
1571
+ const { confirm } = await inquirer_1.default.prompt([
1572
+ {
1573
+ type: 'confirm',
1574
+ name: 'confirm',
1575
+ message: chalk_1.default.yellow(`⚠️ Pay ${percentage}% of ${partialIds.length} payout(s)?`),
1576
+ default: false,
1577
+ },
1578
+ ]);
1579
+ if (confirm) {
1580
+ await processChristmasPayoutsWithProgress(partialIds, 'partial', percentage);
1581
+ }
1582
+ }
1583
+ }
1584
+ break;
1585
+ case 'reject':
1586
+ const rejectIds = await selectChristmasPayoutsInteractive('pending_review');
1587
+ if (rejectIds.length > 0) {
1588
+ const { confirm } = await inquirer_1.default.prompt([
1589
+ {
1590
+ type: 'confirm',
1591
+ name: 'confirm',
1592
+ message: chalk_1.default.yellow(`⚠️ Reject ${rejectIds.length} payout(s)?`),
1593
+ default: false,
1594
+ },
1595
+ ]);
1596
+ if (confirm) {
1597
+ await processChristmasPayoutsWithProgress(rejectIds, 'reject');
1598
+ }
1599
+ }
1600
+ break;
1601
+ case 'help':
1602
+ case '?':
1603
+ console.log(chalk_1.default.red('\n 🎄 Christmas Payout Commands:'));
1604
+ console.log(chalk_1.default.dim(' ────────────────────────────────'));
1605
+ console.log(' refresh, r Refresh pending payouts');
1606
+ console.log(' list, ls List pending_review payouts');
1607
+ console.log(' all List all payouts (any status)');
1608
+ console.log(' done List completed payouts');
1609
+ console.log(' failed List failed payouts');
1610
+ console.log(' select, s Interactive selection mode');
1611
+ console.log(' approve, a Select & approve payouts');
1612
+ console.log(chalk_1.default.cyan(' partial, p Select & pay partial %'));
1613
+ console.log(' reject Select & reject payouts');
1614
+ console.log(' back, q Return to main payouts');
1615
+ console.log(chalk_1.default.dim('\n Note: Payouts > $50 need approval, < $50 auto-sent'));
1616
+ console.log('');
1617
+ break;
1618
+ default:
1619
+ console.log(chalk_1.default.red(` Unknown command: ${cmd}`));
1620
+ console.log(chalk_1.default.dim(' Type "help" for available commands\n'));
1621
+ }
1622
+ }
1623
+ catch (e) {
1624
+ console.log(chalk_1.default.red(` Error: ${e.message}\n`));
1625
+ }
1626
+ await runLoop();
1627
+ };
1628
+ await runLoop();
1629
+ };
1182
1630
  const runPayoutsShell = async () => {
1183
1631
  setTitle('Payouts');
1184
1632
  await showPayoutDashboard();
@@ -1245,30 +1693,19 @@ const runPayoutsShell = async () => {
1245
1693
  }
1246
1694
  }
1247
1695
  else if (action === 'partial') {
1248
- const { percentage } = await inquirer_1.default.prompt([
1249
- {
1250
- type: 'list',
1251
- name: 'percentage',
1252
- message: 'Select percentage of remaining amount to pay:',
1253
- choices: [
1254
- { name: '10%', value: 10 },
1255
- { name: '25%', value: 25 },
1256
- { name: '50%', value: 50 },
1257
- { name: '75%', value: 75 },
1258
- { name: '100% (Full)', value: 100 },
1259
- ],
1260
- },
1261
- ]);
1262
- const { confirm } = await inquirer_1.default.prompt([
1263
- {
1264
- type: 'confirm',
1265
- name: 'confirm',
1266
- message: chalk_1.default.yellow(`⚠️ Confirm: Pay ${percentage}% of ${selectedIds.length} payout(s)?`),
1267
- default: false,
1268
- },
1269
- ]);
1270
- if (confirm) {
1271
- await processPayoutsWithProgress(selectedIds, 'partial', percentage);
1696
+ const percentage = await getPercentageWithCustom();
1697
+ if (percentage !== null) {
1698
+ const { confirm } = await inquirer_1.default.prompt([
1699
+ {
1700
+ type: 'confirm',
1701
+ name: 'confirm',
1702
+ message: chalk_1.default.yellow(`⚠️ Confirm: Pay ${percentage}% of ${selectedIds.length} payout(s)?`),
1703
+ default: false,
1704
+ },
1705
+ ]);
1706
+ if (confirm) {
1707
+ await processPayoutsWithProgress(selectedIds, 'partial', percentage);
1708
+ }
1272
1709
  }
1273
1710
  }
1274
1711
  else if (action === 'cancel') {
@@ -1340,30 +1777,19 @@ const runPayoutsShell = async () => {
1340
1777
  const partialPlan = args.find((a) => !a.startsWith('-') && isNaN(Number(a)));
1341
1778
  const partialSelected = await selectPayoutsInteractive(partialPlan);
1342
1779
  if (partialSelected.length > 0) {
1343
- const { partialPercentage } = await inquirer_1.default.prompt([
1344
- {
1345
- type: 'list',
1346
- name: 'partialPercentage',
1347
- message: 'Select percentage of remaining amount to pay:',
1348
- choices: [
1349
- { name: '10%', value: 10 },
1350
- { name: '25%', value: 25 },
1351
- { name: '50%', value: 50 },
1352
- { name: '75%', value: 75 },
1353
- { name: '100% (Full)', value: 100 },
1354
- ],
1355
- },
1356
- ]);
1357
- const { confirmPartial } = await inquirer_1.default.prompt([
1358
- {
1359
- type: 'confirm',
1360
- name: 'confirmPartial',
1361
- message: chalk_1.default.yellow(`⚠️ Pay ${partialPercentage}% of ${partialSelected.length} payout(s)?`),
1362
- default: false,
1363
- },
1364
- ]);
1365
- if (confirmPartial) {
1366
- await processPayoutsWithProgress(partialSelected, 'partial', partialPercentage);
1780
+ const partialPercentage = await getPercentageWithCustom();
1781
+ if (partialPercentage !== null) {
1782
+ const { confirmPartial } = await inquirer_1.default.prompt([
1783
+ {
1784
+ type: 'confirm',
1785
+ name: 'confirmPartial',
1786
+ message: chalk_1.default.yellow(`⚠️ Pay ${partialPercentage}% of ${partialSelected.length} payout(s)?`),
1787
+ default: false,
1788
+ },
1789
+ ]);
1790
+ if (confirmPartial) {
1791
+ await processPayoutsWithProgress(partialSelected, 'partial', partialPercentage);
1792
+ }
1367
1793
  }
1368
1794
  }
1369
1795
  break;
@@ -1374,6 +1800,11 @@ const runPayoutsShell = async () => {
1374
1800
  case 'validator_v2':
1375
1801
  await listPayoutsCmd({ plan: cmd });
1376
1802
  break;
1803
+ case 'christmas':
1804
+ case 'xmas':
1805
+ // Christmas payouts subshell
1806
+ await runChristmasPayoutsShell();
1807
+ break;
1377
1808
  case 'process':
1378
1809
  // Process specific plan with interactive selection
1379
1810
  const { processPlan } = await inquirer_1.default.prompt([
@@ -1452,6 +1883,7 @@ const runPayoutsShell = async () => {
1452
1883
  console.log(' cancel [ids] Cancel payouts (interactive if no IDs)');
1453
1884
  console.log(' process Process payouts by plan (interactive)');
1454
1885
  console.log(' hermes/alpha/etc List payouts for specific plan');
1886
+ console.log(chalk_1.default.red(' christmas, xmas 🎄 Christmas payouts (TarPay)'));
1455
1887
  console.log(' back, q Return to main menu');
1456
1888
  console.log('');
1457
1889
  break;